Search Unity

Accessing depth buffer from a surface shader.

Discussion in 'Shaders' started by TJHeuvel-net, May 14, 2016.

  1. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    423
    I'm having some issues with converting a fragment shader to a surface one. What the fragment one does, successfully, is check the difference between the depth buffer and the pixel's depth and write another value. However when i try to do this same thing in a Surface shader the output is always 0.

    Left is the surface, right is the fragment.
    http://i.imgur.com/mSzd3R9.png

    The relevant code for the fragment shader is:
    Code (csharp):
    1.  
    2.  
    3. uniform float _FoamStrength;
    4. uniform sampler2D _CameraDepthTexture; //Depth Texture
    5. uniform fixed4 _SpecularEdge, _SpecularDepth;
    6.  
    7. struct appdata {
    8.     float4 vertex : POSITION;
    9.     float3 normal : NORMAL;
    10. };
    11.  
    12. struct v2f {
    13.     float4 pos : SV_POSITION;
    14.     float4 screenPos : TEXCOORD0;
    15. };
    16.  
    17. v2f vert(appdata v)
    18. {
    19.     v2f o;
    20.     o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    21.     o.screenPos = ComputeScreenPos(o.pos);
    22.     return o;
    23. }
    24.  
    25. half4 frag( v2f i ) : SV_Target
    26. {
    27.  
    28.     float sceneZ = LinearEyeDepth (tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)).r);
    29.     float objectZ = i.screenPos.z;
    30.     float intensityFactor = 1 - saturate((sceneZ - objectZ) / _FoamStrength);  
    31.     return lerp(_SpecularEdge, _SpecularDepth, intensityFactor);
    32.  
    33. }
    34.  
    The relevant code for the surface shader is:
    Code (csharp):
    1.  
    2.         struct Input {
    3.             float4 screenPos;
    4.         };
    5.  
    6.         half _FoamStrength;
    7.         fixed4 _SpecularDepth, _SpecularEdge;
    8.         uniform sampler2D _CameraDepthTexture;
    9.  
    10.  
    11.         void surf(Input IN, inout SurfaceOutputStandardSpecular o)
    12.         {
    13.             float sceneZ = LinearEyeDepth(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)).r);
    14.             float objectZ = IN.screenPos.z;
    15.             float intensityFactor = 1 - saturate((sceneZ - objectZ) / _FoamStrength);
    16.      
    17.             fixed4 color = lerp(_SpecularEdge, _SpecularDepth, intensityFactor);
    18.  
    19.             o.Albedo = o.Specular = color;
    20.         }
    21.  
    The depth values just arent right, so i suspect i am using them wrong. When i visualise the screenPosition its also a little different, the fragment is much brighter.
     
    Last edited: May 14, 2016
  2. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    423
    Bump, anyone? Maybe someone has a working example of a surface shaders with access to the depth buffer?
     
  3. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,399
    Try dividing screenPos.z by screenPos.w. Else, use a copy of the clip space position instead. (And again z/w.)
     
  4. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    423
    Changed the code to:

    Code (csharp):
    1. IN.screenPos.z /= IN.screenPos.w;
    2. float sceneZ = LinearEyeDepth(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)).r);
    Now it just kinda flickers. I'm not sure how to get the clipspace position in a surface shader, normally i'd pass it through in a `vert` function but i dont think you can with Surface Shaders? Its not listed as a possible input either.
     
    Last edited: May 17, 2016
  5. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,399
    You can add a vert function with Surface Shader. See the normal extrusion surface shader example.
     
  6. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    423
    Yes, the thing i'm unclear about is how to access the Input struct as you'd do normally.

    void vert (inout appdata_full v) {

    It seems like you just have the appdata.
     
  7. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,399
    Ah, no, you can define your own input and output. appdata_full is just a common predefined struct. I can't really find a good example, but you can change it like this:
    Code (csharp):
    1.  
    2. struct my_struct {
    3.     some_data : TEXCOORD6;
    4. };
    5.  
    6. my_struct vert(appdata_full input) {
    7.     my_struct output;
    8.     // Initialize all values in the struct
    9.     return output;
    10. }
    11.  
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,667
    The problem is screenPos.z is not the same thing as the object depth relative to the linear depth texture, so you need to compute that on your own.

    Code (CSharp):
    1.  
    2. Shader "Custom/SurfaceDepthTexture" {
    3.     Properties {
    4.         _Color ("Color", Color) = (1,1,1,1)
    5.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    6.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    7.         _Metallic ("Metallic", Range(0,1)) = 0.0
    8.         _InvFade ("Soft Factor", Range(0.01,3.0)) = 1.0
    9.     }
    10.     SubShader {
    11.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    12.         LOD 200
    13.        
    14.         CGPROGRAM
    15.         // Physically based Standard lighting model, and enable shadows on all light types
    16.         #pragma surface surf Standard vertex:vert alpha:fade nolightmap
    17.  
    18.         // Use shader model 3.0 target, to get nicer looking lighting
    19.         #pragma target 3.0
    20.  
    21.         sampler2D _MainTex;
    22.  
    23.         struct Input {
    24.             float2 uv_MainTex;
    25.             float4 screenPos;
    26.             float eyeDepth;
    27.         };
    28.  
    29.         half _Glossiness;
    30.         half _Metallic;
    31.         fixed4 _Color;
    32.  
    33.         sampler2D_float _CameraDepthTexture;
    34.         float4 _CameraDepthTexture_TexelSize;
    35.        
    36.         float _InvFade;
    37.  
    38.         void vert (inout appdata_full v, out Input o)
    39.         {
    40.             UNITY_INITIALIZE_OUTPUT(Input, o);
    41.             COMPUTE_EYEDEPTH(o.eyeDepth);
    42.         }
    43.  
    44.         void surf (Input IN, inout SurfaceOutputStandard o) {
    45.             // Albedo comes from a texture tinted by color
    46.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    47.             o.Albedo = c.rgb;
    48.             // Metallic and smoothness come from slider variables
    49.             o.Metallic = _Metallic;
    50.             o.Smoothness = _Glossiness;
    51.  
    52.             float rawZ = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos));
    53.             float sceneZ = LinearEyeDepth(rawZ);
    54.             float partZ = IN.eyeDepth;
    55.  
    56.             float fade = 1.0;
    57.             if ( rawZ > 0.0 ) // Make sure the depth texture exists
    58.                 fade = saturate(_InvFade * (sceneZ - partZ));
    59.  
    60.             o.Alpha = c.a * fade;
    61.         }
    62.         ENDCG
    63.     }
    64. }
     
    Last edited: May 19, 2016
    Nearo1 and SunnySunshine like this.
  9. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    423
    Thank you so much, great example on how to use Vert properly as well. The syntax is still a bit daunting, not sure what we can and cannot get away with.

    One more question, in what case wouldnt we have a depth buffer texture?
     
    Last edited: May 19, 2016
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,667
    The depth texture only exists in specific cases. One of the following must be true:
    • Your camera is rendering using the deferred rendering path, either enabled on the camera or from project settings.
    • Your camera has Camera.depthTextureMode with DepthTextureMode.Depth enabled; DepthNormals (or 5.4's MotionVectors) don't create a _CameraDepthTexture. Usually this gets enabled by a post process effect on the camera, but it can also be done via script. On a project I'm working on I force it on with a simple editor only script.
    • You have SoftParticles enabled in quality settings. I believe this will enable the depth texture for all cameras, though it might only be cameras with a particle system visible.
    • You have a realtime or mixed directional light in your scene with shadows enabled. The brightest shadowing directional light in the scene has it's shadows rendered with a full screen using only the camera depth. Important note, this is only true on non-mobile platforms! It's also possible for this to not be true if you have cascades disabled on PC or consoles as Unity may choose to not use these screen space shadows. I believe there's also a hidden setting in 5.4 to disable this behavior and a future version of Unity will likely disable this entirely.
    Only one of those needs to be true for a camera to render depth. If a camera has a culling mask so that it has no directional lights visible, or doesn't have a post process on it, or is forced to use forward rendering, etc. it will not have a depth texture.

    I also modified the originally posted shader because I realized that while testing _TexelSize should work to test for the existence of a depth texture, I forgot it does not since Unity doesn't update the _TexelSize to reflect null textures. This one tests if the depth texture is exactly zero which should pretty much never happen in real world situations.
     
    antoripa and Guillaumetjk like this.
  11. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,081

    Thank you for this it was extremely useful.

    I have on question though.

    The resulting IN.eyeDepth seems to be sufficient enough for determing the depth of each fragment.

    What does rawZ, sceneZ and the resulting math of sceneZ - partZ actually do?
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,667
    Eye depth is exactly that, the world space depth of that fragment (the pixel being drawn by a particular model) from the camera. The camera depth texture, and the "raw z" is the value in the depth texture. The camera depth texture is either something generated by a separate pass of the scene geometry in forward rendering, or the depth that was rendered during the deferred pass. Depth texture stores the clip space depth, which is a non-linear 0.0 to 1.0 range. Research Z depth and depth buffers elsewhere if you want to understand that. The LinearEyeDepth() converts that non-linear 0.0 to 1.0 into a linear depth, i.e. the world space depth from the camera.

    For opaque objects there's not really a reason to do this as the linearized value from the depth texture should match the eye depth as calculated in the vertex shader, however for transparent objects (which aren't rendered into the depth texture) it lets you get the distance from that fragment to the closest scene geometry behind it. That's what sceneZ - partZ does, subtracts the depth that fragment is from the scene depth so the resulting value is the distance to the scene.
     
    antoripa likes this.
  13. Luuke

    Luuke

    Joined:
    Jan 18, 2017
    Posts:
    4
    Hello,

    I see this topic is a bit old, but I am stuck with it :D
    When I exactly copy your Code (@bgolus) I get the following warning: Unbenannt.PNG
    At Line 103 I have:
    float rawZ = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos));

    Someone know what I am missing here ?
     
  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,667
    Nothing really. Surface shaders sometimes passes IN.screenPos hardcoded as (0,0,0,0) even though it never should.
     
    antoripa likes this.
  15. Luuke

    Luuke

    Joined:
    Jan 18, 2017
    Posts:
    4
    Oh ... okay it somehow does not show up everytime ...
    One last question, do you know where to find documentation about functions like SAMPLE_DEPTH_TEXTURE_PROJ ? I cant find anything about this method
     
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,667
    It's in the HLSLSupport.cginc file in Unity's built in shader code. You can download it from here:
    https://unity3d.com/get-unity/download/archive

    This should just be used as reference, not copied into your project btw.

    Really it's just a macro to call tex2Dproj and return the red channel. The tex2Dproj function is one that takes a float4 uv, but is equivalent to calling tex2D(texture, uv.xy / uv.w)
     
    Luuke likes this.
  17. jRocket

    jRocket

    Joined:
    Jul 12, 2012
    Posts:
    489
    So how would this be done with opaque objects? This solution stops working as soon as alpha:fade is removed. My camera is rendering depth.
     
  18. jRocket

    jRocket

    Joined:
    Jul 12, 2012
    Posts:
    489
    Looking into it further, I think this isn't possible without command buffers because the _DepthTexture isn't written to until after the opaque objects are drawn. I would need to draw my object after depth but before deferred lighting.
     
  19. Aaron-Meyers

    Aaron-Meyers

    Joined:
    Dec 8, 2009
    Posts:
    204
    is it possible to write to the depth buffer inside a Surface Shader fragment (a-la out float depth : SV_Depth)?
     
  20. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,667
    Nope. Not without modifying the generated code.
     
    Aaron-Meyers likes this.