Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Question Adding GPU Instancing to _BaseColor property in URP Lit Shader

Discussion in 'Shaders' started by QuariYune, Nov 7, 2022.

  1. QuariYune

    QuariYune

    Joined:
    Jul 1, 2021
    Posts:
    13
    I'm trying to allow the "_BaseColor" property of the standard URP Lit shader to accept an array of colors in a MaterialPropertyBlock. I've made sure the Material has "Enable GPU Instancing" on. Here is what I have:

    Code (CSharp):
    1. MaterialPropertyBlock baseMPB = new MaterialPropertyBlock();
    2. Vector4[] _colorsBase = new Vector4[myArray.Length];
    3.  
    4. for (int index = 0; index < myArray.Length; index++)
    5. {
    6.     // Some function that sets _colorsBase vectors
    7. }
    8.  
    9. baseMPB.SetVectorArray("_BaseColor", _colorsBase);
    10. Graphics.DrawMeshInstanced(myMesh, 0, myMaterial, myTransformsArray, myArray.Length, baseMPB);
    However, this only uses the very first color in _colorsBase and does not change colors on subsequent meshes.

    I've done quite a lot of looking and it seems according to this thread, Color is not setup to be instanced by default.
    https://forum.unity.com/threads/mat...work-as-expected-with-standard-shader.587302/

    I don't have too much experience with ShaderLab nor HLSL so it's been difficult even trying to figure out where to get started with adding it in. It looks like I have to Copy+Paste both "Lit.Shader" and "LitInput.hlsl" and make changes on those. However, reading through these threads still have me very confused on how to start:
    https://forum.unity.com/threads/pass-an-array-to-shader-using-gpu-instancing.607138/
    https://forum.unity.com/threads/gpu-instancing-in-urp.1282808/
    https://forum.unity.com/threads/mat...etvectorarray-doesnt-work-as-expected.577024/
    https://cmwdexint.com/2020/07/13/urp-and-gpu-instancing-mpb/

    On top of that, taking a look inside LitInput.hlsl seems to include this snippet:
    Code (CSharp):
    1. // NOTE: Do not ifdef the properties for dots instancing, but ifdef the actual usage.
    2. // Otherwise you might break CPU-side as property constant-buffer offsets change per variant.
    3. // NOTE: Dots instancing is orthogonal to the constant buffer above.
    4. #ifdef UNITY_DOTS_INSTANCING_ENABLED
    5. UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
    6.     UNITY_DOTS_INSTANCED_PROP(float4, _BaseColor)
    7.     ...
    8. UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)
    9.  
    10. #define _BaseColor              UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(float4 , _BaseColor)
    11. ...
    12. #endif
    Which seems to indicate that there's already been work done on instancing _BaseColor? Where should I even start for this? I don't even know when the conversion between Color in Lit.shader and float4 in LitInput.hlsl occurs.
     
  2. QuariYune

    QuariYune

    Joined:
    Jul 1, 2021
    Posts:
    13
    Alright I managed to figure it out with this tutorial: https://catlikecoding.com/unity/tutorials/custom-srp/draw-calls/

    For any newbies like me who didn't really understand it, here is what I did:

    I duplicated the lit.shader file. Then I tried to find where _BaseColor gets declared. As it turns out this code essentially means that the LitInput.hlsl and DepthOnlyPass.hlsl files are just copy+pasted into the "#include" part when compiled:

    Code (CSharp):
    1. ...
    2. //--------------------------------------
    3. // GPU Instancing
    4. #pragma multi_compile_instancing
    5. #pragma multi_compile _ DOTS_INSTANCING_ON
    6.  
    7. #include ".../LitInput.hlsl"
    8. #include ".../DepthOnlyPass.hlsl"
    9. ENDHLSL
    As it turns out, _BaseColor gets defined in LitInput.hlsl. In particular this code:
    Code (CSharp):
    1. CBUFFER_START(UnityPerMaterial)
    2. float4 _BaseMap_ST;
    3. float4 _DetailAlbedoMap_ST;
    4. half4 _BaseColor;
    5. half4 _SpecColor;
    6. half4 _EmissionColor;
    7. half _Cutoff;
    8. half _Smoothness;
    9. half _Metallic;
    10. half _BumpScale;
    11. half _Parallax;
    12. half _OcclusionStrength;
    13. half _ClearCoatMask;
    14. half _ClearCoatSmoothness;
    15. half _DetailAlbedoMapScale;
    16. half _DetailNormalMapScale;
    17. half _Surface;
    18. CBUFFER_END
    So what I did was I duplicated LitInput.hlsl and changed the path of the previous #include to point to my duplicate. I then deleted that "half4 _BaseColor" line, and added this snippet right below it:
    Code (CSharp):
    1. ...
    2. CBUFFER_END
    3.  
    4. UNITY_INSTANCING_BUFFER_START(UnityPerMaterialInstanced)
    5. UNITY_DEFINE_INSTANCED_PROP(half4, _BaseColor)
    6. UNITY_INSTANCING_BUFFER_END(UnityPerMaterialInstanced)
    Next, I looked for any _BaseColor usage in the .hlsl files used by the Lit.shader file, and replaced it with this:
    Code (CSharp):
    1. UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterialInstanced, _BaseColor)
    For Lit.shader, _BaseColor gets referenced in LitInput.hlsl, ShadowCasterPass.hlsl, and DepthOnlyPass.hlsl. So I duplicated those files, changed the #include path to point to my duplicates, and replaced the _BaseColor on all those files.

    Applying this new shader to my original material now instances color properly. Of course I'm sure this would cause more issues if my use case wasn't this simple (Literally just changing the color of the base lit shader), but this is how you do it since there doesn't seem to be a guide for it without learning a bit of shader syntax.