Writing to depth buffer with custom shader

I am currently trying to sample the _CameraDepthTexture and it all seems to be okay (although there seems to be several ways of actually doing it), however I have noticed that when an object has one of my custom shaders on the material, the object does not seem to be written to the depth buffer. This means I am unable to sample that pixel from the depth buffer and gain its world position.

So I was wondering whether there was something I needed to do in a vertex/fragment shader that means the object should still write to the depth buffer. I have tried to take a look around and found the documentation on ZWrite:

which sounds like by default my shader should be writing to the depth buffer. When using one of the built-in surface shaders the depth seems to work fine.

Is there a specific reason you are using the depth texture to get the world position of a pixel? You could also just get the world position by doing:

o.wpos = mul(_Object2World, v.vertex);

Not really applicable for screen effects of course :slight_smile:

The images you posted are pretty small so I can’t really see what’s going on. But perhaps you have other geometry on top that blocks what is underneath from that view?

Just tried deferred lighting for the rendering path and it now shows the depth from custom shaders although I’m not entirely sure why?
And I am using it to create some custom shadow effects for certain objects within the game.

Well, for deferred rendering it needs to have the depth buffer to properly do its lighting, so it’s probably forcing an option on somewhere where it seems to be off for you.

Question is, where :wink:

Do you have “RenderType” tag declared in your custom shader?

It’s set to opaque, but I think it was the same regardless.

Its hard to tell without seeing your custom shader code.

Here’s the code from the shader. It’s pretty standard so I’m not sure why it wouldn’t be writing to the depth buffer.

Shader "Custom Shader" {
	Properties {
		_Color ("Texture Colour", Color) = (1.0, 1.0, 1.0, 1.0)
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_DetailTex ("Detail Texture", 2D) = "gray" {}
		_DecalTex ("Decal Texture", 2D) = "black" {}
		
		_DecalStr ("Decal Strength", float) = 1.0
		_Rotation ("Decal Rotation", float) = 0.0
	}
	SubShader {
		Pass {
			//ZWrite On
			Tags { "RenderType"="Opaque" }
			LOD 200
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"
	
			sampler2D _MainTex;
			sampler _DetailTex;
			sampler _DecalTex;
			
			float4 _MainTex_ST;
			float4 _DetailTex_ST;
			float4 _DecalTex_ST;
			
			float _DecalStr;
			fixed4 _Color;
			float _Rotation;
						
			struct vertexOutput {
				float4 vertexPos : POSITION;
				float2 texCoords : TEXCOORD0;
				float2 decalTexCoords : TEXCOORD1;
				float4 worldPos : TEXCOORD2;
				float3 normalDir : TEXCOORD3;
				//float3 viewDir : TEXCOORD4;
			};
	
			vertexOutput vert (appdata_base vIn)
			{
				vertexOutput vOut;
				vOut.texCoords = vIn.texcoord;
				
				// Angle to rotate the decal by
				vIn.texcoord.xy -=0.5;
                float s = sin ( radians( _Rotation ) );
                float c = cos ( radians( _Rotation ) );
                
                float2x2 rotationMatrix = float2x2( c, -s, s, c );
                rotationMatrix *= 0.5;
                rotationMatrix += 0.5;
                rotationMatrix = (rotationMatrix * 2) - 1;
                vOut.decalTexCoords.xy = mul ( vIn.texcoord.xy, rotationMatrix );
                vOut.decalTexCoords.xy += 0.5; 
                
                vOut.worldPos = mul( _Object2World, vIn.vertex );
                vOut.normalDir = normalize( mul( float4( vIn.normal, 0.0 ), _World2Object ).xyz );
				vOut.vertexPos = mul( UNITY_MATRIX_MVP, vIn.vertex );
				return vOut;
			}
			
			// Variables for lighting
			float3 _LightPos;
			fixed4 _LightColour;
			half _LightIntensity;
			
			fixed4 frag ( vertexOutput fIn ) : COLOR
			{
				// General Lighting Calculations -------------------------------------------------------------
				float3 lightDir = normalize( _LightPos - fIn.worldPos.xyz );
				
				fixed3 diffuseLighting = _LightColour * saturate( dot ( normalize(fIn.normalDir), lightDir ) ) * _LightIntensity;
				// Texture Mapping ---------------------------------------------------------------------------
				// Sample the main texture
				fixed4 mainTex = tex2D(_MainTex, fIn.texCoords * _MainTex_ST.xy + _MainTex_ST.zw  ) * _Color;
				
				// Sample the detail map texture
				fixed4 detailTex = tex2D(_DetailTex, fIn.texCoords * _DetailTex_ST.xy + _DetailTex_ST.zw  );
				
				// Sample the decal texture -- Offset the tiling methods by set amounts to centre the decal
				fixed4 decalTex = tex2D(_DecalTex, fIn.decalTexCoords * float2(_DecalTex_ST.x * 3.0, _DecalTex_ST.y * 3.0) + float2(  _DecalTex_ST.z - 1.0f, _DecalTex_ST.w -1.0f )  );
				
				//Test whether other areas of the decal are 0 (using < or > for floating point tests)
				if (decalTex.a > 0.001f)
				{
					// If there not then were sampling the decal, lerp between main texture and decal
					mainTex.xyz = lerp( mainTex.xyz, decalTex.xyz, _DecalStr );
				}
	
				mainTex.xyz *= (detailTex.xyz * 2.0f) * + diffuseLighting;
				mainTex.xyz *= mainTex.a;
								
				return mainTex;
			}
			ENDCG
		}
		
		
	} 
	FallBack "Diffuse"
}

Would be really nice if this got answered - I’m running into basically the same issue. Vertex/Fragment shaders do not seem to zwrite, which breaks a lot of effects that use the depth buffer.

Edit: I think I may have found the issue. Apparently the tags need to go inside the SubShader block, but not inside the Pass block. So if you change your shader to this, it should work:

Shader "Custom Shader" {
    Properties {
        _Color ("Texture Colour", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _DetailTex ("Detail Texture", 2D) = "gray" {}
        _DecalTex ("Decal Texture", 2D) = "black" {}
       
        _DecalStr ("Decal Strength", float) = 1.0
        _Rotation ("Decal Rotation", float) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
           
            #include "UnityCG.cginc"
   
            sampler2D _MainTex;
            sampler _DetailTex;
            sampler _DecalTex;
           
            float4 _MainTex_ST;
            float4 _DetailTex_ST;
            float4 _DecalTex_ST;
           
            float _DecalStr;
            fixed4 _Color;
            float _Rotation;
                       
            struct vertexOutput {
                float4 vertexPos : POSITION;
                float2 texCoords : TEXCOORD0;
                float2 decalTexCoords : TEXCOORD1;
                float4 worldPos : TEXCOORD2;
                float3 normalDir : TEXCOORD3;
                //float3 viewDir : TEXCOORD4;
            };
   
            vertexOutput vert (appdata_base vIn)
            {
                vertexOutput vOut;
                vOut.texCoords = vIn.texcoord;
               
                // Angle to rotate the decal by
                vIn.texcoord.xy -=0.5;
                float s = sin ( radians( _Rotation ) );
                float c = cos ( radians( _Rotation ) );
               
                float2x2 rotationMatrix = float2x2( c, -s, s, c );
                rotationMatrix *= 0.5;
                rotationMatrix += 0.5;
                rotationMatrix = (rotationMatrix * 2) - 1;
                vOut.decalTexCoords.xy = mul ( vIn.texcoord.xy, rotationMatrix );
                vOut.decalTexCoords.xy += 0.5;
               
                vOut.worldPos = mul( _Object2World, vIn.vertex );
                vOut.normalDir = normalize( mul( float4( vIn.normal, 0.0 ), _World2Object ).xyz );
                vOut.vertexPos = mul( UNITY_MATRIX_MVP, vIn.vertex );
                return vOut;
            }
           
            // Variables for lighting
            float3 _LightPos;
            fixed4 _LightColour;
            half _LightIntensity;
           
            fixed4 frag ( vertexOutput fIn ) : COLOR
            {
                // General Lighting Calculations -------------------------------------------------------------
                float3 lightDir = normalize( _LightPos - fIn.worldPos.xyz );
               
                fixed3 diffuseLighting = _LightColour * saturate( dot ( normalize(fIn.normalDir), lightDir ) ) * _LightIntensity;
                // Texture Mapping ---------------------------------------------------------------------------
                // Sample the main texture
                fixed4 mainTex = tex2D(_MainTex, fIn.texCoords * _MainTex_ST.xy + _MainTex_ST.zw  ) * _Color;
               
                // Sample the detail map texture
                fixed4 detailTex = tex2D(_DetailTex, fIn.texCoords * _DetailTex_ST.xy + _DetailTex_ST.zw  );
               
                // Sample the decal texture -- Offset the tiling methods by set amounts to centre the decal
                fixed4 decalTex = tex2D(_DecalTex, fIn.decalTexCoords * float2(_DecalTex_ST.x * 3.0, _DecalTex_ST.y * 3.0) + float2(  _DecalTex_ST.z - 1.0f, _DecalTex_ST.w -1.0f )  );
               
                //Test whether other areas of the decal are 0 (using < or > for floating point tests)
                if (decalTex.a > 0.001f)
                {
                    // If there not then were sampling the decal, lerp between main texture and decal
                    mainTex.xyz = lerp( mainTex.xyz, decalTex.xyz, _DecalStr );
                }
   
                mainTex.xyz *= (detailTex.xyz * 2.0f) * + diffuseLighting;
                mainTex.xyz *= mainTex.a;
                               
                return mainTex;
            }
            ENDCG
        }
       
       
    }
    FallBack "Diffuse"
}

any progress on this issue? I’m working on a similar thing. Getting Custom shadows in a post processing shader/ image effect but im stuck with he same problem. Simple standard shader writes to Z buffer, self-made vertex&frag shader does’nt :confused: deram_scholzar’s suggestion doesn’t make a difference

tried that Unity - Manual: Cameras and depth textures
but no difference in depth buffer

got it. if you’re writing a fragment shader and want it to write to depth buffer dont forget the "Fallback “Diffuse” at the end… sounds weird but it makes the difference

3 Likes

Lol you’re right, thats weird. Though, that does not solve my problem:(

I’m doing something like a custom terrain (actually just to debug a erosion simulation), which is simply a plane, and the vertices are offsetted in the vertex shader. Now, I want to get the water depth (another offsetted plane above the terrain). But the underlying terrain does not write to the depth buffer (Actually it does, but not the offset is not considered unfortunately)! I probably could manually do back in the fragment shader (see this thread), but that seems complicated. I mean, the shader has to write the depth anyways behind the scenes, so, is there a better way?

Don’t suppose anyone has found a solution to this?..

I’ve got the same issue - doing some animation in a vertex shader, and want a working depth/normal pass for SSAO (and I’m stuck with forward rendering for now)

Getting the proper results to appear in the _CameraDepthTexture is fairly straightforward. If you’re using a surface shader it’s as easy as appending “addshadow” to the #pragma surface line. If you’re doing a vertex / frag shader you’ll need to add a custom shadow caster pass. There’s a basic shadow caster pass in Unity’s Vertex Fragment Shader Examples page.

Here’s the copy/paste of the relevant section:

        // shadow caster rendering pass, implemented manually
        // using macros from UnityCG.cginc
        Pass
        {
            Tags {"LightMode"="ShadowCaster"}

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

            struct v2f {
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }

That TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) looks scary, but it’s basically just taking the v.vertex and doing the usual mul(UNITY_MATRIX_MVP, v.vertex), with a couple extra things for shadow maps. All you need to do is do your custom vertex modifications prior to that macro and it will all work.

Example:

            v2f vert(appdata_base v)
            {
                float4 worldPos = mul(_Object2World, v.vertex);
                // do stuff to worldPos.xyz
                v.vertex = mul(_World2Object, worldPos);
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

At this point you’re probably wondering what a shadow caster has to do with the depth. In forward rendering Unity doesn’t use the depth from the camera after it’s rendered the scene, but instead does a pre-pass rendering the entire view using those shadow caster shader passes and saves that as the _CameraDepthTexture, then it renders the camera view you actually see. This is why having ZWrite on or off on the main shader doesn’t seem to have any effect on what shows up in the depth texture, and why any custom vertex manipulations seem to do nothing. The Fallback line is where the shadow caster for most shaders comes from; if your shader doesn’t have a shadow caster pass it’ll use the one the Fallback shader uses instead.

Updating the depth / normals texture is a little more annoying, but basically you need a similar custom shader. Instead of using a special pass in your shader Unity uses the older replacement shader system to create the _CameraDepthNormalsTexture. The replacement shader in question is the Internal-DepthNormalsTexture.shader which you have to replace with your own shader with a new pass appended to it. Then your custom shader needs to have a custom “RenderType” tag and a matching “RenderType” shader in the Internal-DepthNormalsTexture.shader.

In Unity 5.3 and prior I don’t remember if you can get away with just adding a new Internal-DepthNormalsTexture.shader file into your assets folder or if you have to actually replace the one in the actual editor folder. If you’re using Unity 5.4 the graphics settings have an easy way to override this.

edit: Minor note about deferred for completeness sake. In the deferred the _CameraDepthTexture does come from the deferred camera’s depth pass, and the _CameraDepthNormalsTexture comes from the deferred normals and camera depth. However any forward rendered objects that you want to appear in the depth will use the same Shadow Caster pass to “inject” themselves into the deferred camera’s depth. They do not inject into the normals, and there is no way to fix this without actually using a deferred shader! This means fully forward rendered objects when using the deferred rendering path cannot work properly with any effect that needs the camera normals! The only solution is to render the object twice!

12 Likes

bgolus when you say: “All you need to do is do your custom vertex modifications prior to that macro and it will all work.”, you mean that the vertex shader of the shadow casting pass has to do the same transformations as the regular vertex shader? In my case the project is a GPGPU simulation where the positions are read from a texture in the vertex shader, so if I understand correctly I have to have similar logic in the shadow casting part so my particles are properly positioned in the depth texture? Thanks!

Yes, correct - the same modifications are required for proper depth that you do in your normal vertex shader.

Thanks! So I did all of that, basically following something similar to what the Disk shader in this project does:

However, still not a correct depth texture. Anything else I might be missing, like maybe a project-level setting, or a CPU-side thing? Thanks!

Or do I need to do anything special on the script side to make sure each pass is drawn?

@ sorry, not much of an expert. Long time ago since I messed with that, I think in the end I simply resorted to a surface shader because (if I’m correct) I still had some kind of troubles. Not ideal in your case though:/

I’m using a surface shader but I can’t get it to write to the _CameraDepthTexture using either “addshadow” or “fullforwardshadows”. Here’s the relevant section of code using the latter:

        Tags { "Queue"="Transparent" "RenderType"="Transparent" "DisableBatching"="True" }
        LOD 200
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows vertex:vert alpha:fade

I had thought it might be because I was changing the vertices for a billboard effect, but disabling that code didn’t solve the problem.