GPU Instancing 与动态批处理在 Unity 中的使用指南

动态批处理 (Dynamic Batching)

自动部分:

  • Unity 默认启用动态批处理
  • 符合条件的物体会自动进行批处理

手动优化部分:

  • 需要确保物体满足批处理条件
  • 可通过项目设置开关此功能

GPU Instancing

自动部分:

  • 使用相同材质的物体可以自动实例化
  • Unity 会自动管理实例化批次

手动优化部分:

  • 需要在材质上手动启用
  • 高级用法需要编程实现

如何使用动态批处理

1. 确保动态批处理已启用

编辑 > 项目设置 > 播放器 > 其他设置 > 渲染 > 动态批处理✓

![动态批处理设置示意图]

2. 确保物体满足批处理条件

条件检查清单:

  • 顶点数少于300(移动)/900(PC)
  • 使用完全相同的材质
  • 不使用不同UV的光照贴图
  • 不使用光照探针或反射探针
  • 使用相同渲染队列

3. 优化批处理效果

  • 共享材质:

    // 不要这样做
    renderer.material = myMaterial; // 创建材质副本
    
    // 正确做法
    renderer.sharedMaterial = myMaterial; // 使用共享材质
    
  • 减少材质数量:

    • 使用材质图集(Texture Atlas)合并纹理
    • 减少场景中不同的材质变体

如何使用 GPU Instancing

1. 在材质上启用 GPU Instancing

最简单的方法:

  • 选择材质
  • 在材质检查器中勾选 “Enable GPU Instancing”

![GPU Instancing 设置示意图]

2. 确保使用支持实例化的着色器

  • 标准着色器(Standard Shader)支持
  • URP/HDRP 默认支持
  • 自定义着色器需添加实例化支持:
    #pragma multi_compile_instancing
    

3. 基础编程实现(适合高级控制)

// 基础 GPU Instancing 示例
public class SimpleInstancing : MonoBehaviour
{
    public Mesh mesh;
    public Material material;
    public int count = 100;
    public float radius = 10f;
    
    private Matrix4x4[] matrices;
    
    void Start()
    {
        // 确保材质支持实例化
        material.enableInstancing = true;
        
        // 准备实例变换矩阵
        matrices = new Matrix4x4[count];
        
        for (int i = 0; i < count; i++)
        {
            float angle = i * Mathf.PI * 2f / count;
            Vector3 position = new Vector3(
                Mathf.Cos(angle) * radius,
                0,
                Mathf.Sin(angle) * radius
            );
            
            matrices[i] = Matrix4x4.TRS(
                position,
                Quaternion.Euler(0, angle * Mathf.Rad2Deg, 0),
                Vector3.one
            );
        }
    }
    
    void Update()
    {
        // 一次性绘制所有实例
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices, count);
    }
}

4. 实例化属性变化(每个实例不同颜色)

// 带有属性变化的 GPU Instancing
public class ColoredInstancing : MonoBehaviour
{
    public Mesh mesh;
    public Material material;
    public int count = 100;
    public float radius = 10f;
    
    private Matrix4x4[] matrices;
    private MaterialPropertyBlock propertyBlock;
    
    void Start()
    {
        // 确保材质支持实例化
        material.enableInstancing = true;
        
        // 准备实例数据
        matrices = new Matrix4x4[count];
        propertyBlock = new MaterialPropertyBlock();
        
        // 准备颜色数组
        Vector4[] colors = new Vector4[count];
        
        for (int i = 0; i < count; i++)
        {
            float angle = i * Mathf.PI * 2f / count;
            Vector3 position = new Vector3(
                Mathf.Cos(angle) * radius,
                0,
                Mathf.Sin(angle) * radius
            );
            
            matrices[i] = Matrix4x4.TRS(
                position,
                Quaternion.Euler(0, angle * Mathf.Rad2Deg, 0),
                Vector3.one
            );
            
            // 每个实例不同颜色
            colors[i] = new Vector4(
                Mathf.Abs(Mathf.Cos(angle)),
                Mathf.Abs(Mathf.Sin(angle)),
                0.5f,
                1.0f
            );
        }
        
        // 设置颜色属性
        propertyBlock.SetVectorArray("_Color", colors);
    }
    
    void Update()
    {
        // 使用属性块绘制实例
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices, count, propertyBlock);
    }
}

实用技巧与常见问题

如何知道批处理是否生效?

使用 Frame Debugger 查看:

  1. 菜单: Window > Analysis > Frame Debugger
  2. 点击"Enable"
  3. 查找"Batch"或"Draw Mesh Instanced"条目

批处理与实例化选择指南

选择动态批处理:

  • 场景中有大量不同种类但顶点较少的小物体
  • 物体需要频繁移动或旋转
  • 不想修改现有材质设置

选择 GPU Instancing:

  • 有大量相同网格的物体(树木、草、敌人等)
  • 需要每个实例有轻微变化(颜色、缩放等)
  • 物体顶点数较多,超过批处理限制
  • 需要最大化性能优化

常见问题解决

问题: 启用了 GPU Instancing 但没有效果?
解决:

  • 检查材质是否真正启用了 GPU Instancing
  • 确认着色器支持实例化
  • 验证所有实例使用完全相同的材质

问题: 动态批处理没有预期效果?
解决:

  • 使用 Frame Debugger 检查批处理情况
  • 验证物体是否超出顶点数限制
  • 检查材质是否相同(包括所有属性)

进阶技巧

混合使用优化策略

在实际项目中,通常混合使用不同优化技术:

  1. 静态场景元素: 使用静态批处理(标记为Static)
  2. 类似的小装饰物: 利用动态批处理
  3. 大量相同物体: 使用GPU实例化
  4. 核心游戏物体: 单独绘制,避免批处理限制

程序化应用 GPU Instancing

对于程序生成的大量物体(如草、树、粒子):

// 高级实例化系统示例
[System.Serializable]
public class InstanceGroup
{
    public Mesh mesh;
    public Material material;
    public Vector3 positionVariance = new Vector3(10f, 0f, 10f);
    public Vector3 scaleVariance = new Vector3(0.2f, 0.5f, 0.2f);
    public int count = 1000;
}

public class InstanceManager : MonoBehaviour
{
    public List<InstanceGroup> instanceGroups = new List<InstanceGroup>();
    
    private Dictionary<InstanceGroup, List<Matrix4x4>> instanceData = 
        new Dictionary<InstanceGroup, List<Matrix4x4>>();
    
    void Start()
    {
        foreach (InstanceGroup group in instanceGroups)
        {
            // 确保启用实例化
            group.material.enableInstancing = true;
            
            // 创建和存储实例数据
            List<Matrix4x4> matrices = new List<Matrix4x4>();
            instanceData[group] = matrices;
            
            // 生成随机位置
            for (int i = 0; i < group.count; i++)
            {
                Vector3 position = new Vector3(
                    Random.Range(-group.positionVariance.x, group.positionVariance.x),
                    Random.Range(-group.positionVariance.y, group.positionVariance.y),
                    Random.Range(-group.positionVariance.z, group.positionVariance.z)
                );
                
                Vector3 scale = Vector3.one + new Vector3(
                    Random.Range(-group.scaleVariance.x, group.scaleVariance.x),
                    Random.Range(-group.scaleVariance.y, group.scaleVariance.y),
                    Random.Range(-group.scaleVariance.z, group.scaleVariance.z)
                );
                
                Quaternion rotation = Quaternion.Euler(0, Random.Range(0, 360), 0);
                
                matrices.Add(Matrix4x4.TRS(position, rotation, scale));
            }
        }
    }
    
    void Update()
    {
        // 渲染所有实例组
        foreach (InstanceGroup group in instanceGroups)
        {
            List<Matrix4x4> matrices = instanceData[group];
            int batchCount = Mathf.CeilToInt((float)matrices.Count / 1023);
            
            // 支持超过1023个实例(实例化限制)
            for (int i = 0; i < batchCount; i++)
            {
                int batchSize = Mathf.Min(1023, matrices.Count - i * 1023);
                Matrix4x4[] batchMatrices = new Matrix4x4[batchSize];
                
                for (int j = 0; j < batchSize; j++)
                {
                    batchMatrices[j] = matrices[i * 1023 + j];
                }
                
                // 绘制当前批次
                Graphics.DrawMeshInstanced(group.mesh, 0, group.material, 
                    batchMatrices, batchSize);
            }
        }
    }
}

总结

Unity 的动态批处理和 GPU Instancing 既可以自动工作,也可以通过编程优化以获得最佳性能:

动态批处理:

  • 基本上是自动的,但需确保物体满足条件
  • 需要手动优化材质使用和几何体复杂度

GPU Instancing:

  • 需要在材质上手动启用
  • 基础使用简单,高级控制需要编程

两种技术各有优势,理想情况下应组合使用以获得最佳性能。正确应用这些技术可以显著提高帧率,尤其是在有大量相似物体的复杂场景中。

你可能感兴趣的:(unity,游戏引擎)