Unity内置渲染管线Sprite描边效果延迟一帧问题排查
Unity内置管线Sprite描边延迟问题排查
问题描述
在Unity 2019.4.36f1的全新项目中,我用内置渲染管线给一组Sprite做描边效果。实现逻辑是:
- 先收集需要分组的SpriteRenderer,在
SpriteOutliner脚本的OnAwake初始化阶段,创建一个专门承载描边效果的SpriteRenderer - 接着创建绑定
CameraEvent.AfterImageEffects的CommandBuffer,执行以下步骤:- 绘制收集到的Sprite
- 把黑色纹理Blit到描边用的RenderTexture
- 通过特定Shader将绘制好的Sprite Blit到RenderTexture,生成描边效果
- 将生成的纹理复制到描边SpriteRenderer的纹理里
静态状态下效果正常,但移动物体时,描边会比Sprite的移动延迟一帧,帧调试器能明确看到这个问题;可一旦在帧调试器里逐帧步进并移动鼠标,描边又会恢复正常,根本没法定位问题。想问这是Unity的Bug,还是我的实现方案有问题?(注:这是我第一次用CommandBuffer)
相关代码
void CreateCommandBuffer() { _outlineMaterial = new Material(Shader.Find("Hidden/Outline Blit")); _outlineRenderTexture = new RenderTexture(_targetCamera.pixelWidth, _targetCamera.pixelHeight, 24); int activeTex = Shader.PropertyToID("_ActiveTex"); _outlineCommandBuffer = new CommandBuffer(); _outlineCommandBuffer.name = "Outline Buffer"; _outlineCommandBuffer.GetTemporaryRT( activeTex, _targetCamera.pixelWidth, _targetCamera.pixelHeight, 24, FilterMode.Point, RenderTextureFormat.ARGB32 ); _outlineCommandBuffer.SetRenderTarget(activeTex); _outlineCommandBuffer.ClearRenderTarget(true, true, new Color(0f, 0f, 0f, 0f)); foreach (SpriteRenderer spriteRenderer in _spriteRenderers) _outlineCommandBuffer.DrawRenderer(spriteRenderer, spriteRenderer.material); _outlineCommandBuffer.Blit(Texture2D.blackTexture, _outlineRenderTexture); _outlineCommandBuffer.Blit( activeTex, _outlineRenderTexture, _outlineMaterial, _outlineMaterial.FindPass("Outline") ); _outlineCommandBuffer .CopyTexture(_outlineRenderTexture, _outlineEffectRenderer.sprite.texture); _outlineCommandBuffer.ReleaseTemporaryRT(activeTex); _targetCamera .AddCommandBuffer(CameraEvent.AfterImageEffects, _outlineCommandBuffer); }
问题分析与解决建议
这不是Unity的Bug,问题出在你的实现逻辑上,核心是CommandBuffer的执行时机和纹理复制的帧延迟:
执行时机不匹配
你把CommandBuffer绑定到CameraEvent.AfterImageEffects,这个时机是相机完成所有渲染、后期处理之后才会执行。此时生成的描边纹理,要等到下一帧才会被描边SpriteRenderer渲染出来——因为当前帧的渲染队列已经走完了,新生成的纹理赶不上当前帧的渲染提交。纹理复制的同步问题
CopyTexture是把RenderTexture的内容复制到Sprite的纹理,但Sprite的纹理在当前帧已经提交给GPU准备渲染了,修改后的内容只能在下一帧生效,自然就出现了延迟。
修复方案:
- 调整CommandBuffer绑定时机:把
CameraEvent.AfterImageEffects换成CameraEvent.BeforeForwardOpaque或者CameraEvent.BeforeRenderingOpaques,让描边纹理在当前帧的Sprite渲染之前就生成好,确保当前帧就能用上最新的描边内容。 - 直接使用RenderTexture作为材质纹理:别再用
CopyTexture修改原始Sprite的纹理,直接让描边SpriteRenderer的材质用_outlineRenderTexture作为主纹理。这样RenderTexture的内容会实时更新,材质也能立刻用上,完全没帧延迟。 - 记得清理资源:脚本销毁时要释放CommandBuffer、RenderTexture和Material,避免内存泄漏。
调整后的核心代码示例:
void CreateCommandBuffer() { _outlineMaterial = new Material(Shader.Find("Hidden/Outline Blit")); _outlineRenderTexture = new RenderTexture(_targetCamera.pixelWidth, _targetCamera.pixelHeight, 24); // 直接把RenderTexture赋值给描边Sprite的材质主纹理 _outlineEffectRenderer.material.mainTexture = _outlineRenderTexture; int activeTex = Shader.PropertyToID("_ActiveTex"); _outlineCommandBuffer = new CommandBuffer(); _outlineCommandBuffer.name = "Outline Buffer"; _outlineCommandBuffer.GetTemporaryRT( activeTex, _targetCamera.pixelWidth, _targetCamera.pixelHeight, 24, FilterMode.Point, RenderTextureFormat.ARGB32 ); _outlineCommandBuffer.SetRenderTarget(activeTex); _outlineCommandBuffer.ClearRenderTarget(true, true, new Color(0f, 0f, 0f, 0f)); foreach (SpriteRenderer spriteRenderer in _spriteRenderers) _outlineCommandBuffer.DrawRenderer(spriteRenderer, spriteRenderer.material); _outlineCommandBuffer.Blit(Texture2D.blackTexture, _outlineRenderTexture); _outlineCommandBuffer.Blit( activeTex, _outlineRenderTexture, _outlineMaterial, _outlineMaterial.FindPass("Outline") ); // 移除CopyTexture操作,不需要再复制到Sprite纹理 // _outlineCommandBuffer.CopyTexture(_outlineRenderTexture, _outlineEffectRenderer.sprite.texture); _outlineCommandBuffer.ReleaseTemporaryRT(activeTex); // 更换绑定时机为BeforeForwardOpaque _targetCamera.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, _outlineCommandBuffer); } // 脚本销毁时清理资源 void OnDestroy() { if (_outlineCommandBuffer != null) { _targetCamera.RemoveCommandBuffer(CameraEvent.BeforeForwardOpaque, _outlineCommandBuffer); _outlineCommandBuffer.Dispose(); } if (_outlineRenderTexture != null) _outlineRenderTexture.Release(); if (_outlineMaterial != null) Destroy(_outlineMaterial); }
内容的提问来源于stack exchange,提问作者Menyus777
相关产品推荐
相关产品推荐

