:unity性能优化之drawcall优化-1

Posted 魂玉天成

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:unity性能优化之drawcall优化-1相关的知识,希望对你有一定的参考价值。

目录

 前言:

一、什么是drawcall

二、如何合批

 1、什么是合批?

  2、静态批处理

1、什么是静态批处理:

2、静态合批的规则

3、动态批处理

4、GPU Instancing

1、GPU instancing的定义       

2、编写支持GPU instancing Shader步骤

5、Scriptable Render Pipeline Batch(SRP Batch)

1、SRP Batch的定义

2、SRP Batch工作原理

3、SRP Batch规则

4、支持SRP Batch的管线 

总结:


 前言:

       

        有时候内存并不是我们的瓶颈,可能渲染drawcall过多是我们的压力,drawcall过多的最直接后果是程序可能卡,耗电量大等表现。

        移动端的程序不比PC端,留给程序员的优化空间比较小,一般对于相应的渲染指标都比较苛刻。那么设定多少个Drawcall是一个比较合理的标准呢,一般情况下,我们应该尽可能将控制在Drawcall在200平均值一下,对于效果特别要求的可以在250左右,不建议超过这个数

          在讲解drawcall之前,我们先讲解下什么是Drawcall

一、什么是drawcall

        drawcall是cpu对图形绘制接口的调用,CPU通过调用图形库(directx/opengl)接口,命令GPU进行渲染操作。

CPU和GPU之间的数据是通过命令冲区commandBuffer进行传输的。命令缓冲区包含了一个队列,由CPU向其中添加命令,GPU去读取命令,添加和读取的命令都是相互独立的。当CPU需要渲染一个对象时,就可以向命令缓冲区中添加命令,而当GPU完成上一个渲染任务后,就会从命令缓冲区中再取出一条命令并执行它。在渲染绘制过程有很多命令,drawcall就是其中一种。

        我们经常认为,Drawcall是通常认为GPU是Drawcall产生的瓶颈,其实不然,真正的元凶是CPU。

        在每次调用DrawCall之前,CPU需要向GPU发送很多内容,包括数据、状态和命令等。在这一阶段,CPU需要完成很多工作,例如检查渲染状态等。而一旦CPU完成了这些准备工作,GPU就可以开始本次渲染。GPU的渲染能力很强,渲染速度往往快于CPU提交命令的速度,相对来说CPU与GPU命令交互的过程是非常耗时,CPU在等待GPU指令返回之前这期间什么都做不了。如果DrawCall的数量太多,CPU就会把大量时间花费在提交DrawCall上,造成CPU的过载。

        比如:我们渲染10个模型,如果每次渲染时都调用一次Drawcall,那么总共需要调用10次Drawcall;如果10个模型能够合并调用,那么只需要调用一次drawcall,减少了CPU与GPU的指令交互的时间,性能不言而喻,自然就提升了许多。

        既然drawcall是主要的性能瓶颈,那么如何减少Drawcall呢?合批(Draw Call Batching)就是最终的解决办法。


二、如何合批

    1、什么是合批?

                将多个渲染对象的CPU渲染指令统一一起来向GUP提交,将多个独立Drawcall合并成给

        一个drawcall的指令方式。

        绘制调用批处理是一种组合mesh的绘制调用优化方法,以便Unity可以在较少的绘制调用中render mesh 。Unity提供以下内置的绘制调用批处理方法:

                静态批处理(Static batching

                动态批处理(Dynamic batching

                SRP Batcher (只在UPR或SRP项目中有效)
        对于合批是有些限定的,基本规则如下: 

                1)带有如MeshRender、TrailRender、LineRender、ParticleSystem、SpriteRender组

                     件的mesh支持合批。

                2)带有SkinMeshRender和 布料模拟的mesh是不能合批。

                GPU Instancing

  2、静态批处理

            1、什么是静态批处理:

                     静态批处理是一种绘制调用批处理方法,它结合了不移动的mesh以减少绘制调用。

                     它将组合的mesh转换为世界空间,并为它们构建一个共享的顶点和索引缓冲区。然

                     后,对于可见模型,Unity执行一系列简单的绘制调用,每个调用之间几乎没有状态变

                     化。

                     静态批处理不会减少绘制调用的数量,而是减少它们之间的渲染状态更改的数量

                     态批处理比动态批处理更有效,因为静态批处理不会转换CPU上的顶点。

        对于静态mesh,Unity将其组合并一起渲染。将场景的物件勾选static就是告诉编辑器,该Game Object对象不能被移动并且需要合批。

如图:

    2、静态合批的规则

          1)必须符合合批的基本规则

          2)必须是static类型的mesh,不能移动的mesh

          3)mesh是一样的并且材质相同,有meshRender组件

          4)在大多数平台上,批处理限制为 64k 个顶点和 64k 个索引(OpenGLES 上为 48k 个索

                引,在 macOS 上为 32k 个索引)如果超过会合批成另外个mesh

          5)合批的mesh如果Scale不同无法合批,合批会被打断

          6)相同顶线信息和UV的才能一起合批:

                如:Unity可以对使用顶点位置、顶点法线和一个UV的mesh进行批处理,但不能与顶点

                坐标、顶点法线、UV0、UV1和顶点切线的mesh一起批处理

           7)mesh顶点数必须大于0

           8)Mesh Renderer component组件不使用具有DisableBatching标记设置为true的着色器的

                任何材质。

          9)不同的贴图信息的无法合批。如:烘焙的光照贴图不相同的mesh无法合批在一起

          10)模型的GameObject必须是active状态

          11)位置不相邻的中间夹杂着不同材质的其他物体,不会批处理。

          12)动态改变Render.material会造成一个新的material拷贝,应该使用render.shareMaterial

                 保证材质共享,否则不能合批。


3、动态批处理

      1、动态合批的定义

             对于足够小的mesh,这将在CPU上变换它们的顶点,将相似的顶点分组在一起,并在一

              次绘制调用中渲染它们。

              Unity build-in的调用批处理比手动合并mesh有几个优点;最值得注意的是,Unity仍然可

              以单独剔除mesh。然而dynamic batch会导致一些CPU开销。

      2、动态合批规则:

                1)Unity 无法将动态批处理应用于包含超过 900 个顶点属性和 300 个顶点的网格。

                这是因为网格的动态批处理具有每个顶点的开销。例如,如果您的着色器使用顶点

                位置、顶点法线和单个 UV,则 Unity 最多可以批处理 300 个顶点。但是,如果您

                的着色器使用顶点位置、顶点法线、UV0、UV1 和顶点切线,则 Unity 只能批处理 180

                个顶点。

                2)Unity 无法将动态批处理应用于在其变换组件中包含镜像的对象。例如,如果一个对

                    象的比例为 1,而另一个游戏对象的缩放比例为 –1,Unity 无法将它们批处理在一

                    起。

       

                 3)如果对象使用不同的material实例,Unity 无法将它们批处理在一起,即使它们本质

                    上是相同的。Shadow cast是个例外,仅管Shadow casters使用不同的材质,但是只

                    要它们的材质中给Shadow Caster Pass使用的参数是相同的,他们也能够进行

                    Dynamic batching。

                4)具有lightmap的GameObject具有其他渲染器参数。这意味着,如果要批量光照贴图

                     游戏对象,它们必须指向相同的光照贴图位置。

                5)Unity 无法将dynamic batch完全应用于使用多passShader的对象。

                6)几乎所有 Unity 着色器都支持Forward render中的多个光源。为了实现这一点,他们

                     为每个光源处理一个额外的render pass。Unity 仅对第一个pass进行批处理。它无法

                dynamic batch附加的每个光源的所产生的pass 进行合批处理。

                7)旧版延迟渲染路径,不支持动态批处理,因为它在两个渲染通道中绘制对象。第一遍

                     是轻量级预传递,第二遍渲染对象。


4、GPU Instancing

1、GPU instancing的定义       

        GPU Instancing是一种drawcall优化方法,它在一次绘图调用中使用相同材质render mesh 的

        多个副本。多个mesh的副本都称为Instance。这对于绘制场景中多次出现的对象非常有用,

        例如树或灌木丛。GPU实例化在同一drawcall中渲染相同的mesh。

        每个实例可以具有不同的属性,例如“Color或“Scale”。要对材质使用GPU实例,请在

        material 中Inspector中属性中选择"Enable GPU instacing”选项。、

是否是所有的都支持GPU Instancing?答案肯定不是。只有在支持GPU Instancing的shader才有可能

2、编写支持GPU instancing Shader步骤

shader编写一定需要经历如下几步:

Shader"MyGPUInstance"
    Properties
        ...
    
    SubShader
        ...
        Pass
            ...
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile_instancing //第一步
            ...
            struct a2v
                ...
                UNITY_VERTEX_INPUT_INSTANCE_ID //第二步

            ;
            struct v2f
                ...
                UNITY_VERTEX_INPUT_INSTANCE_ID //第二步
            ;
            v2f vert(a2v v)
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v); //这里第三步
                UNITY_TRANSFER_INSTANCE_ID(v,o); //第三步
                ...
                return o;
            
            fixed4 frag(v2f i):SV_Target
                UNITY_SETUP_INSTANCE_ID(i); //最后一步
                ...
            
            ENDCG
        
    
    FallBack"Diffuse"

并且在材质面中勾选 Eanble GPU Instancing选项。如图:


5、Scriptable Render Pipeline Batch(SRP Batch)

1、SRP Batch的定义

SRP Batcher是一个渲染循环(loop),可以让相同的shader Variant的材质的GameObject能够进行合批,加速你的CPU渲染。

SRP Batcher 通过批处理(batching)一系列绑定(Bind)和绘制(Draw)GPU 命令,来减少DrawCalls之间的GPU设置(工作量)。也就是之前一堆绑定和绘制的GPU命令能够集合的集合起来,不需要一步步设置而来减少CPU与GPU交互次数,从而减少CPU执行的时间。

2、SRP Batch工作原理

传统的合批方法是减少绘制的对象。相反,SRP Batch是减少渲染的状态和执行过程。

我们看到在内置渲染管线中我们需要设置Meterial和Object CBUFFER,如果材质参数不同,会被打断;但是对于SRP渲染管线,只有变体不同时,才会被打断合批。

3、SRP Batch规则:

  1. GameObject必须包含mesh或者 skinned mesh。它不能是粒子。
  2. Game Object没有使用MaterialPropertyBlocks设置材质信息
  3. Shader必须兼容SRP

         

4、支持SRP Batch的管线 

功能

内置渲染管线

通用渲染管线 (URP)

高清渲染管线 (HDRP)

Custom Scriptable

Render Pipeline (SRP)

SRP Batcher


总结:

        对于合批,我们应该实际情况做出选择,它们的合批效率从高到低依次是static->GPU instancing-->Dynamic->SRP。我们知道合批的基本条件是同一个模型,并且是同一份材质信息(动态合批和SRP可以不同)。我们不仅要了解合批的规则,我们也要了解合批限制与在什么情况下被中断,同时要了解他们的优劣,才能选择合适的合批方式。

        由于篇幅原因,该篇主要讲解内容是drawcall 在什么情况产生的以及 减少drawcal的常用方法。下篇我会以项目的具体内容角度讲解如何对drawcall细致优化。

Unity优化之Drawcall

一、什么是Drawcalls

在Unity中,每次CPU准备数据并通知GPU的过程就称之为一个DrawCall。这个过程会指定一个Mesh被渲染,绘制材质。

二、Drawcalls有什么影响

为了CPU和GPU可以进行并行工作,需要一个命令缓冲区,由CPU向其中添加命令,然后由GPU从中读取命令,这样就实现了通过CPU准备数据,通知GPU进行渲染。在每次调用DrawCall之前,CPU需要向GPU发送很多内容,主要是包括数据,渲染状态,命令等。所以如果DrawCall数量过多就会导致CPU进行大量计算,进而导致CPU的过载,影响程序运行效率。

三、查看Drawcalls

在unity中查看drawcalls有2个方法,如图1所示查看Game窗口中的State下的Batces,这个数量和drawcalls数量相同。第二种方法是查看Window  → Analysis → Profiler 中的Rendering下的Drawcalls,如图2所示。 

图1                                                                                图2

四、Drawcalls优化

1. 3D场景优化

1.1 静态批处理

静态批处理首先需要到 Project Setting → Player → Other Setting 中将Static Bathing 勾选上,然后把需要静止的物体标记为Static,然后无论大小,相同材质的都会组成Batch。如下图所示,当没有勾选 static 的时候,场景中2个cube的drawcalls为4,勾选了之后drawcalls变成了3。

1.2 动态批处理

动态批处理需要在Project Setting → Player → Other Setting 中将Dynamic Bathing 勾选上,Unity会自动将使用相同材质的物体合并处理。如图所示,场景中虽然有3个cube,但是drawcalls还是只有3。

不过,在使用动态批处理的时候,具有一些局限性。

  • 顶点属性最大限制900的可移动物体,

  • 使用lightmap的物体不行进行批处理

  • 使用多通道的shader也不会进行批处理

  • 缩放比不同的物体不会批处理

1.3 勾选 Enable GPU Instancing

在使用大量重复的物体时,需要将该物体的材质的 Enable GPU Instancing 勾选上,这样Unity会将Mesh相同的物体合并处理,常用于树木,植被,粒子等。如图所以虽然我放置了5个cube,但是drawcalls依然只有3。

正在上传…重新上传取消

1.4 减少实时光照和阴影效果

当开启灯光,在没有开启阴影的时候drawcalls为3,开启了之后drawcalls变成了7。实时阴影会导致drawcalls大幅上升,建议关闭实时阴影,使用lightmap满足你想要的阴影效果。

1.5 合并Mesh和材质球

如果一个模型有2个或者以上的材质球的时候,drawcalls会直线上升,所以应该劲量将mesh和材质球合并成为一个,以减少drawcalls。如图所示我在一个测试cube中放置了3个material,这个时候drawcalls从3变成了10(和不同的material有关,每个material最低为1)。

1.6 渲染顺序调整

Unity的渲染是有顺序的,这个顺序我们可以自己调整,相机按照深度进行渲染。在图中我使用了3个cube梯次排序,其中第一个和第三个使用相同的材质球。当第二个cube在中间时,drawcalls为5,第二个cube在前或者后时,drawcalls为4。

出现图中情况的原因是在绘制第一个cube时候使用材质球A,绘制第二个cube的时候使用材质球B,绘制第三个cube的时候使用材质球A,这个时候第二个打断了材质球A的渲染使用,使第一个和第三个材质球分开渲染了。

2. 2D UI优化

2.1 图集制作

在制作UI的时候,文件夹中小的图片可以在Unity中制作成图集再给UI使用,这样可以减少drawcalls,具体操作为首先在Project Setting →Editor → Sprite Packer 中选择Mode 为Enable。然后在Assets中右键 Create → Sprite Altas,再将需要打包图集的图片资源拖到Altas中的Objects for Packing中,再点击Pack Preview,图集就制作好了。

如图所示,在没有使用图集的时候,3张图drawcalls为5,当使用的图集时,drawcalls变成了3,3个图使用同一张图集,drawcalls也就变成了3。

2.2 不同图集重叠会打断合批

不同的图集之间重叠会打断合批,在制作UI的时候应该注意不同的UI放置位置,避免出现这种问题,如图,2个图集的UI在分开放的时候drawcalls为4,重叠隔开之后drawcalls变成了5。

2.3 谨慎使用多canvas

每添加一个canvas会添加一个drawcalls,哪怕你下面的资源使用同一个图集也会添加。如图所示当我添加一个canvas,改变了第三个图的层次之后,drawcalls从3变成了4。

2.4 Mask会打断合批,新增drawcalls

当我给使用了图集的一个UI添加一个Mask组件之后,drawcalls从3变成了5。这里可以使用Rect Mask 2D,效果相同但是drawcalls不会增加,回到了3。当我在有Mask组件的UI下添加一个Image,sprite还是使用图集中的图片时,发现drawcalls从5变成了6,说明mask组件还会打断合批,使Mask内外资源分开渲染。这个时候将Mask改为Rect Mask 2D,drawcalls从6变回4,但是也比最初的3多了1,说明使用Rect Mask 2D 也会打断合批,分开渲染。

2.5 图文混排打断合批

新加入两个Text文本(Text Mesh Pro效果相同),drawcalls从3变到4,因为text和image有不同写渲染方式需要分开渲染,移动Text,当2个Text打断了图的连续深度排序时,图片的合批会被,打断,生成多的drawcalls,在图文排版是应特别注意。下图列举了一些错误排版方式和正确方法以及建议排版。

正确:

错误:

建议:

GPU instance 使用一次渲染调用来绘制多个物体,来节省每次绘制物体时CPU→GPU的通信

使用限制优点
静态合批静止的问题,大内存,大包体 StaticBatchingUtility.Combine适用范围较广
动态合批* 顶点属性最大限制900,<br>* 使用lightmap的物体不行进行批处理<br>* 使用MultiplePass的shader也不会进行批处理在一些动的物体上面,内存不会显著增长,不会影响打包的包体
GPUInstance一般用于大批生成的物体没有动态合批那样对网格数量的限制,也没有静态网格那样需要这么大的内存
SRP Batcher不同mesh,只要使用相同shader且变体一样即可 缺点:constant Buffer显存固定开开销,不支持节省Uniform Buffer的写入操作;按shader分 Batch,预先生存Uniform Buffer,Batch 内部无CPU Write

以上是关于:unity性能优化之drawcall优化-1的主要内容,如果未能解决你的问题,请参考以下文章

[Unity优化] unity性能优化

Unity优化之Drawcall

unity 简单的性能优化

cocos creator 性能优化之减少drawcall数量

unity3d 如何UI优化和减少DC(DrawCall)

Unity中的批处理优化与GPU Instancing