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
