Search Unity

MaterialPropertyBlock's SetVectorArray doesn't work as expected

Discussion in 'General Graphics' started by Noxalus, Oct 31, 2018.

  1. Noxalus

    Noxalus

    Joined:
    Jan 9, 2018
    Posts:
    80
    Hello everyone!

    I need to draw a large amount of moving objects that can change their color over time.

    All objects are rendered with the same texture that I put on a quad (like a 2D sprite). So, to do this, I currently use the famous Graphics.DrawMeshInstanced method like that:

    Code (CSharp):
    1. foreach (var batch in transformMatrices)
    2.     Graphics.DrawMeshInstanced(quadMesh, 0, material, batch, batch.Length, materialPropertyBlock);
    I've also written a custom shader with a _Color parameter using the [PerRendererData] flag (and there is no color field in the material that uses this shader). But if I have an array of Vector4 representing the color of each object and that I give it to my MaterialPropertyBlock using the SetVectorArray method, it will only take the first element of the array for every objects:

    Code (CSharp):
    1. materialPropertyBlock.SetVectorArray("_Color", colors);
    Do you know why? Did I miss something?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    You're not showing enough of your code for us to know. Post your shader, post more of your c#.
     
  3. Noxalus

    Noxalus

    Joined:
    Jan 9, 2018
    Posts:
    80
    Thank you for your answer @bgolus!

    I'm sorry, I wouldn't drown you under a pile of code!

    But in fact, my attempt is pretty simple. Here is the shader I use:

    Code (CSharp):
    1. Shader "Sprites/Instanced"
    2. {
    3.   Properties
    4.   {
    5.     _MainTex ("Texture", 2D) = "white" {}
    6.     [PerRendererData] _Color ("Tint", Color) = (1,1,1,1)
    7.   }
    8.   SubShader
    9.   {
    10.     Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    11.     LOD 100
    12.     Blend SrcAlpha OneMinusSrcAlpha
    13.  
    14.     Pass
    15.     {
    16.       CGPROGRAM
    17.       #pragma vertex vert
    18.       #pragma fragment frag
    19.       #pragma multi_compile_instancing
    20.       #include "UnityCG.cginc"
    21.  
    22.       struct appdata
    23.       {
    24.         float4 vertex : POSITION;
    25.           UNITY_VERTEX_INPUT_INSTANCE_ID
    26.         float2 uv : TEXCOORD0;
    27.         float4 color : COLOR;
    28.       };
    29.  
    30.       struct v2f
    31.       {
    32.         float2 uv : TEXCOORD0;
    33.         float4 vertex : SV_POSITION;
    34.         fixed4 color : COLOR;
    35.       };
    36.  
    37.       sampler2D _MainTex;
    38.       float4 _MainTex_ST;
    39.       fixed4 _Color;
    40.  
    41.       // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    42.       // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    43.       // #pragma instancing_options assumeuniformscaling
    44.       UNITY_INSTANCING_BUFFER_START(Props)
    45.           //stuff
    46.       UNITY_INSTANCING_BUFFER_END(Props)
    47.  
    48.       v2f vert (appdata v)
    49.       {
    50.         v2f o;
    51.         UNITY_SETUP_INSTANCE_ID(v);
    52.         o.vertex = UnityObjectToClipPos(v.vertex);
    53.         o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    54.         o.color = v.color * _Color;
    55.  
    56.         return o;
    57.       }
    58.  
    59.       fixed4 frag (v2f i) : SV_Target
    60.       {
    61.         // sample the texture
    62.         fixed4 c = tex2D(_MainTex, i.uv) * i.color;
    63.  
    64.         return c;
    65.       }
    66.       ENDCG
    67.     }
    68.   }
    69. }
    I generate my mesh quad with this method:

    Code (CSharp):
    1. public static Mesh GenerateQuad(float size, Vector2 pivot)
    2.     {
    3.         Vector3[] vertices =
    4.         {
    5.             new Vector3(size - pivot.x, size - pivot.y, 0),
    6.             new Vector3(size - pivot.x, 0 - pivot.y, 0),
    7.             new Vector3(0 - pivot.x, 0 - pivot.y, 0),
    8.             new Vector3(0 - pivot.x, size - pivot.y, 0)
    9.         };
    10.  
    11.         Vector2[] uv =
    12.         {
    13.             new Vector2(1, 1),
    14.             new Vector2(1, 0),
    15.             new Vector2(0, 0),
    16.             new Vector2(0, 1)
    17.         };
    18.  
    19.         int[] triangles =
    20.         {
    21.             2, 3, 0,
    22.             0, 1, 2,
    23.         };
    24.  
    25.         return new Mesh
    26.         {
    27.             vertices = vertices,
    28.             uv = uv,
    29.             triangles = triangles
    30.         };
    31.     }
    That I use in my InstantiatedSpriteRenderer class:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class InstantiatedSpriteRenderer : MonoBehaviour
    4. {
    5.     public Material Material;
    6.  
    7.     private Mesh _mesh;
    8.     MaterialPropertyBlock _materialPropertyBlock;
    9.     readonly Vector4[] _colors = new Vector4[5];
    10.  
    11.     public void Awake()
    12.     {
    13.         // Generate the quad mesh
    14.         var size = 1f;
    15.         var pivot = (Vector2.one / 2f) * size;
    16.         _mesh = MeshUtils.GenerateQuad(size, pivot);
    17.  
    18.         // Create the material property block
    19.         _materialPropertyBlock = new MaterialPropertyBlock();
    20.  
    21.         // Fill _colors array with different color
    22.         _colors[0] = new Vector4(1f, 1f, 0f, 1f);
    23.         _colors[1] = new Vector4(0f, 1f, 0f, 1f);
    24.         _colors[2] = new Vector4(0f, 0f, 1f, 1f);
    25.         _colors[3] = new Vector4(1f, 0f, 1f, 1f);
    26.         _colors[4] = new Vector4(1f, 1f, 1f, 1f);
    27.  
    28.         _materialPropertyBlock.SetVectorArray("_Color", _colors);
    29.     }
    30.  
    31.     private void Update()
    32.     {
    33.         // Note: TransformMatrices come from outside of this class
    34.         foreach (var batch in TransformMatrices)
    35.             Graphics.DrawMeshInstanced(_mesh, 0, Material, batch, batch.Length, _materialPropertyBlock);
    36.     }
    37. }
    With this example, my 5 sprites appear and move according to their transform matrix, but they all have the same color : the color specified at the first index of my _colors array. I did test to change the color of the first element of the array in the Update method, and I confirm all sprites color change at the sime time. Isn't that weird?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    So, first thing. [PerRendererData] doesn't do anything. It hides the property from the inspector, but otherwise doesn't do anything at all. It's not even related to instancing, it was added for setting textures on sprites, which don't use instancing.

    Second thing, the answer to your problem is in your shader already.
    Goto that link and look at the first example shader on that page.
     
  5. Noxalus

    Noxalus

    Joined:
    Jan 9, 2018
    Posts:
    80
    Great, I finally made it work as expected! The answer was indeed written in the documentation, sorry for that... And thank you very much for your help! :)

    Here is the modified shader:

    Code (CSharp):
    1. Shader "Sprites/Instanced"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Color ("Color", Color) = (1,1,1,1)
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    12.         LOD 100
    13.         Blend SrcAlpha OneMinusSrcAlpha
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #pragma multi_compile_instancing
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    27.                 float2 uv : TEXCOORD0;
    28.             };
    29.  
    30.             struct v2f
    31.             {
    32.                 float2 uv : TEXCOORD0;
    33.                 float4 vertex : SV_POSITION;
    34.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    35.             };
    36.  
    37.             sampler2D _MainTex;
    38.             float4 _MainTex_ST;
    39.  
    40.             // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    41.             // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    42.             // #pragma instancing_options assumeuniformscaling
    43.             UNITY_INSTANCING_BUFFER_START(Props)
    44.                 UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
    45.             UNITY_INSTANCING_BUFFER_END(Props)
    46.  
    47.             v2f vert (appdata v)
    48.             {
    49.                 v2f o;
    50.  
    51.                 UNITY_SETUP_INSTANCE_ID(v);
    52.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    53.  
    54.                 o.vertex = UnityObjectToClipPos(v.vertex);
    55.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    56.  
    57.                 return o;
    58.             }
    59.  
    60.             fixed4 frag (v2f i) : SV_Target
    61.             {
    62.                 UNITY_SETUP_INSTANCE_ID(i);
    63.  
    64.                 // sample the texture
    65.                 fixed4 c = tex2D(_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    66.  
    67.                 return c;
    68.             }
    69.             ENDCG
    70.         }
    71.     }
    72. }
     
    Kennai likes this.
  6. Noxalus

    Noxalus

    Joined:
    Jan 9, 2018
    Posts:
    80
    I have another silly question. I would like to make _MainTex_ST vary too. But it's not directly used in the shader, but by the TRANSFORM_TEX macro according to what I understood... I've already tried to move the variable into the instancing buffer using UNITY_DEFINE_INSTANCED_PROP, but without any surprise, I have an error that says the _MainTex_ST variable doesn't exist line 55. That was expected because the macro shouldn't use the UNITY_ACCESS_INSTANCED_PROP macro to access to the value of _MainTex_ST, but how am I supposed to do that?

    EDIT: Ok, the content of the TRANSFORM_TEX macro can be read at Unity\Editor\Data\CGIncludes\UnityCG.inc and it's just that:

    Code (CSharp):
    1. // Transforms 2D UV by scale/bias property
    2. #define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
    So I've just replaced this:

    Code (CSharp):
    1. o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    By that:

    Code (CSharp):
    1. o.uv = (v.uv * UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_ST).xy) + UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_ST).zw;
    And it's working like a charm :)
     
    Last edited: Nov 1, 2018
    richardkettlewell and bgolus like this.