I am trying to create a shader in Unity that shows where intersections occur between objects.

Most of the shader I have so far is based off of this youtube tutorial. However, I have made some slight modifications to it. The main one being that it is only using a camera's depth texture instead of the depthnormal texture.

Shader "Intersection/Unlit"
{
    Properties
    {
        _Color("Color", Color) = (0,0,0,0)
        _GlowColor("Glow Color", Color) = (1, 1, 1, 1)
        _FadeLength("Fade Length", Range(0, 2)) = 0.15
    }
    SubShader
    {
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite On

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

        Pass
        {
            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float2 screenuv : TEXCOORD1;
                float4 vertex : SV_POSITION;
                float depth : DEPTH;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.screenuv = ((o.vertex.xy / o.vertex.w) + 1) / 2;
                o.screenuv.y = 1 - o.screenuv.y;
                o.depth = -UnityObjectToViewPos(v.vertex).z *_ProjectionParams.w;

                return o;
            }

            sampler2D _CameraDepthTexture;
            fixed4 _Color;
            fixed3 _GlowColor;
            float _FadeLength;

            fixed4 frag (v2f i) : SV_Target
            {
                float screenDepth = Linear01Depth(tex2D(_CameraDepthTexture, i.screenuv));
                float diff = screenDepth - i.depth;
                float intersect = 0;

                if(diff > 0)
                    intersect = 1 - smoothstep(0, _ProjectionParams.w * _FadeLength, diff);

                fixed4 glowColor = fixed4(lerp(_Color.rgb, _GlowColor, pow(intersect, 4)), 1);

                fixed4 col = _Color * _Color.a + glowColor;
                col.a = _Color.a;
                return col;
            }
            ENDCG
        }
    }
}

This shader produces a result that looks like this.

enter image description here

However, this is a problem as there are large areas of the object that remain the initial blue colour. What I was expecting was something that looked like this.

enter image description here

I think the issue is caused when calculating screen depth from i.screenuv

float screenDepth = Linear01Depth(tex2D(_CameraDepthTexture, i.screenuv));

I have tried removing the difference check, this initially appeared to work. Although, when I placed an object in front of the object with this shader, the shader behaved as if there was a collision when there was not. As you can see below.

enter image description here

I would appreciate any help in resolving the issue

up vote 1 down vote accepted
+50

This issue exists with the original shader as well, but is less apparent with the sphere mesh.

Notice how it looks alright when the model's vertices are close to the intersecting object.

Looks ok

But as they move further away we start to see the problem.

Looks bad

And with a little rotation it becomes really bad.

Looks really bad

When things look alright near the vertices but get wonky further away, it almost always means that something is being done in the vertex shader when it should be done in the fragment shader. In this case it's the depth calculation. So how do we move the depth calculation to the fragment shader?

In Unity the answer is:

Screen space pixel position: VPOS

A fragment shader can receive position of the pixel being rendered as a special VPOS semantic. This feature only exists starting with shader model 3.0, so the shader needs to have the #pragma target 3.0 compilation directive.

On different platforms the underlying type of the screen space position input varies, so for maximum portability use the UNITY_VPOS_TYPE type for it (it will be float4 on most platforms, and float2 on Direct3D 9).

Additionally, using the pixel position semantic makes it hard to have both the clip space position (SV_POSITION) and VPOS in the same vertex-to-fragment structure. So the vertex shader should output the clip space position as a separate “out” variable.

(See https://docs.unity3d.com/Manual/SL-ShaderSemantics.html)

Applying that to your shader gives you something like:

Shader "Intersection/Unlit"
{
    Properties
    {
        _Color("Color", Color) = (0,0,0,0)
        _GlowColor("Glow Color", Color) = (1, 1, 1, 1)
        _FadeLength("Fade Length", Range(0, 2)) = 0.15
    }
    SubShader
    {
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite On

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

        Pass
        {
            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert(appdata v, out float4 vertex : SV_POSITION)
            {
                v2f o;
                vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            sampler2D _CameraDepthTexture;
            fixed4 _Color;
            fixed3 _GlowColor;
            float _FadeLength;

            fixed4 frag (v2f i, UNITY_VPOS_TYPE vpos : VPOS) : SV_Target
            {
                float2 screenuv = vpos.xy / _ScreenParams.xy;
                float screenDepth = Linear01Depth(tex2D(_CameraDepthTexture, screenuv));
                float diff = screenDepth - Linear01Depth(vpos.z);
                float intersect = 0;

                if(diff > 0)
                    intersect = 1 - smoothstep(0, _ProjectionParams.w * _FadeLength, diff);

                fixed4 glowColor = fixed4(lerp(_Color.rgb, _GlowColor, pow(intersect, 4)), 1);

                fixed4 col = _Color * _Color.a + glowColor;
                col.a = _Color.a;
                return col;
            }
            ENDCG
        }
    }
}

It's mostly self explanatory once you know about VPOS, but there's at least one thing worth noting: the uv of the depth texture also had to be calculated in the fragment shader, and since vpos.xy is in screen pixel coordinates, we have to divide by the screen resolution (_ScreenParams.xy) to get texture coordinates.

And now we get the intended effect no matter where the vertices are.

Purtier

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.