走进LWRP(Universal RP)的世界
原文:https://connect.unity.com/p/zou-jin-lwrp-universal-rp-de-shi-jie
LWRP自Unity2018发布以来,进入大家视野已经有一段时间了,不过对于广大Unity开发者来说,依然相对比较陌生,原因有几个,一是一直以来LWRP都处于预览状态,使用中会遇到一些bug,所以也就止于尝试。第二个原因就是LWRP的升级跨度太大,基本所有的shader都需要重写,所有的材质球都得重新调整,这对于已经开始进入Production 阶段的项目来说是不可接受的。
最近有幸在项目中使用LWRP,经过好几个月的使用,回过来看发现目前不少用户对于LWRP的认识与实际会有不少出入,加之目前关于LWRP的分享大多仅仅是对于其Shader和材质球用法的教程,很少有从原理上介绍LWRP的文章。因此想借着这篇文章,跟大家分享一下这几个月使用的心得,和踩过的坑。
从LWRP到Universal RP
为什么我要在最开头说这个话题呢,原因是LWRP这个名字即将退出历史的舞台,从2019.3开始,LWRP将会以Universal RP的名字跟大家见面。
当然,最根本的原因是LWRP这个名字给使用者带来了不少误导和困扰,这也是Unity官方要对管线进行重命名的原因。LW是轻量级的缩写,大家听到这个名字,以及Unity最初对这个管线的宣传,都给大家带来了这个管线是专为移动游戏简化并且高度优化的一个管线。换句话说,我们大家都以为,只要换了LWRP,妈妈再也不用担心做出来的手游卡了。
费尽千辛万苦,将测试场景从内置管线转换成LWRP之后,我迫不及待的跑了一波性能测试,测试结果emmmmm…
无论在简单还是复杂的场景里,最终的渲染耗时和帧率,并没有发现有什么肉眼可见的性能提升,仿佛之前转换管线是自己想象出来的,其实并没有切换管线。
然而这肯定是不可能的,事实上其实是我并没有搞清楚LWRP的设计初衷和他的优劣势,于是痛定思痛,我决定从图形学开始补习,并把LWRP的原理摸透
Universal RP的优势
之前有讲到Unity已经正式将LWRP的名称变更为Universal RP——即通用渲染管线,并将正式接过内置管线的大旗,成为新一代Unity的兼容所有平台的通用渲染管线。
大家肯定都会跟我有一样的疑惑,内置管线不是本来就跨平台好好的么,Universal RP也没看出来性能上有什么特别的优势,干嘛放着好好的又稳定的内置管线不用,要跑来折腾什么URP。其实这个问题的关键在于,我们之前的打开方式不对。
URP在性能上的优势
说到性能优势,那首先我们就必须得弄清楚,到底在哪种情况下,URP的哪一方面会有性能优势。要对比性能那就首先要比较一下,URP跟内置管线到底在渲染上有什么区别。
1、渲染路径的差别
其中,最大的一点区别是,URP是单Pass前向渲染管线,而内置管线是多Pass前向渲染管线和延迟渲染管线
URP没有延迟渲染,因此我们只对比前向渲染这一项(其实手游也基本只会用前向渲染,延迟渲染的G-Buffer所需要的带宽带来的开销太大)。
所谓的前向渲染,就是在渲染物体受点光光照的时候,分别对每个点光对该物体产生的影响进行计算,最后将所有光的渲染结果相加得到最终物体的颜色。内置管线的做法是,用多个pass来渲染光照,第一个pass只渲染主光源,然后多出来的光每个光用一个pass单独渲染。这也是为什么我们在做手游的时候很少会用点光源。因为对于内置管线来说,每多一盏光,整个场景的drawcall就会翻倍,这个性能开销基本是无法接受的。
URP的做法则是,在一个pass当中,对这个物体受到的所有光源通过一个for循环一次性计算。这么做的好处有:
-
一个物体的光照可以在一次DrawCall中计算完毕
-
省去了多个Pass的上下文切换以及光栅化等开销
但是这么做的坏处也很明显:
-
只支持1盏直光
-
单个物体最多支持4盏点光
-
单个相机最多支持16盏灯光
因此,有了URP之后,只要控制好点光的范围,我们在手游里面也可以做多点光照明了,例如释放一个火球照亮周围物件,这在内置管线里基本是可以放弃的功能(或者用其他作假的方式模拟)
2、GrabPass
还有一个内置管线中,很难运用到手游中的技术,那就是GrabPass
这个技术通常会用来制作空气扰动或者刀光等特效带来的折射效果,在内置管线中如果使用这个功能,则会在每个用到这个Shader的地方对屏幕缓冲区进行一次抓取操作, 这个操作会大幅消耗GPU的带宽。 在移动设备上带宽是非常有限的资源,因此这个效果在手机上几乎是没法使用的
在URP中,可以在渲染管线的配置文件中,开启Opaque Texture,这张图是管线在完成不透明物体渲染后,将屏幕缓冲区中(也可能是相机的ColorRT)的颜色信息抓取出来保存到一张单独的RT当中,然后在特效中就可以直接拿这张图去进行扭曲和扰动计算。
这么做的好处就是可以将抓取操作的次数恒定下来,不会因为同屏有多少个需要扰动的对象而额外增加开销。当然坏处也比较明显,那就是没办法对不透明物体进行扰动操作了,也就是说只能对场景物件而没法对特效进行扰动。
而且Unity非常贴心的在URP的粒子特效Shader当中直接内置了扰动的效果,通过勾选框就能开启了
3、SRP Batcher
在所有SRP管线中(URP和HDRP或者你自己的自定义管线),都可以受益于SRP Batcher,这个功能可以将没有进行静态合并,也没法通过Instancing渲染的使用相同Shader的物体,通过CBuffer去保存每个物体材质球的参数,进而在不进行SetPassCall的情况下完成绘制。这个功能的效果是,可以大幅降低相同DrawCall情况下单个DrawCall的开销,当这个功能开启的时候,你会发现,也许你的场景有500个DrawCall,但实际上SetPassCall只有不到100,在相同情况下的渲染性能是要高于内置管线不少的。不过Shader要支持SRP Batcher还是有些条件的,详细的大家去参考SRP Batcher的文档吧
URP在扩展性上的优势
Unity提出SRP,其中一个最大的初衷就是提高渲染管线的灵活性,给使用者提供最大的自定义空间来满足各个项目的需求。要想一个渲染管线满足所有类型项目的需求,这根本就是不可能的,这必然会造成对性能或者功能上的妥协。
因此URP自然而然的具备了非常强大的可扩展性,其大多数功能都是完全模块化的,可以自由搭配组合,而且对她进行扩展大多数情况下是不需要修改URP本身源码的!
1、RenderFeature/RenderObject
这个功能是LWRP6.x之后加入的新功能,其中RenderObject是RenderFeature的一个默认实现,可以让大家在不写一行代码的情况下对渲染管线进行扩展。
为了使用RenderObject,我们首先需要建立一个新的前向渲染器的配置文件
在项目文件夹中右键创建,找到ForwardRenderer这个选项
第二步是在刚创建的前向渲染器配置中添加一个新的RenderObject
然后我们就可以看见,我们可以指定一个Layer,然后对这一层的物件使用指定的材质球再在某个特定的时间点(比如渲染完不透明物体后)再进行一次统一的绘制。
这个功能能用来做什么呢? 比如主角的遮挡透明,在内置管线中我们只能用2个Pass来做这个事情,而且因为被遮挡时的显示是半透,所以需要严格控制渲染顺序不然就会在不少情况穿帮,在内置管线中可能会需要手动修改一系列shader的renderqueue来保证最终的显示正确,非常麻烦。 但是在URP中,利用RenderObject就能非常轻松愉快的实现这个功能。
Unity还提供了不少利用RenderObject实现的效果的例子,大家可以移步Github:https://github.com/Unity-Technologies/LWRP-CustomRendererExamples
那RenderFeature又是什么呢? RenderFeature提供了比RenderObject更低一级的自定义,用户通过继承RenderFeature,可以通过代码手动在自己需要的地方加入自定义Pass,去做自己想做的事情,写完Feature和Pass之后就能跟RenderObject一样,在渲染器设置里通过加号添加上去了。由于使用RenderFeature完全看项目需求,因此在这里就不详细展开了
2、ScriptableRenderer
URP还支持你对ScriptableRenderer进行继承,从而实现一个完全自己的Renderer(自带的ForwardRenderer就是一个实现),因此如果你足够强,完全可以自己写个延时渲染器出来,最棒的是,他已经实现的Pass可以随便抓来用,避免去写一些重复的东西。因此如果大家对管线中有任何不爽的地方或者觉得没必要的地方,大可自由裁切和扩展,自己的项目需要啥咱就用啥,不需要啥就砍啥,主动权完全抓在自己手里!
总结
现在回过头看最开始的性能试验,其实并不是因为LWRP没用,而是我当时的测试场景并没有使用到能带来提升的功能(例如点光,扭曲等),这些效果其实在默认管线时期都是压根不会去想用的效果因为根本跑不动。希望大家在看了这篇文章后,能对新的管线有哪些优势以及适合用在哪些地方有个更深入的了解,然后再根据大家的项目需求进行取舍。这也是为什么我最初说LWRP这个名字给大家带来了很多误解和困惑,因为LWRP性能好,并不是因为他轻量,而是因为他实现就不一样,他选择了一条可伸缩性和适配性更强的实现方式,因此新名字Universal RP,比LWRP能更好的说明他的目的和作用。
使用中遇到的坑
多相机叠加
这应该是使用URP过程中遇到的最大的坑,多相机叠加对于UI来说至关重要,不支持这个也就相当于不支持在UI上放3D模型和特效,不支持这个基本上对于国内的用户来说就相当于没法用了
好在Unity官方已经确认即将加入多相机叠加功能,并在2019.3中正式实现。在等待19.3的这段时间,跟大家分享一个强制开启多相机叠加的方法,以方便大家提前入坑。 其实方法简单到只需要注释3行代码!
打开ScriptableRenderer.cs(适用用LWRP6.7以上,也许以下也行,需要大家自己看),找到GetCameraClearFlag这个方法,按照下面的方法注释掉对应的3行代码
注释完上面的3行代码后,只需要确保第二个相机的Background Type为Don't Care,并且将Opaque Texture和Depth Texture设置为Off,然后就能开心的使用UGUI的Screen Space-Camera了。 不过上面的修改只能保证Editor和PC平台下的显示正常,手机上的支持需要大家等Unity官方的新版本,或者也可以尝试自行修改LWRP的源码,应该也能改好
版本升级带来的重构
LWRP在升级过程中发生了2次大幅重构,一次是5.x过后将原本的_MainTex等变量名修改为了_BaseMap,第二次是将LWRP更名为UniversalRP。
最痛苦的是第一次更名,直接导致我们很多使用自定义shader的材质球报废…… 主贴图都找不到了,虽然Unity提供了升级工具,但是只对使用内置Shader的材质球起效……
第二次更名带来的影响就小不少了,只是命名空间变更,Unity也提供了升级工具,理论上不会出现特别大的问题,但是自己修改过的东西难免还是得手动处理下
TLDR;版
LWRP/URP是个非常值得大家尝试的管线,目前的稳定性也已经足够用于商业项目,他所带来的扩展性绝对是用过就会爱上的。充分了解LWRP的特性之后,相信大家能更进一步提升项目的表现力和性能