I’m attempting to add a new instanced property to a shader that’s used to render sprites. The Sprite Renderer is able to set two instanced properties, _RendererColor and _Flip, which I’m able to make use of in my shader and which work correctly with instancing. I now need to add my own instanced properties but doing so breaks instancing and I’m unsure why.
I’m making use of a MaterialPropertyBlock to set an additional Color property called _InstancedColor (as I know _RendererColor works so the type is supported), the Property is declared in my .shader, and in the .hlsl it’s declared with UNITY_DEFINE_INSTANCED_PROP(float4, _InstancedColor) within a UNITY_INSTANCING_BUFFER_START/END and accessed with UNITY_ACCESS_INSTANCED_PROP. I’ll post the full shader and script code below with some screens of the frame debugger to show what is happening.
The main difference between my property and the ones coming from the SpriteRenderer is that the SpriteRenderer property names don’t match in the Instancing buffer and the Property block. In the Property block _RendererColor is used, but within the Instancing buffer they declare unity_SpriteRendererColorArray and then use #define _RendererColor UNITY_ACCESS_INSTANCED_PROP(PerDrawSprite, unity_SpriteRendererColorArray) to allow the use of the Property name. I’ve attempted to do something similar but it still caused the same issues and I’ve found no explanation in the documentation as to why this would need to be done.
Also if I clear the MaterialPropertyBlock that comes from the SpriteRenderer and then just set my instanced Color value the instancing does seem to work correctly but as soon as I try and set _MainTex to a texture the instancing breaks again.
The .shader file
Shader "My Pipeline/L2DLSpriteStandard"
{
Properties
{
[PerRendererData] _MainTex ("Sprite", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
[HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
[HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
[HideInInspector] _InstancedColor ("Instanced Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
Name "L2DLSpriteStandard"
Tags
{
"Queue" = "Transparent"
"RenderType"="Transparent"
"PreviewType"="Plane"
}
ZWrite Off
Blend One OneMinusSrcAlpha
HLSLPROGRAM
#pragma target 3.5
#pragma multi_compile_instancing
#pragma vertex SpriteVert
#pragma fragment SpriteFrag
#include "../ShaderLibrary/L2DLSpriteStandard.hlsl"
ENDHLSL
}
}
}
The .hlsl file
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
CBUFFER_START(UnityPerFrame)
float4x4 unity_MatrixVP;
CBUFFER_END
CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
CBUFFER_END
// Instancing support
#define UNITY_MATRIX_M unity_ObjectToWorld
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"
UNITY_INSTANCING_BUFFER_START(PerDrawSprite)
UNITY_DEFINE_INSTANCED_PROP(float4, unity_SpriteRendererColorArray)
UNITY_DEFINE_INSTANCED_PROP(float2, unity_SpriteFlipArray)
UNITY_DEFINE_INSTANCED_PROP(float4, _InstancedColor)
UNITY_INSTANCING_BUFFER_END(PerDrawSprite)
#define _RendererColor UNITY_ACCESS_INSTANCED_PROP(PerDrawSprite, unity_SpriteRendererColorArray)
#define _Flip UNITY_ACCESS_INSTANCED_PROP(PerDrawSprite, unity_SpriteFlipArray)
// Material Color.
float4 _Color;
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
inline float4 UnityFlipSprite(in float3 pos, in float2 flip)
{
return float4(pos.xy * flip, pos.z, 1.0);
}
inline float4 UnityObjectToClipPos(in float4 vert)
{
return mul(unity_MatrixVP, mul(UNITY_MATRIX_M, float4(vert.xyz, 1.0)));
}
v2f SpriteVert(appdata_t IN)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID (IN);
OUT.vertex = UnityFlipSprite(IN.vertex, _Flip);
OUT.vertex = UnityObjectToClipPos(OUT.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color * _RendererColor * UNITY_ACCESS_INSTANCED_PROP(PerDrawSprite, _InstancedColor);
#ifdef PIXELSNAP_ON
OUT.vertex = UnityPixelSnap (OUT.vertex);
#endif
return OUT;
}
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float4 SpriteFrag(v2f IN) : SV_Target0
{
float4 spriteColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.texcoord);
spriteColor.rgb *= spriteColor.a;
return spriteColor * IN.color;
}
The script on the object
[RequireComponent(typeof(SpriteRenderer))]
public class InstancedSprite : MonoBehaviour
{
private static MaterialPropertyBlock s_matPropBlock;
private static int s_colorPropertyID = Shader.PropertyToID("_InstancedColor");
[SerializeField] private Color m_color = Color.white;
private void Awake()
{
SetColorToMesh();
}
private void OnValidate()
{
SetColorToMesh();
}
private void SetColorToMesh()
{
if(s_matPropBlock == null)
{
s_matPropBlock = new MaterialPropertyBlock();
}
GetComponent<Renderer>().GetPropertyBlock(s_matPropBlock);
s_matPropBlock.SetColor(s_colorPropertyID, m_color);
GetComponent<Renderer>().SetPropertyBlock(s_matPropBlock);
}
}
When the script is not attached instancing works fine using the SpriteRenderer’s color value, which is set to unity_SpriteRendererColorArray behind the scenes.
When I attach the script and change the _InstancedColor value the instancing breaks due to 'Non-instanced properties set for instanced shader."
When I attach the script but don’t Get the MaterialPropertyBlock first (removing the properties set by the SpriteRenderer) instancing works correctly and I can change the colors using my new _InstancedColor property (but I then loose the texture and renderer color information).
Any help is obviously appreciated, it’s pretty likely there’s a macro or something that I’ve missed but I’m running out of things to try.
Jack