• Unity 场景优化策略


    Unity 场景优化策略

    GPU instancing

    使用GPU Instancing可以将多个网格相同、材质相同、材质属性可以不同的物体合并为一个批次,从而减少Draw Calls的次数。这可以提高性能和渲染效率。

    GPU instancing可用于绘制在场景中多次出现的几何体,例如树木或灌木丛。

    渲染管线兼容性

    特征内置渲染管线通用渲染管线 (URP)高清渲染管线 (HDRP)自定义可编程渲染管线 (SRP)
    GPU instancing是的是 (1)是 (1)是 (1)

    注意

    1. 仅当着色器与 SRP Batcher 不兼容时。

    ** 设置GPU instancing**

    设置很简单只需一步

    在默认的材质球下找到Enable GPU Instancing,勾选就可以。


    对比效果

    没有勾选时:Batches是230左右,Saved By Batching是0

    勾选后:Batches是8,Saved By Batching是222左右

    使用下文的shader给材质添加随机色后:Batches是4,Saved By Batching是63(这里没有添加阴影Batch会少)

    补充:

    MaterialPropertyBlock

    批处理一般作用与相同的材质。当需要对shader相同材质属性不同的模型批处理时可以用MaterialPropertyBlock。

    MaterialPropertyBlock除了在 Renderer.SetPropertyBlock 被使用,还可以在 Graphics.DrawMesh使用。

        void Start()
        {
            MaterialPropertyBlock material = new MaterialPropertyBlock();
            material.SetColor("_Color", new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)));
            GetComponent().SetPropertyBlock(material);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里可以发现,虽然颜色不一样,但是材质球指向的是同一个

    ** 创建支持 GPU instancing的着色器**

    渲染管线兼容性

    特征内置渲染管线通用渲染管线 (URP)高清渲染管线 (HDRP)自定义可编程渲染管线 (SRP)
    自定义 GPU instancing着色器是的

    顶点和片元着色器示例

    Shader "Custom/SimplestInstancedShader"
    {
        Properties
        {
            _Color ("Color", Color) = (1, 1, 1, 1)
        }
    
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
               //生成实例化变体。它是可选的。
                #pragma multi_compile_instancing
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                   //在顶点着色器输入/输出结构中定义INSTANCE_ID。
                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    //使INSTANCE_ID访问片元着色器中的实例化属性。
                    UNITY_VERTEX_INPUT_INSTANCE_ID 
                };
                //声明名为 的每个实例常量缓冲区的开始
                UNITY_INSTANCING_BUFFER_START(Props)
                    UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
               //声明名为 的每个实例常量缓冲区的结尾     
                UNITY_INSTANCING_BUFFER_END(Props)
    
                v2f vert(appdata v)
                {
                    v2f o;
    				//允许顶点着色器函数访问INSTANCE_ID
                    UNITY_SETUP_INSTANCE_ID(v);
                    //将INSTANCE_ID从输入结构复制到顶点着色器中的输出结构。
                    UNITY_TRANSFER_INSTANCE_ID(v, o);
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    return o;
                }
    
                fixed4 frag(v2f i) : SV_Target
                {
                    //允许片元着色器函数访问INSTANCE_ID
                    UNITY_SETUP_INSTANCE_ID(i);
                    //访问实例化常量缓冲区中的每个实例着色器属性
                    return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
                }
                ENDCG
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    静态批处理

    它通过将多个静态物体合并成一个批次来减少渲染调用,从而减少CPU和GPU的负载,可以显著减少渲染过程中的Draw Call数量,从而提高性能。

    静态批处理适用于那些不会在运行时改变位置、旋转或缩放的物体,例如地形、建筑物等。

    渲染管线兼容性

    特征内置渲染管线通用渲染管线 (URP)高清渲染管线 (HDRP)自定义可编程渲染管线 (SRP)
    静态批处理是的是的是的是的

    静态批处理的设置

    设置也很简单只需两步

    1. 在ProjectSettings→player→OtherSetting→StaticBatching,勾选

    2. 在默认的材质球下找到Enable GPU Instancing,勾选就可以。

    静态批处理的限制

    • 游戏对象处于活动状态且禁止(非禁止对象的会强制禁止)。
    • 游戏对象具有MeshFilter组件,并且该组件已启用。
    • MeshFilter 组件引用了Mesh
    • 网格的顶点数大于 0。
    • 该网格尚未与另一个网格合并。
    • 游戏对象具有MeshRenderer组件,并且该组件已启用。
    • 要批处理在一起的网格,使用相同的顶点属性。例如,Unity 可以对使用顶点位置、顶点法线和一个 UV 的网格进行批处理,但不能对使用顶点位置、顶点法线、UV0、UV1 和顶点切线的网格进行批处理。

    效果

    Batches是8,Saved By Batching是211

    静态批处理的控制

    使用StaticBatchingUtility类来控制静态批处理。

    using UnityEngine;
    
    public class StaticBatchingController : MonoBehaviour
    {
        void Start()
        {
           StartStaticBatch();
        }
    
        GameObject[] GetGameObjectsToBatch()
        {
            // 返回需要静态批处理的游戏对象数组
            // 例如:可以通过标签、层级或者其他方式来获取需要静态批处理的游戏对象
            // 这里只是一个简单示例,实际情况可能需要根据具体需求来获取游戏对象
            return GameObject.FindGameObjectsWithTag("StaticBatchingObject");
        }
        void StartStaticBatch()
        {
            // 获取所有需要静态批处理的游戏对象
            GameObject[] gameObjectsToBatch = GetGameObjectsToBatch();
            // 执行静态批处理
            StaticBatchingUtility.Combine(gameObjectsToBatch, this.gameObject);
        }
        void StopStaticBatch()
        {
            // 禁用静态批处理
            StaticBatchingUtility.Combine(null, this.gameObject);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    动态批处理

    通过将运行时动态地将多个静态或动态的游戏对象合并成一个批次,减少渲染调用的数量,提高帧率和性能。

    渲染管线兼容性

    特征内置渲染管线通用渲染管线 (URP)高清渲染管线 (HDRP)自定义可编程渲染管线 (SRP)
    动态批处理是的是的是的

    静态批处理的设置

    设置也很简单只需一步

    在ProjectSettings→player→OtherSetting→DynamicBatching,勾选

    动态批处理的限制

    • Unity 无法对包含超过 225 个顶点的网格应用动态批处理。这是因为网格的动态批处理每个顶点都有开销。
      使用顶点位置、顶点法线和单个 UV,则 Unity 最多可以批处理 225 个顶点。但是,如果着色器使用顶点位置、顶点法线、UV0、UV1 和顶点正切,则 Unity 只能批处理 180 个顶点。
    • 如果对象使用不同的材质实例,则 Unity 无法将它们批处理在一起,即使它们本质是使用一个shader。唯一的例外是阴影投射器渲染。
    • 带有光照贴图的游戏对象具有额外的渲染器参数。这意味着,如果要对光照映射的游戏对象进行批处理,它们必须指向相同的光照贴图位置。
    • Unity 无法将动态批处理完全应用于使用多个pass的shader对象。
      • 几乎所有 Unity 着色器都支持多个灯光前向渲染为了实现这一点,他们为每个光源处理一个额外的渲染通道。Unity 仅对第一个渲染通道进行批处理。它无法对额外的每像素光源的绘制调用进行批处理。

    效果

    Batches是8,Saved By Batching是220左右

    手动合并网格

    手动将多个网格合并为一个网格,在网格靠得很近且彼此不相对移动的情况下,可以很好地替代,静态批处理和动态批处理。

    警告:Unity 无法单独剔除您组合的网格。这意味着,如果组合网格的一部分出现在屏幕上,Unity 会绘制整个组合网格。如果网格是静态的,并且希望 Unity 单独剔除它们,使用静态批处理。

    合并网格的设置方法

    • 在创作网格时在资源生成工具中。即模型制作阶段将其合并到一起,同时这样处理相同的模型会照成模型体量的变大。
    • 在 Unity 中使用 Mesh.CombineMeshes

    CombineMeshes的用法

    //强制添加组件
    [RequireComponent(typeof(MeshFilter))]
    [RequireComponent(typeof(MeshRenderer))]
    public class ExampleClass : MonoBehaviour
    {
        void Start()
        {
            MeshFilter[] meshFilters = GetComponentsInChildren();
         //  CombineInstance 结构体 用于描述要使用 Mesh.CombineMeshes 组合的网格。
            CombineInstance[] combine = new CombineInstance[meshFilters.Length];
    
            int i = 0;
            while (i < meshFilters.Length)
            {
                combine[i].mesh = meshFilters[i].sharedMesh;
                combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
                meshFilters[i].gameObject.SetActive(false);
    
                i++;
            }
    
            Mesh mesh = new Mesh();
            mesh.CombineMeshes(combine);
            transform.GetComponent().sharedMesh = mesh;
            transform.gameObject.SetActive(true);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    CombineInstance 字段说明
    lightmapScaleOffset应用于网格的烘焙光照贴图UV比例和偏移量。
    mesh要组合的网格。
    realtimeLightmapScaleOffset应用于网格的实时光照贴图UV比例和偏移。
    subMeshIndex网格的子网格索引。
    transform在组合之前要变换网格的矩阵。有关示例,请参阅 Mesh.CombineMeshes。

    效果

    Batches是8,Saved By Batching是0

    遮挡剔除

    遮挡剔除不说了

    频繁使用遮挡剔除可能会导致一些性能问题,特别是在复杂的场景中。因为每次相机移动或场景发生变化时,都需要重新计算遮挡剔除信息,这可能会消耗一定的计算资源。

    LOD

    Unity中使用LOD(Level of Detail)时,可以根据相机与物体的距离,自动切换不同级别的模型,以提高性能和减少渲染开销。

    unityLOD设置方法

    1. 创建多个不同级别的模型,分别代表远、中、近距离的模型。

    2. 将这些模型作为同一个游戏对象的子对象,并添加LOD Group组件。

    3. 在LOD Group组件中设置每个级别的距离和对应的模型。

    4. Unity会根据相机与物体的距离自动切换不同级别的模型。

    效果

    可以与静态批处理和动态批处理结合

    Batches是11,Saved By Batching是310左右

    层剔除 layerCullDistances

    用于指定摄像机在渲染不同图层时的剔除距离。通过设置layerCullDistances,可以控制摄像机在渲染不同图层时的剔除距离,从而提高渲染性能。层剔除和lod类似,也是按距离剔除,不同的是层剔除不会替换

    float[] distances = new float[32];//设定32个默认图层
    distances[11] = 300;//为第11层设定距离
    Camera.mian.layerCullDistances = distances;//将剔除层传递给相机
    
    • 1
    • 2
    • 3
  • 相关阅读:
    设计模式---适配器模式
    代码随想录二刷day42
    博客项目(前台功能实现)
    【JavaSE】多线程篇(一)线程的相关概念与线程的基本使用
    java工作内存与主内存之间相互刷新的时机
    jmeter提取request body中的数据,作为下个接口的入参
    博客无限滚动加载(html、css、js)实现
    关于JavaScript中reduce的使用场景
    微信小程序:EventChannel实现页面间事件通信通道
    OpenCV14-图像平滑:线性滤波和非线性滤波
  • 原文地址:https://blog.csdn.net/dxs1990/article/details/134338121