【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<MeshRenderer>().SetPropertyBlock(material);

    }

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

** 创建支持 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
        }
    }
}

静态批处理

它通过将多个静态物体合并成一个批次来减少渲染调用,从而减少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);
    }
}

动态批处理

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

渲染管线兼容性

特征 内置渲染管线 通用渲染管线 (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<MeshFilter>();
     //  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<MeshFilter>().sharedMesh = mesh;
        transform.gameObject.SetActive(true);
    }
}
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;//将剔除层传递给相机

猜你喜欢

转载自blog.csdn.net/dxs1990/article/details/134338121