Search Unity

Pass an array to shader using gpu instancing

Discussion in 'Shaders' started by VictorKs, Jan 4, 2019.

  1. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    materialpropertyblock has a SetFloatArray() function, so can I pass a small array of 8 floats with GPU instancing?
     
  2. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,609
    The array size limit should be 1023, according to this information:
    https://forum.unity.com/threads/passing-array-to-shader.392586/#post-2663970

    Also important to know:
    https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.SetFloatArray.html
     
    VictorKs likes this.
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    So, it'd be helpful if you gave a bit more detail about what you're trying to do.

    When setting up material properties in a shader, you can set them up to be normal shader uniforms, or instanced properties. A normal uniform looks something like this:
    Code (csharp):
    1. fixed4 _Color;
    Where an instanced property looks like this:
    Code (csharp):
    1. UNITY_INSTANCING_BUFFER_START(Props)
    2.     UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
    3. UNITY_INSTANCING_BUFFER_END(Props)
    https://docs.unity3d.com/Manual/GPUInstancing.html

    Instanced properties can only be those existing non-array variable types, like int, or fixed4, or float, etc. There's no way to have an instanced property be an array. That's because internally those macros are creating an array of that property which can be index by instance ID, and HLSL doesn't support arrays in arrays. So, if you use an array you're stuck with a non-instanced uniform, which for instancing to work needs to have the same value for all instances, and should be set on the parent material itself, not using a MaterialPropertyBlock. If you set a non-instanced property with a property block that mesh will no longer be instanced and will be rendered normally as a unique draw, since for instancing to work it requires the material properties be the same for all instances, and you're setting that array to something unique.

    Instanced properties can be matrices, like float4x4. You could technically use a float3x3 (or float4x2 if you don't need to support OpenGL, which only supports square matrices) to store your floats in a single instanced matrix. Otherwise you could store the values in two float4 values which is honestly going to be the best option if the values need to be unique per object.

    Code (csharp):
    1.  
    2. UNITY_INSTANCING_BUFFER_START(Props)
    3.     UNITY_DEFINE_INSTANCED_PROP(float4, _FloatArrayA)
    4.     UNITY_DEFINE_INSTANCED_PROP(float4, _FloatArrayB)
    5. UNITY_INSTANCING_BUFFER_END(Props)
    6.  
    7. // in the shader function
    8. float4 floatArrayA = UNITY_ACCESS_INSTANCED_PROP(Props, _FloatArrayA);
    9. float4 floatArrayB = UNITY_ACCESS_INSTANCED_PROP(Props, _FloatArrayB);
    10. float floatArray[8] = {
    11.     floatArrayA.x,
    12.     floatArrayA.y,
    13.     floatArrayA.z,
    14.     floatArrayA.w,
    15.     floatArrayB.x,
    16.     floatArrayB.y,
    17.     floatArrayB.z,
    18.     floatArrayB.w
    19. };

    If you're using Unity's built in instancing of renderer components, then the answer to your question is effectively no, you cannot use SetFloatArray() and still have instancing work. You need to pass the values in using something other than an array.




    As an aside, if you're using DrawMeshInstanced functions, you can use SetFloatArray(), but that's because you have to create the arrays for instanced properties on your own. Technically you could define an array that's 8 times the size of the number of instances you're drawing and then index into the first element of each 8 values using instanceID * 8.
     
    Last edited: Jan 4, 2019
    VictorKs likes this.
  4. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    So I'm trying to render my characters and their attachments as one mesh using a texture array.
    Instead of using submeshes each submesh has a different uv0.z eg. body=0, helmet=1... so I was hoping that each character would receive an array pointing to the appropriate texture array layer.
    Array{bodyLayer,HelmetLayer...} ->Holds the actual layer No of the texArray

    eg. float layerNo= Array[uv0.z]

    maybe I'm overdoing this but I guess float3x3 could work, otherwise I will have to create different materials.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Yeah, use two float4 values like the example code. You'd have to convert the float3x3 into an array in the shader to index the values anyway, and two float4 values will be more efficient than a float 3x3.
     
    VictorKs likes this.
  6. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    This works nicely thanks for helping out, and one last rookie question I'm using surface shaders how can I access uv0.z INPUT uses uv_MainTex which I guess is const float2?

    Normally I'd calculate it on vertex shader and pass the layerNo to the frag.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    You are correct that uv_MainTex is always a float2. My recommendation is actually to totally skip using uv_MainTex, or any Input that includes the uv prefix, as Surface Shaders will always try to treat it as a float2. Which of course leads to the question: "how?"

    You can still do that. Surface Shaders have support for custom vertex functions, to an extent. You can put arbitrary variables in the Input struct and fill them with data calculated during the vertex shader stage. See the "Custom data computed per-vertex" example here:
    https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

    Code (csharp):
    1. #pragma surface surf ... vertex:vert
    2.  
    3. Input {
    4.     float3 texcoord;
    5. };
    6.  
    7. void vert (inout appdata_full v, out Input o) {
    8.     o.texcoord = v.texcoord.xyz;
    9. }
    Note the example uses the UNITY_INITIALIZE_OUTPUT macro. This just zeros out all values of the struct to avoid compiler warnings if you forget to assign them all. I don't use it as I'd prefer to see the warnings as it lets me know I made a mistake. You should never have variables passed between the vertex and fragment shader that aren't being assigned because that likely means they're not being used, and there's a cost to transferring that data.
     
    VictorKs likes this.
  8. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    Got it so I should skip preconfigured uv_MainTex and manually transform the texcoords in the vert function and pass it to Input so I will retain the 3rd parameter?
    So I'm doing a vert->INPUT->surf right?
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Yep. Alternatively you could pass the third component as a float and keep using uv_MainTex, but I also never use the per texture scale offset settings which the uv_MainTex will automatically apply. It only saves a single MAD instruction, but I know I don't want it.

    If you do make liberal use of the per texture scale offset, and have multiple textures using the first UV with different scale offset values, it's still better to not use uv_MainTex as you likely have a separate uv_* for each texture. It's far faster to pass the first UV unmodified and call the TRANSFORM_TEX macro in the surface function for each texture.
     
    VictorKs likes this.
  10. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    Yes I never use per texture scale/offset anyways and I always wondered why each texture needs different preconfigured uvs, now I get it. So since I don't use scale offset I guess I should TRANSFORM_TEX in vert() instead of surf right?? Thanks a lot for the help!
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    No, you shouldn't use TRANSFORM_TEX at all. It only exists to apply the scale offset values.
     
    VictorKs likes this.
  12. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    ohh yes you are right I somehow got mixed up :D Anyway thanks a lot for the help.
     
    netdevo likes this.