Search Unity

My PBR shader goes completely black when it shouldn't be at Roughness value of 1

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

  1. PlazmaInteractive

    PlazmaInteractive

    Joined:
    Aug 8, 2016
    Posts:
    114
    I know I've made a forum about this before but it didn't have any answer. So in the meantime, I decided to completely rewrite my PBR shader and following this guy's video (with some tweaking), I was able to create a PBR shader except that there's one small issue that I haven't figured out yet. When I set the value of roughness in the shader to 1, while at the same time having a value of 1 for metallic, the material goes completely black. As far as I'm aware, this should only occur if the roughness is 0 and metallic is 1. What this should have done is darken the surface instead of making it completely black. Also, changing the Roughness lower won't any specular highlight to appear and it'll just stay black.

    Here's what it should look like, comparing it to the standard shader:
    Comparison.png

    The only difference other than how it looks is that the standard shader use a Smoothness value rather than Roughness. Mine is basically an invert of that so think of Smoothness at 0 as being a completely rough material. Other than that, I believe the shader is working so anyone who have any idea regarding this issue, please let me know.

    The code:
    Code (csharp):
    1. Shader "ShaderChallenge/Custom PBR"
    2. {
    3.     Properties
    4.     {
    5.         _Albedo("Albedo", 2D) = "white" {}
    6.         _Tint("Tint", Color) = (1.0, 1.0, 1.0, 1.0)
    7.         _Roughness("Roughness", Range(0.0, 1.0)) = 1.0
    8.         _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
    9.         _Fresnel("Fresnel", Range(1, 18)) = 5.0
    10.     }
    11.     SubShader
    12.     {
    13.         Pass
    14.         {
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.             #include "UnityStandardBRDF.cginc"
    19.             #include "UnityStandardUtils.cginc"
    20.  
    21.             sampler2D _Albedo;
    22.             float4 _Albedo_ST;
    23.             float4 _Tint;
    24.             float _Roughness;
    25.             float _Metallic;
    26.             float _Fresnel;
    27.  
    28.             struct v2f
    29.             {
    30.                 float4 pos : SV_POSITION;
    31.                 float2 uv : TEXCOORD0;
    32.                 float3 normal : TEXCOORD1;
    33.                 float3 worldPos : TEXCOORD2;
    34.             };
    35.  
    36.             v2f vert(appdata_base v)
    37.             {
    38.                 v2f o;
    39.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    40.                 // Convert vertex position to world space
    41.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    42.                 // Macro used to tile and offset texture
    43.                 o.uv = TRANSFORM_TEX(v.texcoord, _Albedo);
    44.                 // Transform normal vector position from object space to world space
    45.                 o.normal = UnityObjectToWorldNormal(v.normal);
    46.                 return o;
    47.             }
    48.  
    49.             // Oren-Nayar
    50.             float OrenNayar(float3 n, float3 v, float3 l)
    51.             {
    52.                 float nl = dot(n, l);
    53.                 float nv = dot(n, v);
    54.  
    55.                 float anglenl = acos(nl);
    56.                 float anglenv = acos(nv);
    57.  
    58.                 float alpha = max(anglenv, anglenl);
    59.                 float beta = min(anglenv, anglenl);
    60.                 float gamma = dot(v - n * nv, l - n * nl);
    61.  
    62.                 float a2 = pow(_Roughness, 2.0);
    63.  
    64.                 float A = 1.0 - 0.5 * (a2 / (a2 + 0.57));
    65.  
    66.                 float B = 0.45 * (a2 / (a2 + 0.09));
    67.  
    68.                 float C = sin(alpha) * tan(beta);
    69.  
    70.                 float result = max(0.0, nl) * (A + B * max(0.0, gamma) * C);
    71.  
    72.                 return result;
    73.             }
    74.  
    75.             // Trowbridge-Reitz GGX
    76.             float GGX(float3 h, float3 n)
    77.             {
    78.                 float a2 = pow(_Roughness, 2);
    79.                 float nh2 = pow(dot(n, h), 2);
    80.                 float denom = (nh2 * (a2 - 1.0) + 1.0);
    81.  
    82.                 const float pi = 3.14159265359;
    83.                 denom = pi * pow(denom, 2);
    84.  
    85.                 return a2 / denom;
    86.             }
    87.  
    88.             // Cook-Torrance
    89.             float CookTorrance(float3 n, float3 h, float3 v, float3 l)
    90.             {
    91.                 float nh = dot(n, h);
    92.                 float nv = dot(n, v);
    93.                 float nl = dot(n, l);
    94.                 float vh = dot(v, h);
    95.  
    96.  
    97.                 float f0 = (2.0 * nh * nv) / vh;
    98.                 float f1 = (2.0 * nh * nl) / vh;
    99.  
    100.                 float m = min(f0, f1);
    101.  
    102.                 float result = min(1, m);
    103.  
    104.                 return result;
    105.             }
    106.  
    107.             // Schlick's Approximation
    108.             float FresnelSchlick(float3 n, float3 v, float3 albedo)
    109.             {
    110.                 float cosTheta = dot(n, v);
    111.                 float3 F0 = 0.04;
    112.                 F0 = lerp(F0, albedo, 1.0 - _Metallic);
    113.  
    114.                 return F0 + (1.0 - F0) * pow(1.0 - cosTheta, _Fresnel);
    115.             }
    116.  
    117.             float Microfacet(float3 n, float3 h, float3 v, float3 l, float3 albedo)
    118.             {
    119.                 float f = FresnelSchlick(n, v, albedo);
    120.                 float g = CookTorrance(n, h, v, l);
    121.                 float d = GGX(h, n);
    122.  
    123.                 float nl = max(dot(n, l), 0.0);
    124.                 float nv = max(dot(n, v), 0.0);
    125.  
    126.                 float result = (f * g * d) / 4 * nv * nl + 0.001;
    127.  
    128.                 return result;
    129.             }
    130.  
    131.             float4 frag(v2f i) : SV_TARGET
    132.             {
    133.                 const float PI = 3.14159265359;
    134.                 // Get the first light color
    135.                 float3 lightCol = _LightColor0.rgb;
    136.  
    137.                 // Normalize the normal
    138.                 float3 n = normalize(i.normal);
    139.                 // Light vector from mesh's surface
    140.                 float3 l = normalize(_WorldSpaceLightPos0.xyz);
    141.                 // Viewport(camera) vector from mesh's surface
    142.                 float3 v = normalize(_WorldSpaceCameraPos - i.worldPos.xyz);
    143.                 // Halfway vector
    144.                 float3 h = normalize(l + v);
    145.  
    146.                 // Albedo of the material
    147.                 float3 albedo = tex2D(_Albedo, i.uv).rgb * _Tint.rgb;
    148.                 float3 specularTint;
    149.                 float oneMinusReflectivity;
    150.  
    151.                 albedo = DiffuseAndSpecularFromMetallic(albedo, _Metallic, specularTint, oneMinusReflectivity);
    152.  
    153.                 // Fresnel using Schlick's Approximation
    154.                 float fresnel = FresnelSchlick(n, v, albedo);
    155.  
    156.                 // Ambient lighting
    157.                 float ambient = unity_AmbientSky * fresnel;
    158.                 // Diffuse reflection using Oren-Nayar BRDF
    159.                 float diffuse = OrenNayar(n, v, l) * fresnel;
    160.                 float specular = Microfacet(n, h, v, l, albedo);
    161.  
    162.                 float3 color = albedo * (ambient + (diffuse * lightCol) + (specular * lightCol));
    163.                 color = float3(pow(color.x, 1.0 / 2.0), pow(color.y, 1.0 / 2.0), pow(color.z, 1.0 / 2.0));
    164.  
    165.                 return float4(color, 1.0);
    166.             }
    167.  
    168.             ENDCG
    169.         }
    170.     }
    171. }
     
    Last edited: Apr 8, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Metallic surfaces have little to no diffuse, so lighting ambient comes from an ambient specular. Usually this is done using a pre-blurred cubemap which you don't have implemented in your code.