Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Depth Intersection Shader not accurate

Discussion in 'Shaders' started by Vusa360, Apr 30, 2018.

  1. Vusa360

    Vusa360

    Joined:
    Oct 4, 2017
    Posts:
    12
    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.

    Code (CSharp):
    1. Shader "Intersection/Unlit"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (0,0,0,0)
    6.         _GlowColor("Glow Color", Color) = (1, 1, 1, 1)
    7.         _FadeLength("Fade Length", Range(0, 2)) = 0.15
    8.     }
    9.     SubShader
    10.     {
    11.         Blend SrcAlpha OneMinusSrcAlpha
    12.         ZWrite On
    13.  
    14.         Tags
    15.         {
    16.             "RenderType" = "Transparent"
    17.             "Queue" = "Transparent"
    18.         }
    19.  
    20.         Pass
    21.         {
    22.             CGPROGRAM
    23.             #pragma target 3.0
    24.             #pragma vertex vert
    25.             #pragma fragment frag
    26.  
    27.             #include "UnityCG.cginc"
    28.  
    29.             struct appdata
    30.             {
    31.                 float4 vertex : POSITION;
    32.                 float2 uv : TEXCOORD0;
    33.                 float3 normal : NORMAL;
    34.             };
    35.  
    36.             struct v2f
    37.             {
    38.                 float2 uv : TEXCOORD0;
    39.                 float2 screenuv : TEXCOORD1;
    40.                 float4 vertex : SV_POSITION;
    41.                 float depth : DEPTH;
    42.             };
    43.  
    44.             sampler2D _MainTex;
    45.             float4 _MainTex_ST;
    46.  
    47.             v2f vert(appdata v)
    48.             {
    49.                 v2f o;
    50.                 o.vertex = UnityObjectToClipPos(v.vertex);
    51.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    52.  
    53.                 o.screenuv = ((o.vertex.xy / o.vertex.w) + 1) / 2;
    54.                 o.screenuv.y = 1 - o.screenuv.y;
    55.                 o.depth = -UnityObjectToViewPos(v.vertex).z *_ProjectionParams.w;
    56.  
    57.                 return o;
    58.             }
    59.  
    60.             sampler2D _CameraDepthTexture;
    61.             fixed4 _Color;
    62.             fixed3 _GlowColor;
    63.             float _FadeLength;
    64.  
    65.             fixed4 frag (v2f i) : SV_Target
    66.             {
    67.                 float screenDepth = Linear01Depth(tex2D(_CameraDepthTexture, i.screenuv));
    68.                 float diff = screenDepth - i.depth;
    69.                 float intersect = 0;
    70.  
    71.                 if(diff > 0)
    72.                     intersect = 1 - smoothstep(0, _ProjectionParams.w * _FadeLength, diff);
    73.  
    74.                 fixed4 glowColor = fixed4(lerp(_Color.rgb, _GlowColor, pow(intersect, 4)), 1);
    75.  
    76.                 fixed4 col = _Color * _Color.a + glowColor;
    77.                 col.a = _Color.a;
    78.                 return col;
    79.             }
    80.             ENDCG
    81.         }
    82.     }
    83. }
    This shader produces a result that looks like this.



    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.



    I think the issue is caused when calculating screen depth from 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.



    Thanks in advance for any help
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,230
    You cannot do the perspective divide (o.vertex.xy / o.vertex.w) in the vertex shader, that must happen in the fragment shader. You should be using o.screenuv.xyzw = ComputeScreenPos(o.vertex); and then do i.screenuv.xy / i.screenuv.w in the fragment shader, or use the built in macros for sampling the depth texture. See the built in particle shaders:
    Code (CSharp):
    1. float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
    Also I don't remember if Linear01Depth() is linear 0.0 to 1.0 from the near plane to the far plane, or from the camera position to the far plane. The view depth you're calculating is definitely from the camera position to the far plane. You're likely better off using the LinearEyeDepth() function that the particle shaders use as that will match the viewPos -z exactly. You could also use the COMPUTE_EYEDEPTH macro that particle shaders use for that too.

    Lastly:
    Code (CSharp):
    1.                 float2 screenuv : TEXCOORD1;
    2.                 float depth : DEPTH;
    Don't use the DEPTH semantic here. DEPTH should only be used as an output from the fragment shader, never as an output from the vertex (and it should be SV_Depth). Instead make the screenuv a float4 and store the depth in the z component. Again, like the particle shaders.
     
    guavaman likes this.
  3. Vusa360

    Vusa360

    Joined:
    Oct 4, 2017
    Posts:
    12
    I tried looking at the particle shaders as you suggested and I managed to unintentionally create some sort of fog type effect.



    The shader code looks like

    Code (CSharp):
    1. Shader "Dan/Intersection Shaders/bgolus idea"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Color("Color", Color) = (0,0,0,0)
    7.         _GlowColor("Glow Color", Color) = (1, 1, 1, 1)
    8.         _FadeLength("Fade Length", Range(0, 2)) = 0.15
    9.     }
    10.     SubShader
    11.     {
    12.         Blend SrcAlpha OneMinusSrcAlpha
    13.         ZWrite On
    14.  
    15.         Tags
    16.         {
    17.             "RenderType" = "Transparent"
    18.             "Queue" = "Transparent"
    19.         }
    20.  
    21.         Pass
    22.         {
    23.             CGPROGRAM
    24.             #pragma target 3.0
    25.             #pragma vertex vert
    26.             #pragma fragment frag
    27.  
    28.             #include "UnityCG.cginc"
    29.  
    30.             struct appdata
    31.             {
    32.                 float4 vertex : POSITION;
    33.                 float2 uv : TEXCOORD0;
    34.             };
    35.  
    36.             struct v2f
    37.             {
    38.                 float2 uv : TEXCOORD0;
    39.                 float4 screenuv : TEXCOORD1;
    40.                 float4 vertex : SV_POSITION;
    41.             };
    42.  
    43.             sampler2D _MainTex;
    44.             float4 _MainTex_ST;
    45.             sampler2D _CameraDepthTexture;
    46.             fixed4 _Color;
    47.             fixed4 _GlowColor;
    48.             float _FadeLength;
    49.  
    50.             v2f vert(appdata v)
    51.             {
    52.                 v2f o;
    53.                 o.vertex = UnityObjectToClipPos(v.vertex);
    54.                 o.screenuv = ComputeScreenPos(o.vertex);
    55.                 COMPUTE_EYEDEPTH(o.screenuv.z);
    56.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    57.  
    58.                 return o;
    59.             }
    60.  
    61.             fixed4 frag (v2f i) : SV_Target
    62.             {
    63.                 float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenuv)));
    64.                 float partZ = i.screenuv.z;
    65.                 float diff = sceneZ - partZ;
    66.                 float intersect = 0;
    67.  
    68.                 if(diff > 0)
    69.                     intersect = saturate(_FadeLength * diff);
    70.  
    71.                 return fixed4(lerp(tex2D(_MainTex, i.uv) * _Color, _GlowColor, intersect));
    72.             }
    73.             ENDCG
    74.         }
    75.     }
    76. }
    I imagine the mistake is quite obvious but I am afraid I have not been able to solve the issue. I would appreciate any help in fixing the issue
     
    amcakebread likes this.
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,230
    I suspect you want this:
    intersect = saturate(_FadeLength * diff);

    Also I wouldn't bother with the if (diff >0) above that. That's not actually going to save you anything and is just making the shader slightly more expensive to produce the same results. An if statement in a shader doesn't work the same as it does in something like C# or javascript. Best case you're adding two extra instructions to your shader, worse case the equivalent of 6~16 instructions depending on the hardware and how the shader compiler decides to handle dynamic branches.
     
    guavaman likes this.
  5. Vusa360

    Vusa360

    Joined:
    Oct 4, 2017
    Posts:
    12
    Thanks for the information about the if statement. That has now been removed. But, apologies if I have not understood you correctly, did I not already have:
    intersect = saturate(_FadeLength * diff);


    It seems to have made no difference
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,230
    bleh ... sorry, meant this:

    intersect = saturate(diff / _FadeLength);
     
    guavaman likes this.
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,230
    The "Soft Particles Factor" is one of the most confusing aspects of the original particle shaders (and something they fixed with the new standard particle shaders). But the original code is written to expect a value that is "1.0 / _FadeDistance". This is because a divide is more expensive than a multiply, so Unity was using an reciprocal value as an optimization. For modern GPUs it is still true, but matters less.
     
    guavaman likes this.
  8. Vusa360

    Vusa360

    Joined:
    Oct 4, 2017
    Posts:
    12
    That's amazing. Thanks for all your help and extra information
     
  9. Vusa360

    Vusa360

    Joined:
    Oct 4, 2017
    Posts:
    12
    Out of interest, is there any reason this would not work in a surface shader like this?

    Code (CSharp):
    1. Shader "Dan/Intersection Shaders/Surface Intersection Shader" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    6.         _Metallic ("Metallic", Range(0,1)) = 0.0
    7.         _GlowColor("Glow Color", Color) = (1, 1, 1, 1)
    8.         _FadeLength("Fade Length", Range(0, 2)) = 0.15
    9.     }
    10.     SubShader {
    11.         Blend SrcAlpha OneMinusSrcAlpha
    12.         ZWrite On
    13.  
    14.         Tags
    15.         {
    16.             "RenderType" = "Transparent"
    17.             "Queue" = "Transparent"
    18.         }
    19.  
    20.         CGPROGRAM
    21.         #pragma surface surf Standard fullforwardshadows vertex:vert alpha:fade
    22.         #pragma target 4.0
    23.  
    24.         sampler2D _MainTex;
    25.  
    26.         struct Input {
    27.             float2 uv_MainTex : TEXCOORD0;
    28.             float4 screenPos;
    29.             float4 vertex : SV_POSITION;
    30.         };
    31.  
    32.         half _Glossiness;
    33.         half _Metallic;
    34.         sampler2D _CameraDepthTexture;
    35.         fixed4 _Color;
    36.         fixed4 _GlowColor;
    37.         float _FadeLength;
    38.  
    39.         UNITY_INSTANCING_BUFFER_START(Props)
    40.         UNITY_INSTANCING_BUFFER_END(Props)
    41.  
    42.         void vert(inout appdata_full v, out Input o)
    43.         {
    44.             UNITY_INITIALIZE_OUTPUT(Input, o);
    45.             o.vertex = UnityObjectToClipPos(v.vertex);
    46.             o.screenPos = ComputeScreenPos(o.vertex);
    47.             COMPUTE_EYEDEPTH(o.screenPos.z);
    48.         }
    49.  
    50.         void surf (Input IN, inout SurfaceOutputStandard o) {
    51.             float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)));
    52.             float partZ = IN.screenPos.z;
    53.             float diff = sceneZ - partZ;
    54.             float intersect = 1 - saturate(diff / _FadeLength);
    55.  
    56.             fixed4 col = fixed4(lerp(tex2D(_MainTex, IN.uv_MainTex) * _Color, _GlowColor, pow(intersect, 4)));
    57.             o.Albedo = col.rgb;
    58.             o.Alpha = col.a;
    59.             o.Metallic = _Metallic;
    60.             o.Smoothness = _Glossiness;
    61.         }
    62.         ENDCG
    63.     }
    64.     FallBack "Diffuse"
    65. }
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,230
    First thing, the surface shaders' Input struct ignores pretty much anything you set for semantics, it'll assign them automatically for transferring data. Also it internally defines an SV_Posiiton so there's no need to add that.

    Second thing is screenPos is a special Input variable name for surface shaders and it'll calculate the screen position values for you. This is identical to what you're already doing with the ComputeScreenPos(o.vertex) function, but it doesn't do the eye depth part so that'll just get stomped on. You can either rename screenPos to projPos or something similar (and just use a temporary float4 variable in place of o.vertex) , or reconstruct the eye depth in the surf function using the world position input, like this:

    Code (CSharp):
    1. Shader "Custom/SurfaceScreenPos" {
    2.     Properties {
    3.         _FadeLength("Fade Length", Range(0, 2)) = 2.0
    4.     }
    5.     SubShader {
    6.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    7.         LOD 200
    8.  
    9.         CGPROGRAM
    10.         // Physically based Standard lighting model, and enable shadows on all light types
    11.         #pragma surface surf Standard fullforwardshadows alpha:fade
    12.  
    13.         // Use shader model 3.0 target, to get nicer looking lighting
    14.         #pragma target 3.0
    15.  
    16.         struct Input {
    17.             float4 screenPos;
    18.             float3 worldPos;
    19.         };
    20.  
    21.         sampler2D _CameraDepthTexture;
    22.         float _FadeLength;
    23.  
    24.         void surf (Input IN, inout SurfaceOutputStandard o) {
    25.             float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)));
    26.             float surfZ = -mul(UNITY_MATRIX_V, float4(IN.worldPos.xyz, 1)).z;
    27.  
    28.             o.Emission = saturate((sceneZ - surfZ) / _FadeLength);
    29.  
    30.             o.Albedo = 0;
    31.             o.Alpha = 1;
    32.         }
    33.         ENDCG
    34.     }
    35.     FallBack "Diffuse"
    36. }
     
    guavaman likes this.
  11. Vusa360

    Vusa360

    Joined:
    Oct 4, 2017
    Posts:
    12
    Again, that is amazing. Thank you very much for your help