粗略的随笔,未来会整理成文。
起因:
希望做屏幕扰动 PostEffect.
改动方向:两个相机,第一个相机的渲染结果作为第二个渲染相机的输入参数,以便后续处理。
目前的现状: PostEffect 流程是个单件,且和MainCamera绑死了。
调查:
u3d有一个 PostEffectStack 的实现,里面超级复杂,整合了大量PostEffect,并可以协同工作。大致参考了下结构和流程,就开始动手改项目里的了。
仿照其工作原理,设计了三个层次的类:
PostEffectProcess , 负责各种 PostEffect的具体实现。
PostEffectQueue, 和某个具体的Camera关联, 负责组织各种PostEffect, 传递U3D的 onRenderImage 事件给各个PostEffectProcess。
PoestEffectManager, 负责Queue的管理,也是整个PostEffect系统对外开放功能接口的地方。
这里因为不了解u3d的渲染流程(其实其他常规引擎的流程也是一知半解),走了弯路。有几个特别影响设计的前置知识不能不知道:
知识1、Double Buffer 。
U3D系统存在一个DB,但是无法直接得到这俩Buffer(更别说精确控制用哪个了)。唯一可以影响DB的是 onRenderImage(RenderTexture source, RenderTexture destination) 函数里,如果 destination 为 null ,则在使用 Graphics.Blit(source, null) 时是往DB里拷贝贴图内容。没有尝试在其他时机是否也这样。(onPreRender, onPostRender, 以及新的 CommandBuffer里定义的其他时机等)
为了将各种PostEffect串联起来,有两种做法:
A. 利用 MonoBehaviour 的 onRenderImage 规则: 在一个Camera上挂若干个MonoBehaviour,它们的 onRenderImage函数会依次串联起来,前一个destination是后一个的source。但不保证source或destination一定是主DB对应的RenderTarget,也有可能是u3d自己开的一个临时RenderTexture.
B. 自行开辟2个RT, 手工控制串联流程。 ( 我选择了此方法,理由参看知识2)
知识2、降低渲染分辨率
为了做到此效果,有两种做法:
A. 修改 Application.resolution ,整体降低渲染分辨率。 3D相机和UI相机的质量都降低了。
B.UI保持精度不变,只降低3D相机的输出精度。 这就要求必须将3d Camera输出到一张指定的RT, 而UI则输出到DB。且在u3d里,要将一个camera的RT拷贝到DB中,在不增加新的Camera的情况下,只有如下写法才成立:
B.1 onPreRender里设置 camera.targetTexture = yourRT;
B.2 onPostRender里将 camera.targetTexture = null;
B.3 脚本里不可以写 onRenderImage 函数
知识3、RenderTexture 的 cache ,不要自己做
完全交给U3D的 RenderTexture.CreateTemporary / ReleaseTemprorary , 它已经做了。
知识4、 Process 之间的关系
其实有共存和覆盖逻辑的,一般不需要搞一个二维表来做配置,只需要合理的安排先后顺序和排他性即可。这需要多年图形学经验的老司机来指导。
实现:
PostEffectQueue
开RT是一个需要极力避免的事情,而只缩3D渲染分辨率(后简称ScreenScale)是后续所有PostEffect的起点,因此针对Scale是否为1,写了两套MonoBehaviour,区别则只有是否 写了 onRenderImage。按需做enable.
在scale=1时,不创建2个RT,直接使用原始流程。
在Scale != 1时, 创建两个缩放过的 RT, 模拟DB的工作流, 在 onPostRender函数中调用 各 postEffectProcess的 onRenderImage(source, destination).
PostEffectProcess
各种PostEffectProcess 因为总是伴随着参数调整,为了不自己写serializing相关代码,可以将其作为UnityEngine.ScriptableObject 的子类。 这样 PostEffectProcess里的 onRenderImage 需要改成 doRenderImage,不然会报错。
PostEffectManager
对外的主要接口有两个:
BeginEffect( string key, int queueIndex)
EndEffect(string key, int queueIndex)
其他接口都是辅助的,比如获取某个Process的中间RT之类的(屏幕虚化之类).
设计完成后, PostEffect 流程就每相机N份,跨相机的流程要共享信息也很方便扩展了。