Normals-only deferred shader

I need a shader for deferred rendering that writes only to the normals buffer, without any color. In other words, it should only output to RT2 of the G-Buffer.

The reason for this is that I need an invisible object that still affects other shaders that consider depth / normals (occlusion, DOF, etc.) for some experimental effects.

I’ve tried the following, but as soon as I add the alpha tag, nothing is written to the normal buffer, even with the ZWrite flags.

Shader "Transparent Depth"
{
    Properties
    {
        [MaterialToggle] _ZWrite ("ZWrite", Float) = 1
    }

    SubShader
    {
        ZWrite On

        CGPROGRAM

        #pragma surface surf Lambert alpha
     
        struct Input
        {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            o.Alpha = 0;
        }

        ENDCG
    }
}

I don’t need it to be a surface shader; it could be a vert/frag too.

I don’t think what you’re trying to do is possible with a surface shader to begin with, so you’ll want to use a vert/frag shader.

Now for that it’ll actually be easier than you think. You should be able to find any existing depth only / depth mask shader and add:
Tags { “LightMode” = “Deferred” }

For example, mostly copied from http://wiki.unity3d.com/index.php?title=DepthMask :

Shader "Masked/Mask" {
    SubShader {
        Tags {"Queue" = "Geometry+10" "LightMode" = "Deferred"}
        ColorMask 0
        ZWrite On
        Pass {}
    }
}

I haven’t tried it, but I’m pretty sure that’ll “just work” and do exactly what you’re asking for. I’m just not sure if it’ll result in what you think it will.

Thanks!

It’s something, but it doesn’t quite work yet.

Here’s what I get:

And here’s what I need:

And it messes with the actual rendering (although I think that’s a side effect):

That’s not the depth buffer, that’s the normals buffer. And yes, messing with the rendering is exactly what’s going to happen when you do this, even if you’re modifying the normals buffer.

You’re right, sorry, I updated the wording. I believe the rendering “artifacts” I would get are actually what I’m looking for. Do you have an idea as to how I could write to the normals buffer?

You’d need a shader that is LightMode Deferred and outputs the world normal to SV_Target2. Completely untested:

Shader "Deferred/Normals Only"
{
    Properties
    {
        _BumpMap("Normal Map", 2D) = "bump" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="Deferred"}

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
          
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;            
                half3 tangent : TEXCOORD2;
                half3 bitangent : TEXCOORD3;
                half3 normal : TEXCOORD4;
            };

            sampler2D _BumpMap;
          
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;

                half3 wNormal = UnityObjectToWorldNormal(v.normal);
                half3 wTangent = UnityObjectToWorldDir(v.tangent.xyz);
                // compute bitangent from cross product of normal and tangent
                half tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                half3 wBitangent = cross(wNormal, wTangent) * tangentSign;

                o.tangent = wTangent;
                o.bitangent = wBitangent;
                o.normal = wNormal;

                return o;
            }
          
            half4 frag (v2f i) : SV_Target2
            {
                half3 normal = UnpackNormal(tex2D(_BumpMap, i.uv));
                half3x3 tbn = half3x3(i.tangent, i.bitangent, i.normal);
                half3 worldNormal = normalize(mul(normal, tbn));

                return fixed4(worldNormal * 0.5 + 0.5, 0);
            }
            ENDCG
        }
    }
}
1 Like

The normals buffer now gets properly affected, but RT0 / 1 / 3 also change from this. For example, the diffuse (RT0) becomes:

Do you think there’s anyway to avoid this?

I feel like doing a grabpass might help here, but I’m not sure how / where. Graphics are not my field of expertise, I really appreciate your help.

Opps, needs a Zwrite Off

Almost.

(The other buffers are fixed).

Odd, maybe try adding in “Queue”=“Geometry+10”

Didn’t work. I’m assuming this happens because with no ZWrite, it doesn’t know how to order the renderer in the normals buffer. We’d need ZWrite only for RT2 then… if that makes sense?

The queue might not actually do anything if you just change it in the shader:

And you can’t just do Zwrite for a single render target and not another. The depth buffer is a global thing and the deferred rendering path makes a copy of the global depth buffer, unlike forward which generates it as a separate scene pass.

1 Like

That did it, thanks! I’ll need to experiment some more, but for now this is a great start.