URP源码学习(一)整体框架理解

7 篇文章 2 订阅

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去做真正的渲染。

URP源码学习 (二) 管线,Pipeline

默认渲染实现

之前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实现方式和传统的有一些优化,后续更新在光照部分。

URP源码学习 (四) 光照

阴影处理

12版的效果和内置的差不多,实时光支持shadowmap和屏幕空间阴影两种,屏幕空间阴影也会用到shadowmap贴图。点光源和聚光灯的实时阴影也支持,还加了shadowmask。

相比于内置的实现,也做了一些修改和优化,之后更新到对应的博客中,先加个地址~

URP源码学习 (五) 阴影

后处理

这部分看起来没有多少特殊的地方,还没看太细

URP源码学习 (六) 后处理

获取各种rt的方式修改

渲染用到的rt,一般是depth和opaque。需要copy颜色和深度,到单独rt的原因是,同一张rt不能即作为被采样的贴图,又被当做渲染的目标。

URP提供了管线的全局设置,以及相机单独设置的方式,color通过CopyColorPass获取,在渲染天空盒之后,透明之前调用。depth有两种获取方式,CopyDepthPass用一个pass,但是需要硬件和图形API支持,DepthOnlyPass没有限制条件,但是会导致drawcall数量增加。

Opaque的贴图,变相实现了内置管线grabpass的效果,并且手机上性能更好。但是不能在任意时间点多次采样。

在渲染管线中,rt是个挺重要的东西,之前写了一点理解,还准备再更新一下,先放个链接

URP源码学习 (七) 一些细节和理解

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的一篇博客有比较清楚的说明(地址在评论里,不贴了,不过博客对应的版本有点老,)。下面的是一些自己的理解。

  1. 渲染的最小单元是pass,许多pass组合成为renderer。核心功能是在指定事件点,决定哪些物体被渲染,渲染指定shader pass到指定目标buffer,也就是渲染数据保存到哪里。
  2. renderer代表一个渲染策略,以什么样的顺序调用pass,包括feature里添加的pass,渲染一个相机看到的物体。对每个相机生效,一个相机一个,可动态切换策略。这个renderer才是常规说的渲染管线,URP默认有forward、deferred和2D。
  3. pipeline驱动整个管线,多个相机分别渲染,每个相机有自己的renderer,按指定方式渲染,renderer调用pass,以及feature,执行指定shader逻辑。

引擎和图形API的交互,通过CommandBuffer,主要的接口是设置rt,以及触发渲染DrawXXX。

URP提供的扩展,主要以feature的形式,简单说就是在指定的渲染事件点,插入pass。另外就是可以自己实现Renderer策略,可以通过相机动态指定。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值