GPU instancing 很早就支持手机了(Android只支持Opengl ES 3.0),但是我一直不知道将它应用到哪里,刚好最近在调研这个我对它又重新测试了一下。
如果是不动的物体勾选static静态合并批次(40-50帧率)
自定义Shader中勾选Enable GPU Instancing
帧率竟然还不如静态合批次(帧率 30-40)
自定义Shader
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 |
Shader "SimplestInstancedShader1" { Properties { _Color ("Color", Color) = (1, 1, 1, 1) _MainTex ("Texture", 2D) = "white" {} } 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 // necessary only if you want to access instanced properties in fragment Shader. }; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Color) UNITY_INSTANCING_BUFFER_END(Props) sampler2D _MainTex; float4 _MainTex_ST; v2f vert(appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader. 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); // necessary only if any instanced properties are going to be accessed in the fragment Shader. return tex2D (_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color); } ENDCG } } } |
所以,静态不动的物体就没必要用上面的方法了,于是我又测试了Graphics.DrawMeshInstanced()方法,终于满意了。(稳定60帧)
Graphics.DrawMeshInstanced()方法不需要游戏对象以及游戏组件的额外开销,在Update()方法中一气呵成,不过它也有限制,最多可以画1023个。
还有个方法是Graphics.DrawMeshInstancedIndirect()它没有画多少的限制,而且更加灵活,我也搞了好一会儿才在游戏中跑起来,后来才知道它不能再手机上用,只有PC上可以.
在shader中接收位置 颜色 矩阵
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 |
Shader "Instanced/InstancedIndirectSelection" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags{ "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 4.5 #include "UnityCG.cginc" StructuredBuffer<float4> positionBuffer; StructuredBuffer<float4> colorBuffer; StructuredBuffer<float4x4> matrix4x4Buffer; struct appdata { fixed4 color : COLOR; float4 vertex : POSITION; float4 texcoord : TEXCOORD0; }; struct v2f { float4 color: COLOR; float4 vertex : SV_POSITION; float2 texcoord : TEXCOORD0; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert(appdata v, uint instanceID : SV_InstanceID) { float4 data = positionBuffer[instanceID]; float4x4 materix = matrix4x4Buffer[instanceID / matrix4x4Buffer.Length]; float3 worldPosition = data.xyz + mul(materix,v.vertex.xyz * data.w); v2f o; o.vertex = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0f)); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); o.color = colorBuffer[instanceID]; return o; } fixed4 frag(v2f i) : SV_Target { return tex2D (_MainTex, i.texcoord) * i.color; } ENDCG } } } |
在代码中将位置 颜色 矩阵传进去就行了
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 |
using UnityEngine; public class InstancedIndirectExample : MonoBehaviour { public int instanceCount = 100000; public Mesh instanceMesh; public Material instanceMaterial; private ComputeBuffer positionBuffer; private ComputeBuffer argsBuffer; private ComputeBuffer colorBuffer; private ComputeBuffer matrix4x4Buffer; private uint[] args = new uint[5] { 0, 0, 0, 0, 0 }; void Start() { argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments); UpdateBuffers(); } void Update() { // instanceMaterial.SetBuffer("positionBuffer", positionBuffer); Graphics.DrawMeshInstancedIndirect(instanceMesh, 0, instanceMaterial, new Bounds(Vector3.zero, new Vector3(100.0f, 100.0f, 100.0f)), argsBuffer); } void UpdateBuffers() { if ( instanceCount < 1 ) instanceCount = 1; // Positions & Colors if (positionBuffer != null) positionBuffer.Release(); if (colorBuffer != null) colorBuffer.Release(); if (matrix4x4Buffer != null) matrix4x4Buffer.Release(); positionBuffer = new ComputeBuffer(instanceCount, 16); colorBuffer = new ComputeBuffer(instanceCount, 4*4); matrix4x4Buffer = new ComputeBuffer(instanceCount,16); Vector4[] positions = new Vector4[instanceCount]; Vector4[] colors = new Vector4[instanceCount]; Matrix4x4[] materix4X4 = new Matrix4x4[instanceCount]; for (int i=0; i < instanceCount; i++) { positions [i] = new Vector4 (i, 0f, 0f, 1f); colors[i] = new Vector4( 1f, 1f, 1f, 1f ); materix4X4[i] = Matrix4x4.TRS (positions [i], Quaternion.Euler (0F, 0F, 0F), Vector3.one); } positionBuffer.SetData(positions); colorBuffer.SetData(colors); matrix4x4Buffer.SetData (materix4X4); instanceMaterial.SetBuffer("positionBuffer", positionBuffer); instanceMaterial.SetBuffer("colorBuffer", colorBuffer); instanceMaterial.SetBuffer("matrix4x4Buffer", matrix4x4Buffer); // indirect args uint numIndices = (instanceMesh != null) ? (uint)instanceMesh.GetIndexCount(0) : 0; args[0] = numIndices; args[1] = (uint)instanceCount; argsBuffer.SetData(args); } void OnDisable() { if (positionBuffer != null) positionBuffer.Release(); positionBuffer = null; if (colorBuffer != null) colorBuffer.Release(); colorBuffer = null; if (argsBuffer != null) argsBuffer.Release(); argsBuffer = null; if (matrix4x4Buffer != null) matrix4x4Buffer.Release(); matrix4x4Buffer = null; } } |
如果是发生移动的物体顶点数量在900以内会动态合并批次,如果需要支持更多的顶点GPU Instaning的优势就更明显了。 目前来看草、石头、植被、比较合适用它。
- 本文固定链接: https://www.xuanyusong.com/archives/4488
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!
你好!请问Image上的材质可以使用GPUInstancing吗?试了下,好像不太行,有什么办法可以让Image也使用GPUInstancing吗?
ugui的image是会根据canvas完整的合并mesh,如果想用GPUInstancing那得用spriterender将material block传进去
你好,请问这个错误怎么处理,对shader不太了解
ArgumentException: ComputeBuffer.SetData() : Accessing 64000 bytes at offset 0 for Compute Buffer of size 16000 bytes is not possible.
Unity3D 用几何着色器制作的草可以使用Graphics.DrawMeshInstancedIndirect()吗?
只要支持ComputeShader就可以用Graphics.DrawMeshInstancedIndirect()了
StructuredBuffer的长度是不是不可以通过Length来取了? 我经过了尝试,shader会因此而报错
Graphics.DrawMeshInstancedIndirect()可以再mobile上面用