GPU instancing 很早就支持手机了(Android只支持Opengl ES 3.0),最近在调研这个就对它测试了一下。
如果是不动的物体勾选static静态合并批次(40-50帧率)
自定义Shader中勾选Enable GPU Instancing
帧率竟然还不如静态合批次(帧率 30-40)
自定义Shader
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中接收位置 颜色 矩阵
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 positionBuffer;
StructuredBuffer colorBuffer;
StructuredBuffer 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
}
}
}
在代码中将位置 颜色 矩阵传进去就行了
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的优势就更明显了。 目前来看草、石头、植被、比较合适用它。