I'm currently trying to port an existing engine to PBR. The engine was designed with some PBR features like IBL, energy conservation however it still uses the gloss map + specular map.
My goal is to port it to metalness + roughness to make artist's job easier, however I'm facing some issues.
- 1) The original engine dev left the project
- 2) While I have glsl/programming knowledge I'm more on the artistic side
Before the engine compute the diffuse and specular coefficient with those function:
While the input var is named roughness, it's in fact the gloss map from the 3D model
// Blinn Phong with emulated fresnel factor vec3 SpecularBRDF(vec3 normal, vec3 eyedir, vec3 lightdir, vec3 color, float roughness) { float exponentroughness = exp2(10. * roughness + 1.); // Half Light View direction vec3 H = normalize(eyedir + lightdir); float NdotH = clamp(dot(normal, H), 0., 1.); float normalisationFactor = (exponentroughness + 2.) / 8.; vec3 FresnelSchlick = color + (1.0f - color) * pow(1.0f - clamp(dot(eyedir, H), 0., 1.), 5.); return max(pow(NdotH, exponentroughness) * FresnelSchlick * normalisationFactor, vec3(0.)); } // Lambert model vec3 DiffuseBRDF(vec3 normal, vec3 eyedir, vec3 lightdir, vec3 color, float roughness) { return color; }
And the main function
void main() { vec2 uv = gl_FragCoord.xy / u_screen; float z = texture(dtex, uv).x; vec4 xpos = getPosFromUVDepth(vec3(uv, z), u_inverse_projection_matrix); vec3 norm = normalize(DecodeNormal(2. * texture(ntex, uv).xy - 1.)); float roughness =texture(ntex, uv).z; vec3 eyedir = -normalize(xpos.xyz); vec3 Lightdir = SunMRP(norm, eyedir); float NdotL = clamp(dot(norm, Lightdir), 0., 1.); vec3 Specular = SpecularBRDF(norm, eyedir, Lightdir, vec3(1.), roughness); vec3 Diffuse = DiffuseBRDF(norm, eyedir, Lightdir, vec3(1.), roughness); // Shadows float factor; if (xpos.z < split0) factor = getShadowFactor(xpos.xyz, 0); else if (xpos.z < split1) factor = getShadowFactor(xpos.xyz, 1); else if (xpos.z < split2) factor = getShadowFactor(xpos.xyz, 2); else if (xpos.z < splitmax) factor = getShadowFactor(xpos.xyz, 3); else factor = 1.; Diff = vec4(factor * NdotL * Diffuse * sun_color, 1.); Spec = vec4(factor * NdotL * Specular * sun_color, 1.); }
I'm attempting to implement the function described here http://github.khronos.org/glTF-WebGL-PBR/ Currently I'm focused on implementing roughness. Metalness will come later
#define saturate(value) clamp(value, 0.0f, 1.0f);
#define PI 3.1415926f
#define EPSILON 10e-5f
// Attempt at better implementation ========================
// Surface Reflection Ratio (F)
float specularReflection_F(float metalness, float VdotH)
{
return metalness + (1.0 - metalness) * pow(1.0 - VdotH, 5.0);
}
vec3 diffuse(vec3 diffuseColor)
{
return diffuseColor / PI;
}
// Geometric Occlusion (G)
float geometricOcclusion_G(float NdotV, float NdotL, float alphaRoughness)
{
float NdotL2 = NdotL * NdotL;
float NdotV2 = NdotV * NdotV;
float v = ( -1.0 + sqrt ( alphaRoughness * (1.0 - NdotL2 ) / NdotL2 + 1.)) * 0.5;
float l = ( -1.0 + sqrt ( alphaRoughness * (1.0 - NdotV2 ) / NdotV2 + 1.)) * 0.5;
return (1.0 / max((1.0 + v + l ), 0.000001));
}
// Microfaced Distribution (D)
float microfacetDistribution_D(float NdotH, float alphaRoughness)
{
float roughnessSq = alphaRoughness * alphaRoughness;
float f = (NdotH * roughnessSq - NdotH) * NdotH + 1.0;
return roughnessSq / (PI * f * f);
}
float getShadowFactor(vec3 pos, int index)
{
vec4 shadowcoord = (u_shadow_projection_view_matrices[index] * u_inverse_view_matrix * vec4(pos, 1.0));
shadowcoord.xy /= shadowcoord.w;
vec2 shadowtexcoord = shadowcoord.xy * 0.5 + 0.5;
//float d = .5 * shadowcoord.z + .5;
float d = .5 * shadowcoord.z + .5 - 1. / (shadow_res * 5.);
float result = 0.;
for (float i = -1.; i <= 1.; i += 1.)
{
for (float j = -1.; j <= 1.; j += 1.)
{
result += texture(shadowtex, vec4(shadowtexcoord + vec2(i,j) / shadow_res, float(index), d));
}
}
return result / 9.;
}
void main() {
vec2 uv = gl_FragCoord.xy / u_screen;
float z = texture(dtex, uv).x;
vec4 xpos = getPosFromUVDepth(vec3(uv, z), u_inverse_projection_matrix);
vec3 norm = normalize(DecodeNormal(2. * texture(ntex, uv).xy - 1.));
float roughness =texture(ntex, uv).z;
vec3 eyedir = -normalize(xpos.xyz);
vec3 Lightdir = SunMRP(norm, eyedir);
float NdotL = clamp(dot(norm, Lightdir), 0., 1.);
vec3 Specular = SpecularBRDF(norm, eyedir, Lightdir, vec3(1.), roughness);
vec3 Diffuse = DiffuseBRDF(norm, eyedir, Lightdir, vec3(1.), roughness);
// Custom computation for PBR engine ======================================
vec4 BaseColor = vec4(sun_color.rgb, 1.0f);
vec4 SpecularColor = vec4(sun_color.rgb, 1.0f);
vec3 normal = norm;
vec3 LightDirection = Lightdir;
vec3 ViewDirection = eyedir;
vec3 HalfVector = normalize(ViewDirection + LightDirection);
float Roughness = 0.0;
float RefractiveIndex = 0.24f; // RI for Gold materials. I got this from http://refractiveindex.info/
float F0 = pow(((1.0f - RefractiveIndex) / (1.0f + RefractiveIndex)), 2);
NdotL = saturate(dot(LightDirection, normal));
float NdotV = abs(dot(ViewDirection, normal)) + EPSILON; // Avoid artifact - Ref: SIGGRAPH14 - Moving Frosbite to PBR
float LdotH = saturate(dot(LightDirection, HalfVector));
float NdotH = saturate(dot(normal, HalfVector));
// Additional for testing the new implementation bellow
float VdotH = saturate(dot(ViewDirection, HalfVector));
// New implementation: ##################################
float alphaRoughness = roughness;
float F = specularReflection_F(0.0, VdotH);
vec3 diffuseContrib = (1.0 - F) * diffuse(sun_color);
float G = geometricOcclusion_G(NdotV, NdotL, alphaRoughness);
float D = microfacetDistribution_D(alphaRoughness, NdotH);
//float D = 1.0;
G = clamp(G, 0.0, 2.0);
D = clamp(D, 0.0, 2.0);
vec3 specContrib = (vec3(F) * G * D) / (4.0 * NdotL * NdotV);
// Shadows ================================================================
float factor;
if (xpos.z < split0)
factor = getShadowFactor(xpos.xyz, 0);
else if (xpos.z < split1)
factor = getShadowFactor(xpos.xyz, 1);
else if (xpos.z < split2)
factor = getShadowFactor(xpos.xyz, 2);
else if (xpos.z < splitmax)
factor = getShadowFactor(xpos.xyz, 3);
else
factor = 1.;
Diff = vec4(diffuseContrib, 1.0);
//Diff = DiffuseFactor;
//Diff = vec4(factor * NdotL * DiffuseFactor * sun_color, 1.);
Spec = vec4(specContrib, 1.0);
//Spec = SpecularColor * SpecularFactor;
//Spec = vec4(factor * NdotL * SpecularFactor * sun_color, 1.);
//Diff = FinalColor;
}
However when launching the game, I have weird dark artifacts everywhere. I removed the shadows to ouput only the specular and diffuse factor
- 1) Are my input corrects (the VdotH, NdotV and LdotH)
- 2) Did I made any obvious mistake?
I didn't included other files to keep it short. However the full source code of the engine is open source and you can check it here https://github.com/Benau/stk-code/tree/combine/data/shaders