5

I'm trying to implement physically-based rendering (PBR) in our project (we started a small game engine for academic and learning purposes) and I cannot understand what is the right way to calculate specular and diffuse contribution based on material's metallic and roughness.

We don't use any third party libraries/engines for rendering, everything is hand written in OpenGL 3.3.

Right now I have this (I'll put the full code below):

// Calculate contribution based on metallicity
vec3 diffuseColor  = baseColor - baseColor * metallic;
vec3 specularColor = mix(vec3(0.00), baseColor, metallic);

But I'm under the impression that specular term has to be depended by roughness somehow. I was thinking to change it to this:

vec3 specularColor = mix(vec3(0.00), baseColor, roughness);

But again, I'm not sure. What is the right way to do it? Is there even a right way or should I just use the 'trial and error' method until I get a satisfying result?

Here is the full GLSL code:

// Calculates specular intensity according to the Cook - Torrance model
float CalcCookTorSpec(vec3 normal, vec3 lightDir, vec3 viewDir, float roughness, float F0)
{
    // Calculate intermediary values
    vec3 halfVector = normalize(lightDir + viewDir);
    float NdotL = max(dot(normal, lightDir), 0.0);
    float NdotH = max(dot(normal, halfVector), 0.0);
    float NdotV = max(dot(normal, viewDir), 0.0); // Note: this could also be NdotL, which is the same value
    float VdotH = max(dot(viewDir, halfVector), 0.0);

    float specular = 0.0;
    if(NdotL > 0.0)
    {
        float G = GeometricalAttenuation(NdotH, NdotV, VdotH, NdotL);
        float D = BeckmannDistribution(roughness, NdotH);
        float F = Fresnel(F0, VdotH);

        specular = (D * F * G) / (NdotV * NdotL * 4);
    }
    return specular;
}

vec3 CalcLight(vec3 lightColor, vec3 normal, vec3 lightDir, vec3 viewDir, Material material, float shadowFactor)
{
    // Helper variables
    vec3  baseColor = material.diffuse;
    vec3  specColor = material.specular;
    vec3  emissive  = material.emissive;
    float roughness = material.roughness;
    float fresnel   = material.fresnel;
    float metallic  = material.metallic;

    // Calculate contribution based on metallicity
    vec3 diffuseColor  = baseColor - baseColor * metallic;
    vec3 specularColor = mix(vec3(0.00), baseColor, metallic);

    // Lambertian reflectance
    float Kd = DiffuseLambert(normal, lightDir);

    // Specular shading (Cook-Torrance model)
    float Ks = CalcCookTorSpec(normal, lightDir, viewDir, roughness, fresnel);

    // Combine results
    vec3 diffuse  = diffuseColor * Kd;
    vec3 specular = specularColor * Ks;
    vec3 result   = lightColor * (emissive + diffuse + specular);
    return result * (1.0 - shadowFactor);
}
emackey
  • 11,818
  • 2
  • 38
  • 58
TheCrafter
  • 1,909
  • 2
  • 23
  • 44

1 Answers1

1

What you are looking for is the bidirectional reflectance distribution function (BRDF) for a material. In your code you reference the "Cook - Torrance model" which is a common and effective (but also computationally expensive) BRDF. It seems like you might be getting ideas from both "metallic/roughness" model and the "specular/glossiness" model. This is a huge topic but understanding the two might help.

Anyway, in a physically based shading model the BRDF must conserve energy. Therefore the contribution of diffuse + specular must not exceed 1 or:

Kd = 1 - Ks

The physical accuracy of your shaders are dependent on the computations you perform on the material properties, but in your case you can incorporate the metallic term into the BRDF like this:

BRDF = (1-m)*diffuse + m*specular

From here you can handle the lighting etc.

-- Metalness/Roughness shader origins

Disney came up with a shader method that was more realistic. UnrealEngine4 implemented this model-ish and now there is a lot of confusion around terminology and texture workflow.

UE4 BRDF code - signup required

Disney's BRDF

Basic Background

Tara
  • 1,673
  • 22
  • 30
adon
  • 168
  • 1
  • 4
  • Yeah but still, how should I blend the diffuse contribution from Lambert with specular from CookTorrance based on material's roughness and metallicity? – TheCrafter Apr 09 '16 at 08:12
  • I edited the answer to hopefully provide better info. – adon Apr 11 '16 at 15:38
  • I think I'm starting to get it. What do you mean when you're saying "It seems like you might be getting ideas from both 'metallic/roughness' model and the 'specular/glossiness' model." ? – TheCrafter Apr 11 '16 at 16:51
  • It's a big topic, that may or may not affect how you are writing your engine. I added a bit more info – adon Apr 11 '16 at 20:13
  • So a pure metal will have literally 0% diffuse reflection? It will be black pixels when not aligned with NdotH slash RdotV? –  Oct 06 '16 at 02:18
  • @racarate No, it won't, since in that case, you'll have specular reflections only. And those aren't much affected by fresnel for metals. – Tara Jan 10 '17 at 11:23
  • All links are dead, I am looking for answers to this question... Could you update the links in your answer ? – Tab Nov 15 '20 at 16:33