Is it possible to have Graphics.DrawInstancedIndirect objects to receive lighting?

I’ve been trying to get a Graphics.DrawMeshInstancedIndirect mesh to have a material that can receive lighting. To a degree I’ve gotten ambience working. Is this actually possible to have them individually receive shadows from the main light and shadow casters? Will this require a compute shader to accomplish or something?

Here’s the code I’ve got and I’ve included an unlisted yt video below of the problem…

I’m not going to include all of the code for using Graphics.DrawMeshInstancedIndirect here, but I will show the method call. I am making sure recieve shadows is set to true:

            Graphics.DrawMeshInstancedIndirect(mesh, 0, material, bounds, argsBuffer, 0, null, UnityEngine.Rendering.ShadowCastingMode.On, true);

And I’ve tried basically for 16 hours straight right now going over shader after shader to try to get it working.

Shader "TestGrass/GrassF" {
    Properties{
        _Color("_Color", Color) = (0.26,0.78,0.36,1)
        _Brightness("_Brightness", float) = 1.0
        _MaxStrength("_MaxStrength", float) = 0.5
        _MinStrength("_MinStrength", float) = 0.25
        _StrengthScale("_StrengthScale", float) = 0.05
        _Speed("_Speed", float) = 60
        _Offset("_Offset", float) = 0.0
        _Interval("_Interval", float) = 1.0
        _HeightOffset("_HeightOffset", float) = 2.1
        _Detail("_Detail", float) = 1.0
    }
        SubShader{
            Tags { "RenderType" = "Opaque" "LightMode" = "ForwardBase" }

            Pass {
                CGPROGRAM
                 #pragma vertex vert
                 #pragma fragment frag
                #pragma multi_compile_fwdbase

                #include "UnityCG.cginc"
                 #include "UnityLightingCommon.cginc"
                #include "AutoLight.cginc"

                 float _Brightness;
                 float _MaxStrength;
                 float _MinStrength;
                 float _StrengthScale;
                 float _Speed;
                 float _Offset;
                 float _Interval;
                 float _HeightOffset;
                 float _Detail;

                 struct appdata_t {
                     float4 vertex   : POSITION;
                     float4 color    : COLOR;
                     float4 texcoord : TEXCOORD0;
                 };

                 struct v2f
                 {
                     float4 pos : SV_POSITION;
                     LIGHTING_COORDS(0, 1)
                     float4 color : COLOR;
                 };

                 struct MeshProperties {
                     float4x4 mat;
                     float4 color;
                 };
                 float rand(float2 p)
                 {
                     return frac(sin(dot(p, float2(12.9898, 78.233))) * 43758.5453);
                 }
                 float getWind(float2 vertex, float2 uv, float time) {
                     float detailOffset = rand(float2(1.0, 2.0));
                     float diff = pow(_MaxStrength - _MinStrength, 2.0);
                     float strength = clamp(_MinStrength + diff + sin(time / _Interval) * diff, _MinStrength, _MaxStrength) * _StrengthScale;
                     float wind = (sin(time) + cos(time * _Detail)) * strength * max(0.0, (1.0 - uv.y) - _HeightOffset);

                     return wind;
                 }

                 fixed4 _Color;
                 StructuredBuffer<MeshProperties> _Properties;

                 v2f vert(appdata_full v, uint instanceID: SV_InstanceID) {

                     float time = _Time * _Speed + _Offset;
                     v.vertex.x += getWind(v.vertex.xy, v.texcoord.xy * -1, time);

                     v2f o;

                     float4 pos = mul(_Properties[instanceID].mat, v.vertex);
                     o.pos = UnityObjectToClipPos(pos);
                     o.color = _Properties[instanceID].color;

                     half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                     half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                     o.color = nl * _LightColor0;
                     o.color.rgb += ShadeSH9(half4(worldNormal, 1));

                     TRANSFER_VERTEX_TO_FRAGMENT(o);
                     return o;
                }


                 fixed4 frag(v2f i) : SV_Target{
                     fixed4 col = _Color * _Brightness;
                     col *= i.color;

                     float attenuation = LIGHT_ATTENUATION(i);
                     col *= attenuation;
                     return col;
                }

                ENDCG
            }
    }
    Fallback "VertexLit"
}

I have an update for today on trying to get this working.

While I got the grass to spawn on terrain, around dirt roads, etc…, and I got lighting to work:

The last thing is that the shadow is showing up in front of the blades of grass.

I can’t really figure out what it would be, still digging around…

Shader "Lit/Diffuse With Shadows"
{
    Properties
    {
        [NoScaleOffset] _MainTex("Texture", 2D) = "white" {}
    }
        SubShader
    {
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

        // compile shader into multiple variants, with and without shadows
        // (we don't care about any lightmaps yet, so skip these variants)
        #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
        // shadow helper functions and macros
        #include "AutoLight.cginc"

        struct MeshProperties {
            float4x4 matrice;
            float4 uv;
            float4 color;
        };

        StructuredBuffer<MeshProperties> _Properties;

        struct v2f
        {
            float2 uv : TEXCOORD0;
            SHADOW_COORDS(1) // put shadows data into TEXCOORD1
            fixed3 diff : COLOR0;
            fixed3 ambient : COLOR1;
            float4 pos : SV_POSITION;
        };
        v2f vert(appdata_base v, uint instanceID: SV_InstanceID)
        {
            float4 pos = float4(0, 0, 0, 0);

            pos += mul(_Properties[instanceID].matrice, v.vertex);
            float4 uv = _Properties[instanceID].uv;


            //v2f o;
            //o.pos = UnityObjectToClipPos(pos);
            //o.uv.xy = v.texcoord;

            v2f o;
            o.pos = UnityObjectToClipPos(pos);
            o.uv.xy = v.texcoord;
            half3 worldNormal = UnityObjectToWorldNormal(v.normal);
            half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
            o.diff = nl * _LightColor0.rgb;
            o.ambient = ShadeSH9(half4(worldNormal,1));
            // compute shadows data
            TRANSFER_SHADOW(o)
            return o;
        }

        sampler2D _MainTex;

        fixed4 frag(v2f i) : SV_Target
        {
            fixed4 col = tex2D(_MainTex, i.uv);
            // compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
            fixed shadow = SHADOW_ATTENUATION(i);
            // darken light's illumination with shadow, keep ambient intact
            fixed3 lighting = i.diff * shadow + i.ambient;
            col.rgb *= lighting;
            return col;
        }
    ENDCG
}

// shadow casting support
UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
    }
}

Solved!

I’ll upload the full code to github, but finally figured out how to get shadows working ‘correctly’ in procedurally generated (GPU Instanced) objects using Graphics.DrawInstancedIndirect! Millions of blades of grass with working shadows at a great framerate huzzah!

Not an easy one, but what you do is from this post:

Using world position to sample shadows in ForwardBase pass - Unity Forum

Thanks to bgolus (again)…

You first in a C# script do something along the lines of this:

        public RenderTexture shadowMapRenderTexture;

You need a single directional light, I have the script require to be called ‘MainLight’, because it can only work with a single directional light… (point lights next!)

 mainLightCommandBuffer = new CommandBuffer();
            RenderTargetIdentifier shadowMapRenderTextureIdentifier = BuiltinRenderTextureType.CurrentActive;
            mainLightCommandBuffer.SetGlobalTexture("_SunCascadedShadowMap", shadowMapRenderTextureIdentifier);
         
            mainLight.AddCommandBuffer(LightEvent.AfterShadowMap, mainLightCommandBuffer);

Then you have to include this in your shader…I created a modified simplified version of Gaxil’s code. (Gaxil · GitHub)

What we want from this is the function ‘GetSunShadowsAttenuation()’:

#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCAD

UNITY_DECLARE_SHADOWMAP(_SunCascadedShadowMap);
float4 _SunCascadedShadowMap_TexelSize;

inline float4 getShadowCoord( float4 wpos, fixed4 cascadeWeights )
{
    float3 sc0 = mul (unity_WorldToShadow[0], wpos).xyz;
    float3 sc1 = mul (unity_WorldToShadow[1], wpos).xyz;
    float3 sc2 = mul (unity_WorldToShadow[2], wpos).xyz;
    float3 sc3 = mul (unity_WorldToShadow[3], wpos).xyz;
    float4 shadowMapCoordinate = float4(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3], 1);
#if defined(UNITY_REVERSED_Z)
    float  noCascadeWeights = 1 - dot(cascadeWeights, float4(1, 1, 1, 1));
    shadowMapCoordinate.z += noCascadeWeights;
#endif
    return shadowMapCoordinate;
}

inline fixed4 getCascadeWeights_splitSpheres(float3 wpos)
{
    float3 fromCenter0 = wpos.xyz - unity_ShadowSplitSpheres[0].xyz;
    float3 fromCenter1 = wpos.xyz - unity_ShadowSplitSpheres[1].xyz;
    float3 fromCenter2 = wpos.xyz - unity_ShadowSplitSpheres[2].xyz;
    float3 fromCenter3 = wpos.xyz - unity_ShadowSplitSpheres[3].xyz;
    float4 distances2 = float4(dot(fromCenter0,fromCenter0), dot(fromCenter1,fromCenter1), dot(fromCenter2,fromCenter2), dot(fromCenter3,fromCenter3));
    fixed4 weights = float4(distances2 < unity_ShadowSplitSqRadii);
    weights.yzw = saturate(weights.yzw - weights.xyz);
    return weights;
}

#define GET_CASCADE_WEIGHTS(wpos, z)    getCascadeWeights_splitSpheres(wpos)
#define GET_SHADOW_FADE(wpos, z)        getShadowFade_SplitSpheres(wpos)

#define GET_SHADOW_COORDINATES(wpos,cascadeWeights)    getShadowCoord(wpos,cascadeWeights)

half unity_sampleShadowmap( float4 coord )
{
    half shadow = UNITY_SAMPLE_SHADOW(_SunCascadedShadowMap,coord);
    shadow = lerp(_LightShadowData.r, 1.0, shadow);
    return shadow;
}

half GetSunShadowsAttenuation(float3 worldPositions, float screenDepth)
{
    fixed4 cascadeWeights = GET_CASCADE_WEIGHTS(worldPositions.xyz, screenDepth);
    return unity_sampleShadowmap(GET_SHADOW_COORDINATES(float4(worldPositions, 1), cascadeWeights));
}

In our v2f structure…

You have to pass in the world position as a TEXCOORD value in the fragment shader’s v2f struct:

8647359--1163244--upload_2022-12-8_21-23-28.png

In the vertex shader pass it in:

8647359--1163247--upload_2022-12-8_21-23-48.png
8647359--1163250--upload_2022-12-8_21-23-54.png

In the fragment shader:

8647359--1163253--upload_2022-12-8_21-24-17.png

So now my full fragment function in my shader looks like this:

                fixed4 frag(v2f i) : SV_Target
                {
                    fixed4 col = _MainColor;
                    col = lerp(col,_SecondColor,_GradientMix * i.uv.y);

                    float zDepth = i.pos.z / i.pos.w;
                    float shadow = GetSunShadowsAttenuation(i.worldPosition.xyz,zDepth);
                 
                    fixed3 lighting = i.diff * shadow + i.ambient;
                    col.rgb *= lighting; 
                 
                    UNITY_APPLY_FOG(i.fogCoord, col);

                    return col;
                }

There are a couple of other things I missed. Mainly, Unity wants your shaders to have very specifically named values.

3 Likes

Why do you have SHADOW_COORDS in your v2f?