Unity中的静态合批、动态合批、GPU Instance 以及SRP Batching

Unity中的静态合批、动态合批、GPU Instance 以及SRP Batching

四种合批简介

GPU instancing

GPU instancing: 对同一网格,同时渲染多个副本时使用,底层调用的是多实例渲染接口,例如OpenGL的glDrawArraysInstanced接口。GPU实例对于绘制场景中多次出现的几何图形(例如,灌木丛)非常有用。首先使用GPU Instance,需要材质着色器支持 GPU 实例化,接着就可以在 Project 窗口中选择材质,最后在 Inspector 中勾选 Enable Instancing 复选框。
在这里插入图片描述

特点:

  • 使用 GPU 实例化可使用少量绘制调用一次绘制(或渲染)同一网格的多个副本。
  • GPU 实例化在每次绘制调用时仅渲染相同的网格,但每个实例可以具有不同的参数(例如,颜色或比例)以增加变化并减少外观上的重复。
  • GPU 实例化可以降低每个场景使用的绘制调用数量。可以显著提高项目的渲染性能。

使用GPU Instance的限制条件:

  • Unity 自动选取要实例化的网格渲染器组件和 Graphics.DrawMesh 调用。请注意,不支持 SkinnedMeshRenderer(骨骼蒙皮渲染)。

  • Unity 仅在单个 GPU实例化绘制调用中,批量处理那些共享相同网格和相同材质的游戏对象。使用少量网格和材质可以提高实例化效率。要创建变体,请修改着色器脚本为每个实例添加数据,下述shaderlab代码为官方示例代码。

  • 还可以使用 Graphics.DrawMeshInstanced 和 Graphics.DrawMeshInstancedIndirect 调用来通过脚本执行 GPU 实例化。

使用实例化渲染实例代码

Shader "Custom/InstancedColorSurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        CGPROGRAM
        // 基于物理的标准光照模型,并对所有光照类型启用阴影
        #pragma surface surf Standard fullforwardshadows
        // 使用 Shader Model 3.0 目标
        #pragma target 3.0
        sampler2D _MainTex;
        struct Input {
            float2 uv_MainTex;
        };
        half _Glossiness;
        half _Metallic;
        UNITY_INSTANCING_BUFFER_START(Props)
           UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_BUFFER_END(Props)
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

然后可以在C#脚本中给对应的对象设置Color属性

MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer renderer;

foreach (GameObject obj in objects)
{
   float r = Random.Range(0.0f, 1.0f);
   float g = Random.Range(0.0f, 1.0f);
   float b = Random.Range(0.0f, 1.0f);
   props.SetColor("_Color", new Color(r, g, b));
   
   renderer = obj.GetComponent<MeshRenderer>();
   renderer.SetPropertyBlock(props);
}

更多关于Unity GPU Instance的内容,请点击这里查看官方文档

static Batching

  • static Batching:静态合批,一般合批的对象是场景中不能动的物体,静态物体,并且在inspector界面勾选上static选项;Unity预先组合静态GameObjects的网格,然后将组合的数据发送到GPU,但单独渲染组合中的每个网格,静态合批不会减小drawcall,只是减少了渲染状态的改变次数。Unity仍然可以单独筛选网格,但每次绘制调用都不会占用大量资源,因为数据的状态永远不会改变。在Unity开启Static Batching,依次点击 Edit > Project Settings > Playe->Other Settings->enable Static Batching。对于Unity使用静态合批的具体要求可以参照官方文档
  • 支持静态合批的Unity渲染管线:默认渲染管线、URP(通用渲染管线)、HDRP、SRP。
  • 在运行时,通过脚本使用静态合批:Unity提供了运行时,通过脚本使用静态合批口:StaticBatchingUtility.Combine,这是一个静态方法,通过这种方式调用静态合批,就不需要在编辑器的inspector界面勾选上Static选项。
  • static Batching缺点与限制使用静态合批会增加内存!,因为使用静态合批,需要在内存中存储合成的几何体数据;如果多个GameObject使用了相同的网格数据,Unity会为每一个GameObject创建一个网格副本,这就意味着相同的几个数据有可能会出现多次!所以如果内存过大,就尽量避免使用静态合批。对于每一次静态合批,顶点数的限制时64000,如果超过这个值,Unity就会再次创建另外一个批次(Batch)。

Dynamic batching

  • Dynamic batching :动态合批,一般处理的是动态物体;在CPU上变换网格顶点,对共享相同配置的顶点进行分组,并在一次绘制调用中渲染它们。如果顶点存储相同数量和类型的属性,则它们共享相同的配置。例如,位置和法线。 在Unity开启动态合批,依次点击Edit > Project Settings > Player->Other Settings->enable Dynamic Batching。对于Unity使用动态合批的具体要求可以参照官方文档
  • 特点:由于动态合批是通过将多个符合要求的GameObject的顶点一起转换到世界空间,提升性能;相当于是作用在CPU,所以只有在顶点的转换过程比一个绘制调用(Draw Call)过程消耗更低,这种策略才是一个合理的选择。
  • 支持是动态合批的Unity渲染管线:默认渲染管线、URP、SRP。
  • Dynamic batching的限制:动态合批对于顶点属性的数量有限制,不能超过900个顶点属性,例如,材质的shader使用了位置法线UV坐标,那么顶点数就是900 / 3 = 300,如果使用了vertex position, vertex normal, UV0, UV1, 和 vertex tangent,那么顶点数量的限制就是 900 / 5 = 180;

SRP Batcher

  • **SRP Batcher:**如果项目使用脚本化渲染管道(SRP),使用SRP批处理,可以减少渲染状态的切换,这样会同样可以提升渲染新能,因为满足srp管线的材质,在显存中都有一个CBuffer,只要材质的参数没变,就不用每一帧都提交和设置材质的渲染状态 ,按照官网上上面的文档描述,使用SRP Batcher,其中一个要求是:材质可以不同,但是材质使用的Shader变体必须一致,这个和上面的三种合批方式很不一样

在SRP Batcher中,GameObject的内置引擎属性(transform等)是有专门的提交路径(下图实线箭头所示),和材质的提交是分开的(下图虚线所示),这样做的好处是,我们每帧都更新一些必要的属性,例如位置,大小等信息,而材质就可以以增量改变的方式提交给GPU,而恰好材质的提交(渲染状态的改变)是非常印象效率的,在SRP Batcher管线中,每一种材质在GPU内存中都有一个CBuffer存放对应的参数,只要材质的参数没有发生变化,那么在每一帧中就不必从CPU提交材质到GPU。从而减少CPU的消耗,提升渲染效率,官网上的示意图如下:
在这里插入图片描述

图集的作用

在上述的合批中,多数都是要求使用相同的材质(meterail),而贴图也是属于材质的一种属性,如果两个材质仅仅贴图不一样,这也会导致材质不一样,就不印象合批,所以把多张贴图打包为图集,这样就可以是的材质引用同一份贴图,使得合批得以进行。

不同合批的优先级

  1. SRP Batcher and static batching
  2. GPU instancing
  3. Dynamic batching

SRP Batcher和static batching可以共存,如果一个GameObject使用了static batching,Unity就会禁用GPU instancing ,即使GameObject使用的是GPU instancing Shader; 如果一个GameObject使用了GPU instancing , Unity就会禁用Dynamic batching;

合批的优先级官网文档参考链接

UGUI中的mask组件,会增加draw call分析:

Stencil 状态
Stencil 状态即模板测试,通过模板缓冲来实现特定的效果,在 Unity 中,Mask 组件就是通过该功能实现,一个 Mask 组件及其控制的渲染节点,需要至少三次 Draw call。第一次开启模板测试并调用一次 Draw call,刷新模板缓冲。第二次绘制对需要通过模板测试的区域进行设置。第三次再进行实际的子节点内容绘制,绘制结束再关闭模板测试。因此使用 Mask 组件就无法与其他相邻节点进行批次处理,但是 Mask 组件内部的连续节点在满足合并规则的情况下还是会进行合批。

Stencil 使用的最佳实践
如果界面内使用大量 Mask 组件会带来 Draw call 的剧增,因此应该尽量减少 Mask 组件的使用。如有使用 Mask 组件的节点,应该尽量不要穿插在连续并且可以进行批次合并的节点层级内,这样也可以尽量规避 Mask 打断本可以合并批次的一系列连续节点。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>