Diffuse Lighting Using OpenGL

Diffuse lighting gives the object more brightness the closer its fragments are aligned to the light rays from a light source.

To the left, we find a light source with a light ray targeted at a single fragment of our object. We need to measure at what angle the light ray touches the fragment. To measure the angle between the light ray and the fragment we use something called a normal vector, that is a vector perpendicular to the fragment's surface (here depicted as a yellow arrow). The angle between the two vectors can then easily be calculated with the dot product. The resulting dot product thus returns a scalar that we can use to calculate the light's impact on the fragment's color, resulting in differently lit fragments based on their orientation towards the light.

So to calculate Diffuse Lighting, we need to:

  1. Find normal vector

  2. Calculate Diffuse Color

Normal Vectors

We can use a little trick to calculate the normal vectors for all the cube's vertices by using the cross product, but since a 3D cube is not a complicated shape we can simply manually add them to the vertex data. Try to visualize that the normals are indeed vectors perpendicular to each plane's surface (a cube consists of 6 planes).
Since we added extra data to the vertex array we should update the cube's vertex shader:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
...

Now that we added a normal vector to each of the vertices and updated the vertex shader. We should update the vertex attribute pointers as well. Note that the light source's cube uses the same vertex array for its vertex data, but the lamp shader has no use of the newly added normal vectors. We don't have to update the lamp's shaders or attribute configurations, but we have to at least modify the vertex attribute pointers to reflect the new vertex array's size:


glVertexAttribPointer(0,     
                      3, 
                      GL_FLOAT, 
                      GL_FALSE, 
                      6 * sizeof(float), 
                      (void*)0);
glEnableVertexAttribArray(0);

All the lighting calculations are done in the fragment shader so we need to forward the normal vectors from the vertex shader to the fragment shader. Let's do that:

out vec3 Normal;  
void main() {     
    gl_Position = projection * view * model * vec4(aPos, 1.0);  
    Normal = aNormal; 
}

Declaring the corresponding input variable in the fragment shader:

in vec3 Normal;

Calculating the Diffuse Color

We now have the normal vector for each vertex, but we still need the light's position vector and the fragment's position vector. Since the light's position is a single static variable we can declare it as uniform in the fragment shader:

uniform vec3 lightPos;

Then update the uniform. Here lightPos is the vector which is the location of the diffuse light source.

lightingShader.setVec3("lightPos", lightPos);

Then the last thing we need is the actual fragment's position. We're going to do all the lighting calculations in world space so we want a vertex position that is in world space first. We can accomplish this by multiplying the vertex position attribute with the model matrix only (not the view and projection matrix) to transform it to world space coordinates. This can easily be accomplished in the vertex shader so let's declare an output variable and calculate its world space coordinates:

out vec3 FragPos;  
out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = aNormal;
}

and then add corresponding input variable to the fragment shader.

in vec3 FragPos;

Now that all the required variables are set we can start the lighting calculations.

The first thing we need to calculate is the direction vector between the light source and the fragment's position, which is equal to the difference vector between the light's position vector and the fragment's position vector. We also want to make sure all the relevant vectors end up as unit vectors so we normalize both the normal and the resulting direction vector.

vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

Next, we calculate the diffuse impact of the light on the current fragment by taking the dot product between the norm and lightDir vectors. The resulting value is then multiplied by the light's color to get the diffuse component.

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

Now that we have both an ambient and a diffuse component we add both colors to each other and then multiply the result with the color of the object to get the resulting fragment's output color:

vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);

That should be enough and should give an output.