How to calculate intesections with other objects properly in surf shader?

I’ve seen a bunch of shaders regarding water etc on the net, but I can’t figure out how does depth texture sampling work for those.

I’m trying to add a line to the object where intersection with other objects occur, with a base plane mesh.
Here’s what I’ve got so far, but the intersection doesn’t work:

Shader "Custom/Lava_Shader"
{
    Properties
    {
        [HDR]
        _EmissionColor ("Emission Color", Color) = (0,0,0,0)
        
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _NormalTex ("Normal Map", 2D) = "bump" {}
       
        _Speed ("Speed, Vector", Vector) = (1,1,1)
        _ColorHeight ("Color Height", Float) = 2
       
        _GlobalTimerId ("Global Timer Id", Int) = 0
       
        _IntersectionColor("Intersection Color", Color) = (1,1,1,1)
        _IntersectionColorThreshold ("Intersection Threshold Color", Float) = 0.5   
    }
   
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard addshadow fullforwardshadows vertex:vert

        #pragma target 3.0
       
        #pragma instancing_options assumeuniformscaling
       
        #include "UnityCG.cginc"

        struct Input {
            float2 uv_MainTex;
            float2 uv_NormalTex;
            float4 screenPos;
           
            UNITY_VERTEX_INPUT_INSTANCE_ID
        };

        sampler2D _MainTex;
        sampler2D _NormalTex;
        sampler2D _CameraDepthTexture;
       
        UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(float4, _EmissionColor)
            UNITY_DEFINE_INSTANCED_PROP(int, _GlobalTimerId)
           
            UNITY_DEFINE_INSTANCED_PROP(float3, _Speed)
            UNITY_DEFINE_INSTANCED_PROP(float, _ColorHeight)
            UNITY_DEFINE_INSTANCED_PROP(fixed3, _IntersectionColor)
            UNITY_DEFINE_INSTANCED_PROP(float, _IntersectionColorThreshold)
        UNITY_INSTANCING_BUFFER_END(Props)
       
        float4 _GlobalTimers[100];
       
        fixed3 _GlowColor;
        float _FadeLength;
       
        float Repeat(float t, float length)
        {
            return clamp(t - floor(t / length) * length, 0.0f, length);
        }
       
        void vert (inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input,o);
           
            UNITY_SETUP_INSTANCE_ID(v);
            UNITY_TRANSFER_INSTANCE_ID(v, o);
           
            float4 pos = v.vertex;
           
            int timerId = UNITY_ACCESS_INSTANCED_PROP(Props, _GlobalTimerId);
           
            float colorHeight = UNITY_ACCESS_INSTANCED_PROP(Props, _ColorHeight);
            float speed = UNITY_ACCESS_INSTANCED_PROP(Props, _Speed);
       
            // Equal to _Time.x
            float3 offset = _GlobalTimers[UNITY_ACCESS_INSTANCED_PROP(Props, _GlobalTimerId)].x;
       
            v.texcoord.xyz += offset * speed;
           
            float4 tex = tex2Dlod (_MainTex, float4(v.texcoord.xy,0,0));
           
            float sum = tex.r + tex.g + tex.b;
            pos.z += sum * colorHeight;
           
            v.vertex = pos;
        }
       
        void surf(Input i, inout SurfaceOutputStandard o) {
            UNITY_SETUP_INSTANCE_ID(i);
       
            fixed4 col = tex2D(_MainTex, i.uv_MainTex);

            float depth = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos));
            float intersectDepth = smoothstep(0.0, UNITY_ACCESS_INSTANCED_PROP(Props, _IntersectionColorThreshold), Linear01Depth(depth));
            depth = LinearEyeDepth(depth);
           
            fixed3 glowColor = lerp(fixed3(0,0,0), UNITY_ACCESS_INSTANCED_PROP(Props, _IntersectionColor), intersectDepth);
           
            o.Albedo = col;
            o.Normal = UnpackNormal(tex2D (_NormalTex, i.uv_NormalTex));
            o.Emission = glowColor ;//+ UNITY_ACCESS_INSTANCED_PROP(Props, _EmissionColor);//+ glowColor;
        }
       
        ENDCG
    }
    FallBack "Diffuse"
}

This one does something weird:

Shader "Custom/Lava_Shader"
{
    Properties
    {
        [HDR]
        _EmissionColor ("Emission Color", Color) = (0,0,0,0)
        
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _NormalTex ("Normal Map", 2D) = "bump" {}
       
        _Speed ("Speed, Vector", Vector) = (1,1,1)
        _ColorHeight ("Color Height", Float) = 2
       
        _GlobalTimerId ("Global Timer Id", Int) = 0
       
        _IntersectionColor("Intersection Color", Color) = (1,1,1,1)
        _DepthMaxDistance ("Depth Max Distance", Float) = 1
    }
   
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderQueue" = "Transparent" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard addshadow fullforwardshadows vertex:vert

        #pragma target 3.0
       
        #pragma instancing_options assumeuniformscaling
       
        #include "UnityCG.cginc"

        struct Input {
            float2 uv_MainTex;
            float2 uv_NormalTex;
            float4 screenPosition;
           
            UNITY_VERTEX_INPUT_INSTANCE_ID
        };

        sampler2D _MainTex;
        sampler2D _NormalTex;
        sampler2D _CameraDepthTexture;
       
        UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(float4, _EmissionColor)
            UNITY_DEFINE_INSTANCED_PROP(int, _GlobalTimerId)
           
            UNITY_DEFINE_INSTANCED_PROP(float3, _Speed)
            UNITY_DEFINE_INSTANCED_PROP(float, _ColorHeight)
            UNITY_DEFINE_INSTANCED_PROP(fixed3, _IntersectionColor)
            UNITY_DEFINE_INSTANCED_PROP(float, _DepthMaxDistance)
        UNITY_INSTANCING_BUFFER_END(Props)
       
        float4 _GlobalTimers[100];
       
        fixed3 _GlowColor;
        float _FadeLength;
       
        float Repeat(float t, float length)
        {
            return clamp(t - floor(t / length) * length, 0.0f, length);
        }
       
        void vert (inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input,o);
           
            UNITY_SETUP_INSTANCE_ID(v);
            UNITY_TRANSFER_INSTANCE_ID(v, o);
           
            float4 pos = v.vertex;
           
            int timerId = UNITY_ACCESS_INSTANCED_PROP(Props, _GlobalTimerId);
           
            float colorHeight = UNITY_ACCESS_INSTANCED_PROP(Props, _ColorHeight);
            float speed = UNITY_ACCESS_INSTANCED_PROP(Props, _Speed);
       
            // Equal to _Time.x
            float3 offset = _GlobalTimers[UNITY_ACCESS_INSTANCED_PROP(Props, _GlobalTimerId)].x;
       
            v.texcoord.xyz += offset * speed;
           
            float4 tex = tex2Dlod (_MainTex, float4(v.texcoord.xy,0,0));
           
            float sum = tex.r + tex.g + tex.b;
            pos.z += sum * colorHeight;
           
            v.vertex = pos;
           
            o.screenPosition = ComputeScreenPos(v.vertex);
        }
       
        void surf(Input i, inout SurfaceOutputStandard o) {
            UNITY_SETUP_INSTANCE_ID(i);
       
            fixed4 col = tex2D(_MainTex, i.uv_MainTex);

            float existingDepth01 = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPosition)).r;
            float existingDepthLinear = LinearEyeDepth(existingDepth01);
           
            float depthDifference = existingDepthLinear - i.screenPosition.w;
            float intersectionDiff = saturate(depthDifference / UNITY_ACCESS_INSTANCED_PROP(Props, _DepthMaxDistance));
           
            fixed3 glowColor = lerp(fixed3(0,0,0), UNITY_ACCESS_INSTANCED_PROP(Props, _IntersectionColor), intersectionDiff);
           
            o.Albedo = col;
            o.Normal = UnpackNormal(tex2D (_NormalTex, i.uv_NormalTex));
            o.Emission = glowColor ;//+ UNITY_ACCESS_INSTANCED_PROP(Props, _EmissionColor);//+ glowColor;
        }
       
        ENDCG
    }
    FallBack "Diffuse"
}

It feels like screenPosition is incorrect somehow.

Got it “solved” by transfering shader to the Amplify and using Depth Fade node instead.

Would be nice to know where I f’d up though. If anyone figures this out, please let me know.

That’s an opaque shader with an addshadow pass. That’s going to be drawing itself into the depth texture, so all you’re going to get from sampling the depth texture (correctly) will be mesh’s own depth. For this to work your shader cannot be part of the opaque queue.

Shader "Custom/Surface Shader Depth Intersection"
{
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        LOD 200
        CGPROGRAM
        #pragma surface surf Standard alpha
        #pragma target 3.0
   
        #include "UnityCG.cginc"
        struct Input {
            float4 screenPos;
        };

        UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
   
        void surf(Input IN, inout SurfaceOutputStandard o) {
            float depthZ = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, IN.screenPos.xy / IN.screenPos.w);
            float sceneZ = LinearEyeDepth(depthZ);
            float fragZ = IN.screenPos.w; // only works with perspective cameras!

            o.Emission = 1 - saturate(sceneZ - fragZ);

            o.Albedo = 0;
            o.Alpha = 1;
        }
   
        ENDCG
    }
}

Indeed it is.

ComputeScreenPos() expects a clip space position, not a local object space position. To match what the existing Input struct’s screenPos value is, you need to do:
o.screenPosition = ComputeScreenPos(UnityObjectToClipPos(v.vertex));

Also, you have several instancing related functions in the vert and surf functions that aren’t needed, like the UNITY_SETUP_INSTANCE_ID lines. Those aren’t needed since the vert and surf functions you’re editing in a Surface Shader are not the main functions of the vertex and fragment shaders, which are already calling those. I recommend you look at the generated shader that the Surface Shader is creating to better understand where the functions you’re writing actually exist.

1 Like