I use the following formula to calculate PBR color for each light source. For the sake of this post, let's assume that there is only one directional light. I am using Vulkan; so, +Y is "down":

// we pass tangents; so, TBN matrix is calculated
// at vertex shader and passed here
vec3 getNormal() {
  if (materialData.normalTexture >= 0) {
    vec3 n = texture(tex[materialData.normalTexture],
                 .rgb *
    return normalize(tbn * n);
  } else {
    // tbn[2] = transpose(inverse(modelMatrix)) * normalInInputVertexData;
    return normalize(tbn[2]);

// all light properties are coming from uniform
vec3 lightDirection = vec3(0.0f, 1.0, 0.0); // light looks directly down at the scene
vec4 lightColor = vec4(1.0, 1.0, 1.0, 1.0);
float lightIntensity = 1.0;

float shadow = 1.0; // shadow disabled to keep it simple
vec3 normal = getNormal();

float NdotL = clamp(dot(normal, normalize(lightDirection)), 0.0, 1.0);
// ^ This formula is problematic

vec3 F = schlickFresnel(f0, VdotH);
float G = schlickSpecularGeometricAttenuation(alpha, NdotV, NdotL);
float D = ggxNormalDistribution(alpha, NdotH);

vec3 diffuseBRDF = (vec3(1.0) - F) * (1 / PI) * diffuseColor;
vec3 specularBRDF = F * D * G / (4 * NdotL * NdotV);

color = vec3(lightColor) * lightIntensity * shadow * NdotL *
             (diffuseBRDF + specularBRDF);

// emissive and occlusion maps are disabled to keep it simple 

Here is an object that I drew in Blender and tried to render with my shader:

Too dark front face of thehouse

As you can see, the front of the house is too dark. So, I did some digging and found out that the dot product of normal and light direction is zero in all the places around the house. So, I tried to debug the normals:

Debugging normals

As you can see, the front of the house is full blue color since it is looking in +Z axis.

So, normal = vec3(0.0, 0.0, 1.0) for the front of the house. Because of that

dot(normal, lightDirection) = 0.0 * 0.0 + 0.0 * 1.0 + 1.0 * 0.0 = 0.0

Because of that NdotL will always be zero for any face that is perpendicular to light direction. However, this looks very unrealistic. Directional lights should represent lights that are coming from sun. If there is a light coming from sun, even if a place that is technically unilluminated, it should still have some light casting to it to give a sense of realism.

Am I doing something wrong in my shader that I get these types of unrealistic results? Is there some kind of a technique that can be used to cast lights in places where cosine law is zero?

EDIT: Updated post with new screenshots.

  • 2
    \$\begingroup\$ you usually have a ambient term in your lighting. you simply add it as a constant to your lighting formula. This can be a simple color or a complex technique like light probes. For the time being, you may use a simple constant value \$\endgroup\$
    – Raildex
    Aug 21 at 6:56
  • \$\begingroup\$ Thank you for the simple explanation. I didn't know how to solve this issue as I did not know what to search for. Searching ambient lighting took me to deep into sinkhole of Global Illumination :) One question, do ambient occlusion maps work with Ambient Lights or are they a different method of calculating global illumination. \$\endgroup\$
    – Gasim
    Aug 21 at 9:19
  • 1
    \$\begingroup\$ ambient occlusion is a technique to fake global illumination. the result of the AO is often multiplied with the ambient light term. \$\endgroup\$
    – Raildex
    Aug 21 at 9:55

Your Answer

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

Browse other questions tagged or ask your own question.