生成并显示数千个多边形的最佳实践及性能优化技术问询
嘿,我完全懂你的痛点——实例化上千个带Mesh的对象在低配设备上确实会吃爆性能,毕竟每个实例都要占用独立的Draw Call和内存资源,尤其是手机这类资源有限的设备。既然这些都是静态多边形,咱们有几个高效的解决方案,其中Shader驱动的方案是最适合的,我给你一步步拆解,从省心的官方方案到极致性能的自定义实现:
方案一:GPU Instancing(最省心,几乎不用写自定义Shader)
这是Unity官方提供的批量渲染方案,专门针对大量重复对象的场景,完全适配你的静态多边形需求:
- 第一步:配置资源
把你的单个多边形Mesh做成Prefab,打开Prefab的Mesh Renderer面板,勾选Enable GPU Instancing选项。然后用Unity内置的Shader(比如Universal Render Pipeline/Lit或者Standard),这些Shader默认已经支持GPU Instancing。 - 第二步:批量渲染代码
不要一个个Instantiate对象,而是用Graphics.DrawMeshInstanced一次性把所有多边形渲染出来。你只需要提前准备好每个多边形的位置/旋转/缩放矩阵,以及颜色数组:public Mesh polygonMesh; public Material instancedMaterial; private Matrix4x4[] _instanceMatrices; private Color[] _instanceColors; private int _polygonCount = 10000; void Start() { // 提前初始化所有实例的数据(静态对象只需初始化一次) _instanceMatrices = new Matrix4x4[_polygonCount]; _instanceColors = new Color[_polygonCount]; for (int i = 0; i < _polygonCount; i++) { // 随机生成示例位置,你可以替换成自己的坐标数据 Vector3 pos = new Vector3(Random.Range(-20, 20), Random.Range(-20, 20), 0); _instanceMatrices[i] = Matrix4x4.TRS(pos, Quaternion.Euler(0, 0, Random.Range(0, 360)), Vector3.one); _instanceColors[i] = new Color(Random.value, Random.value, Random.value); } // 把颜色设为实例化属性,让每个多边形能单独改色 instancedMaterial.SetColorArray("_BaseColor", _instanceColors); } void Update() { // 一次性渲染所有实例,仅需1个Draw Call Graphics.DrawMeshInstanced(polygonMesh, 0, instancedMaterial, _instanceMatrices, _polygonCount); } - 颜色修改技巧:如果后期需要修改部分多边形的颜色,只需更新
_instanceColors数组,再调用instancedMaterial.SetColorArray("_BaseColor", _instanceColors)即可,静态场景下尽量少做动态修改,性能会更好。
方案二:自定义Shader + Compute Buffer(极致性能,适合超大量多边形)
如果GPU Instancing还满足不了你的需求(比如数万个甚至数十万个多边形),可以用Compute Buffer把所有多边形的参数传到GPU,让Shader直接生成顶点并渲染,全程只有1个Draw Call:
- 第一步:定义数据结构
不用存每个多边形的所有顶点,而是用更轻量化的参数(中心位置、尺寸、旋转、颜色)来推导顶点,节省内存:public struct PolygonData { public Vector3 center; public Vector2 size; public float rotation; public Color color; } - 第二步:初始化Compute Buffer
把所有多边形的数据传到GPU的Buffer中:public ComputeBuffer _polygonBuffer; public Material _polygonMaterial; private int _polygonCount = 50000; void Start() { PolygonData[] polygons = new PolygonData[_polygonCount]; for (int i = 0; i < _polygonCount; i++) { polygons[i] = new PolygonData { center = new Vector3(Random.Range(-30, 30), Random.Range(-30, 30), 0), size = new Vector2(Random.Range(0.5f, 2f), Random.Range(0.5f, 2f)), rotation = Random.Range(0, Mathf.PI * 2), color = new Color(Random.value, Random.value, Random.value) }; } // 创建GPU Buffer,传入数据大小和数量 _polygonBuffer = new ComputeBuffer(_polygonCount, System.Runtime.InteropServices.Marshal.SizeOf(typeof(PolygonData))); _polygonBuffer.SetData(polygons); // 把Buffer传给Shader _polygonMaterial.SetBuffer("_PolygonBuffer", _polygonBuffer); } void OnDestroy() { // 必须释放Buffer,避免内存泄漏 if (_polygonBuffer != null) _polygonBuffer.Release(); } void OnRenderObject() { // 激活Shader Pass,调用GPU渲染所有多边形 _polygonMaterial.SetPass(0); // 按四边形拓扑渲染,每个多边形占4个顶点 Graphics.DrawProcedural(MeshTopology.Quads, _polygonCount * 4); } - 第三步:编写自定义Shader(URP版本)
这个Shader会读取Compute Buffer里的参数,实时生成每个多边形的顶点并上色:Shader "Custom/ProceduralStaticPolygons" { SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityURP.cginc" // 和C#端对应的数据结构 struct PolygonData { float3 center; float2 size; float rotation; float4 color; }; // GPU端的Buffer StructuredBuffer<PolygonData> _PolygonBuffer; struct appdata { uint vertexID : SV_VertexID; uint instanceID : SV_InstanceID; }; struct v2f { float4 vertex : SV_POSITION; float4 color : COLOR; }; v2f vert (appdata v) { v2f o; // 获取当前实例的多边形数据 PolygonData poly = _PolygonBuffer[v.instanceID]; // 生成四边形的四个本地顶点坐标 float2 uv = float2(v.vertexID % 2, v.vertexID / 2) * 2 - 1; // 应用旋转 float rotSin = sin(poly.rotation); float rotCos = cos(poly.rotation); float2 rotatedUV = float2(uv.x * rotCos - uv.y * rotSin, uv.x * rotSin + uv.y * rotCos); // 计算世界空间位置并转成裁剪空间 float3 worldPos = poly.center + float3(rotatedUV * poly.size * 0.5, 0); o.vertex = TransformWorldToHClip(worldPos); o.color = poly.color; return o; } float4 frag (v2f i) : SV_Target { return i.color; } ENDCG } } } - 适配三角形多边形:如果你的多边形是三角形,只需把
MeshTopology.Quads改成MeshTopology.Triangles,每个实例对应3个顶点即可。
额外小贴士
- 静态对象的所有数据尽量在
Start里一次性初始化,不要每帧更新,能最大化性能。 - 如果需要修改颜色,只需更新C#端的
PolygonData数组,再调用_polygonBuffer.SetData(polygons)即可,静态场景下尽量减少动态修改次数。
内容的提问来源于stack exchange,提问作者MrPanda
相关产品推荐
相关产品推荐

