2021.12.2更新。源码基于URP12,最新的URP已经是13,但是功能上没有大的改动,还是以12为基础理解URP。
开篇
unity新出的SRP功能,可能是渲染的未来(希望是),但是资料比较少,做手游开发,又只能用到URP,资料更少,最近查了很多资料,加上看源码,对于URP有了一些了解,个人感觉还是很实用的,在手游上应该潜力很大。
准备把看的东西和自己的理解整理下来,由于对渲染理解的不深,可能有很多地方理解不到位或是错了,希望看到的大佬们及时指出,感谢~
目前准备写的东西,包括框架的整体理解(就是这篇),结合源码分析渲染流程各部分的细节,光照和阴影的流程,shader代码分析,以及后续可能发现的新资料。
渲染这块陆续也学了几年,都是零散时间看些零散资料,总觉得没有多深的理解,SRP可能是个不错的切入点,能操控更多细节,而又不像dx那样复杂,unity还有不错的demo,以我对渲染的了解啃起来挺有难度,但是这也是做技术的乐趣所在吧。
所有内容基于URP 12 版本。
URP的优势
最核心的优势是相比内置管线提高渲染效率,附带提高渲染效果。手机上,渲染效果的限制主要是性能方面,而不是技术方面。
另一个优势是方便定制管线,可以单独更新package,在不更新unity版本的情况下单独更新URP代码。我们项目的做法就是用本地的package,有新的功能把代码合并过来,相比更新unity要容易的多。
对于开发来说,了解源码,面对一些bug和需求,更容易发现问题,以及选择更好的解决方案。
管线驱动
管线做了两件事,一是设置各种全局数据,二是驱动各个pass去做真正的渲染。
默认渲染实现
之前unity的博客介绍,URP是单pass前向渲染管线,到12已经不是了,URP内置forward和deferred渲染。ForwardRenderer也改成了UniversalRenderPipeline。
前向渲染的单pass,并不是只能调用一个pass,而是对光照而言,没有之前内置管线的forward base和add,而是在一个pass里,处理多个光源的计算。需要多个pass实现的效果,需要设置DrawingSettings的shaderPassNames,就会依次调用指定名字的pass进行渲染了。
URP源码学习 (三) UniversalRenderer渲染管线
光照处理
URP支持forward和deferred两种光照策略。
forward中,光照计算在一个pass,循环每个光源数据计算,也就是一个物体的光照,在一个drawcall计算完成,而不用每个光源一个drawcall。这样的好处是可以有多个光源,但又不会造成drawcall翻倍的现象。只是目前URP的版本,对光源和阴影有一些限制,后续会详细说明。
deferred实现方式和传统的有一些优化,后续更新在光照部分。
阴影处理
12版的效果和内置的差不多,实时光支持shadowmap和屏幕空间阴影两种,屏幕空间阴影也会用到shadowmap贴图。点光源和聚光灯的实时阴影也支持,还加了shadowmask。
相比于内置的实现,也做了一些修改和优化,之后更新到对应的博客中,先加个地址~
后处理
这部分看起来没有多少特殊的地方,还没看太细
获取各种rt的方式修改
渲染用到的rt,一般是depth和opaque。需要copy颜色和深度,到单独rt的原因是,同一张rt不能即作为被采样的贴图,又被当做渲染的目标。
URP提供了管线的全局设置,以及相机单独设置的方式,color通过CopyColorPass获取,在渲染天空盒之后,透明之前调用。depth有两种获取方式,CopyDepthPass用一个pass,但是需要硬件和图形API支持,DepthOnlyPass没有限制条件,但是会导致drawcall数量增加。
Opaque的贴图,变相实现了内置管线grabpass的效果,并且手机上性能更好。但是不能在任意时间点多次采样。
在渲染管线中,rt是个挺重要的东西,之前写了一点理解,还准备再更新一下,先放个链接
SRP Batcher
这部分可能是SRP性能提升的关键点。
可以将没有进行静态合并,也没法通过instancing渲染的使用相同shader的物体,通过CBuffer去保存每个物体材质球的参数,进而在不进行SetPassCall的情况下完成绘制。实际不会减少drawcall数量,但是减少了setpass,性能会提升很多。
原理是底层渲染循环可以使材质数据在GPU内存中具有持久性。如果材质内容没变,则不需要设置并上传缓冲区到GPU。相当于数据的缓存。
使用条件:对象必须处于网格中,对象不可以是粒子,shader要和SRP兼容,也就是用CBUFFER声明变量。
使用方法则很简单,只要在声明时用CBUFFER,并指定存储的方式(瞎猜的),目标看到的有PerMaterial、PerDraw、PerCamera。只看到了CBUFFER宏定义的地方,参数都可以有哪些没找到。看名字像是会分到不同的缓冲区,具体逻辑没找到相关资料,希望有大佬看到了教学一下。
使用FrameDebugger查看的一些tips,主要看的是drawcall的数量,数量越多越好,表示一个setpass对应了更多drawcall,但是这样合批的,在选中时,不能自动选中对应的transform了,有点不方便,但实际上是对应了多个transform,也没法显示。
对渲染框架的理解
首先管线的功能:一是组织渲染策略(实现核心渲染),二是封装和图形API的交互接口(GPU buffer交互,渲染数据提交接口封装)。三是提供用户扩展机制。
渲染的实现,分3个部分,pipeline,renderer和pass。Unity的一篇博客有比较清楚的说明(地址在评论里,不贴了,不过博客对应的版本有点老,)。下面的是一些自己的理解。
- 渲染的最小单元是pass,许多pass组合成为renderer。核心功能是在指定事件点,决定哪些物体被渲染,渲染指定shader pass到指定目标buffer,也就是渲染数据保存到哪里。
- renderer代表一个渲染策略,以什么样的顺序调用pass,包括feature里添加的pass,渲染一个相机看到的物体。对每个相机生效,一个相机一个,可动态切换策略。这个renderer才是常规说的渲染管线,URP默认有forward、deferred和2D。
- pipeline驱动整个管线,多个相机分别渲染,每个相机有自己的renderer,按指定方式渲染,renderer调用pass,以及feature,执行指定shader逻辑。
引擎和图形API的交互,通过CommandBuffer,主要的接口是设置rt,以及触发渲染DrawXXX。
URP提供的扩展,主要以feature的形式,简单说就是在指定的渲染事件点,插入pass。另外就是可以自己实现Renderer策略,可以通过相机动态指定。