You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ManimGL制作带视频纹理的波浪动画时触发OpenGL无效枚举错误

ManimGL 长时长动画抛出 Invalid Enum 错误(OpenGL GLError 1280)

问题描述

使用 ManimGL 制作带视频帧纹理的波浪动画时,短时长(约5秒)动画运行正常,但时长超过10秒会抛出 invalid enum error,动画时长由 PinkyWaving 类的 self.wait 方法控制,目标是生成2分钟完整动画。

错误信息

self.construct()
File "pinky_waving.py", line 119, in construct
    self.wait(10)
File "f:\instalaciones\anaconda\envs\manim2\lib\site-packages\manimlib\scene\scene.py", line 417, in wrapper
    func(self, *args, **kwargs)
File "f:\instalaciones\anaconda\envs\manim2\lib\site-packages\manimlib\scene\scene.py", line 508, in wait
    self.emit_frame()
File "f:\instalaciones\anaconda\envs\manim2\lib\site-packages\manimlib\scene\scene.py", line 179, in emit_frame
    self.file_writer.write_frame(self.camera)
File "f:\instalaciones\anaconda\envs\manim2\lib\site-packages\manimlib\scene\scene_file_writer.py", line 238, in write_frame
    raw_bytes = camera.get_raw_fbo_data()
File "f:\instalaciones\anaconda\envs\manim2\lib\site-packages\manimlib\camera\camera.py", line 294, in get_raw_fbo_data
    gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self.fbo_msaa.glo)
File "src/errorchecker.pyx", line 58, in OpenGL_accelerate.errorchecker._ErrorChecker.glCheckError
OpenGL.error.GLError: GLError(
        err = 1280,
        description = b'numeraci\xf3n no v\xe1lida',
        baseOperation = glBindFramebuffer,
        cArguments = (GL_READ_FRAMEBUFFER, 2)
)

环境配置

  • Python 3.8
  • ManimGL v1.5.0
  • moderngl 5.7.2

原始实现代码

from manimlib import *
import os

VIDEO_FRAMES  = 'pinky_60fps-opencv'
TEXTURES = os.listdir(VIDEO_FRAMES)
N = len(TEXTURES)
FRAME_INDEX = 0 


class TexturedPlaneWavingVideo(TexturedSurface):
    CONFIG = {   
        'waving_plane_config':
            {
            "u_range": (-5*2,5*2),
            "v_range": (-5*2,5*2),
            "resolution": (32,32),
            "color": BLUE_E,
            "opacity": 0.85,
            }
    }
    def __init__(self, phi=0, height_factor=1.0, d=1.0, alpha=0.1,scale_factor = 0.5, init_frame= None,**kwargs):
        """
        init_frame: str
            frame used as texture
        """
        digest_config(self,self.CONFIG)
        self.init_frame = init_frame

        # Parameters
        # heigth of wave
        self.height_factor = height_factor
        self.d = d
        self.alpha = alpha
        self.phi = phi
        #overall scale of plane
        self.scale_factor = scale_factor
        
        # Initialize as a plane
        surface = ParametricSurface(
            uv_func = self.wave_func,
            **self.waving_plane_config).scale(self.scale_factor)

        self.surface = surface
       
        super().__init__(uv_surface= surface,
            image_file = os.path.join(VIDEO_FRAMES,self.init_frame))


    def update_wave(self,dt):
        self.phi += 4*dt
        global FRAME_INDEX
        FRAME_INDEX= (FRAME_INDEX+1)%N
        
        texture = TEXTURES[FRAME_INDEX]
        texture_path =os.path.join(VIDEO_FRAMES, TEXTURES[FRAME_INDEX])
       

        # Create a new TexturedPlaneWavingVideo
        plane = TexturedPlaneWavingVideo(
            phi=self.phi, height_factor=self.height_factor, d=self.d ,alpha=self.alpha,scale_factor = self.scale_factor, init_frame=texture )
        
        self.init_frame = texture

        
        self.texture_paths = {
            "LightTexture": get_full_raster_image_path(texture_path),
            "DarkTexture": get_full_raster_image_path(texture_path),}

        self.become(plane)
        
        self.init_shader_data()
        
       
    def plane_func(self,u,v):
        return np.array([
            u,
            v,
            0])

    def wave_func(self, u, v):

        return (u,
                v,
                np.sin((np.sqrt(u**2/self.d + v ** 2 / self.d) + self.phi)) * self.height_factor + self.alpha)

    def wave_func_np(self,u,v):
        return  np.array(
            [u,
             v,
             np.sin( (np.sqrt(u**2/self.d + v ** 2 / self.d ) + self.phi) ) * self.height_factor + self.alpha])

    def waving(self,speed = 1):
        """
        speed: float
            speed of waving animation
        """
        self.add_updater(lambda m,dt:self.update_wave(dt*speed)  )

        
    def stop_waving(self):
        self.clear_updaters()

class PinkyWaving(ThreeDScene):
    def construct(self):
        p = TexturedPlaneWavingVideo(phi=0, height_factor=3.0, d=1.0, alpha=0.1,scale_factor = 0.5,init_frame="frame0-00-00.00.jpg")
        
        self.play(ShowCreation(p))
        
        p.waving()
        
        self.wait(10)

问题根源与解决方案

问题分析

错误的核心原因是每次更新时创建新的TexturedPlaneWavingVideo实例,导致大量OpenGL资源(帧缓冲、纹理对象)无法被正确回收,长时间运行后资源耗尽,触发GL_INVALID_ENUM错误(操作了已失效的GL对象)。

修改后的代码

from manimlib import *
import os

VIDEO_FRAMES = 'pinky_60fps-opencv'
TEXTURES = sorted(os.listdir(VIDEO_FRAMES))  # 确保帧顺序正确
N = len(TEXTURES)


class TexturedPlaneWavingVideo(TexturedSurface):
    CONFIG = {
        'waving_plane_config': {
            "u_range": (-5*2, 5*2),
            "v_range": (-5*2, 5*2),
            "resolution": (32, 32),
            "color": BLUE_E,
            "opacity": 0.85,
        }
    }

    def __init__(self, phi=0, height_factor=1.0, d=1.0, alpha=0.1, scale_factor=0.5, init_frame=None, **kwargs):
        digest_config(self, self.CONFIG)
        self.init_frame = init_frame
        self.height_factor = height_factor
        self.d = d
        self.alpha = alpha
        self.phi = phi
        self.scale_factor = scale_factor
        self.frame_index = 0  # 用实例变量替代全局变量

        # 初始化曲面
        surface = ParametricSurface(
            uv_func=self.wave_func,
            **self.waving_plane_config
        ).scale(self.scale_factor)
        self.surface = surface

        super().__init__(
            uv_surface=surface,
            image_file=os.path.join(VIDEO_FRAMES, self.init_frame)
        )

    def update_wave(self, dt):
        # 更新波浪相位
        self.phi += 4 * dt
        # 更新帧索引
        self.frame_index = (self.frame_index + 1) % N
        current_frame = TEXTURES[self.frame_index]
        texture_path = os.path.join(VIDEO_FRAMES, current_frame)

        # 更新纹理
        self.texture_paths = {
            "LightTexture": get_full_raster_image_path(texture_path),
            "DarkTexture": get_full_raster_image_path(texture_path),
        }
        self.init_shader_data()

        # 更新曲面形状(无需重建曲面,直接刷新参数)
        self.surface.set_uv_func(self.wave_func)
        self.surface.generate_points()
        self.surface.make_smooth()

    def plane_func(self, u, v):
        return np.array([u, v, 0])

    def wave_func(self, u, v):
        return (
            u,
            v,
            np.sin((np.sqrt(u**2/self.d + v ** 2 / self.d) + self.phi)) * self.height_factor + self.alpha
        )

    def wave_func_np(self, u, v):
        return np.array(
            [
                u,
                v,
                np.sin((np.sqrt(u**2/self.d + v ** 2 / self.d) + self.phi)) * self.height_factor + self.alpha
            ]
        )

    def waving(self, speed=1):
        self.add_updater(lambda m, dt: self.update_wave(dt*speed))

    def stop_waving(self):
        self.clear_updaters()


class PinkyWaving(ThreeDScene):
    def construct(self):
        p = TexturedPlaneWavingVideo(
            phi=0, height_factor=3.0, d=1.0, alpha=0.1, 
            scale_factor=0.5, init_frame="frame0-00-00.00.jpg"
        )
        self.play(ShowCreation(p))
        p.waving()
        self.wait(120)  # 直接设置2分钟时长

关键修改点

  1. 移除全局变量:用实例变量self.frame_index替代全局FRAME_INDEX,避免多实例状态冲突
  2. 避免重复创建实例:删除每次更新时创建新TexturedPlaneWavingVideo的逻辑,减少OpenGL资源开销
  3. 直接更新现有对象:
    • 仅更新纹理路径并重新初始化着色器数据
    • 调用set_uv_func+generate_points刷新曲面形状,无需重建曲面
  4. 确保帧顺序:对TEXTURES列表排序,保证视频帧播放顺序正确

额外优化建议

  • 预加载纹理:将所有视频帧提前加载到内存,避免磁盘IO拖慢动画速度
  • 禁用MSAA:如果对画质要求不高,可在场景中添加CONFIG = {"camera_config": {"use_msaa": False}},减少帧缓冲资源消耗

运行命令保持不变:manimgl pinky.py PinkyWaving -w

内容的提问来源于stack exchange,提问作者Hōgyoku

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:25:26