讲些什么?
绝大多数的游戏或多或少都会使用些后处理效果.
早期版本中,Unity在提供的接口有限,优化空间不大,属于放任自流。官方推出了Post-Processing(下文简称PP)并在Github上长期维护,很好的将后处理与其提供的一些新的渲染API结合起来。无论是在易用性还是性能上都要比以前好。
本文主要针对于PP的自定义扩展上进行一些讨论。只有当需要添加新的自定义效果或者修改PP内置效果时才需要考虑这些问题。顺便连带着提一提涉及到的Unity新的渲染概念。
由PostProcessEvent引出
为了能正确理解下文讨论的内容,需阅读链接处Wiki上的文字了解添加自定义后处理的步骤。你会首先看到PostProcessEvent这个枚举,这个枚举是贯穿始终的。
1 public enum PostProcessEvent 2 { 3 BeforeTransparent = 0, 4 BeforeStack = 1, 5 AfterStack = 2, 6 } 7 8 ... 9 10 public PostProcessAttribute(Type renderer, PostProcessEvent eventType, string menuItem, bool allowInSceneView = true) 11 { 12 ... 13 builtinEffect = false; 14 } 15 16 internal PostProcessAttribute(Type renderer, string menuItem, bool allowInSceneView = true) 17 { 18 ... 19 builtinEffect = true; 20 }
PP会根据枚举标记将所有后处理归类放到不同的序列,序列之间最大差异是执行顺序问题(这同引擎渲染的RenderQueue类似)。
问:第一次看到BeforeStack和AfterStack会有疑惑,这里的Before和After是相对于什么的?
答:注意上面这个Attribute的构造函数重载代码中后面一种重载没有指定eventType,但是将builtinEffect赋为true.实际上builtinEffect这个布尔变量暗含着一个Built-in Stack的序列(Wiki中解释了常见的Vignette,Bloom等都属于这个序列).
为什么要分不同序列
有两个主要原因:
1.方便扩展:
后处理效果是对执行顺序敏感的,比如不想影响特效(大多数为半透明),那就需要标记为BeforeTransparent,以保证后处理发生在所有半透明物体绘制之前。
有了CommandBuffer之后,Unity提供了更多可选择的渲染时机,PP进行简化。
如下图,Unity提供的几个序列,实际上就是方便你将自己的后处理选择一个适合的渲染时机。
2.提高性能
同一个序列内的后处理效果大概率对资源存在复用可能,特别是RenderTarget。虽然单像素的计算复杂度对后处理有影响,但频繁的切换RenderTarget(俗称翻Buffer)造成更大且不必要的开销。
用FrameDebugger去查看后处理的各个Pass,会发现一个叫Uber的步骤,Uber不对应某个具体效果,它是个特殊的Pass,通过将之前分散在各个后处理着色器中的渲染逻辑整合到一起,并利用ShaderKeywords来进行代码段的屏蔽开启。从而最大程度上提高资源复用,降低DC。
我画了个图,看上去更直观,左侧是优化前,右侧是优化后的,无论是DrawCall,还是对RenderTexture的使用都由性能提升。
如何整合
打开Uber.shader,会看到里面都是Built-in Stack的内容,如果你自定义的后处理Shader,
如果你自定义的后处理仅需一个DC或存在与内置效果有共用资源的情况,那就尽量将其整合入Uber中.
整合过程中提几个需要注意的地方:
1.自定义的后处理脚本中要使用正确的PostProcessAttribute重载。
2.将片元着色器逻辑整合进Uber.shader的FragUber函数中时,注意与其它效果间的顺序,若不是常驻效果,应该通过Keyword来提供开关。
3.PP提供的Shader中大多引其自带的cginc文件,很多平时常用的函数都不存在,需要自行添加。
每一个引擎都需要解决如何最合理和高效的提供后处理的接口。Unity提供的PP源代码值得一看。
提及两个概念
PP的设计是基于CommandBuffer的,并大量使用了MaterialPropertyBlock。这里略带提及一下这两个概念
1.CommandBuffer(CB)
CB就是缓存将一系列渲染命令,在一个合适渲染阶段进行执行。这些阶段可以查看CameraEvent这个枚举:可见Unity考虑的很周到,大多数渲染阶段都提供了,这个东西还是该点赞的。
重点看CameraEvent,BuiltinRenderTextureType,CommandBuffer这几个API文档
2.MaterialPropertyBlock(MPB)
MaterialPropertyBlock is used by Graphics.DrawMesh and Renderer.SetPropertyBlock. Use it in situations where you want to draw multiple objects with the same material, but slightly different properties. For example, if you want to slightly change the color of each mesh drawn. Changing the render state is not supported.
这是文档的一段说明,Unity建议用MPB的API去替换掉传统的Material.setXXX,有用户测试过,提升还是蛮大的。猜测Unity应该是对材质管理和图形API底层Shader赋值的结构上进行了优化,降低了不必要的查找,拷贝等。
以上是近日来在实际工作上的一点分享,希望自己日后有精力时,在后处理方面有更多分享。如果你有任何不同的理解或意见,欢迎留言。
尊重他人智慧成果,非本人同意,请勿转载