GPU Instancing with Surface Shader
I am not being able to get the StructuredBuffer to work with a Surface Shader, simply because UNITY_INSTANCING_ENABLED or UNITY_PROCEDURAL_INSTANCING_ENABLED are not getting enabled. I am trying to render GPU instanced meshes via a DrawMeshInstancedIndirect call, along with an instance buffer (and another indexed color look up buffer for efficient color variation). All the instances are being rendered but at the same location. I have managed to successfully get this working with vertex/fragment shader (image attached from my ECS walkers demo), but am just stuck trying to make it work for a lit Standard Surface Shader. Would really appreciate it if someone can help me understand if I am missing something in any setup.
I have ensured that the Material's "Enable Instancing" checkbox is on. Any word of advice pls @richardkettlewell ?
The shader code is attached below:
Shader "Instanced/InstancedSurfaceShader" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard vertex:vert
#pragma target 4.5
//#pragma multi_compile_instancing
#include "UnityCG.cginc"
#include "UnityInstancing.cginc"
struct instanceBuf
{
float4 posScale;
float4 rotQuat;
float batchID;
};
struct colorTable
{
float3 batchColor;
};
#ifdef UNITY_INSTANCING_ENABLED
StructuredBuffer<instanceBuf> instanceBuffer;
StructuredBuffer<colorTable> colorLUT;
#endif
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID //not sure if this is needed
};
struct Input
{
float2 uv_MainTex : TEXCOORD0;
float4 color : COLOR;
};
void vert(inout appdata v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input,o);
UNITY_SETUP_INSTANCE_ID(v);//not sure if this is needed
#ifdef UNITY_INSTANCING_ENABLED
float4 data1 = instanceBuffer[unity_InstanceID].posScale;
float4 q = instanceBuffer[unity_InstanceID].rotQuat;
float3 color = colorLUT[instanceBuffer[unity_InstanceID].batchID)].batchColor;
#else
float4 data1 = 4;//Purposely scale up 4x for debug purposes
float4 q = 0;//sadly, this block gets called always
float3 color = (.4,.2,.3);
#endif
float3 v0 = v.vertex.xyz * data1.w;//scale
float3 v1 = v0 + 2.0 * cross(q.xyz, cross(q.xyz, v0) + q.w * v0);//rotate
float3 worldPosition = data1.xyz + v1;
float3 n = normalize(v.normal);
float3 worldNormal = n + 2.0 * cross(q.xyz, cross(q.xyz, n) + q.w * n);//rotate
v.vertex.xyz = worldPosition;
v.normal = worldNormal;
o.uv_MainTex = v.texcoord;
o.color = (color,1);
}
half _Glossiness;
half _Metallic;
float4 _Color;
sampler2D _MainTex;
void surf (Input f, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D(_MainTex, f.uv_MainTex);
o.Albedo = c.rgb * (0.3*_Color + 0.7*f.color);
o.Alpha = c.a;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
}
ENDCG
}
FallBack "Diffuse"
}
I am also attaching the relevant snippets that setup the buffers leading up to the draw call here in the C# RenderSystem class. This works well for vertex/fragment shaders but does not yet work for Surface Shaders or Deferred Shaders. Pls. let me know if this is not the correct approach for Surface Shaders:
//1. populate the instance buffer
instanceBuffer.Add(new ComputeBuffer(instanceCount,9*sizeof(float)));
float[] tx = new float[instanceCount * 9];
for (int j = 0, k = 0; j < instanceCount; j++, k += 9)
{
Vector3 p = pos[j].Value;
tx[k] = p.x;
tx[k + 1] = p.y;
tx[k + 2] = p.z;
tx[k + 3] = size[j].Value;
tx[k + 4] = rot[j].Value.value.x;
tx[k + 5] = rot[j].Value.value.y;
tx[k + 6] = rot[j].Value.value.z;
tx[k + 7] = rot[j].Value.value.w;
tx[k + 8] = boid[j].Value;
}
instanceBuffer[i].SetData(tx);
//2. Setup args buffer
args[0] = (uint)instanceMesh[j].GetIndexCount(subMeshIndex);
args[1] = (uint)instanceCount;
args[2] = (uint)instanceMesh[j].GetIndexStart(subMeshIndex);
args[3] = uint)instanceMesh[j].GetBaseVertex(subMeshIndex);
argsBuffer[j].SetData(args);
//3. Link the buffers to the shader material
instanceMaterial.SetBuffer("instanceBuffer", instanceBuffer[i]);
instanceMaterial.SetBuffer("colorLUT", colorLUT);
//4. Now Render all the instances for the given mesh
Graphics.DrawMeshInstancedIndirect(instanceMesh[j], 0, instanceMaterial,new Bounds(new Vector3(0f, 1f, 0f), new Vector3(200f, 200f, 200f)),argsBuffer[j],0,null,0,false);
References
@gashraf Sorry for the late reply, I have the same question. Did you figure this out?