1
\$\begingroup\$

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 PBR scene

  • 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

\$\endgroup\$
0
\$\begingroup\$

Ok after a some research I finally found what was wrong.

Now I compute the final color as following:

float metallic = specMapValue;
    vec3 specularMatColor = mix(vec3(0.04), diffuseMatColor.xyz * 4, metallic);

    vec3 result = DiffuseComponent * mix(diffuseMatColor.xyz, vec3(0.0), metallic) + (specularMatColor * SpecularComponent);

I still have to fix the reflection of the metal part that is a bit too dark (currently I multiply the spec color by 4 but it feels like a hack

Anyway here is a comparison between Substance Painter and the shader result Substance Painter Reference / Engine output

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.