Arcade中如何让GLSL着色器协同Pymunk与相机正常工作
问题根因
三个核心问题导致绘制错位:
- 坐标空间不匹配:直接将精灵的世界坐标传入Shadertoy的uniform变量,但Arcade内置Shadertoy默认使用窗口屏幕坐标空间渲染,不会自动应用游戏世界相机的视图、投影变换。世界坐标随相机移动会产生对应偏移和缩放,直接传入屏幕空间着色器,自然会出现特效移动速度快于实体、坐标尺度不符的问题。
- 渲染流程错误:
on_draw中未先激活世界相机就开始绘制着色器特效,且着色器绘制顺序和普通精灵割裂,两类内容没有走同一套坐标变换管线。 - 参数未同步:初始化Shadertoy时仅传入了窗口初始尺寸,后续窗口resize、相机缩放时没有同步更新Shadertoy的视口、投影参数,进一步放大了坐标错位问题。
修复步骤
1. 修正渲染流程,统一坐标空间
调整on_draw逻辑,所有世界空间内容(普通精灵、着色器特效)必须在激活世界相机后绘制,保证共用同一套视图/投影变换,GUI内容单独切GUI相机绘制:
def on_draw(self): self.clear() # 激活世界相机,所有游戏内世界对象的绘制都放在这之后 self.camera.use() # 批量绘制普通精灵 self.bullet_list.draw() self.glow_bullet_list.draw() # 绘制着色器特效,和普通精灵处于同一坐标空间 for bullet in self.glow_bullet_list: bullet.draw() # 切换GUI相机绘制UI元素 self.camera_gui.use() # 此处绘制血条、分数、菜单等UI内容
之前手动用「玩家坐标减相机坐标」算偏移的逻辑可以全部删掉,坐标变换由相机统一处理,手动计算反而会引入额外偏移。
2. 着色器坐标转换适配
修改GlowBall.draw()方法,将精灵的世界坐标通过当前相机的视图、投影矩阵转换为着色器使用的裁剪空间坐标,同时传入相机缩放值修正特效尺寸:
def draw(self): # 获取当前激活相机的变换矩阵 view_mat = self.window.current_camera.view_matrix proj_mat = self.window.current_camera.projection_matrix # 计算精灵在裁剪空间的实际位置 world_pos = (self.center_x, self.center_y, 0.0, 1.0) clip_pos = proj_mat @ view_mat @ world_pos # 向着色器传参 self.shadertoy.program['pos'] = (clip_pos.x, clip_pos.y) self.shadertoy.program['color'] = arcade.get_three_float_color(self.glowcolor) self.shadertoy.program['cam_zoom'] = self.window.current_camera.zoom self.shadertoy.render()
对应GLSL着色器中,辉光半径计算需要除以cam_zoom,保证相机缩放时特效显示尺寸符合预期。
3. 同步窗口与相机参数变化
监听窗口尺寸变化事件,同步更新Shadertoy的渲染尺寸,避免视口不匹配:
def on_resize(self, width: int, height: int): super().on_resize(width, height) self.glowball_shadertoy.resize((width, height))
4. Pymunk物理引擎适配
Pymunk更新的精灵坐标本身就是标准世界坐标,只要按前面的流程统一在世界相机激活状态下绘制,不需要对物理计算出的坐标做额外转换,之前给辉光子弹加力、设置质量的逻辑可以正常保留,不会和渲染冲突。
优化建议
- 不要使用全屏四边形(
quad_2d_fs)绘制单个辉光子弹,否则每颗子弹都会触发一次全屏渲染,性能损耗极大。替换为和精灵碰撞盒尺寸匹配的小矩形面片绘制单个特效,和普通精灵的绘制逻辑对齐即可。 - 如果使用Arcade 2.6以上版本,推荐直接用
SpriteList自带的自定义着色器功能,不需要单独遍历精灵调用draw方法,引擎会自动处理批渲染、坐标变换、相机适配,性能更高也不会出现坐标错位问题。
内容的提问来源于stack exchange,提问作者Casey MrChiller
相关产品推荐
相关产品推荐

