电竞比分网-中国电竞赛事及体育赛事平台

分享

GPU Instancing 原理解析與 Unity 批量優(yōu)化指南

 勤奮不止 2025-07-02 發(fā)布于北京

GPU Instancing 原理深度解析

基本概念

GPU Instancing 是一種渲染優(yōu)化技術,允許使用單一繪制調用(Draw Call)渲染多個相同網格的不同實例。它通過一次性將模型數據和多個實例的變換數據一起發(fā)送到 GPU,顯著減少了 CPU 與 GPU 之間的通信開銷。

底層工作原理

單一幾何體傳輸

網格數據(頂點、法線、UV等)只傳輸一次到 GPU

頂點著色器多次使用相同的幾何數據

實例數據傳遞

每個實例的唯一數據(如變換矩陣、顏色、自定義參數)通過實例緩沖區(qū)(Instance Buffer)傳送

這些緩沖區(qū)以結構化方式儲存,使 GPU 能高效訪問

著色器執(zhí)行過程

頂點著色器為每個實例獲取特定的實例數據

處理每個頂點時,著色器應用當前實例的變換和屬性

每個實例可以有不同的位置、旋轉、縮放和材質參數,但共享基礎幾何體

渲染效率提升

減少 CPU->GPU 的通信瓶頸

減少渲染狀態(tài)切換

在 GPU 內部高效處理相似對象

與傳統(tǒng)批處理的區(qū)別

特性 靜態(tài)/動態(tài)批處理 GPU Instancing

幾何體合并 合并為單個網格 保持獨立,只合并渲染調用

獨立操作 難以單獨操作合并后的對象 每個實例可獨立控制

內存消耗 需要額外的合并網格內存 相對較低

動態(tài)修改 有限或需重新合并 易于實時修改實例參數

支持限制 受幾何體面數與材質限制 主要受著色器和硬件限制

Unity 中實現 GPU Instancing 的批量優(yōu)化策略

1. 基礎設置 - 啟用 GPU Instancing

材質設置:

// 批量啟用選中材質的 GPU Instancing

[MenuItem("Tools/Optimization/Enable GPU Instancing For Selected Materials")]

public static void EnableGPUInstancingForSelectedMaterials()

{

    foreach (Object obj in Selection.objects)

    {

        if (obj is Material material)

        {

            material.enableInstancing = true;

            Debug.Log($"Enabled GPU Instancing for {material.name}");

        }

    }

}

// 批量啟用場景中所有材質的 GPU Instancing

[MenuItem("Tools/Optimization/Enable GPU Instancing For All Scene Materials")]

public static void EnableGPUInstancingForAllSceneMaterials()

{

    Material[] allMaterials = Resources.FindObjectsOfTypeAll<Material>();

    int count = 0;

    foreach (Material mat in allMaterials)

    {

        if (!mat.enableInstancing)

        {

            mat.enableInstancing = true;

            count++;

        }

    }

    Debug.Log($"Enabled GPU Instancing for {count} materials");

}

AI寫代碼

csharp

運行

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

2. 運行時實例化管理

高效實例化系統(tǒng):

using UnityEngine;

using System.Collections.Generic;

public class GPUInstanceManager : MonoBehaviour

{

    [System.Serializable]

    public class InstanceGroup

    {

        public Mesh mesh;

        public Material material;

        public int maxInstances = 1000;

        [HideInInspector] public List<Matrix4x4> matrices = new List<Matrix4x4>();

        [HideInInspector] public List<Vector4> colors = new List<Vector4>();

    }

    public List<InstanceGroup> instanceGroups = new List<InstanceGroup>();

    private MaterialPropertyBlock propertyBlock;

    // 實例屬性數組

    private static readonly int colorID = Shader.PropertyToID("_Color");

    void Start()

    {

        propertyBlock = new MaterialPropertyBlock();

    }

    void Update()

    {

        foreach (InstanceGroup group in instanceGroups)

        {

            if (group.matrices.Count == 0) continue;

            // 計算批次數量

            int batchCount = Mathf.CeilToInt((float)group.matrices.Count / 1023f);

            for (int batchIndex = 0; batchIndex < batchCount; batchIndex++)

            {

                int batchSize = Mathf.Min(1023, group.matrices.Count - batchIndex * 1023);

                // 準備當前批次的矩陣數組

                Matrix4x4[] batchedMatrices = new Matrix4x4[batchSize];

                System.Array.Copy(group.matrices.ToArray(), batchIndex * 1023, batchedMatrices, 0, batchSize);

                // 設置顏色屬性 (如果需要)

                if (group.colors.Count > 0)

                {

                    Vector4[] colors = new Vector4[batchSize];

                    int startIndex = batchIndex * 1023;

                    int endIndex = Mathf.Min(startIndex + batchSize, group.colors.Count);

                    for (int i = 0; i < endIndex - startIndex; i++)

                    {

                        colors[i] = group.colors[startIndex + i];

                    }

                    propertyBlock.SetVectorArray(colorID, colors);

                }

                // 執(zhí)行實例化渲染

                Graphics.DrawMeshInstanced(group.mesh, 0, group.material, 

                    batchedMatrices, batchSize, propertyBlock);

            }

        }

    }

    // 添加一個實例到組

    public void AddInstance(int groupIndex, Vector3 position, Quaternion rotation, Vector3 scale, Color color)

    {

        if (groupIndex >= instanceGroups.Count) return;

        InstanceGroup group = instanceGroups[groupIndex];

        if (group.matrices.Count >= group.maxInstances) return;

        Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scale);

        group.matrices.Add(matrix);

        group.colors.Add(color);

    }

    // 清除所有實例

    public void ClearInstances(int groupIndex)

    {

        if (groupIndex >= instanceGroups.Count) return;

        instanceGroups[groupIndex].matrices.Clear();

        instanceGroups[groupIndex].colors.Clear();

    }

}

AI寫代碼

csharp

運行

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

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

3. 著色器優(yōu)化

支持 GPU Instancing 的著色器示例:

Shader "Custom/GPUInstancingOptimized"

{

    Properties

    {

        _MainTex ("Texture", 2D) = "white" {}

        _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;

                float2 uv : TEXCOORD0;

                UNITY_VERTEX_INPUT_INSTANCE_ID

            };

            struct v2f

            {

                float2 uv : TEXCOORD0;

                float4 vertex : SV_POSITION;

                UNITY_VERTEX_INPUT_INSTANCE_ID

            };

            sampler2D _MainTex;

            float4 _MainTex_ST;

            UNITY_INSTANCING_BUFFER_START(Props)

                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)

            UNITY_INSTANCING_BUFFER_END(Props)

            v2f vert (appdata v)

            {

                v2f o;

                UNITY_SETUP_INSTANCE_ID(v);

                UNITY_TRANSFER_INSTANCE_ID(v, o);

                o.vertex = UnityObjectToClipPos(v.vertex);

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;

            }

            fixed4 frag (v2f i) : SV_Target

            {

                UNITY_SETUP_INSTANCE_ID(i);

                fixed4 color = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);

                fixed4 tex = tex2D(_MainTex, i.uv);

                return tex * color;

            }

            ENDCG

        }

    }

}

AI寫代碼

hlsl

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

4. 批量分析與轉換工具

場景分析與自動優(yōu)化工具:

using UnityEngine;

using UnityEditor;

using System.Collections.Generic;

public class GPUInstancingAnalyzer : EditorWindow

{

    [MenuItem("Tools/Optimization/GPU Instancing Analyzer")]

    public static void ShowWindow()

    {

        GetWindow<GPUInstancingAnalyzer>("GPU Instancing Analyzer");

    }

    private Dictionary<MeshRenderer, bool> sceneRenderers = new Dictionary<MeshRenderer, bool>();

    private Dictionary<Material, int> materialUsageCounts = new Dictionary<Material, int>();

    private Vector2 scrollPosition;

    void OnEnable()

    {

        AnalyzeScene();

    }

    void AnalyzeScene()

    {

        sceneRenderers.Clear();

        materialUsageCounts.Clear();

        MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>();

        foreach (MeshRenderer renderer in renderers)

        {

            bool isInstancingCandidate = CanUseInstancing(renderer);

            sceneRenderers.Add(renderer, isInstancingCandidate);

            foreach (Material material in renderer.sharedMaterials)

            {

                if (material != null)

                {

                    if (!materialUsageCounts.ContainsKey(material))

                    {

                        materialUsageCounts[material] = 0;

                    }

                    materialUsageCounts[material]++;

                }

            }

        }

    }

    bool CanUseInstancing(MeshRenderer renderer)

    {

        // 檢查是否適合GPU實例化的條件

        if (renderer.gameObject.isStatic) return false; // 靜態(tài)物體已有優(yōu)化

        MeshFilter filter = renderer.GetComponent<MeshFilter>();

        if (filter == null || filter.sharedMesh == null) return false;

        foreach (Material material in renderer.sharedMaterials)

        {

            if (material == null || !material.shader.supportsInstancing)

                return false;

        }

        return true;

    }

    void OnGUI()

    {

        if (GUILayout.Button("Refresh Analysis"))

        {

            AnalyzeScene();

        }

        GUILayout.Label("Materials Used Multiple Times:", EditorStyles.boldLabel);

        scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

        EditorGUILayout.BeginVertical("box");

        foreach (var mat in materialUsageCounts)

        {

            if (mat.Value > 1) // 多次使用的材質

            {

                EditorGUILayout.BeginHorizontal();

                EditorGUILayout.ObjectField(mat.Key, typeof(Material), false, GUILayout.Width(200));

                EditorGUILayout.LabelField($"Used: {mat.Value} times", GUILayout.Width(100));

                bool hasInstancing = mat.Key.enableInstancing;

                GUI.backgroundColor = hasInstancing ? Color.green : Color.red;

                if (GUILayout.Button(hasInstancing ? "Instancing Enabled" : "Enable Instancing", GUILayout.Width(150)))

                {

                    if (!hasInstancing)

                    {

                        mat.Key.enableInstancing = true;

                        EditorUtility.SetDirty(mat.Key);

                    }

                }

                GUI.backgroundColor = Color.white;

                EditorGUILayout.EndHorizontal();

            }

        }

        EditorGUILayout.EndVertical();

        GUILayout.Space(20);

        GUILayout.Label("Instancing Candidates:", EditorStyles.boldLabel);

        EditorGUILayout.BeginVertical("box");

        foreach (var renderer in sceneRenderers)

        {

            if (renderer.Value)

            {

                EditorGUILayout.BeginHorizontal();

                EditorGUILayout.ObjectField(renderer.Key.gameObject, typeof(GameObject), false, GUILayout.Width(200));

                if (GUILayout.Button("Convert To Instanced", GUILayout.Width(150)))

                {

                    ConvertToInstanced(renderer.Key);

                }

                EditorGUILayout.EndHorizontal();

            }

        }

        EditorGUILayout.EndVertical();

        EditorGUILayout.EndScrollView();

        GUILayout.Space(20);

        if (GUILayout.Button("Auto-Optimize All Candidates"))

        {

            AutoOptimizeForInstancing();

        }

    }

    void ConvertToInstanced(MeshRenderer renderer)

    {

        foreach (Material material in renderer.sharedMaterials)

        {

            if (material != null && !material.enableInstancing)

            {

                material.enableInstancing = true;

                EditorUtility.SetDirty(material);

            }

        }

        // 添加必要的組件或標記

        if (!renderer.gameObject.GetComponent<GPUInstancedObject>())

        {

            renderer.gameObject.AddComponent<GPUInstancedObject>();

        }

        EditorUtility.SetDirty(renderer.gameObject);

    }

    void AutoOptimizeForInstancing()

    {

        foreach (var renderer in sceneRenderers)

        {

            if (renderer.Value)

            {

                ConvertToInstanced(renderer.Key);

            }

        }

        // 整理場景中重復使用的材質

        foreach (var mat in materialUsageCounts)

        {

            if (mat.Value > 1 && !mat.Key.enableInstancing)

            {

                mat.Key.enableInstancing = true;

                EditorUtility.SetDirty(mat.Key);

            }

        }

        AssetDatabase.SaveAssets();

    }

}

// 標記組件,表示此對象已優(yōu)化為GPU實例化

public class GPUInstancedObject : MonoBehaviour {}

AI寫代碼

csharp

運行

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

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

5. 創(chuàng)建實例化集合系統(tǒng)

動態(tài)實例分組管理器:

using UnityEngine;

using System.Collections.Generic;

[System.Serializable]

public class InstanceTypeDefinition

{

    public GameObject prefab;

    public int initialPoolSize = 100;

    public int maxInstances = 1000;

}

public class InstancedObjectSystem : MonoBehaviour

{

    public List<InstanceTypeDefinition> instanceDefinitions = new List<InstanceTypeDefinition>();

    private Dictionary<int, InstancedObjectPool> pools = new Dictionary<int, InstancedObjectPool>();

    [System.Serializable]

    private class InstancedObjectPool

    {

        public GameObject prefab;

        public Mesh mesh;

        public Material material;

        public List<Matrix4x4> matrices = new List<Matrix4x4>();

        public List<int> activeIndices = new List<int>();

        public List<Transform> transforms = new List<Transform>();

        public MaterialPropertyBlock propertyBlock;

        public InstancedObjectPool(GameObject prefab, int initialSize)

        {

            this.prefab = prefab;

            MeshFilter meshFilter = prefab.GetComponent<MeshFilter>();

            MeshRenderer renderer = prefab.GetComponent<MeshRenderer>();

            if (meshFilter != null && renderer != null)

            {

                this.mesh = meshFilter.sharedMesh;

                this.material = renderer.sharedMaterial;

                propertyBlock = new MaterialPropertyBlock();

                // 預分配空間

                matrices = new List<Matrix4x4>(initialSize);

                activeIndices = new List<int>(initialSize);

                transforms = new List<Transform>(initialSize);

                // 確保材質啟用了instancing

                if (material != null && !material.enableInstancing)

                {

                    Debug.LogWarning($"Enabling GPU Instancing for {material.name} in runtime");

                    material.enableInstancing = true;

                }

            }

            else

            {

                Debug.LogError($"Prefab {prefab.name} is missing MeshFilter or MeshRenderer!");

            }

        }

    }

    void Start()

    {

        // 初始化所有對象池

        for (int i = 0; i < instanceDefinitions.Count; i++)

        {

            InstanceTypeDefinition def = instanceDefinitions[i];

            pools[i] = new InstancedObjectPool(def.prefab, def.initialPoolSize);

        }

    }

    void Update()

    {

        // 渲染所有實例

        foreach (var pool in pools.Values)

        {

            if (pool.mesh == null || pool.material == null || pool.matrices.Count == 0)

                continue;

            // 更新所有活動實例的矩陣

            for (int i = 0; i < pool.activeIndices.Count; i++)

            {

                int index = pool.activeIndices[i];

                if (index < pool.transforms.Count && pool.transforms[index] != null)

                {

                    pool.matrices[index] = Matrix4x4.TRS(

                        pool.transforms[index].position,

                        pool.transforms[index].rotation,

                        pool.transforms[index].localScale);

                }

            }

            // 分批次渲染(解決1023個限制)

            int totalInstances = pool.activeIndices.Count;

            int batchSize = 1023; // 單批次最大實例數

            for (int i = 0; i < totalInstances; i += batchSize)

            {

                int currentBatchSize = Mathf.Min(batchSize, totalInstances - i);

                Matrix4x4[] batchMatrices = new Matrix4x4[currentBatchSize];

                for (int j = 0; j < currentBatchSize; j++)

                {

                    if (i + j < pool.activeIndices.Count)

                    {

                        int index = pool.activeIndices[i + j];

                        batchMatrices[j] = pool.matrices[index];

                    }

                }

                // 執(zhí)行實例化渲染

                Graphics.DrawMeshInstanced(pool.mesh, 0, pool.material, batchMatrices, 

                    currentBatchSize, pool.propertyBlock);

            }

        }

    }

    // 創(chuàng)建新實例,返回實例ID

    public int CreateInstance(int poolId, Vector3 position, Quaternion rotation, Vector3 scale)

    {

        if (!pools.ContainsKey(poolId)) return -1;

        InstancedObjectPool pool = pools[poolId];

        InstanceTypeDefinition def = instanceDefinitions[poolId];

        // 查找可用的索引或創(chuàng)建新的

        int instanceIndex = -1;

        for (int i = 0; i < pool.matrices.Count; i++)

        {

            if (!pool.activeIndices.Contains(i))

            {

                instanceIndex = i;

                break;

            }

        }

        // 如果沒有找到空閑的索引并且未達到最大數量,則創(chuàng)建新的

        if (instanceIndex == -1 && pool.matrices.Count < def.maxInstances)

        {

            instanceIndex = pool.matrices.Count;

            // 創(chuàng)建變換對象(僅用于跟蹤位置/旋轉/縮放)

            GameObject instanceObj = new GameObject($"{pool.prefab.name}_Instance_{instanceIndex}");

            pool.transforms.Add(instanceObj.transform);

            // 添加新的矩陣占位符

            pool.matrices.Add(Matrix4x4.identity);

        }

        // 如果找到或創(chuàng)建了有效的索引

        if (instanceIndex >= 0 && instanceIndex < pool.matrices.Count)

        {

            if (instanceIndex < pool.transforms.Count)

            {

                // 設置變換

                Transform instanceTransform = pool.transforms[instanceIndex];

                instanceTransform.position = position;

                instanceTransform.rotation = rotation;

                instanceTransform.localScale = scale;

                // 更新矩陣

                pool.matrices[instanceIndex] = Matrix4x4.TRS(position, rotation, scale);

                // 標記為活動

                if (!pool.activeIndices.Contains(instanceIndex))

                {

                    pool.activeIndices.Add(instanceIndex);

                }

                return instanceIndex;

            }

        }

        return -1; // 無法創(chuàng)建新實例

    }

    // 移除實例

    public void RemoveInstance(int poolId, int instanceId)

    {

        if (!pools.ContainsKey(poolId)) return;

        if (instanceId < 0 || instanceId >= pools[poolId].matrices.Count) return;

        pools[poolId].activeIndices.Remove(instanceId);

    }

    // 獲取實例的變換對象(用于腳本控制)

    public Transform GetInstanceTransform(int poolId, int instanceId)

    {

        if (!pools.ContainsKey(poolId)) return null;

        if (instanceId < 0 || instanceId >= pools[poolId].transforms.Count) return null;

        return pools[poolId].transforms[instanceId];

    }

}

AI寫代碼

csharp

運行

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

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

6. 批量檢測和優(yōu)化植被

植被與粒子系統(tǒng)的批量轉換:

using UnityEngine;

using UnityEditor;

using System.Collections.Generic;

public class VegetationInstancingOptimizer : EditorWindow

{

    [MenuItem("Tools/Optimization/Vegetation GPU Instancing")]

    public static void ShowWindow()

    {

        GetWindow<VegetationInstancingOptimizer>("Vegetation Optimizer");

    }

    private bool detectTerrainTrees = true;

    private bool detectGrassObjects = true;

    private bool detectSmallObjects = true;

    private float smallObjectThreshold = 1.0f; // 小于這個尺寸的對象視為小物體

    private int minInstanceCount = 10; // 最少需要多少個相同物體才考慮實例化

    private Vector2 scrollPos;

    private Dictionary<GameObject, List<GameObject>> instanceCandidates = new Dictionary<GameObject, List<GameObject>>();

    void OnGUI()

    {

        GUILayout.Label("Vegetation & Small Props Instancing Optimizer", EditorStyles.boldLabel);

        GUILayout.Space(10);

        detectTerrainTrees = EditorGUILayout.Toggle("Detect Terrain Trees", detectTerrainTrees);

        detectGrassObjects = EditorGUILayout.Toggle("Detect Grass Objects", detectGrassObjects);

        detectSmallObjects = EditorGUILayout.Toggle("Detect Small Props", detectSmallObjects);

        if (detectSmallObjects)

        {

            smallObjectThreshold = EditorGUILayout.Slider("Small Object Size", smallObjectThreshold, 0.1f, 3.0f);

        }

        minInstanceCount = EditorGUILayout.IntSlider("Min Instance Count", minInstanceCount, 3, 20);

        if (GUILayout.Button("Analyze Scene"))

        {

            AnalyzeScene();

        }

        GUILayout.Space(20);

        scrollPos = EditorGUILayout.BeginScrollView(scrollPos);

        if (instanceCandidates.Count > 0)

        {

            foreach (var entry in instanceCandidates)

            {

                if (entry.Value.Count >= minInstanceCount)

                {

                    EditorGUILayout.BeginVertical("box");

                    EditorGUILayout.BeginHorizontal();

                    EditorGUILayout.ObjectField("Prefab", entry.Key, typeof(GameObject), false);

                    EditorGUILayout.LabelField($"Count: {entry.Value.Count}", GUILayout.Width(100));

                    EditorGUILayout.EndHorizontal();

                    EditorGUILayout.BeginHorizontal();

                    if (GUILayout.Button("Select Objects"))

                    {

                        Selection.objects = entry.Value.ToArray();

                    }

                    if (GUILayout.Button("Enable GPU Instancing"))

                    {

                        EnableGPUInstancingForObjects(entry.Key, entry.Value);

                    }

                    if (GUILayout.Button("Convert To Instanced Group"))

                    {

                        ConvertToInstancedGroup(entry.Key, entry.Value);

                    }

                    EditorGUILayout.EndHorizontal();

                    EditorGUILayout.EndVertical();

                    GUILayout.Space(5);

                }

            }

        }

        else

        {

            GUILayout.Label("No analysis data. Click 'Analyze Scene' to begin.");

        }

        EditorGUILayout.EndScrollView();

        GUILayout.Space(10);

        if (GUILayout.Button("Auto-Optimize All Candidates"))

        {

            AutoOptimizeAll();

        }

    }

    void AnalyzeScene()

    {

        instanceCandidates.Clear();

        // 收集場景中所有對象

        GameObject[] allObjects = FindObjectsOfType<GameObject>();

        // 臨時存儲,按網格和材質分組

        Dictionary<MeshMaterialPair, List<GameObject>> objectsByMeshMat = 

            new Dictionary<MeshMaterialPair, List<GameObject>>();

        foreach (GameObject obj in allObjects)

        {

            bool shouldConsider = false;

            // 檢查是否是小物體

            if (detectSmallObjects && IsSmallObject(obj))

            {

                shouldConsider = true;

            }

            // 檢查是否是草或植被

            if (detectGrassObjects && IsVegetationObject(obj))

            {

                shouldConsider = true;

            }

            // 檢查地形樹

            if (detectTerrainTrees && IsTerrainTree(obj))

            {

                shouldConsider = true;

            }

            if (!shouldConsider) continue;

            MeshFilter meshFilter = obj.GetComponent<MeshFilter>();

            MeshRenderer renderer = obj.GetComponent<MeshRenderer>();

            if (meshFilter != null && renderer != null && meshFilter.sharedMesh != null && 

                renderer.sharedMaterial != null)

            {

                MeshMaterialPair pair = new MeshMaterialPair(meshFilter.sharedMesh, renderer.sharedMaterial);

                if (!objectsByMeshMat.ContainsKey(pair))

                {

                    objectsByMeshMat[pair] = new List<GameObject>();

                }

                objectsByMeshMat[pair].Add(obj);

            }

        }

        // 將分組按原型整理

        foreach (var entry in objectsByMeshMat)

        {

            if (entry.Value.Count >= minInstanceCount)

            {

                GameObject prototype = entry.Value[0];

                instanceCandidates[prototype] = entry.Value;

            }

        }

        Debug.Log($"Found {instanceCandidates.Count} instance candidate groups");

    }

    bool IsSmallObject(GameObject obj)

    {

        Bounds bounds = GetObjectBounds(obj);

        float maxDimension = Mathf.Max(bounds.size.x, bounds.size.y, bounds.size.z);

        return maxDimension <= smallObjectThreshold;

    }

    bool IsVegetationObject(GameObject obj)

    {

        string name = obj.name.ToLower();

        return name.Contains("grass") || name.Contains("bush") || 

               name.Contains("flower") || name.Contains("weed") ||

               name.Contains("plant");

    }

    bool IsTerrainTree(GameObject obj)

    {

        // 通常地形上的樹被標記為樹原型的實例

        return obj.name.StartsWith("Tree") || 

               (obj.transform.parent != null && obj.transform.parent.name.Contains("Trees"));

    }

    Bounds GetObjectBounds(GameObject obj)

    {

        Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();

        if (renderers.Length == 0)

            return new Bounds(obj.transform.position, Vector3.zero);

        Bounds bounds = renderers[0].bounds;

        for (int i = 1; i < renderers.Length; i++)

        {

            bounds.Encapsulate(renderers[i].bounds);

        }

        return bounds;

    }

    void EnableGPUInstancingForObjects(GameObject prototype, List<GameObject> instances)

    {

        // 啟用該組所有對象的材質實例化

        foreach (GameObject obj in instances)

        {

            MeshRenderer renderer = obj.GetComponent<MeshRenderer>();

            if (renderer != null)

            {

                foreach (Material mat in renderer.sharedMaterials)

                {

                    if (mat != null && !mat.enableInstancing)

                    {

                        mat.enableInstancing = true;

                        EditorUtility.SetDirty(mat);

                    }

                }

            }

        }

        Debug.Log($"Enabled GPU Instancing for {instances.Count} objects of type '{prototype.name}'");

    }

    void ConvertToInstancedGroup(GameObject prototype, List<GameObject> instances)

    {

        // 創(chuàng)建一個容器來管理這組實例

        GameObject container = new GameObject($"InstancedGroup_{prototype.name}");

        InstancedVegetationGroup groupComponent = container.AddComponent<InstancedVegetationGroup>();

        // 配置組件

        MeshFilter meshFilter = prototype.GetComponent<MeshFilter>();

        MeshRenderer meshRenderer = prototype.GetComponent<MeshRenderer>();

        if (meshFilter != null && meshRenderer != null)

        {

            groupComponent.mesh = meshFilter.sharedMesh;

            groupComponent.material = meshRenderer.sharedMaterial;

            // 確保啟用instancing

            if (!groupComponent.material.enableInstancing)

            {

                groupComponent.material.enableInstancing = true;

                EditorUtility.SetDirty(groupComponent.material);

            }

            // 創(chuàng)建實例數據

            foreach (GameObject obj in instances)

            {

                InstanceData data = new InstanceData

                {

                    position = obj.transform.position,

                    rotation = obj.transform.rotation,

                    scale = obj.transform.lossyScale,

                    gameObject = obj

                };

                groupComponent.instances.Add(data);

            }

            // 添加一個編輯器只讀屬性

            SerializedObject serializedObj = new SerializedObject(groupComponent);

            SerializedProperty prop = serializedObj.FindProperty("instances");

            for (int i = 0; i < prop.arraySize; i++)

            {

                SerializedProperty element = prop.GetArrayElementAtIndex(i);

                element.FindPropertyRelative("gameObject").objectReferenceValue = instances[i];

            }

            serializedObj.ApplyModifiedProperties();

            // 默認隱藏原始對象

            if (groupComponent.hideOriginals)

            {

                foreach (GameObject obj in instances)

                {

                    if (obj != null)

                    {

                        Renderer renderer = obj.GetComponent<Renderer>();

                        if (renderer != null)

                        {

                            renderer.enabled = false;

                        }

                    }

                }

            }

            Debug.Log($"Created instanced group for {instances.Count} objects of type '{prototype.name}'");

        }

    }

    void AutoOptimizeAll()

    {

        foreach (var entry in instanceCandidates)

        {

            if (entry.Value.Count >= minInstanceCount)

            {

                ConvertToInstancedGroup(entry.Key, entry.Value);

            }

        }

    }

    [System.Serializable]

    public struct MeshMaterialPair

    {

        public Mesh mesh;

        public Material material;

        public MeshMaterialPair(Mesh m, Material mat)

        {

            mesh = m;

            material = mat;

        }

        public override bool Equals(object obj)

        {

            if (!(obj is MeshMaterialPair)) return false;

            MeshMaterialPair other = (MeshMaterialPair)obj;

            return mesh == other.mesh && material == other.material;

        }

        public override int GetHashCode()

        {

            unchecked

            {

                return ((mesh != null ? mesh.GetHashCode() : 0) * 397) ^ 

                       (material != null ? material.GetHashCode() : 0);

            }

        }

    }

}

// 實例數據結構

[System.Serializable]

public class InstanceData

{

    public Vector3 position;

    public Quaternion rotation;

    public Vector3 scale;

    public GameObject gameObject; // 編輯器引用,運行時不需要

}

// 用于管理一組實例的組件

public class InstancedVegetationGroup : MonoBehaviour

{

    public Mesh mesh;

    public Material material;

    public bool hideOriginals = true;

    public bool updateTransformsRuntime = false;

    public List<InstanceData> instances = new List<InstanceData>();

    private Matrix4x4[] matrices;

    void Start()

    {

        if (mesh == null || material == null)

        {

            Debug.LogError("Missing mesh or material for instanced group", this);

            return;

        }

        // 確保啟用instancing

        if (!material.enableInstancing)

        {

            material.enableInstancing = true;

        }

        // 準備變換矩陣

        matrices = new Matrix4x4[instances.Count];

        UpdateMatrices();

    }

    void UpdateMatrices()

    {

        for (int i = 0; i < instances.Count; i++)

        {

            if (updateTransformsRuntime && instances[i].gameObject != null)

            {

                // 如果需要從原始對象更新變換

                Transform t = instances[i].gameObject.transform;

                matrices[i] = Matrix4x4.TRS(t.position, t.rotation, t.lossyScale);

            }

            else

            {

                // 使用存儲的變換數據

                matrices[i] = Matrix4x4.TRS(

                    instances[i].position, 

                    instances[i].rotation, 

                    instances[i].scale);

            }

        }

    }

    void Update()

    {

        if (mesh == null || material == null || matrices == null)

            return;

        if (updateTransformsRuntime)

        {

            UpdateMatrices();

        }

        // 分批次渲染(解決1023個限制)

        int totalInstances = instances.Count;

        int batchSize = 1023; // 單批次最大實例數

        for (int i = 0; i < totalInstances; i += batchSize)

        {

            int currentBatchSize = Mathf.Min(batchSize, totalInstances - i);

            Matrix4x4[] batchMatrices = new Matrix4x4[currentBatchSize];

            System.Array.Copy(matrices, i, batchMatrices, 0, currentBatchSize);

            // 執(zhí)行實例化渲染

            Graphics.DrawMeshInstanced(mesh, 0, material, batchMatrices, currentBatchSize);

        }

    }

}

AI寫代碼

csharp

運行

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

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

性能考量與最佳實踐

批次管理限制

每批次最多 1023 個實例

超過 1023 個時需要多次調用 DrawMeshInstanced

變體控制

物體過于不同(縮放、參數)時實例化收益降低

使用 MaterialPropertyBlock 替代創(chuàng)建多個不同材質實例

陰影與光照交互

啟用 GPU Instancing 后陰影投射行為不變

對于大量小物體,可考慮關閉陰影投射或使用陰影貼圖代替

CPU 與 GPU 權衡

GPU Instancing 減輕 CPU 負擔但增加 GPU 工作

在 GPU 性能有限的移動設備上要謹慎使用

調試方法

Unity Frame Debugger 可查看實例化批次

運行時統(tǒng)計實例化對象數量和批次

總結

GPU Instancing 是一種強大的渲染優(yōu)化技術,能在保持場景視覺質量的同時顯著提高性能。通過了解其原理并使用本文提供的批量優(yōu)化工具和代碼示例,您可以系統(tǒng)性地優(yōu)化 Unity 項目中的渲染性能。最大的收益通常來自于場景中大量重復的小物體,如植被、裝飾物和粒子效果。正確實施后,可以在不犧牲視覺效果的情況下,大幅提升幀率和整體游戲體驗

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多