Lighting PBR Geometry Shader

Disclaimer: My code is super ugly right now, I’m still in “get it to work” mode.

I am making a Geometry Grass Shader with PBR textures/lighting and I am getting some bastard results!

Unlit (looks great)

Added lighting :frowning:



I turned the smoothness up a bit to kinda amplify the effect, but you can see that some of the blades appear to be rendering correctly. I have tried several different things, toying with my tangents, normals, CULL settings, ZWrite setting, Render Queue, Render Type, etc. This is the best result I can get.

Do any wizards know off the top of their head what could be causing this. Btw, I want CULL OFF so it will render both sides of the blade - do I have to do something special in my lighting for this?

Here is the code:

Shader "Custom/Surreal Grass Shader v3" {
    Properties{
        [Header(Grass Blade 1)]
        _Albedo1("Albedo", 2D) = "white" {}
        _Normal1("Normal", 2D) = "bump" {}
        _Weight1("Distribution Weight", Range(0.0, 1.0)) = 0.5
        _Tint1("Tint", Color) = (1,1,1,0)

        [Space]
        [Space]
        [Space]

        [Header(Grass Blade 2)]
        _Albedo2("Albedo", 2D) = "white" {}
        _Normal2("Normal", 2D) = "bump" {}
        _Weight2("Distribution Weight", Range(0.0, 1.0)) = 0.5
        _Tint2("Tint", Color) = (1,1,1,0)

        [Space]
        [Space]
        [Space]

        [Header(Grass Blade 3)]
        _Albedo3("Albedo", 2D) = "white" {}
        _Normal3("Normal", 2D) = "bump" {}
        _Weight3("Distribution Weight", Range(0.0, 1.0)) = 0.5
        _Tint3("Tint", Color) = (1,1,1,0)

        [Space]
        [Space]
        [Space]

        [Header(Root Settings)]
        _RootTint("Tint", Color) = (1,1,1,0)
        _RootTintStart("Tint Start Height", Range(0.1, 1.0)) = 0.2
        _RootTintSpread("Tint Spread", Range(0.2, 1.0)) = 0.8

        [Space]
        [Space]
        [Space]

        [Header(PBR Settings)]
        _Glossiness("Smoothness", Range(0, 1)) = 0.5
        [Gamma] _Metallic("Metallic", Range(0, 1)) = 0
        _OcclusionMap("Occlusion Map", 2D) = "white" {}
        _OcclusionStrength("Strength", Range(0, 1)) = 1

        [Space]
        [Space]
        [Space]

        [Header(Advanced Grass Settings)]
        _Cutoff("Cutoff", Range(0,1)) = 0.25
        _GrassHeight("Grass Height", Float) = 0.25
        _GrassWidth("Grass Width", Float) = 0.25
        _WindSpeed("Wind Speed", Float) = 100
        _WindStength("Wind Strength", Float) = 0.05
        _Noise("Noise", 2D) = "white" {}
    }
    SubShader{
        Tags{ "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" }

        Pass
        {
            CULL OFF

            Tags{ "LightMode" = "Deferred" }

            CGPROGRAM
            #include "UnityCG.cginc"
            #include "UnityGBuffer.cginc"
            #include "UnityStandardUtils.cginc"
            #pragma target 4.0
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag
            #pragma multi_compile_prepassfinal noshadowmask nodynlightmap nodirlightmap nolightmap


            sampler2D _Albedo1;
            sampler2D _Albedo2;
            sampler2D _Albedo3;
            sampler2D _Albedo1_ST;
            sampler2D _Albedo2_ST;
            sampler2D _Albedo3_ST;
            sampler2D _Normal1;
            sampler2D _Normal2;
            sampler2D _Normal3;
            half _Glossiness;
            half _Metallic;
            sampler2D _OcclusionMap;
            float _OcclusionStrength;
            sampler2D _Noise;
            float4 _Noise_ST;
            float _Weight1;
            float _Weight2;
            float _Weight3;
            float4 _Tint1;
            float4 _Tint2;
            float4 _Tint3;
            float4 _RootTint;
            float _RootTintStart;
            float _RootTintSpread;
            half _GrassHeight;
            half _GrassWidth;
            half _Cutoff;
            half _WindStength;
            half _WindSpeed;
            float3 noise;

        struct v2g
        {
            float4 pos : POSITION;
            float3 norm : NORMAL;
            float2 uv : TEXCOORD0;
            float3 noise : TEXCOORD1;
        };

        struct g2f
        {
            float4 pos : POSITION;
            float3 norm : NORMAL;
            float2 uv : TEXCOORD0;
            float3 noise : TEXCOORD2;
            float4 tspace0 : TEXCOORD4;
            float4 tspace1 : TEXCOORD5;
            float4 tspace2 : TEXCOORD6;
            half3 ambient : TEXCOORD7;
        };

        struct Varyings
        {
            float4 position : SV_POSITION;
            float3 normal : NORMAL;
            float2 texcoord : TEXCOORD0;
            float4 tspace0 : TEXCOORD1;
            float4 tspace1 : TEXCOORD2;
            float4 tspace2 : TEXCOORD3;
            half3 ambient : TEXCOORD4;
            float3 noise : TEXCOORD5;
        };

        v2g vert(appdata_full v)
        {
            float3 v0 = v.vertex.xyz;

            v2g OUT;
            OUT.pos = v.vertex;
            OUT.norm = v.normal;
            OUT.uv = v.texcoord;

            float3 noise = tex2Dlod(_Noise, float4(v.vertex.xz * _Noise_ST.xy + _Noise_ST.zw, 0, 0)).xyz;
            noise = noise * 2 - 1;

            OUT.noise = noise;

            float noiseValue = noise.x;
            float totalWeight = _Weight1 + _Weight2 + _Weight3;
            float weight1Per = _Weight1 / (totalWeight);
            float weight2Per = _Weight2 / (totalWeight);

            float weight1Marker = weight1Per * 2.0;
            float weight2Marker = weight2Per * 2.0;
       
            return OUT;
        }

        float3 rotateVector(float3 vec, float ang)
        {
            return float3(vec.x * cos(ang) - vec.z * sin(ang), 0, vec.x * sin(ang) + vec.z * cos(ang));
        }

        Varyings VertexOutput(float4 wpos, half3 nrm, half4 wtan, float2 uv, float3 noise)
        {
            Varyings o;
            half3 bi = cross(nrm, wtan) * wtan.w * unity_WorldTransformParams.w;
            o.position = UnityObjectToClipPos(wpos);
            o.normal = nrm;
            o.texcoord = uv;
            o.tspace0 = float4(wtan.x, bi.x, nrm.x, wpos.x);
            o.tspace1 = float4(wtan.y, bi.y, nrm.y, wpos.y);
            o.tspace2 = float4(wtan.z, bi.z, nrm.z, wpos.z);
            o.ambient = ShadeSHPerVertex(nrm, 0);
            o.noise = noise;
            return o;
        }

        [maxvertexcount(12)]
        void geom(point v2g IN[1], inout TriangleStream<Varyings> triStream)
        {
            float3 lightPosition = _WorldSpaceLightPos0;

            float3 v0Norm = IN[0].norm;
            float3 noise = IN[0].noise;
            float3 pAngle = float3(1, 0, 0);
            pAngle = rotateVector(pAngle, ((noise.x + noise.z) * 360) * (3.14159 / 180));
            float3 faceNormal = cross(pAngle, v0Norm);

       
       
            float3 v0 = IN[0].pos.xyz;
            float3 v1 = v0 + (faceNormal * _GrassHeight * .05) + v0Norm * _GrassHeight * (0.333 - .05);
            float3 v2 = v0 + (faceNormal * _GrassHeight * .2) + v0Norm * _GrassHeight * (0.666 - .1);
            float3 v3 = v0 + (faceNormal * _GrassHeight * .45) + v0Norm * _GrassHeight * (1 - .15);

            float3 wind = float3(sin(_Time.x * _WindSpeed + v0.x) + sin(_Time.x * _WindSpeed + v0.z * 2) + sin(_Time.x * _WindSpeed * 0.1 + v0.x), 0,
                cos(_Time.x * _WindSpeed + v0.x * 2) + cos(_Time.x * _WindSpeed + v0.z));
            v1 += wind * _WindStength * .333;
            v2 += wind * _WindStength * .666;
            v3 += wind * _WindStength;

            float3 v0tov1Normal = cross(v1, pAngle);
            float3 v1tov2Normal = cross(v2, pAngle);
            float3 v2tov3Normal = cross(v3, pAngle);


            g2f OUT;

            // Quad 1
            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v0 + pAngle * 0.5 * _GrassWidth),
                            UnityObjectToWorldNormal(v0tov1Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                            float2(0, 0),
                            noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v0 - pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v0tov1Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(1, 0),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v1 + pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v0tov1Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(0, .333),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v1 - pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v0tov1Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(1, .333),
                noise));
            triStream.RestartStrip();

            // Quad 2
            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v1 + pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v1tov2Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(0, .333),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v1 - pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v1tov2Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(1, .333),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v2 + pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v1tov2Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(0, .666),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v2 - pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v1tov2Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(1, .666),
                noise));
            triStream.RestartStrip();

            // Quad 3
            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v2 + pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v2tov3Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(0, .666),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v2 - pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v2tov3Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(1, .666),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v3 + pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v2tov3Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(0, 1),
                noise));

            triStream.Append(VertexOutput(mul(unity_ObjectToWorld, v3 - pAngle * 0.5 * _GrassWidth),
                UnityObjectToWorldNormal(v2tov3Normal),
                float4(UnityObjectToWorldDir(pAngle), 1),
                float2(1, 1),
                noise));
            triStream.RestartStrip();
        }

        void frag(Varyings IN,
            out half4 outGBuffer0 : SV_Target0,
            out half4 outGBuffer1 : SV_Target1,
            out half4 outGBuffer2 : SV_Target2)
        {

            float3 noise = IN.noise;

            float noiseValue = noise.x;

            float totalWeight = _Weight1 + _Weight2 + _Weight3;
            float weight1Per = _Weight1 / (totalWeight);
            float weight2Per = _Weight2 / (totalWeight);

            float weight1Marker = weight1Per * 2.0;
            float weight2Marker = weight2Per * 2.0;

            if (noiseValue > -1.0 && noiseValue <= -1.0 + weight1Marker)
            {

                half4 normal = tex2D(_Normal1, IN.texcoord);
                normal.xyz = UnpackScaleNormal(normal, 1.0);
                float3 wn = normalize(float3(
                    dot(IN.tspace0.xyz, normal),
                    dot(IN.tspace1.xyz, normal),
                    dot(IN.tspace2.xyz, normal)
                    ));

                fixed4 c = tex2D(_Albedo1, IN.texcoord) * _Tint1;
                c *= lerp(_RootTint, float4(1, 1, 1, 1), (IN.texcoord.y - _RootTintStart) / (_RootTintSpread - _RootTintStart));
                clip(c.a - _Cutoff);

                half occ = tex2D(_OcclusionMap, IN.texcoord).g;
                occ = LerpOneTo(occ, _OcclusionStrength);

                half3 c_diff, c_spec;
                half refl10;
                c_diff = DiffuseAndSpecularFromMetallic(
                    c, _Metallic,
                    c_spec, refl10
                );
           
                UnityStandardData data;
                data.diffuseColor = c_diff;
                data.occlusion = occ; // data.occlusion = occ;
                data.specularColor = c_spec;//data.specularColor = c_spec;
                data.smoothness = _Glossiness;//data.smoothness = _Glossiness;
                data.normalWorld = wn;
                UnityStandardDataToGbuffer(data, outGBuffer0, outGBuffer1, outGBuffer2);

                // Calculate ambient lighting and output to the emission buffer.
                //float3 wp = float3(IN.tspace0.w, IN.tspace1.w, IN.tspace2.w);
                //half3 sh = ShadeSHPerPixel(data.normalWorld, IN.ambient, wp);
                //outEmission = half4(sh * c_diff, 1) * occ;
            }
            if (noiseValue > -1.0 + weight1Marker && noiseValue <= -1.0 + weight1Marker + weight2Marker)
            {
                half4 normal = tex2D(_Normal2, IN.texcoord);
                normal.xyz = UnpackScaleNormal(normal, 1.0);
                float3 wn = normalize(float3(
                    dot(IN.tspace0.xyz, normal),
                    dot(IN.tspace1.xyz, normal),
                    dot(IN.tspace2.xyz, normal)
                    ));

                fixed4 c = tex2D(_Albedo2, IN.texcoord) * _Tint2;
                c *= lerp(_RootTint, float4(1, 1, 1, 1), (IN.texcoord.y - _RootTintStart) / (_RootTintSpread - _RootTintStart));
                clip(c.a - _Cutoff);

                half occ = tex2D(_OcclusionMap, IN.texcoord).g;
                occ = LerpOneTo(occ, _OcclusionStrength);

                half3 c_diff, c_spec;
                half refl10;
                c_diff = DiffuseAndSpecularFromMetallic(
                    c, _Metallic, // input
                    c_spec, refl10     // output
                );

                UnityStandardData data;
                data.diffuseColor = c_diff;
                data.occlusion = occ; // data.occlusion = occ;
                data.specularColor = c_spec;//data.specularColor = c_spec;
                data.smoothness = _Glossiness;//data.smoothness = _Glossiness;
                data.normalWorld = wn;
                UnityStandardDataToGbuffer(data, outGBuffer0, outGBuffer1, outGBuffer2);

                // Calculate ambient lighting and output to the emission buffer.
                //float3 wp = float3(IN.tspace0.w, IN.tspace1.w, IN.tspace2.w);
                //half3 sh = ShadeSHPerPixel(data.normalWorld, IN.ambient, wp);
                //outEmission = half4(sh * c_diff, 1) * occ;
            }
            else
            {
                half4 normal = tex2D(_Normal3, IN.texcoord);
                normal.xyz = UnpackScaleNormal(normal, 1.0);
                float3 wn = normalize(float3(
                    dot(IN.tspace0.xyz, normal),
                    dot(IN.tspace1.xyz, normal),
                    dot(IN.tspace2.xyz, normal)
                    ));

                fixed4 c = tex2D(_Albedo3, IN.texcoord) * _Tint3;
                c *= lerp(_RootTint, float4(1, 1, 1, 1), (IN.texcoord.y - _RootTintStart) / (_RootTintSpread - _RootTintStart));
                clip(c.a - _Cutoff);

                half occ = tex2D(_OcclusionMap, IN.texcoord).g;
                occ = LerpOneTo(occ, _OcclusionStrength);

                half3 c_diff, c_spec;
                half refl10;
                c_diff = DiffuseAndSpecularFromMetallic(
                    c, _Metallic, // input
                    c_spec, refl10     // output
                );

                UnityStandardData data;
                data.diffuseColor = c_diff;
                data.occlusion = occ; // data.occlusion = occ;
                data.specularColor = c_spec;//data.specularColor = c_spec;
                data.smoothness = _Glossiness;//data.smoothness = _Glossiness;
                data.normalWorld = wn;
                UnityStandardDataToGbuffer(data, outGBuffer0, outGBuffer1, outGBuffer2);

                // Calculate ambient lighting and output to the emission buffer.
                //float3 wp = float3(IN.tspace0.w, IN.tspace1.w, IN.tspace2.w);
                //half3 sh = ShadeSHPerPixel(data.normalWorld, IN.ambient, wp);
                //outEmission = half4(sh * c_diff, 1) * occ;
            }
        }
            ENDCG

        }
    }
}

So I don’t think there was actually anything “wrong” with my code. I think that’s just the way lighting is working on this setup. The dark blades are the CULL faces away from the sun. I’m sure I can kind of hack it to make it look decent but I think I’m going to remove the normal map and just make world.up the normal and use specular for a “fresnel” look.

So I did that, and I’m getting a decent result at first, but I’m still having the issue where the texture doesn’t render against the skybox.

Camera HDR ON:

Camera HDR OFF:

What is causing this???

I’ve tried playing with the Render Queue for both my shader and the Skybox material. I tried playing with a lot of different blending combinations as well. But none of that effects the way my shader renders against the sky it’s always translucent when HDR is on and white when HDR is off.

Search for VFACE on the forums. You’ll find some posts on the topic. You’d want something like this in your shader:

normal.z *= facing;

You may want to try passing the original terrain normal along with the tangent to world matrix. You could use a blend of the world space transformed normal map and the terrain normal so the lighting direction is slightly peturbed so it’s not so consistent.

You really need to be rendering to SV_Target3. While it’s called the “Emission” buffer, it’s actually the final frame buffer which holds the ambient or baked lighting after the initial gbuffer pass. The later lighting passes add to this buffer, which if it’s initially filled with the skybox and base terrain is going to cause the issue you’re seeing. At the very least write out black, but it’d be better to output the ambient lighting like you originally did.

1 Like

I actually got it working and I forgot to post here. It was in fact the Emission buffer. I had the code in place, just had it commented out lol After I added that in it looked pretty good. Look at my last two posts in this thread to see the results:
https://discussions.unity.com/t/701277