### Physically-Based Rendering in WebGL

According to the image from Physically Based Shading At Disney as below, the left is the real chrome, the middle is PBR approach, and the right is Blinn-Phong. We can find PBR is more closer to the real case, and the difference part is the specular lighting part.

Blinn-Phong

The most important part of specular term in Blinn-Phong is it uses half-vector instead of using dot(lightDir, normalDir) to avoid the traditional Phong lighting model hard shape problem.

``````
vec3 BRDF_Specular_BlinnPhong( vec3 lightDir, vec3 viewDir, vec3 normal, vec3 specularColor, float shininess ) {
vec3 halfDir = normalize( lightDir + viewDir );
float dotNH = saturate( dot( normal, halfDir ) );
float dotLH = saturate( dot( lightDir, halfDir ) );
vec3 F = F_Schlick( specularColor, dotLH );
float G = G_BlinnPhong_Implicit( );
float D = D_BlinnPhong( shininess, dotNH );
return F * ( G * D );
}
``````

Physically-Based rendering

Regarding to the lighting model of GGX, UE4 Shading presentation by Brian Karis, it takes the Cook-Torrance separation of terms as three factors:

D) GGX Distribution
F) Schlick-Fresnel
V) Schlick approximation of Smith solved with GGX

``````
float G1V(float dotNV, float k) {
return 1.0 / (dotNV * (1.0 - k) + k);
}

float BRDF_Specular_GGX(vec3 N, vec3 V, vec3 L, float roughness, float f0) {
float alpha = roughness * roughness;
float H = normalize(V+L);

float dotNL = saturate(dot(N, L));
float dotNV = saturate(dot(N, V));
float dotNH = saturate(dot(N, H));
float dotLH = saturate(dot(L, H));

float F, D, vis;

// D
float alphaSqr = alpha * alpha;
float pi = 3.14159;
float denom = dotNH * dotNH * (alphaSqr - 1.0) + 1.0;
D = alphaSqr / (pi * denom * denom);

// F
float dotLH5 = pow(1.0 - dotLH, 5);
F = f0 + (1.0 - f0) * (dotLH5);

// V
float k = alpha / 2.0;
vis = G1V(dotNL, k) * G1V(dotNL, k);

float specular = dotNL * D * F * vis;
return specular;
}
``````

Unreal engine utilizes an approximate approach from Physically Based Shading on Mobile. We can see the specular term is shorten for the performance of mobile platform. (three.js' Standard material adopts this approach as well)

``````
half3 EnvBRDFApprox( half3 SpecularColor, half Roughness,half NoV )
{
const half4 c0 = { -1, -0.0275, -0.572, 0.022 };
const half4 c1 = { 1, 0.0425, 1.04, -0.04 };
half4 r = Roughness * c0 + c1;
half a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
half2 AB = half2( -1.04, 1.04 ) * a004 + r.zw;
return SpecularColor * AB.x + AB.y;
}
``````

Result:

http://dsmu.me/pbr/webgl_materials_pbr.html

Reference:
 GGX Shading Model For Metallic Reflections,  http://www.neilblevins.com/cg_education/ggx/ggx.htm
 Optimizing GGX Shaders with dot(L,H), http://filmicworlds.com/blog/optimizing-ggx-shaders-with-dotlh/
 Physically Based Shading in Call of Duty: Black Ops, http://blog.selfshadow.com/publications/s2013-shading-course/lazarov/s2013_pbs_black_ops_2_notes.pdf