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

Weird and hard edged shadow in my attempt at replicating PBR/PBS

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

  1. PlazmaInteractive

    PlazmaInteractive

    Joined:
    Aug 8, 2016
    Posts:
    114
    I've been working on this for 2 days straight, trying to implement my own physically based shader with the Cook-Torrance BDRF model in mind to no avail. I think it's looking close enough but there must be something in my code or calculation that I'm overlooking because my shader is having hard edges in the shadow (plus it looks really dark) and I'm pretty sure that doesn't meant to happen. I've looked over a lot of sources including Unity's standard shader code so there are a few implementations here and there from those.

    Here's what a sphere looks like with 0 roughness and 0 metallic. Shadow looks okay but it's too dark.
    First.png
    With 1 roughness and 1 metallic. Hard edges is apparent here.
    Second.png

    I'm trying to mimic the standard shader first and I might make a few implementations once I solve this problem. Diffuse looks fine but I'm sure there's something wrong with my specular calculation although I can't figure that out. Also the shadow is too dark for my taste.

    The code:
    Code (csharp):
    1. Shader "ShaderChallenge/Cook-Torrance"
    2. {
    3.     Properties
    4.     {
    5.         _Albedo("Albedo", 2D) = "white" {}
    6.         _Fresnel("Fresnel", Range(1, 18)) = 5.0
    7.         _Roughness("Roughness", Range(0, 1)) = 1.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.             sampler2D _Albedo;
    22.             float _Fresnel;
    23.             float _Roughness;
    24.             float _Metallic;
    25.  
    26.             struct v2f
    27.             {
    28.                 float4 pos : SV_POSITION;
    29.                 float3 worldPos : TEXCOORD0;
    30.                 float3 normal : TEXCOORD1;
    31.                 float2 uv : TEXCOORD2;
    32.             };
    33.  
    34.             v2f vert(appdata_base v)
    35.             {
    36.                 v2f o;
    37.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    38.                 // Convert vertex position to world space
    39.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    40.                 // Normal vector
    41.                 o.normal = UnityObjectToWorldNormal(v.normal);
    42.                 o.uv = v.texcoord;
    43.                 return o;
    44.             }
    45.  
    46.             float DistributionGGX(float3 N, float3 H)
    47.             {
    48.                 float a2 = pow(_Roughness, 2);
    49.                 float nDotH = pow(DotClamped(N, H), 2);
    50.                 float denom = (nDotH * (a2 - 1.0) + 1.0);
    51.  
    52.                 const float pi = 3.14159265359;
    53.                 denom = pi * pow(denom, 2);
    54.  
    55.                 return a2 / denom;
    56.             }
    57.            
    58.             float GeometrySchlickGGX(float dot, float k)
    59.             {
    60.                 float nom   = dot;
    61.                 float denom = dot * (1.0 - k) + k;
    62.    
    63.                 return nom / denom;
    64.             }
    65.  
    66.             float GeometrySchlick(float3 N, float3 V, float3 L)
    67.             {
    68.                 float nDotV = DotClamped(N, V);
    69.                 float nDotL = DotClamped(N, L);
    70.                 // Direct lighting
    71.                 float k = pow(_Roughness + 1, 2) / 8;
    72.                 float ggx1 = GeometrySchlickGGX(nDotV, k);
    73.                 float ggx2 = GeometrySchlickGGX(nDotL, k);
    74.  
    75.                 return ggx1 * ggx2;
    76.             }
    77.  
    78.             float FresnelSchlick(float3 N, float3 V, float3 albedo)
    79.             {
    80.                 float cosTheta = DotClamped(N, V);
    81.                 float3 F0 = 0.04;
    82.                 F0 = lerp(F0, albedo, _Metallic);
    83.  
    84.                 return F0 + (1.0 - F0) * pow(1.0 - cosTheta, _Fresnel);
    85.             }
    86.  
    87.             float DisneyDiffuse(float3 N, float3 V, float3 L, float3 H)
    88.             {
    89.                 float fd90 = 0.5 + 2 * pow(DotClamped(L, H), 2.0) * _Roughness;
    90.                
    91.                 float lightScatter   = (1 + (fd90 - 1) * pow(1 - DotClamped(N, L), 5.0));
    92.                 float viewScatter    = (1 + (fd90 - 1) * pow(1 - DotClamped(N, V), 5.0));
    93.  
    94.                 return lightScatter * viewScatter;
    95.             }
    96.  
    97.             float4 frag(v2f i) : SV_TARGET
    98.             {
    99.                 const float pi = 3.14159265359;
    100.                 float3 lightCol = _LightColor0.rgb;
    101.  
    102.                 // Normalize the normal
    103.                 float3 N = normalize(i.normal);
    104.                 // Light vector from mesh's surface
    105.                 float3 L = normalize(_WorldSpaceLightPos0.xyz);
    106.                 // Viewport(camera) vector from mesh's surface
    107.                 float3 V = normalize(_WorldSpaceCameraPos - i.worldPos.xyz);
    108.                 // Halfway vector
    109.                 float3 H = normalize(L + V);
    110.  
    111.                 float3 albedo = tex2D(_Albedo, i.uv).rgb;
    112.  
    113.                 // Cook-Torrance BRDF model
    114.                 float D = DistributionGGX(N, H);
    115.                 float F = FresnelSchlick(N, V, albedo);
    116.                 float G = GeometrySchlick(N, V, L);
    117.  
    118.                 float3 specular = (D * F * G) / (4.0 * DotClamped(N, L) * DotClamped(N, V));
    119.  
    120.                 // Diffuse color
    121.                 float3 diffuse = DisneyDiffuse(N, V, L, H) * DotClamped(N, L) * albedo;
    122.  
    123.                 return float4(lerp(diffuse, specular, _Metallic) * lightCol, 1.0);
    124.             }
    125.             ENDCG
    126.         }
    127.     }
    128. }