1
votes

I am trying to implement the Cook-Torrance lighting mode with four point lights. While I am getting nice results by using just only one point light, I can't understand which is the correct way to sum up the specular term inside my light loop.

I am defining the materials as follows:

struct material {
    vec3 ambient; /* ambient color */
    vec3 diffuse; /* diffuse color */
    vec3 specular; /* speculr color */
    float metallic;
    float roughness;
};

...whereby my lights only have one color/intensity property,

struct light {
    vec3 position;
    vec3 color;
    bool enabled;
};

Here is the function inside my fragment shader with the fragment color computation:

vec3 lighting() {
    vec3 color = vec3(0.0,0.0,0.0);
    float r0 = pow(material.metallic - 1.0,2.0)/pow(material.metallic + 1.0,2.0);
    vec3 V = normalize(-v_viewpos);
    vec3 N = normalize(v_normal);
    for (int i = 0; i < 4; i++) {
        if (light[i].enabled) {
            vec3 L = normalize(light[i].position - v_viewpos);
            // Half-way vector 
            vec3 halfVector = normalize(L + V);
            float NdotL = max(dot(N, L),0.0);
            float NdotV = max(dot(N, V),0.0);
            if (NdotL > 0.001 && NdotV > 0.001) {
                float NdotH = max(0.0, dot(N, halfVector));
                float HdotV = max(0.0, dot(halfVector, V));
                // Beckmann 
                float tanAlpha = sqrt(1.0-NdotH*NdotH)/NdotH;
                float D = exp(-pow(tanAlpha/material.roughness,2.0))/(4.0*pow(material.roughness,2.0)*pow(NdotH,4.0));
                // Shadowing-masking term 
                float G1 = (2.0 * NdotH * NdotV) / HdotV;
                float G2 = (2.0 * NdotH * NdotL) / HdotV;
                float G = min(1.0, min(G1, G2));
                // Fresnel reflection, Schlick approximation 
                float F = r0 + (1.0 - r0) * pow(1.0 - NdotL, 5.0);
                float R = (F*G*D) / (3.14159 * NdotL * NdotV);
                color += light[i].color * R * NdotL;
            }
            color += material.diffuse * light[i].color;
        }
    }
    return color;
}

I believe the key point here is my wrong computation inside the light loop:

color += light[i].color * R * NdotL;

Here is an example of what I mean, the resulting fragment color is either too dark, or too bright. I am not able to sum up each light contribution to get a nice smooth color gradient among the specular term and the material colors.

enter image description here

I am reading here about gamma correction, but I can't understand if this applies to my question or not.

How should I sum up each light.color with the diffuse, ambient and specular colors of the material, to calculate the final fragment color, by correctly including the total amount of specular highlight contribution of each light?

2

2 Answers

1
votes
  • vec3 V should be the normalized vector starting from the fragment position to the camera position.
  • vec3 L should be the normalized vector starting from the fragment position to the light position.

One of those vectors is wrong in your shader, depending on the actual value of v_viewpos.


The fresnel should be based on HoV not NoL: pow(1.0 - HoV, 5.0)


For the diffuse part, you consider your light like ambiant light and not point light.

color += material.diffuse * light[i].color;

should be (for simple Lambertian)

color += material.diffuse * light[i].color * NoL;

1
votes

Most of your computations look good (including the directions of V and L and the Fresnel term). The only thing is that you might have mixed up how to combine the individual lighting components. For specular and diffuse, you have

color += light[i].color * R * NdotL;

R corresponds to the specular part and NdotL to the diffuse part. Both are additive, however. Therefore, the equation should be (plus considering material parameters):

color += light[i].color * (material.specular * R + material.diffuse * NdotL);

For the ambient term you have

color += material.diffuse * light[i].color;

Replace material.diffuse with material.ambient and this should be correct.

And be sure that your lights are not too bright. The screen cannot display anything brighter than white (or fully saturated red).