【Unity】批处理和实例化的底层优化原理
- URP
- 1.基础概念
- SetPassCalls
- DrawCalls
- Batches
- 2.重要性排序
- 既然如此为什么仍然要合批?
- 3.unity主流的合批优化方案和优先级
- Early-Z
- 透明物体情况
- 4.合批(小场景但是很复杂很多小物件刚需)
- 合并纹理图集更改uv映射(共享材质)
- 静态合批(一批次最多是64000顶点)
- 刺客信条场景案例
- 手动合批
- 动态合批
- GPU Instancing(256)
- SRP
- 其他
以下图片以及理解均来自b站up主:别看着我笑了。直达链接
侵权请联系我,第一时间删除!
看前须知渲染管线基本流程!!!!!!(games101前9课)
URP
URP 全称为 Universal Render Pipeline,即通用渲染管线。它是 Unity 引擎提供的一种轻量级、可定制的渲染管线。
1.基础概念
SetPassCalls
这是对drawcall的准备工作,CPU设置资源和改变内部设置的地方,称之为渲染状态(下面有Unity的状态图)。例如改变材质,是图形API消耗极大的操作。
- Unity可设置渲染的状态
豆包解释:
渲染的目标就是:不要让渲染跳出Batch外,所以需要合批(合batch)。
DrawCalls
Batches
2.重要性排序
通过实验和例子发现,如果setpass相同的话(这里体现在更换材质),批次的多与少对渲染性能的优化其实是微乎其微的,甚至会更差。
这个例子更能看出区别,上面是合批优化的,下面是减少setpassCall的,可以发现前者虽然合批到了极致,但是后者只通过将不同的材质合批减少了一次setpassCall耗时就低于了前者,说明在渲染消耗的时间setpassCall是占大头的。
既然如此为什么仍然要合批?
既然合批之后的时间甚至不如不合批,那为什么我们还要去优化批次呢?
原因:unity可以识别一些简单的物体是否是相同的材质,并且自动合批,但是在大多数复杂的环境下unity在渲染的时候不能分辨哪些是同一个材质,只是在某些复杂场景下,比如动态物体较多、材质变体复杂等情况,可能无法完美做到所有相同材质物体都能高效合并,导致仍存在多次切换相同材质引发 SetPass Call 的情况,所以合批是为了让尽可能减少的切换到相同材质的情况导致多次调用 SetPass Call。
3.unity主流的合批优化方案和优先级
Early-Z
可以在渲染之前确定不透明物体是否要渲染,是否遮挡并且剔除。然而如果遇到半透明物体由于需要确定透明物体后面的材质,所以不可避免的需要切换渲染状态。
透明物体情况
左图勾选静态合批,如果没有红球,那么在profile页面确确实实看到了静态合批减少了2次DrawCall(显示在Static Batching栏中),而加上红球后,由于前面的透明物体的渲染必须先知道后面的物体是怎么渲染的才能渲染,所以前面和最后面的透明物体无法合批一同渲染,因此批次增加了3(2透明球,1红色球)。
4.合批(小场景但是很复杂很多小物件刚需)
本质:
合并纹理图集更改uv映射(共享材质)
- 如果多个模型相同或不同的物体(主要看Transform是不是静态的),材质不同,可以通过合并图集更改uv映射,来减少材质更换的次数。
- !局限性:合并图集可能增加纹理内存占用,若纹理利用率低,会浪费内存;调整 UV 映射过程较复杂,尤其是模型结构复杂时,手动调整工作量大,还可能影响纹理映射效果;此外,如果模型材质有复杂的动态变化效果,这种方式可能无法很好地满足需求。
静态合批(一批次最多是64000顶点)
原理:仅仅一次处理!!!
-
当使用的材质是多 Pass(一个渲染过程中执行多个渲染通道,比如一些复杂材质可能既要渲染基础颜色,又要渲染高光、阴影等,每个步骤算一个 Pass )时,静态批处理依然有效
-
然而如果合并多个相同网格会增加CPU的内存,不合并又会增加渲染时间,up主给出的建议是合并不同模型相同材质的静态网格物体,不过我个人认为下面说法更正确。
-
实际开发中要怎么权衡这种空间换时间的利弊呢?
总结:
1.合并相同且靠近的网格,但又不能过多。
2.考虑平台对内存资源的紧张程度,以及优先性。例如CSGO需要高帧数那么应该在许可的范围内以空间换时间,而在移动平台等资源紧张的平台应该更关注CPU内存的使用。
3.大型复杂场景对GPU压力大的情况下,尽可能利用CPU缓解压力。
刺客信条场景案例
把一个大的模型拆分成多个小块,通过小块之间的不同组合,实现不同的形状,使用同一个材质对应的uv地段,再通过shader脚本的编写就可以达成宏达而完美的场景甚至不用切换材质!
手动合批
任何相对运动且相对静止的物体,都可以通过再unity中调用Mesh.CombineMeshes,或者在建模软件中给它合批了。
然而unity不能自主的剔除手动合批的物体,您需要自己剔除(cull)!
动态合批
是在CPU中处理的。
据说很鸡肋使用条件十分之苛刻,只能在很有限的范围内进行优化,甚至是负优化,HDRP甚至不支持。
然而动态合批能够很好的处理unity的粒子系统。
GPU Instancing(256)
GPU Instancing(GPU 实例化)是一种渲染技术,其原理核心在于高效地多次渲染相同的几何体(包括缩放),减少 CPU 与 GPU 之间的数据传输和渲染状态切换开销,以此提升渲染性能。
基本原理
传统渲染方式下,若要绘制多个相同的模型,每次绘制都需要 CPU 向 GPU 发送完整的模型数据(如顶点数据、纹理等)和渲染指令,这会产生大量的重复数据传输,而且每次绘制前都要进行渲染状态的设置和切换,消耗大量的时间。
GPU Instancing 则是一次性将模型的基本数据(如顶点数据、索引数据)发送到 GPU,同时将每个实例的差异化数据(如位置、缩放、旋转、颜色等)打包成一个实例数据数组也发送给 GPU。在渲染时,GPU 根据这些实例数据对同一个基本几何体进行多次绘制,每次绘制使用不同的实例数据,从而实现多个相同模型的高效渲染。
具体步骤:
1.顶点数据:定义了一个三角形的顶点数据。
2.实例数据:定义了每个实例的位置偏移量。
3.着色器代码:顶点着色器负责将实例的位置偏移量应用到顶点位置上,片段着色器则设置了每个实例的颜色。
4.渲染循环:在渲染循环中,使用 glDrawArraysInstanced 函数进行实例化渲染。
不使用于顶点少的网格的合批
SRP
SRP 全称为 Scriptable Render Pipeline,也就是可编程渲染管线。传统的渲染管线是引擎预设好的,开发者只能在有限范围内进行参数调整。而 SRP 允许开发者使用脚本来自定义渲染流程,从底层实现对渲染的精细控制。这意味着开发者能够根据项目的具体需求和目标平台,灵活地设计渲染算法和效果。
和别的管线的区别与关系?
组成部分
- URP(Universal Render Pipeline):通用渲染管线,是 SRP 的一种实现,具有轻量级、易于使用和跨平台的特点,适合大多数类型的项目,尤其是移动游戏和独立游戏。
- HDRP(High Definition Render Pipeline):高清晰度渲染管线,专注于提供高质量的渲染效果,支持先进的光照、阴影和后处理技术,适用于对画面质量要求较高的项目,如 AAA 级游戏和影视制作。
- 自定义渲染管线:开发者可以根据自己的需求,完全从头开始创建自定义的渲染管线,实现独特的渲染效果和性能优化。
- 由于我们说URP在不同材质的情况下除了打成图集之外一定会切换渲染状态从而增加SetPassCall。渲染状态(如材质、着色器、纹理、混合模式等)的切换需要 CPU 生成并提交大量渲染命令(如 SetPassCall),这些命令的生成和组织会消耗 CPU 资源,尤其是当状态切换频繁时。
所以引出SRP,SRP是通过可编辑的自定义程序来控制合批的,shader发生改变的时候才会增加setpasscall,当材质发生改变时,如果只是调整了材质的参数(如改变颜色、更换纹理贴图、调整粗糙度数值等),而没有更改所使用的 Shader 本身(即渲染逻辑未变),那么 Shader 就不会改变。例如在基于物理的渲染(PBR)Shader 下,可以创建出金属、木头、布料等不同材质,它们使用相同的 Shader 渲染逻辑,只是各自的颜色、粗糙度、金属度等参数不同 。只有当需要采用不同的渲染方式,比如从普通的漫反射渲染切换到基于光线追踪的渲染时,才会更换 Shader 。
上: URP
下: SRP
- CPU 执行逻辑
仅处理引擎属性:CPU 专注于更新物体变换等引擎属性,存入 Per Object large buffer。
材质数据按需处理:只有当材质内容真正改变时,才触发材质数据的设置代码(“Only when material content change”),否则复用 GPU 内存中已有的材质 CBUFFER。
何时会改变?
左:URP
右: SRP
其他
来自hansu1佬
- 避免过多的 UI 绘制:
在使用 Unity 的 UI 系统时,过多的 UI 元素可能导致 Draw Call 数量激增。可以通过合理布局、合并 UI 元素、减少透明度变化等方式来优化 UI 渲染。
- 减少实时光照和阴影:
实时光照和阴影会增加 Draw Call,尤其是多个光源和动态阴影的情况下。尽量使用 烘焙光照 来替代实时光照,可以显著降低 Draw Call 数量。
-合并网格(Mesh Combining):
- 如果场景中有多个使用相同材质的小型网格,可以在运行时将它们合并为一个较大的网格。这样可以减少需要渲染的物体数量,进而减少 Draw Call。
例如,在 Unity 中可以使用 Mesh.CombineMeshes() 来合并多个网格。
博主认为作为一个初学者理解到此就足够面对面试的八股了,往后自行哔站看。