|
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 項目中的渲染性能。最大的收益通常來自于場景中大量重復的小物體,如植被、裝飾物和粒子效果。正確實施后,可以在不犧牲視覺效果的情況下,大幅提升幀率和整體游戲體驗 |
|
|
來自: 勤奮不止 > 《游戲引擎unity》