Shader Help

Hi there,

I am trying to use Unity for a research application and am having a bit of difficulty sorting things out. (not very well versed with Unity in general)

Background:
I have a scene with occluding objects (for testing, a few capsules and spheres) which is viewed by the main camera (orthographic, far-near: [2,1], sitting at (-30,0,-1) looking along z+ axis).
There is another camera (same config, sitting at (0,0,-1)) pointed at a group of objects without occlusion and which renders to a RTT (set to color format Depth) .
What I need to do is to only draw content from the first group of objects which are at the same depth as the objects in the second group.
At the moment, I have a post-processing script attached to the main camera (built using the tutorial https://www.ronja-tutorials.com/post/017-postprocessing-depth/ ). The shader compares the depths from the RTT passed to the shader and the depth texture (_CameraDepthTexture) and pulls color from the _MainTexture if the depths match.
Obviously, this doesn’t work for occluded objects as the _CameraDepthTexture has no information about occulded objects and the _MainTexture has no information about the color either. But overall, this configuration works for non-occluded objects in the main camera view.

Attempt 2, the idea is to somehow get the vertex distance from the camera in the main scene and compare it with the RTT depth. If the depth matches, then “somehow” get the color also.
The first stumbling block is to get distance of the vertex from camera into the fragment shader. I found this Unity - Manual: Cameras and depth textures (which doesn’t work out of the box itself).

Attempt 3-ish, for object color, to use a surface shader ( https://www.ronja-tutorials.com/post/021-plane-clipping/ ) by replacing the plane configuration with RTT.

Can anyone point me to an alternative method or something that I am getting wrong?

Thanks.

—The shader code—

Shader "Custom/Backup-RTTMainCam"{
//show values to edit in inspector
    Properties{
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {}
        _RTTDepth ("Texture", 2D) = "white" {}
    }

    SubShader{
        // markers that specify that we don't need culling
        // or reading/writing to the depth buffer
        Cull Off
        ZWrite Off
        ZTest Always

        Pass{
            CGPROGRAM
            //include useful shader functions
            #include "UnityCG.cginc"

            //define vertex and fragment shader
            #pragma vertex vert
            #pragma fragment frag

            //texture and transforms of the texture
            sampler2D _MainTex;
            //the depth texture
            sampler2D _CameraDepthTexture;
            //source depth texture
            sampler2D _RTTDepth;

           
            //the object data that's put into the vertex shader
            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            //the data that's used to generate fragments and can be read by the fragment shader
            struct v2f{
                float4 position : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            //the vertex shader
            v2f vert(appdata v){
                v2f o;
                //convert the vertex positions from object space to clip space so they can be rendered
                o.position = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            //the fragment shader
            fixed4 frag(v2f i) : SV_TARGET{
                //get source color from texture
                fixed4 col = tex2D(_MainTex, i.uv);
                float2 dPos = float2(i.uv.x, 0.5);
                float depth = tex2D(_CameraDepthTexture, i.uv).r;
                float dSlice = tex2D(_RTTDepth, dPos).r;
                float2 dist = float2(LinearEyeDepth(depth)-1,LinearEyeDepth(dSlice)-1);

                if(abs(dist.x-dist.y)>0.001) col=0;
                if(dist.x==1 ||dist.y==1) col=0;
               
    //return depth;
                return col;
            }
           

            ENDCG
        }
    }
}

In the frag function, i.position.z is the value that’s eventually stored in the depth buffer (and of which the depth texture is a copy of). So you should be able to compare the _RTTDepth and i.position.z directly with a small amount of wiggle room and get something that works.

Thanks, but I tried experimenting with this and it doesn’t seem to work as expected.
The i.position.z is a number between 0.991 and 0.992 and doesn’t match anything within the scene.

To test, I created a simple scene with a capsule (p:0,0,0.7, s:0.2,0.5,0.2) and a cube (p:0,0,0.184, s:0.8,0.8,0.01) viewed by an orthographic camera (p:0,0,-1, clip n-f:1,2, occlusion culling, hdr, msaa: all off).

The camera has the following c# script attached to it:

using UnityEngine;

//behaviour which should lie on the same gameobject as the main camera
public class MainCamDepth : MonoBehaviour {
    //material that's applied when doing postprocessing
    [SerializeField]
    private Material postprocessMaterial;
 
    private void Start(){
        //get the camera and tell it to render a depth texture
        Camera cam = GetComponent<Camera>();
        cam.depthTextureMode = cam.depthTextureMode | DepthTextureMode.Depth;
    }

   
    //method which is automatically called by unity after the camera is done rendering
    private void OnRenderImage(RenderTexture source, RenderTexture destination){
        //draws the pixels from the source texture to the destination texture
        Graphics.Blit(source, destination, postprocessMaterial);
    }
}

It also has material attached (which has the following shader applied to it):

Shader "Custom/MainCamDepth"
{
    //show values to edit in inspector
    Properties{
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {}
    }

    SubShader{
        // markers that specify that we don't need culling
        // or comparing/writing to the depth buffer
        Tags{ "RenderType"="Transparent" "Queue"="Transparent"}
        Cull Off
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off
        ZTest Always
       

        Pass{
            CGPROGRAM
            //include useful shader functions
            #include "UnityCG.cginc"

            //define vertex and fragment shader
            #pragma vertex vert
            #pragma fragment frag

            //the rendered screen so far
            sampler2D _MainTex;

            //the depth texture
            sampler2D _CameraDepthTexture;         


            //the object data that's put into the vertex shader
            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            //the data that's used to generate fragments and can be read by the fragment shader
            struct v2f{
                float4 position : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            //the vertex shader
            v2f vert(appdata v){
                v2f o;
                //convert the vertex positions from object space to clip space so they can be rendered
                o.position = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            //the fragment shader
            fixed4 frag(v2f i) : SV_TARGET{
                fixed4 col =fixed4(1,0,0,1);
                //get depth from depth texture
                float texDepth = tex2D(_CameraDepthTexture, i.uv).r;
                texDepth = LinearEyeDepth(texDepth); //we know this varies from 0->1
                col.r = texDepth-1;//-1; //camera near clip is 1 unit
               
                float depth = i.position.z;           
               
                //identify the range of output for depth [0.5->1]
                if(depth>0.991) col.b = 1;
                if(depth<0.992) col.g = 1;               
               
                //cull out the background
                if(texDepth==2) col.a = 0;
               
                return col;
            }
            ENDCG
        }
    }
}

The value in i.position.z is the depth of the geometry currently being rendered. When you’re doing a Blit, that geometry is a full screen quad. You need to be doing that check when actually rendering the scene geometry using custom shaders on the objects. Doing this as a post process is too late as you no longer have the data you need.

Thanks again.

I think I nearly have what I need for this basic step to work (excluding the actual color of the objects)
For test purpose I am just using a Depth property value to see if things disappear at a certain depth.

My current chain is Shader>Material > GameObject (via Mesh Renderer).
However, I would like to keep the original texture applied to the GameObjects.
Is there a simple way to achieve this?

Shader "Custom/MSO"{
    Properties{
        _Color ("Tint", Color) = (0, 0, 0, 1)
        [HideInspector]_MainTex ("Texture", 2D) = "white" {}
        _Depth  ("Depth", Range(0.01, 2.0)) = 0.01
    }

    SubShader{
        Tags{ "RenderType"="Transparent" "Queue"="Transparent"}

        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite off
        Cull off

        Pass{
            CGPROGRAM

            #include "UnityCG.cginc"

            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Depth;

            fixed4 _Color;

            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f{
                float4 position : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(appdata v){
                v2f o;
                o.position = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET{
                fixed4 col = tex2D(_MainTex, i.uv);
                float depth = LinearEyeDepth(i.position.z);
                if(depth<_Depth) col.a=0;
               
                return col;
            }

            ENDCG
        }
    }
}

Then you need your custom shader to otherwise reproduce the original look of the object prior to hiding it. I’m not sure what your objects look like to begin with, ie: if they’re unlit textures or solid colors, or if they’re fully lit. Also unless your objects are already transparent, you’ll probably want to use alpha testing instead of alpha blending.

The object is a simple gameObject (capsule and cube) with texture/material applied to it. However, to apply the Custom/MSO shader in my previous response, the previous texture/material gets removed.

I am not clear how both the Custom/MSO shader and the prior texture/material can be applied to the object at the same time.
I tried applying the Custom/MSO to an empty gameObject and then tried to push the capsule and cube as the children of that gameObject, but that didnt’ work.

If I try to apply the Custom/MSO shader/material under MeshRenderer of a game object and then try to increase the number of materials Unity says, “No sub-meshes, use multiple passes”. But I am not sure how to apply multiple passes with different textures and shaders.

The final version of the application will be dynamically loading obj+mtl/textures into the main viewspace. So if I can use the hiearchy somehow to apply the Custom/MSO shader to all objects within the view volume but retain their original mtl/textures, it would work fine.

P.S. There’s no lighting effects (transparency, reflection or shadows either) to worry about. Before the Custom/MSO kicks in, it’s a plain vanilla scene of show an obj model in front of the camera without any special effects.

Yes. Your custom shader needs to itself replicate whatever the original shader you were using did, on top of also doing the depth comparison and clipping. You can’t use the original shader at all.

The original shader doesn’t know it needs to clip, and subsequent or previous shader passes can’t change that. Once the original shader renders, it’s too late to clip it because the data you want (the color of objects behind them) is already lost. Passes before that can write to the stencil or the depth buffer, but stencils won’t have any effect since the original shader doesn’t know to look at those, and a depth buffer write would potentially block everything on that pixel which isn’t what you want.

If you were using an unlit texture shader, then your custom shader just needs to use the _MainTex it already has. Remove that [HideInInspector] line and change the shader on your original materials to the new shader.

Thanks for the inputs bgolus.

I have now reached a point where there’s a good chance I can get the initial step to work.
Each main scene object has a material (with it’s own texture, plus a common renderTexture rtt being passed to it).
These materials have the Custom/MS Cull shader attached to them.

I believe I should be able to somehow script access to each material and trigger the update to the _Offset property to new values as needed.

This may not be the best pathway, but it may be the way forward for now.

In case anyone is interested, the final code for the shader looks like this:

Shader "Custom/MS Cull"{
        Properties{
            _MainTex ("Texture", 2D) = "white" {}
            _Offset  ("Depth", Range(0.00, 1.0)) = 0.01
            _RTTDepth ("Texture", 2D) = "white" {}
        }
    
        SubShader{
            Tags{ "RenderType"="Transparent" "Queue"="Transparent"}
            Cull Off
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            ZTest Always
    
            Pass{
                CGPROGRAM
    
                #include "UnityCG.cginc"
    
                #pragma vertex vert
                #pragma fragment frag
    
                sampler2D _MainTex;
                //source depth texture
                sampler2D _RTTDepth;
                float _Offset;
    
                struct appdata{
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f{
                    float4 position : SV_POSITION;
                    float2 uv : TEXCOORD0;
                    float2 xy : TEXCOORD1;
                };
    
                v2f vert(appdata v){
                    v2f o;
                    o.position = UnityObjectToClipPos(v.vertex);
                    o.uv = v.uv;
                    o.xy = o.position.xy;
                    return o;
                }
    
                fixed4 frag(v2f i) : SV_TARGET{
                    fixed4 col = tex2D(_MainTex, i.uv);
                    float depth = LinearEyeDepth(i.position.z)-1;
                    i.xy.x = (i.xy+1)*0.5; //transform from [-1,1] to [0,1]
                   
                    float dSlice = tex2D(_RTTDepth, float2(i.xy.x,_Offset)).r;
                   
                    if(abs(depth-dSlice)>0.002) col.a=0;
                    return col;
                }
    
                ENDCG
            }
        }
    }