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\$

1 Answer 1

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\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .