【Unity Rendering】Unity SRP管线的底层执行过程
本文未经允许禁止转载
作者:Heskey0
B站:https://space.bilibili.com/455965619
邮箱:3495759699@qq.com
SRP底层
一. Scriptable Culling
我们先思考一个问题:调用ScriptableRenderContex.Cull()
的时候底层会发生什么
(1) Shadow Culling
先介绍阴影的Culling过程。首先,Unity会遍历场景中实时的Light,如果勾选了CastShadow,那么这个Light会遍历场景中的物体,如果物体也勾选了CastShadow,那么就会产生阴影。
底层:每个Cast Shadow的Real-time Light会分配一个Shadow的Job,每一个单独的Job完成对Cast Shadow物体的Cull
(2) Dynamic Objects Culling
我们再来看Unity对动态物体的Culling过程。Unity为场景中所有挂载了Renderer Component的物体维护了一个Renderer的List,IndexList存储了当前所有可见的Renderer在List中的下标。
【生成IndexList】
List中的Renderer会分组,每一组会分配一个Cull Job,每个Cull Job独自执行完Cull之后,所有Cull Job的结果合并,生成IndexList。这个过程不需要引入锁。
【ExtractRenderNodeQueue】
Unity为了保证渲染的速度,引入了RenderNode。对于所有IndexList中对应的Renderer对象,把Renderer里所有引用类型的数据展开,拷贝到一个struct中,这个struct就是RenderNode。RenderNode在内存中是连续的,RenderNode组成一个队列,称为RenderNodeQueue,方便做多线程渲染。
(注:因为RenderNodeQueue是由IndexList生成的,所以RenderNode对应的Renderer是场景中可见的)
二. Scriptable Draw
现在,我们知道了动态物体Culling的结果就是RenderNodeQueue。Unity接下来就要进行绘制了,先介绍一些绘制过程中的函数。
CommandBuffer.Blit();
CommandBuffer.DrawMesh();
ScriptableRenderContext.DrawRenderers();
ScriptableRenderContext.DrawShadows();
......
这些函数有什么关系呢?我们写着往下看
ExecuteCommandBuffer & DrawRenderers
有4个跟Command相关的List:
dynamic_array<ShadowDrawingSettings> m_DrawShadowCommands;
dynamic_array<DrawRenderersCommand> m_DrawRenderersCommands;
dynamic_array<RenderingCommandBuffer*> m_COmmandBuffers;
dynamic_array<Command> m_Commands;
调用DrawRenderers时,会产生1个DrawRenderersCommand添加到对应List中,同时也会产生一个Commands添加到对应List中,用来记录这个Command的类型及其在List中的下标。
调用ExecuteCommandBuffer时,会产生1个CommandBuffer添加到对应List中,同上。
m_Commands存储了所有command队列中的每一个command的类型和下标
三. Scriptable Render Loop
在插入Command之后,继续调用ScriptableRenderContext.Submit()
就会执行Render Loop
(1) PrepareDrawRenderersCommand()
我们先看Renderer,material,pass之间的关系
一个Renderer可以有多个材质( 有很多材质插槽 ),一个材质可以有多个Pass
在Culling的过程中,我们已经得到了RenderNodeQueue。然后我们要通过sort决定Draw的顺序
【执行过程】
通过RenderNode找到Materials,通过每一个Material找到Pass,通过每一个Pass生成一个
ScriptableLoopObjectData
,这个SctiptableLoopObjectData
中有一个标识,标识它是不是SRP batcher兼容的。对所有的
ScriptableLoopObjectData
进行排序
(2) Scriptable Render Loop
【CommandBuffer】
遍历提交的m_Commands,找到所有的command的类型和下标,然后Execute
【ScriptableLoopObjectData】
遍历
ScriptableLoopObjectData
,找到兼容SRP batcher的,然后扔到SRP batcher渲染器进行渲染,剩余的会交给传统的Draw渲染路径进行渲染。
(5) SRP batcher
SRP Batcher就是
-
把调用draw call前,一大堆CPU的设置工作给一口气处理了,增加了效率。
-
把材质的属性数据直接永久放入到显卡的CBUFFER里,那只要数据不变,CPU就可以
不需要把这些数据重新做设置工作。节省了CPU调用,增加了效率。
- 用专用的代码将引擎的属性(比如objects transform)直接放入到GPU显存,这个专用的代码是不是更快更强呢,
官方是这样么说的,用的词语是quickly,就是快。
- SRP Batcher并没有减少drawcalls,而仅仅是提高了效率。相当于一个人减肥了,减去了多余的脂肪和水分,但是器官结构啥的一个没少。总之就是有用。
SRP Batcher的工作原理
SRP Batcher诞生的原因:
在一个Drawcall被一个新的material使用的时候,有很多工作要做。
所以如果场景有越多的materials,就会有越多的CPU必须使用去设置GPU 数据。
传统的方法是减少DrawCalls的数量去优化CPU渲染性能。
因为Unity必须在调用drawcall前设置很多东西。
并且真正的CPU消耗来自那些设置工作,而不是GPU drawcall本身。
Drawcall只是一些Unity向GPU command buffer发送的bytes。
补充
-
SRP batcher是工作在CPU层面的,它做的事情就是减少SetPass Call。Unity在很久以前就把Draw Call和SetPass Call做了区分:Draw Call本身就是调用一个图形的API,它本身的开销并不耗。而开销高是高在我们做切换渲染状态的时候要提前为显卡准备非常多的数据,也就是SetPass Call的工作,准备这些数据往往来说是开销比较高的。评判标准:不管是默认管线还是SRP,SetPass Call最好都不要超过150,Draw Call的话可以高一些。
-
Vaulkan:SRP batcher在CPU层面的开销,比较可以关注的一个点是android上的vaulkan,它已经越来越成熟,有不少项目在立项阶段把vaulkan作为首选的API。其实使用了vaulkan的话,会有一个明显的发现就是,vaulkan在CPU上的开销要远远小于OpenGL。所以推荐!!!
-
SRP batcher和GPU Instance用的技术是差不多,如果大家是想绘制单一的物体(像草这样的),推荐大家使用GPU Instance。但是如果想做正常的场景渲染,比如说场景里的material多于5个,SRP batcher的速度要比我们手动做GPU Instance要划算的多的。
本文未经允许禁止转载
作者:Heskey0
B站:https://space.bilibili.com/455965619
邮箱:3495759699@qq.com