Cast and receive shadows with Graphics.DrawProcedural

I want to use Graphics.DrawProcedural to draw procedural meshs with a surface shader. I can’t get the shader to cast and receive shadows.

Unity version: 2019.4.4f1

I have a test shader:

Shader "Unlit/shaderTest03_surf_no_buffs"
{
 
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 200

        //Cull Off

        CGPROGRAM

        #pragma surface surf Standard vertex:vert addshadow
        //#pragma surface surf Standard vertex:vert fullforwardshadows
        //#pragma surface surf Standard fullforwardshadows
        //#pragma surface surf Standard vertex:vert fullforwardshadows addshadow
        //#pragma surface surf Standard vertex:vert

        //#pragma multi_compile_instancing //don't need in surface shader
        #pragma target 4.5

        #include "UnityCG.cginc"



    struct appdata
    {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        //float4 tangent : TANGENT;
        float4 color : COLOR;
        float4 texcoord : TEXCOORD0;
        float4 texcoord1 : TEXCOORD1;
        float4 texcoord2 : TEXCOORD2;
        uint vId : SV_VertexID;
        uint iId: SV_InstanceID;
    };



    struct Input
    {
        float2 uv_MainTex;
    };


    float4x4 _LocalToWorld;
    float4x4 _WorldToLocal;

    void vert(inout appdata v)
    {
      

        v.normal = float3(0.,0.,-1.);

        if (v.iId == 0) {
            if (v.vId == 0) {
                v.vertex = float4(0., 0., 0., 0. );
                v.texcoord = float4(0., 0., 0., 0.);
            }
            else if (v.vId == 1) {
                v.vertex = float4(0., 1., 0., 0. );
                v.texcoord = float4(0., 1., 0., 0.);
            }
            else if (v.vId == 2) {
                v.vertex = float4(1., 1., 0., 0. );
                v.texcoord = float4(1., 1., 0., 0.);
            }

        }
        else {
            if (v.vId == 0) {
                v.vertex = float4(0., 0., -1., 0.);
                v.texcoord = float4(0., 0., 0., 0.);
            }                               
            else if (v.vId == 1) {          
                v.vertex = float4(0., 1., -1., 0.);
                v.texcoord = float4(0., 1., 0., 0.);
            }                               
            else if (v.vId == 2) {           
                v.vertex = float4(1., 1., -1., 0. );
                v.texcoord = float4(1., 1., 0., 0.);
            }
        }


        // Transform modification
        unity_ObjectToWorld = _LocalToWorld;
        unity_WorldToObject = _WorldToLocal;
    }

    fixed4 _Color;
    sampler2D _MainTex;

    void surf(Input IN, inout SurfaceOutputStandard o)
    {

        float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
      
        o.Albedo = c.rgb;
      
        //o.Albedo = IN.color.rgb;
        //o.Metallic = _Metallic;
        //o.Smoothness = _Smoothness;
        //o.Normal = float3(0, 0, IN.vface < 0 ? -1 : 1); // back face support
        //o.Emission = _Emission * IN.color.rgb;
    }

    ENDCG
    }
        //FallBack "Diffuse"


}

My test script calls Graphics.DrawProcedural from update:

void Update()
    {

        Bounds bounds = new Bounds(Vector3.zero, new Vector3(10, 10, 10));

        matTest03.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);
        matTest03.SetMatrix("_WorldToLocal", transform.worldToLocalMatrix);

        matTest03.SetPass(0);
        Graphics.DrawProcedural(
            matTest03,
            bounds,
            MeshTopology.Triangles,
            3, //triArray.Length
            2,
            null,
            null,
            ShadowCastingMode.On,
            true,
            gameObject.layer
        );

    }

What am I missing to get the procedural mesh to cast and receive shadows? The shader sometimes has an error: invalid subscript ‘instanceID’ ‘UnitySetupInstanceID’ no matching 1 parameeter function. Sometimes it pops up and other times not. I compile and show code but can’t find the error. Maybe this is breaking the shadow casting pass. I try using addshadow and fullforwardshadows. addshadow should be Generating a shadow caster pass.

My end goal is to read the verts, etc from a StructuredBuffer but can’t get even this simple procedural shader to to cast and receive shadows.

Any help would be appreciated. I would be great if Unity would put a working example shader in the Graphics.DrawProcedural doc.

This shader is a better test to show what I want. Basically generate verts, etc in a compute shader and populate a StructuredBuffer. then use Graphics.DrawProcedural to create the mesh. But I need shadows.

This shader also gives the same invalid subscript ‘instanceID’ ‘UnitySetupInstanceID’ no matching 1 parameter function D3D 125 I assume one of the varients can’t handle the instanceID.

The shader runs and draws the mesh and reacts to light, just no shadows cast or received.

Does anyone have an example of a surface shader with a vertex funtion that can cast and receive shadows they can share?

run in Unity 2019.4.4f1

Shader "Unlit/shaderTest01_surf02"
{
  
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 200

        //Cull Off

        CGPROGRAM

        //#pragma surface surf Standard vertex:vert addshadow
        //#pragma surface surf Standard vertex:vert fullforwardshadows
        #pragma surface surf Standard vertex:vert fullforwardshadows addshadow
        //#pragma surface surf Standard vertex:vert

        //#pragma multi_compile_instancing //don't need in surface shader
        #pragma target 4.5

        #include "UnityCG.cginc"




    struct appdata
    {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        //float4 tangent : TANGENT;
        float4 color : COLOR;
        float4 texcoord : TEXCOORD0;
        float4 texcoord1 : TEXCOORD1;
        float4 texcoord2 : TEXCOORD2;
        uint vId : SV_VertexID;
        uint iId: SV_InstanceID;
    };


       
   

    struct Input
    {
        float2 uv_MainTex;
    };


    struct vertData
    {
        float3 pos;
        float2 uvs;
        float3 norms;
    };

#ifdef SHADER_API_D3D11

    StructuredBuffer<vertData> vertsBuff;
    StructuredBuffer<int> triBuff;

#endif


    float4x4 _LocalToWorld;
    float4x4 _WorldToLocal;

    void vert(inout appdata v)
    {
       
#ifdef SHADER_API_D3D11

        int vertsBuff_Index = triBuff[(v.iId * 6) + v.vId];
        vertData vData = vertsBuff[vertsBuff_Index];

        v.vertex.xyz = vData.pos;
        v.normal.xyz = vData.norms;
        v.texcoord = float4(vData.uvs, 0., 0.);
        v.color = float4(1.,0.,0.,1.);
       

#endif


        // Transform modification
        unity_ObjectToWorld = _LocalToWorld;
        unity_WorldToObject = _WorldToLocal;
    }

    fixed4 _Color;
    sampler2D _MainTex;

    void surf(Input IN, inout SurfaceOutputStandard o)
    {

        float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
       
        o.Albedo = c.rgb;
       
       
    }

    ENDCG
    }
        FallBack "Diffuse"


}

This is the simple test script populating the buffers:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class Test01 : MonoBehaviour
{


    ComputeBuffer vertsBuff = null;
    ComputeBuffer triBuffer = null;

    //public Shader test01Shader;
    public Material matTest01;

    struct vertData
    {
        public Vector3 pos;
        public Vector2 uvs;
        public Vector3 norms;
    };

    int[] triArray = new int[12];
    vertData[] vDataArray = new vertData[8];

    //============================




    private void setupShader()
    {
        matTest01.SetBuffer(Shader.PropertyToID("vertsBuff"), vertsBuff);
        matTest01.SetBuffer(Shader.PropertyToID("triBuff"), triBuffer);

    }

    private void setupTriArray()
    {
       

        triArray[0] = 0;
        triArray[1] = 1;
        triArray[2] = 2;
        triArray[3] = 0;
        triArray[4] = 2;
        triArray[5] = 3;

        triArray[6] = 4;
        triArray[7] = 5;
        triArray[8] = 6;
        triArray[9] = 4;
        triArray[10] = 6;
        triArray[11] = 7;


        int triBuffStride = sizeof(int);
        triBuffer = new ComputeBuffer(triArray.Length, triBuffStride, ComputeBufferType.Default);

    }

    private void setupVertsBuff()
    {

        Vector3 off01 = new Vector3();
        Vector3 off02 = new Vector3(0,0.5f,-0.5f);


        vDataArray[0] = new vertData();
        vDataArray[0].pos = new Vector3(0,0,0) + off01;
        vDataArray[0].norms = new Vector3(0,0,-1);
        vDataArray[0].uvs = new Vector2(0,0);

        vDataArray[1] = new vertData();
        vDataArray[1].pos = new Vector3(0,1,0) + off01;
        vDataArray[1].norms = new Vector3(0, 0, -1);
        vDataArray[1].uvs = new Vector2(0, 1);


        vDataArray[2] = new vertData();
        vDataArray[2].pos = new Vector3(1,1,0) + off01;
        vDataArray[2].norms = new Vector3(0, 0, -1);
        vDataArray[2].uvs = new Vector2(1, 1);

        vDataArray[3] = new vertData();
        vDataArray[3].pos = new Vector3(1,0,0) + off01;
        vDataArray[3].norms = new Vector3(0, 0, -1);
        vDataArray[3].uvs = new Vector2(1, 0);

        //-----

        vDataArray[4] = new vertData();
        vDataArray[4].pos = new Vector3(0,0,0) + off02;
        vDataArray[4].norms = new Vector3(0, 0, -1);
        vDataArray[4].uvs = new Vector2(0, 0);

        vDataArray[5] = new vertData();
        vDataArray[5].pos = new Vector3(0, 1, 0) + off02;
        vDataArray[5].norms = new Vector3(0, 0, -1);
        vDataArray[5].uvs = new Vector2(0, 1);


        vDataArray[6] = new vertData();
        vDataArray[6].pos = new Vector3(1, 1, 0) + off02;
        vDataArray[6].norms = new Vector3(0, 0, -1);
        vDataArray[6].uvs = new Vector2(1, 1);

        vDataArray[7] = new vertData();
        vDataArray[7].pos = new Vector3(1, 0, 0) + off02;
        vDataArray[7].norms = new Vector3(0, 0, -1);
        vDataArray[7].uvs = new Vector2(1, 0);


        int vertsBuffstride = 8 * sizeof(float);
        vertsBuff = new ComputeBuffer(vDataArray.Length, vertsBuffstride, ComputeBufferType.Default);


    }

    private void setBuffData()
    {
        vertsBuff.SetData(vDataArray);
        triBuffer.SetData(triArray);

        matTest01.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);
        matTest01.SetMatrix("_WorldToLocal", transform.worldToLocalMatrix);

    }

    private void setup()
    {
        setupVertsBuff();
        setupTriArray();
        setupShader();
        setBuffData();

    }

    // Start is called before the first frame update
    void Start()
    {
        setup();
    }

    // Update is called once per frame
    void Update()
    {

        Bounds bounds = new Bounds(Vector3.zero, new Vector3(10,10,10));

        matTest01.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);
        matTest01.SetMatrix("_WorldToLocal", transform.worldToLocalMatrix);

        matTest01.SetPass(0);
        Graphics.DrawProcedural(
            matTest01,
            bounds,
            MeshTopology.Triangles,
            6, //triArray.Length
            2,
            null,
            null,
            ShadowCastingMode.On,
            true,
            gameObject.layer
        );

    }


    private void OnDestroy()
    {
        vertsBuff.Dispose();
        triBuffer.Dispose();
    }
}

Hi,

Graphics.DrawProcedural didn’t support shadow casting / receiving, but it was updated in 2019.1. Have you checked Keijiro’s examples, like NoiseBall3? Although the readme says it supports standard lighting features, if I’m not mistaken, it did not cast shadows.

Here’s the link:

I used a while ago Graphics.DrawMeshInstancedIndirect on 2018.4 to get shadow casting and receiving and then used a custom vertex program in a surface shader to populate the mesh vertices/colors etc. It worked just fine (receiving shadows and shadow casting) and I think I also got the pointers to right way of doing things from Keijiro’s examples. But I don’t right now remember which one it was.
EDIT: I think it was NoiseBall2: GitHub - keijiro/NoiseBall2: A small example of procedural modeling with compute shaders.

Olmi thanks for the response. Much appreciated.

I’ve loaded and dissected NoiseBall3. NoiseBall3 does not cast or receive shadows. It has a shadow effect on the bottom plane produced by post processing. I think you are correct that Graphics.DrawProcedural does not support shadow casting / receiving. The updated 2019.1 adds Rendering.ShadowCastingMode castShadows, bool receiveShadows. (Unity - Scripting API: Graphics.DrawProcedural) I would like Unity to comment on what ‘castShadows’ ‘receiveShadows’ does and give a shader example on how to use it. The documentation also write about a , GraphicsBuffer indexBuffer. How does one use this indexBuffer? Does it populate appdata, does it have vertex info in it, or is it just a int array with the triangle indexes. I’m guessing it’s a custom vertex index and populates SV_VertexID.

Thanks for the help. I believe Unity has to give some more information about DrawProcedural and the shaders that will work with it. They give shader examples for Graphics.DrawMeshInstancedIndirect.

You’re only setting the v.vertex.xyz. The v.vertex.w component is being left as junk data when it needs to be 1.0. Not sure why it works in the main rendering pass at all.

Change:
v.vertex.xyz = vData.pos;
to:
v.vertex = float4(vData.pos, 1.0);

3 Likes

Thanks @bgolus ,
No wonder the shadows did not work in Keijiro’s example. V.vertex is set similarly wrong so that only xyz are set. It indeed casts shadow after w is set to have a value of 1.0. Would be nice to Unity had some examples for these things…

Haha, I can’t believe this was it! I was about to start reimplementing the Standard shader as per https://twitter.com/bonzajplc/status/962765069846278144

1 line change and now shadows are working with Graphics.DrawProceduralIndirect + surface shaders. You’ve saved me again!