Search Unity

[SOLVED] SeeThrough shader help

Discussion in 'Shaders' started by Serepa, Nov 20, 2019.

  1. Serepa

    Serepa

    Joined:
    May 15, 2015
    Posts:
    32
    Trying to make shader which would smootly fade occluding geometry around my object (player, for example).
    Here is the shader:

    Code (CSharp):
    1. Shader "Custom/NewSurfaceShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    6.         _Metallic ("Metallic", 2D) = "black"
    7.         _Normal ("Normal (RGB)", 2D) = "bump" {}
    8.      
    9.         _Position("Position", Vector) = (.0, .0, .0)
    10.     }
    11.     SubShader
    12.     {
    13.         Tags { "Queue" = "Transparent" "RenderType"="Transparent" }
    14.         LOD 200
    15.  
    16.         CGPROGRAM
    17.         // Physically based Standard lighting model, and enable shadows on all light types
    18.         #pragma surface surf Standard fullforwardshadows alpha
    19.  
    20.         // Use shader model 3.0 target, to get nicer looking lighting
    21.         #pragma target 3.0
    22.  
    23.         sampler2D _MainTex;
    24.         sampler2D _Metallic;
    25.         sampler2D _Normal;
    26.      
    27.         uniform float3 _Position;
    28.  
    29.         struct Input
    30.         {
    31.             float2 uv_MainTex;
    32.             float3 worldPos;
    33.         };
    34.  
    35.  
    36.         // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    37.         // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    38.         // #pragma instancing_options assumeuniformscaling
    39.         UNITY_INSTANCING_BUFFER_START(Props)
    40.             // put more per-instance properties here
    41.         UNITY_INSTANCING_BUFFER_END(Props)
    42.  
    43.         void surf (Input IN, inout SurfaceOutputStandard o)
    44.         {
    45.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    46.             o.Albedo = c.rgb;
    47.             o.Alpha = c.a;
    48.             o.Metallic = tex2D(_Metallic, IN.uv_MainTex);
    49.             o.Normal = UnpackScaleNormal(tex2D(_Normal, IN.uv_MainTex), 1);
    50.          
    51.             float4 pixPos = mul(UNITY_MATRIX_V, float4(IN.worldPos, 1.0));
    52.             float4 objPos = mul(UNITY_MATRIX_V, float4(_Position, 1.0));
    53.             if (pixPos.z > objPos.z) o.Alpha *= saturate(distance(objPos.xy, pixPos.xy) * .5);
    54.         }
    55.         ENDCG
    56.     }
    57.     FallBack "Diffuse"
    58. }
    And it works as needed:

    Well... almost. When object is close to camera z axis it looks ok, but if I move it somwhere (bottom righ, for example) it looks like this:

    Looks like I just forget to adjust for perspective somewhere. But I can't spot where. I tried UNITY_MATRIX_VP instead UNITY_MATRIX_V without success. It gives the same result but makes the transparent spot shape oval.
     

    Attached Files:

    • 1.png
      1.png
      File size:
      66.5 KB
      Views:
      4,075
    • 2.png
      2.png
      File size:
      45 KB
      Views:
      3,535
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    The
    UNITY_MATRIX_V
    matrix transforms a position from world to view space. View space is not viewport space, it is just an object's position relative to the camera's position and orientation, but still in world space scaling and with no knowledge of the camera's perspective projection. In the above setup it looks like your camera is basically world space axis aligned, so applying
    UNITY_MATRIX_V
    isn't going to produce a significantly different result than just using the x and y in world space.

    What you want is the screen space (aka viewport) position with aspect ratio correction. With Surface Shaders you can get the screen space position of the current object by adding
    float4 screenPos;
    to the
    Input
    struct, then in the
    surf
    function use:
    Code (csharp):
    1. // apply perspective divide to get the 0.0 to 1.0 screen position
    2. float2 pixScreenPos = IN.screenPos.xy / IN.screenPos.w;
    3.  
    4. // correct for aspect ratio
    5. pixScreenPos.x *= _ScreenParams.y / _ScreenParams.x;
    To get the screen space position of a world position, you need to do a little extra work, but not much.
    Code (csharp):
    1. // transform world position into clip space
    2. float4 objClipPos = mul(UNITY_MATRIX_VP, float4(_Position.xyz, 1));
    3.  
    4. // convert clip space position into screen space
    5. float4 objScreenPos = ComputeScreenPos(objClipPos);
    6.  
    7. // apply perspective divide
    8. objScreenPos.xy /= objScreenPos.w;
    9.  
    10. // correct for aspect ratio
    11. objScreenPos.x *= _ScreenParams.y / _ScreenParams.x;
    After that, you're free to use the same distance test you were doing before. One minor tweak though, you'll want to use the
    pixScreenPos.w
    and
    objScreenPos.w
    to replace the
    pixPos.z
    and
    objPos.z
    you're using the if statement. They're actually the same values! (The clip space w is the view space z.)
     
    Serepa likes this.
  3. Serepa

    Serepa

    Joined:
    May 15, 2015
    Posts:
    32
    Thank you very much for your help, @bgolus!

    I edited code as you said and now it looks like this:
    Code (CSharp):
    1. Shader "Custom/NewSurfaceShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    6.         _Metallic ("Metallic", 2D) = "black"
    7.         _Normal ("Normal (RGB)", 2D) = "bump" {}
    8.      
    9.         _Position("Position", Vector) = (.0, .0, .0)
    10.     }
    11.     SubShader
    12.     {
    13.         Tags { "Queue" = "Transparent" "RenderType"="Transparent" }
    14.         LOD 200
    15.  
    16.         CGPROGRAM
    17.         // Physically based Standard lighting model, and enable shadows on all light types
    18.         #pragma surface surf Standard fullforwardshadows alpha
    19.  
    20.         // Use shader model 3.0 target, to get nicer looking lighting
    21.         #pragma target 3.0
    22.  
    23.         sampler2D _MainTex;
    24.         sampler2D _Metallic;
    25.         sampler2D _Normal;
    26.      
    27.         uniform float3 _Position;
    28.  
    29.         struct Input
    30.         {
    31.             float2 uv_MainTex;
    32.             float4 screenPos;
    33.         };
    34.  
    35.  
    36.         // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    37.         // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    38.         // #pragma instancing_options assumeuniformscaling
    39.         UNITY_INSTANCING_BUFFER_START(Props)
    40.             // put more per-instance properties here
    41.         UNITY_INSTANCING_BUFFER_END(Props)
    42.  
    43.         void surf (Input IN, inout SurfaceOutputStandard o)
    44.         {
    45.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    46.             o.Albedo = c.rgb;
    47.             o.Alpha = c.a;
    48.             o.Metallic = tex2D(_Metallic, IN.uv_MainTex);
    49.             o.Normal = UnpackScaleNormal(tex2D(_Normal, IN.uv_MainTex), 1);
    50.          
    51.             float2 pixScreenPos = IN.screenPos.xy / IN.screenPos.w; // apply perspective divide to get the 0.0 to 1.0 screen position
    52.             pixScreenPos.x *= _ScreenParams.y / _ScreenParams.x; // correct for aspect ratio
    53.          
    54.             float4 objClipPos = mul(UNITY_MATRIX_VP, float4(_Position.xyz, 1)); // transform world position into clip space
    55.             float4 objScreenPos = ComputeScreenPos(objClipPos); // convert clip space position into screen space
    56.             objScreenPos.xy /= objScreenPos.w; // apply perspective divide
    57.             objScreenPos.x *= _ScreenParams.y / _ScreenParams.x; // correct for aspect ratio
    58.          
    59.             if (IN.screenPos.w < objScreenPos.w) o.Alpha *= saturate(distance(objScreenPos.xy, pixScreenPos.xy) * 10);
    60.         }
    61.         ENDCG
    62.     }
    63.     FallBack "Diffuse"
    64. }
    It starts to fade when the object was in front and became opaque when the object behind, so I guessed I should check IN.screenPos.w < objScreenPos.w instead of ">" but don't quite understand why so. Could you please explain. What is the w exactly?

    Also console sometimes warnings me about "floating point division by zero at line 51 (on d3d11)" and here how it looks:


    The hole is even more oval than when I used VP matrix. Shouldn't it be round?
     

    Attached Files:

    • 1.png
      1.png
      File size:
      50.2 KB
      Views:
      3,473
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    I always get aspect ratio correction backwards the first time. Swap the x and y in the
    _ScreenParams.y / _ScreenParams.x;
    lines.
     
    Serepa likes this.
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    Well, because I lied a little bit.

    The w component of clip space (and the screen space position) is that position's view space depth. The actual view space in UNITY_MATRIX_V is -z forward, so in your original code further away objects's z value was lower than objects close. But in clip space, the view space z is inverted so it's a positive value, so further objects are have a higher w value than close ones.

    So, it'd be more accurate to say that clipSpace.w == -viewSpace.z.

    But this is a bigger question. And technically the clip space w being the view space -z is only true for perspective cameras. If you want to understand why, it's used to correct for screen space distortion when interpolating perspective values in screen space. If you divide by w in the vertex shader and interpolate the xy values alone you'll get strange warping. Think of old PS1 games and the strange way those textures looked. That was caused by the PS1 not handling perspective interpolation correctly. You can try reading up on perspective correction and "perspective divide" elsewhere.
     
    Serepa likes this.
  6. Serepa

    Serepa

    Joined:
    May 15, 2015
    Posts:
    32
    Thank you a lot for your help!
     
    Last edited: Nov 21, 2019
  7. Serepa

    Serepa

    Joined:
    May 15, 2015
    Posts:
    32
    @bgolus, did I understand correct from my research and your answers to other guys on this forum that now it is impossible to make such "see through" effect with this plane receiving shadows without huge performance impact?

    If it is possible at all (even with high performance cost) is there a way to add shadows to my surface shader? Or I should switch to LWRP/HDRP and do something there?
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    No. Unity does not support shadows on transparent objects when using the built in rendering paths.

    There are work around a for supporting shadows from the main directional light that you can use, but all other lights are essentially impossible at any cost. See this project for an example of reading from the main directional light’s shadow maps directly:
    https://github.com/Gaxil/Unity-InteriorMapping

    Otherwise, yes, you’ll need to use the URP or HDRP to get shadows on transparent objects, and for the HDRP you’ll have to disable shadow cascades entirely since there’s a now year old bug that the directional light shadows are broken on transparent objects when using cascaded shadow maps. You’ll have to rewrite any custom shaders using Shader Graph though, since the SRPs don’t support Surface Shaders.
     
    Serepa likes this.
  9. Serepa

    Serepa

    Joined:
    May 15, 2015
    Posts:
    32
    Aaaarrgh... tried to switch to URP but can't even just to reproduce the same shader in shader graph! I think the problem with world to screen space conversion but I'm not sure. How to use Unity helper functions from shader graph? There is no such node or anything. There is a function ComputeScreenPos in Packages/Shader Graph/ShaderGraphLibrary/Functions.hlsl but how to use it from graph? I made custom node but it doesn't work with type:File because it has no "_float" suffix in the script, so i just copied its content in string mode of custom node:

    Is it correct approach? Feels very wrong. Why I can't just use built in Unity functions as I did from code before?

    Here is my graph without Z distance check and aspect ratio adjustments (for simplicity) but it doesn't work and I completely stuck to figure out why:


    It renders just opaque plane but with shadow of occluded object for some reason:


    Could anyone say what's wrong with this freacking graph?
     

    Attached Files:

    • 11.png
      11.png
      File size:
      183.1 KB
      Views:
      3,507
    • 2.png
      2.png
      File size:
      14.6 KB
      Views:
      3,496
    • 3.png
      3.png
      File size:
      39.1 KB
      Views:
      3,479
    Last edited: Nov 25, 2019
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    Yes(ish), and yes, and no idea. Some built in functions work fine, some don't, and I'm not entirely sure why since those same functions work totally fine if you manually edit the generated hlsl code. I'm personally pretty unhappy with the Custom Function node in general and I can't get it to work properly half the times I use it; it's poorly documented and confusingly implemented, and doesn't seem to get a lot of use internally so it remains in a semi-broken state when used with the in-line text version of the node rather than the separate .hlsl file option that they seem to want to make others use (and almost all of their internally made shader examples use).

    One tweak is the Camera node's Z Buffer Sign is not the property you want there. In fact the one you need is not exposed by any node! Luckily, that doesn't matter and the real one can be accessed by a Custom Function node without any problem.

    Here's how I wrote my version of that node:
    upload_2019-11-25_10-35-38.png

    Also note the property names in the blackboard (what they call the area you define your properties in) are just the display names. Equivalent to the text in the quotes when you write shaders directly. The actual property name that you would set from script is set by the Reference text field you see when you expand a property.

    Here's a working version of the shader with some notes. The Custom Function node is the one above.
    upload_2019-11-25_10-58-44.png
     
    Serepa likes this.
  11. Serepa

    Serepa

    Joined:
    May 15, 2015
    Posts:
    32
    Yay, dude, thanks a ton again! Finally it works in shader graph as it were in surface shader. But I realized that our custom node could looks just like that:


    But I allow myself to disagree with you here:
    Because I found it in documentation here: https://docs.unity3d.com/Packages/com.unity.shadergraph@6.9/manual/Camera-Node.html

    And because it finally works as expected in old way similar to surface shader variant:


    Thanks again for your help and nice suggestion for improvement with smoothstep. I'll deffenetly add it later when finally understand math behind it and how it works. :D
     

    Attached Files:

    bgolus likes this.
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    Indeed you’re right! That’s funny because that’s not technically what that parameter is supposed to mean, but they do usually line up. It’s entirely possible they always line up.
     
  13. TheJetstream

    TheJetstream

    Joined:
    Jan 7, 2019
    Posts:
    4
    Hello Serepa, I am currently trying to achieve that exact effect but my shader graph (that I just copied from your screenshot :D) does not seem to be working. Could you maybe post your asset?
     
    ligafacens likes this.
  14. Serepa

    Serepa

    Joined:
    May 15, 2015
    Posts:
    32
    Didn't you forget to update "_Position" field in the shader from script?
     

    Attached Files:

    ligafacens likes this.
  15. TheJetstream

    TheJetstream

    Joined:
    Jan 7, 2019
    Posts:
    4
    Thank you for the graph, I had some minor (stupid) erros in my graph (for example: I forgot to set the workflow mode to transparent). I also added a "feature" to your solution. With your graph the "wall" (or similar) would get this see-through effect every time the camera and target object (the player for example) line up, even if the wall is not in between camera and player.

    I added some logic in the shader graph that uses the direction vector from the camera to the object as a normal for a plane and checks whether the camera and the player object are on the same side of the plane, if so, the alpha value is set to 1, if not (if the wall is between camera and player) your effect is used in the alpha channel.

    For anyone interested, the Logic part can be seen here (I left the effect part out because it would be hard to see everything):

    Screenshot_165.png

    Edit 1: In the Vector3 which is constructed using the direction vector from the camera to the "wall" I set the y-Value to 0 to get a plane which is only rotated around the y-axis.
     
    Last edited: Jun 19, 2020