Search Unity

Confusion about worldNormal in Surface shader

Discussion in 'Shaders' started by SunnySunshine, Aug 23, 2016.

  1. SunnySunshine

    SunnySunshine

    Joined:
    May 18, 2009
    Posts:
    977
    I'm trying to get worldNormal to work in a surface shader together with a normal map, but it's proving difficult.

    The documentation mentions you need to use worldNormal; INTERAL_DATA if writing to o.Normal, and then access it using WorldNormalVector, like so:

    Code (CSharp):
    1. struct Input {
    2.     float3 worldNormal; INTERNAL_DATA
    3. };
    Code (CSharp):
    1. float3 worldNormal = WorldNormalVector(IN, o.Normal);
    However, the result of doing this isn't what I'm expecting.

    Here are two simple fresnel surface shaders - one writing to o.Normal and using WorldNormalVector, the other one does not. This is what you get:



    As you can see, the starting right sphere behaves very oddly.

    Here are the two shaders:

    Writing to o.Normal and using WorldNormalVector (not working)
    Code (CSharp):
    1. Shader "Custom/NormalTest" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _BumpMap ("Normalmap", 2D) = "bump" {}
    6.         _FresnelPower ("FresnelPower", Range(0,1)) = 0.5
    7.     }
    8.     SubShader {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 200
    11.      
    12.         CGPROGRAM
    13.         // Physically based Standard lighting model, and enable shadows on all light types
    14.         #pragma surface surf Standard fullforwardshadows
    15.  
    16.         // Use shader model 3.0 target, to get nicer looking lighting
    17.         #pragma target 3.0
    18.  
    19.         sampler2D _MainTex;
    20.         sampler2D _BumpMap;
    21.  
    22.         struct Input {
    23.             float2 uv_MainTex;
    24.             float3 worldNormal; INTERNAL_DATA
    25.             float3 viewDir;
    26.         };
    27.  
    28.         half _FresnelPower;
    29.         fixed4 _Color;
    30.  
    31.         void surf (Input IN, inout SurfaceOutputStandard o) {
    32.             // Albedo comes from a texture tinted by color
    33.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * 0;
    34.             o.Albedo = c.rgb;
    35.             o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
    36.             // Metallic and smoothness come from slider variables
    37.             // o.Metallic = _Metallic;
    38.             // o.Smoothness = _Glossiness;
    39.             o.Alpha = c.a;
    40.          
    41.             float3 worldNormal = WorldNormalVector(IN, o.Normal);
    42.             float fresnel = pow(abs(1 - dot(IN.viewDir, worldNormal)), _FresnelPower * 10);
    43.          
    44.             o.Emission = fresnel;
    45.         }
    46.         ENDCG
    47.     }
    48.     FallBack "Diffuse"
    49. }
    50.  

    Not writing to o.Normal (working as expected):
    Code (CSharp):
    1. Shader "Custom/NormalTest 1" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         // _BumpMap ("Normalmap", 2D) = "bump" {}
    6.         _FresnelPower ("FresnelPower", Range(0,1)) = 0.5
    7.     }
    8.     SubShader {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 200
    11.      
    12.         CGPROGRAM
    13.         // Physically based Standard lighting model, and enable shadows on all light types
    14.         #pragma surface surf Standard fullforwardshadows
    15.  
    16.         // Use shader model 3.0 target, to get nicer looking lighting
    17.         #pragma target 3.0
    18.  
    19.         sampler2D _MainTex;
    20.         // sampler2D _BumpMap;
    21.  
    22.         struct Input {
    23.             float2 uv_MainTex;
    24.             float3 worldNormal;
    25.             float3 viewDir;
    26.         };
    27.  
    28.         half _FresnelPower;
    29.         fixed4 _Color;
    30.  
    31.         void surf (Input IN, inout SurfaceOutputStandard o) {
    32.             // Albedo comes from a texture tinted by color
    33.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * 0;
    34.             o.Albedo = c.rgb;
    35.             // o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
    36.             // Metallic and smoothness come from slider variables
    37.             // o.Metallic = _Metallic;
    38.             // o.Smoothness = _Glossiness;
    39.             o.Alpha = c.a;
    40.          
    41.             // float3 worldNormal = WorldNormalVector(IN, o.Normal);
    42.             float fresnel = pow(abs(1 - dot(IN.viewDir, IN.worldNormal)), _FresnelPower * 10);
    43.          
    44.             o.Emission = fresnel;
    45.         }
    46.         ENDCG
    47.     }
    48.     FallBack "Diffuse"
    49. }
    50.  

    What am I doing wrong?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The view dir for surface shaders is already in tangent space, so you just need to do:
    float fresnel = pow(abs(1 - dot(IN.viewDir, float3(0,0,1)), _FresnelPower * 10);

    Which if you're not using normal maps can be simplified down to:
    float fresnel = pow(abs(1 - IN.viewDir.z), _FresnelPower * 10);
     
    ccaner37 and SunnySunshine like this.
  3. SunnySunshine

    SunnySunshine

    Joined:
    May 18, 2009
    Posts:
    977
    Thanks a lot @bgolus! Totally overlooked the fact that viewDir is in tangent space. This makes things so much easier.

    Thanks again.
     
  4. adamgryu

    adamgryu

    Joined:
    Mar 1, 2014
    Posts:
    188
    I know this is an old thread, but where is it documented that viewDir is in tangent space? The documentation for this seems very poor. I've been trying to reason about why viewDir.y is 0 when looking at a surface from top down (I had assumed it was in world space), and this thread provided the explanation I was looking for.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    It's not documented anywhere, and it's only in tangent space if you're also writing to o.Normal.

    Code (CSharp):
    1. Shader "Custom/WorldViewDirSurfaceShader" {
    2.     Properties {
    3.     }
    4.     SubShader {
    5.         Tags { "RenderType"="Opaque" }
    6.         LOD 200
    7.      
    8.         CGPROGRAM
    9.         // Physically based Standard lighting model, and enable shadows on all light types
    10.         #pragma surface surf Standard fullforwardshadows
    11.  
    12.         // Use shader model 3.0 target, to get nicer looking lighting
    13.         #pragma target 3.0
    14.  
    15.         struct Input {
    16.             float3 viewDir;
    17.         };
    18.  
    19.         void surf (Input IN, inout SurfaceOutputStandard o) {
    20.             o.Emission = IN.viewDir; // world space view dir
    21.         }
    22.         ENDCG
    23.     }
    24.     FallBack "Diffuse"
    25. }
    Code (CSharp):
    1. Shader "Custom/TangentViewDirSurfaceShader" {
    2.     Properties {
    3.     }
    4.     SubShader {
    5.         Tags { "RenderType"="Opaque" }
    6.         LOD 200
    7.      
    8.         CGPROGRAM
    9.         // Physically based Standard lighting model, and enable shadows on all light types
    10.         #pragma surface surf Standard fullforwardshadows
    11.  
    12.         // Use shader model 3.0 target, to get nicer looking lighting
    13.         #pragma target 3.0
    14.  
    15.         struct Input {
    16.             float3 viewDir;
    17.         };
    18.  
    19.         void surf (Input IN, inout SurfaceOutputStandard o) {
    20.             o.Emission = IN.viewDir; // tangent space view dir because ...
    21.             o.Normal = float3(0,0,1); // writing this line makes the Surface shader pass IN.viewDir to the surf function in tangent space
    22.         }
    23.         ENDCG
    24.     }
    25.     FallBack "Diffuse"
    26. }
     
    OhiraKyou and Reanimate_L like this.
  6. Reanimate_L

    Reanimate_L

    Joined:
    Oct 10, 2009
    Posts:
    2,788
    As usual you always shed a light in the darkest area on unity documentation
     
  7. adamgryu

    adamgryu

    Joined:
    Mar 1, 2014
    Posts:
    188
    Ah, that would explain some other weird behavior I was seeing! I was trying to calculate the Blinn Phong specular value from within the surface shader, and I spent hours trying to understand why it was only "sort-of" working. It would be nice for them to add these details to the Shader Reference.