Receive and cast shadows (custom GEOMETRY Grass Sh

2019-08-30 23:45发布

问题:

I was following a tutorial made by Sam Wronski aka. World of Zero (Tutorial by World of Zero) where he codes a geometry shader for a point cloud grass generator. Great tutorial but I am wondering (and didn't find a proper solution after days of research) how I could implement shadows to the shader (casting and receiving shadows). I am trying to dig deeper into shaders, but this is a level to high for me yet.

My question is : how can I implement shadow casting and receiving for this grass shader? The code that exists and works fine so far is the following:

Shader "Custom/GrassGeometryShader" {

    // https://www.youtube.com/watch?v=HY6qFbmbij8 und http://www.battlemaze.com/?p=153

    Properties {
    // --> HDR allows High Dynamic Colors
        [HDR]_BackgroundColor ("Background Color", Color) = (1,0,0,1) // default to red
        [HDR]_ForegroundColor ("Foreground Color", Color) = (0,1,0,1) // default to green 
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Cutoff("Alpha Cuttoff", Range (0,1)) = 0.15 // Wieviel abgeschnitten sien soll
        _GrassHeight("GrasHeight", Float) = 0.25
        _GrassWidt("GrasWidth", Float) = 0.25
        _WindSpeed ("WindSpeed", Float) = 100
        _WindStrength("WindStrength", Float) = 0.05
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        Pass
        {

        Cull OFF 
        CGPROGRAM
        #include "UnityCG.cginc" // like: "using" in C# 
        // Vertex-Shader with vert-function
        #pragma vertex vert
        // Fragment-Shader with frag-function
        #pragma fragment frag
        // Geometry-Shader with geom-function 
        #pragma geometry geom

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 4.0 // needs to be 4.0 !

        sampler2D _MainTex;

        // vertex to graphics (v2g)
        struct v2g 
        {
            float4  pos : SV_POSITION;
            float3  norm : NORMAL;
            float2  uv : TEXCOORD0;
            float3 color : TEXCOORD1;
        };

        //graphics to fragments (g2f)
        struct g2f 
        {
            float4  pos : SV_POSITION;
            float3  norm : NORMAL;
            float2  uv : TEXCOORD0;            
            float3 diffuseColor : TEXCOORD1;
            //float3 specularColor : TEXCOORD2;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _BackgroundColor;
        fixed4 _ForegroundColor;
        half _GrassHeight;
        half _GrassWidth;
        half _Cutoff;
        half _WindStrength;
        half _WindSpeed;

        // Vertex-Shader from Battlemaze.com
        v2g vert(appdata_full v)
        {
            float3 v0 = mul(unity_ObjectToWorld, v.vertex).xyz;

            v2g OUT;
            OUT.pos = v.vertex;
            OUT.norm = v.normal;
            OUT.uv = v.texcoord;
            OUT.color = tex2Dlod(_MainTex, v.texcoord).rgb;
            return OUT;         
        }

        void buldQuad(inout TriangleStream<g2f> triStream, float3 points[4], float3 color) {
            g2f OUT;
            float3 faceNormal = cross(points[1]-points[0], points[2]-points[0]);
            for(int i; i < 4; ++i) {
            OUT.pos = UnityObjectToClipPos(points[i]);
            OUT.norm = faceNormal;
            OUT.diffuseColor = color;
            OUT.uv = float2(i%2, (int)i/2);
            triStream.Append(OUT);
            }
            triStream.RestartStrip();
        }

        // geom-Funktion
        [maxvertexcount(24)]
        void geom(point v2g IN[1], inout TriangleStream<g2f> triStream)
        {
            float3 lightPosition = _WorldSpaceLightPos0;

            float3 perpendicularAngle = float3(0,0,1);
            float3 faceNormal = cross(perpendicularAngle, IN[0].norm); // normal of gras

            float3 v0 = IN[0].pos.xyz; // Tip of the gras
            float3 v1 = IN[0].pos.xyz + IN[0].norm * _GrassHeight; // base of the gras
            float3 v2 = IN[0].pos.xyz + IN[0].norm * _GrassHeight / 2; // middle part (?)

            float3 wind = float3(sin(_Time.x * _WindSpeed + v0.x) + sin(_Time.x * _WindSpeed + v0.z * 2), 0, cos(_Time.x * _WindSpeed + v0.x * 2) + cos(_Time.x * _WindSpeed + v0.z)); // Anzahl oder Stärke der Manipulation an den Eckpunkten 
            // (_Time.x + v0.x + v0.z looks "random", because it's using time + coordinates)

            v1 += wind * _WindStrength;
            v2 += (wind * _WindStrength/2)/2;

            float3 color = (IN[0].color); // color of the gras

            float sin30 = 0.5;
            float sin60 = 0.866f;
            float cos30 = sin60;
            float cos60 = sin30;

            g2f OUT;

            // Quad 1 - the following code could fit in one function (BUT!) it did not work on MacOSX, that's why it's still calculated the long way

        OUT.pos = UnityObjectToClipPos(v0 + perpendicularAngle * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(1, 0);
        triStream.Append(OUT);


        OUT.pos = UnityObjectToClipPos(v1 + perpendicularAngle * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(1, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1 - perpendicularAngle * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0 - perpendicularAngle * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 1);
        triStream.Append(OUT);

        // Quad 2

        OUT.pos = UnityObjectToClipPos(v0 + float3(sin60, 0, -cos60) * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(1, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1 + float3(sin60, 0, -cos60)* 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(1, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0 - float3(sin60, 0, -cos60) * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1 - float3(sin60, 0, -cos60) * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 1);
        triStream.Append(OUT);

        // Quad 3 - Positive

        OUT.pos = UnityObjectToClipPos(v0 + float3(sin60, 0, cos60) * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(1, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1 + float3(sin60, 0, cos60)* 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(1, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0 - float3(sin60, 0, cos60) * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1 - float3(sin60, 0, cos60) * 0.5 * _GrassHeight);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0, 1);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v0);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 0);
        triStream.Append(OUT);

        OUT.pos = UnityObjectToClipPos(v1);
        OUT.norm = faceNormal;
        OUT.diffuseColor = color;
        OUT.uv = float2(0.5, 1);
        triStream.Append(OUT);

        }

        // Fragment-Shader by Battlemaze.com --> gets input v2g and renders it on screen
        half4 frag(g2f IN) : COLOR
        {
            fixed4 c = tex2D(_MainTex, IN.uv);
            clip(c.a - _Cutoff);
            return c;
            //return float4 (IN.diffuseColor.rgb, 1.0);
        }

        ENDCG
        }
    }
}

As I mentioned I work with a machine running MacOS which does unfortunately not work with compute shaders.

I would appreciate any help on this.

回答1:

To add support for shadow receiving

To the top of the CGPROGRAM block, add this:

#include "AutoLight.cginc"

It is also possible that you might need to add this:

#pragma multi_compile_fwdbase

Inside your v2g struct, add the following line:

SHADOW_COORDS(3) // (3) means we are using TEXCOORD3

Inside your geometry shader, for each new vertex, add this line after you've assigned OUT.pos:

TRANSFER_SHADOW(OUT)

And finally, in your fragment function, add this:

half shadow = SHADOW_ATTENUATION(IN)

Now, the "shadow" variable contains your shadow mask. In a lit shader, you'd multiply this with the light color, but in your case you can just multiply it with the output albedo color.


To add support for shadow casting

Add the following pass to your shader:

Pass {
    Name "ShadowCaster"
    Tags { "LightMode" = "ShadowCaster" }

    ZWrite On ZTest LEqual

    CGPROGRAM
    #pragma target 2.0

    #pragma multi_compile_shadowcaster

    #pragma vertex vertShadowCaster
    #pragma fragment fragShadowCaster

    #include "UnityStandardShadow.cginc"

    ENDCG
}