Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Need help understanding the Fresnel equation for PBR

Discussion in 'Shaders' started by PlazmaInteractive, Apr 3, 2017.

  1. PlazmaInteractive

    PlazmaInteractive

    Joined:
    Aug 8, 2016
    Posts:
    114
    Hello everyone. I've been studying the Cg (or HLSL) shading language for a couple of months and I finally decided to write a shader that's based on PBR. I understand that there's energy conservation, microfacets and other things but the thing that confuses me the most right now is fresnel.

    Based on my understanding, fresnel states that in grazing angles, you'll see reflection on the surface of objects more clearly than looking straight on. I'm following this website and while it's in OpenGL, it had helped me understand the concept of PBR. I'm replicating Schlick's approximation in my Cg code with the help of the website and this is the output.
    Fresnel.PNG

    I'm pretty sure that's how it work isn't it? When I increase the Metalness value, the meshes turns red. I added that Fresnel slider so I can adjust the rim I guess to increase or decrease it. I'm not sure if this is needed but in Blender, I think you're able to do this. Correct me on this if I'm wrong.

    And lastly, here's the Cg code in case I'm missing something or made some mistake.
    Code (csharp):
    1. Shader "ShaderChallenge/Fresnel-Schlick"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
    6.         _Fresnel("Fresnel", Range(1, 18)) = 5.0
    7.         _Metalness("Metalness", Range(0, 1)) = 0.0
    8.     }
    9.     SubShader
    10.     {
    11.         Pass
    12.         {
    13.             Tags { "LightMode" = "ForwardBase" }
    14.  
    15.             CGPROGRAM
    16.             #pragma vertex vert // Use vert function for vertex shader
    17.             #pragma fragment frag // Use frag function for fragment shader
    18.             #include "UnityStandardBRDF.cginc" // Include UnityStandardBRDF.cginc file
    19.  
    20.             float4 _Color;
    21.             float _Fresnel;
    22.             float _Metalness;
    23.  
    24.             struct v2f
    25.             {
    26.                 float4 pos : SV_POSITION;
    27.                 float3 normal : TEXCOORD1;
    28.                 float3 worldPos : TEXCOORD2;
    29.             };
    30.  
    31.             v2f vert(appdata_base v)
    32.             {
    33.                 v2f o;
    34.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    35.                 // Convert vertex position to world space
    36.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    37.                 // Transform normal vector position from object space to world space
    38.                 o.normal = UnityObjectToWorldNormal(v.normal);
    39.                 return o;
    40.             }
    41.  
    42.             float4 frag(v2f i) : SV_TARGET
    43.             {
    44.                 // Color of light
    45.                 float3 lightCol = _LightColor0.rgb;
    46.                 // Light vector in world space
    47.                 float3 lightDir = _WorldSpaceLightPos0.xyz;
    48.                 // Viewport(camera) vector from mesh's surface
    49.                 float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
    50.  
    51.                 float cosTheta = DotClamped(i.normal, viewDir);
    52.                 float3 F0 = 0.04;
    53.                 F0 = lerp(F0, _Color.rgb, _Metalness);
    54.  
    55.                 return float4((F0 + (1.0 - F0) * pow(1.0 - cosTheta, _Fresnel)) * lightCol, 1.0);
    56.             }
    57.             ENDCG
    58.         }
    59.     }
    60. }
     
  2. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    The complete Fresnel equation is actually pretty complex and includes the index of refraction and absorption (for metals only.) For many materials like metals the index of refraction and absorption also varies with wavelength.

    So the first usual approximation is to use it for non-metals and just use a white to metal color fresnel falloff for metals.

    Then there are still two fresnel equations. One for parallel light and one for perpendicular. The usual approximation to that is 50/50.

    Then finally you come to Schlick's approximation, which is a very simple approximation indeed.

    My opinion is that PBR is just some overhyped word like so many. In my view it just means you're not doing it obviously wrong. So when it comes to fresnel, it means you use the fresnel value to lerp between reflection and diffuse. In the old days reflection was often just added to the diffuse part. This is obviously wrong, since you see less of the material underneath where it reflects more. As far as I know that's all you need to reach the "PBR-station".

    In reality there are quite some aspects of this that don't seem photorealistic yet:
    - Schlick's approximation is very simple and can easily be expanded to be more accurate for a wider range of (non-metal) materials.
    - Metals have a way more complicated Fresnel equation.
    - The effect of roughness on fresnel is often ignored, leading to overbright edges.
    - If your reflection isn't very accurate, what's the use of calculating the fresnel value very accurately.

    Energy conservation is a natural law, but not the magical way to make things look realistic. Many more advanced BRDF's like Oren-Nayar are not energy conserving at all. And many energy conserving BRDF's don't look that photorealistic.

    Using linear values instead of gamma adjusted and conserving energy are good practices of course, but there is a lot more to think about.
     
  3. PlazmaInteractive

    PlazmaInteractive

    Joined:
    Aug 8, 2016
    Posts:
    114
    Hmmm, I quite understood what you meant. I'm using Schlick's approximation for fresnel because I'm trying to implement the Cook-Torrance BRDF model (with no luck so far). I definitely heard that there was other BRDF such as the one you mentioned so I'll read them a bit and see if I'll go with any of them.

    Meanwhile, I modified my fresnel shader a bit as I was using wrong terms and I may miscalculate some things wrong. I also noticed I'm only able to use the dot product between the normal and view vector in my calculation when some source online states that I can replace the normal vector with the halfway vector. Anyone know why this is the case?
    Code (csharp):
    1. Shader "ShaderChallenge/Fresnel-Schlick"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
    6.         _RimTint("Rim Tint", Color) = (1.0, 1.0, 1.0, 1.0)
    7.         _Fresnel("Fresnel", Range(1, 18)) = 5.0
    8.         _Metallic("Metallic", Range(0, 1)) = 0.0
    9.     }
    10.     SubShader
    11.     {
    12.         Pass
    13.         {
    14.             Tags { "LightMode" = "ForwardBase" }
    15.  
    16.             CGPROGRAM
    17.             #pragma vertex vert // Use vert function for vertex shader
    18.             #pragma fragment frag // Use frag function for fragment shader
    19.             #include "UnityStandardBRDF.cginc" // Include UnityStandardBRDF.cginc file
    20.  
    21.             float4 _Color;
    22.             float4 _RimTint;
    23.             float _Fresnel;
    24.             float _Metallic;
    25.  
    26.             struct v2f
    27.             {
    28.                 float4 pos : SV_POSITION;
    29.                 float3 normal : TEXCOORD1;
    30.                 float3 worldPos : TEXCOORD2;
    31.             };
    32.  
    33.             v2f vert(appdata_base v)
    34.             {
    35.                 v2f o;
    36.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    37.                 // Convert vertex position to world space
    38.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    39.                 // Normal vector
    40.                 o.normal = v.normal;
    41.                 return o;
    42.             }
    43.  
    44.             float4 frag(v2f i) : SV_TARGET
    45.             {
    46.                 // Normalize the normal
    47.                 float3 N = normalize(i.normal);
    48.                 // Light vector from mesh's surface
    49.                 float3 L = normalize(_WorldSpaceLightPos0.xyz);
    50.                 // Viewport(camera) vector from mesh's surface
    51.                 float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
    52.                 // Halfway vector
    53.                 //float3 H = normalize(L + V);
    54.  
    55.                 float cosTheta = DotClamped(N, V);
    56.                 float3 F0 = 0.04;
    57.                 F0 = lerp(F0, _Color.rgb, _Metallic);
    58.  
    59.                 return float4(F0 + (1.0 - F0) * pow(1.0 - cosTheta, _Fresnel) * _RimTint, 1.0);
    60.             }
    61.             ENDCG
    62.         }
    63.     }
    64. }
    The result is this. I think it gives a more accurate representation.
    Fresnel.png
     
    Last edited: Apr 4, 2017
  4. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    There are many BRDF's out there. The more complex ones are usually based on some physical measurements or theoretical models. But, always keep comparing with reality. For fresnel the most common issue is edges that seem overly bright. Usually because the reflection is incorrect or roughness that is not taken into account. I think there is no shame in adding some non-physical adjustment options to counter that. There are many cases where these adjustments are closer to reality than the original BRDF.

    The main vectors that go into a BRDF are normal, view, light and half. (Half is between view and light.) Considering that fresnel assumes a perfect reflection, you could say that the normal and half vectors are aligned. But that does mean that the light vector is based on a perfect reflection, not the direction of an actual light. In short, stick with normal and view for fresnel.