I’ve been working with PBR for a good amount of time and have kind of successfully implemented a version based on Unity’s own standard shader, that is, using a Visibility term (as opposed to Geometric) and a Torrance-Sparrow microfacet model. I decided to go with that approach because I was having trouble implementing a Cook-Torrance version.
But now, I decided to try again being more experienced with shaders. After I looked through various codes in GitHub and other sources, I wrote the shader in Unity and the result comes out looking weird again like before. I can’t really pinpoint where the error is coming from but if I were to guess, it’s probably a dot product calculation doing it but I don’t really know. I’m using:
- Trowbridge-Reitz for Distribution
- Schlick-GGX for Geometric
- Schlick for Fresnel
I’m pretty sure everything was implemented correctly because the components above looked correct when I output them individually (proofs). Only when I tried to multiply them together using the Cook-Torrance formula it comes out looking weird.
Let me know if you find what’s causing this error. Here’s the code:
Shader "ShaderChallenge/Cook-Torrance"
{
Properties
{
_Albedo ("Albedo", Color) = (1.0, 1.0, 1.0, 1.0)
_Roughness ("Roughness", Range(0.0, 1.0)) = 1.0
_Metallic ("Metallic", Range(0.0, 1.0)) = 0.0
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityStandardBRDF.cginc"
#define pi 3.14159265359
float4 _Albedo;
float _Roughness;
float _Metallic;
struct v2f
{
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
float3 normal : TEXCOORD1;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.normal = v.normal;
return o;
}
float DistributionGGX(float3 n, float3 h)
{
float a = pow(_Roughness, 2.0);
float a2 = pow(a, 2.0);
float nh = max(dot(n, h), 0.0);
float nh2 = pow(nh, 2.0);
float nom = a2;
float denom = (nh2 * (a2 - 1.0) + 1.0);
denom = pi * pow(denom, 2.0);
return nom / denom;
}
float GeometrySchlickGGX(float cosTheta)
{
float r = (_Roughness + 1.0);
float k = pow(r, 2.0) / 8.0;
float nom = cosTheta;
float denom = cosTheta * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(float3 n, float3 v, float3 l)
{
float nv = max(dot(n, v), 0.0);
float nl = max(dot(n, l), 0.0);
float ggx1 = GeometrySchlickGGX(nv);
float ggx2 = GeometrySchlickGGX(nl);
return ggx1 * ggx2;
}
float3 FresnelSchlick(float cosTheta, float3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
float4 frag(v2f i) : SV_TARGET
{
float3 n = normalize(i.normal);
float3 l = normalize(_WorldSpaceLightPos0.xyz);
float3 v = normalize(_WorldSpaceCameraPos - i.worldPos.xyz);
float3 h = normalize(l + v);
float3 F0 = 0.04;
F0 = lerp(F0, _Albedo.rgb, _Metallic);
float D = DistributionGGX(n, h);
float G = GeometrySmith(n, v, l);
float F = FresnelSchlick(max(dot(n, v), 0.0), F0);
float3 specular = D * G * F / (4.0 * max(dot(n, v), 0.0) * max(dot(n, l), 0.0));
return float4(specular, 1.0);
}
ENDCG
}
}
}

