OpenGL implements three light sources

The previous article implemented a simple lighting model, and the lighting used came from a point in space.

But in the real world, we have many types of lighting, such as:
1. Directional Light
2.Point Light
3. Spotlight

The purpose of this article is to implement these light sources respectively.

1. Directional light (also called parallel light)
Example: sunlight

When a light source is far away, each ray from the light source will be approximately parallel to each other.
Because all light rays are parallel, the relative position of the object to the light source is unimportant
You can define a light direction vector instead of a position vector to simulate a directional light

data structure

struct DirLight {<!-- -->
    vec3 direction; //direction

    vec3 ambient; //ambient intensity
    vec3 diffuse; //diffuse intensity
    vec3 specular; //specular intensity
};

Lighting formula

//Calculate the color of parallel light
vec3 CalcDirLight(DirLight light)
{<!-- -->
//Texture
    vec3 diffuseCol = texture(material.diffuse, uv).rgb;
    vec3 specularCol = texture(material.specular, uv).rgb;

    //Ambient light
    vec3 ambient = light.ambient * diffuseCol;

    //diffuse reflection
    vec3 lightDir = (light.direction);
    vec3 normalDir = normalize(normalWS);
    vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;

    //High light reflection
    vec3 viewDir = normalize(cameraPos - positionWS);
    vec3 halfDir = normalize(viewDir + lightDir);
    vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;

    vec3 result = ambient + diffuse + specular;
    return result;
}

2. Point light source
Example: light bulb
Points scattered in the scene will emit light in all directions, gradually attenuating with distance.

Attenuation: Gradually reducing the intensity of light as the distance it travels is usually called attenuation.
Attenuation formula: 1 / (constant term + linear term * distance + quadratic term + distance squared)

Generally, it is set according to the distance you want to cover. You can look up the table:
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=-Point + Light + Attenuation

data structure

struct PointLight{<!-- -->
    vec3 position; //position

    vec3 ambient; //ambient intensity
    vec3 diffuse; //diffuse intensity
    vec3 specular; //specular intensity

    float constant; //Attenuation public constant term
    float linear; //attenuation formula linear term
    float quadratic;//attenuation formula quadratic term

};

Lighting formula

//Calculate point light color
vec3 CalcPointLight(PointLight light)
{<!-- -->
    //Calculate the distance from the light source
    float distance = length(light.position - positionWS);

    //Attenuation formula
    float attenuation = 1.0 /
                            (light.constant
                             + light.linear * distance
                             + light.quadratic * (distance * distance) );

//Texture
    vec3 diffuseCol = texture(material.diffuse, uv).rgb;
    vec3 specularCol = texture(material.specular, uv).rgb;

    //Ambient light
    vec3 ambient = light.ambient * diffuseCol;

    //diffuse reflection
    vec3 lightDir = normalize(light.position - positionWS);
    vec3 normalDir = normalize(normalWS);
    vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;

    //High light reflection
    vec3 viewDir = normalize(cameraPos - positionWS);
    vec3 halfDir = normalize(viewDir + lightDir);
    vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;

    vec3 result = (ambient + diffuse + specular) * attenuation;
    return result;
}

3. Spotlight
A light source located somewhere in the environment that emits light in only one specific direction rather than in all directions
Represented by a world space position, a direction and a cutoff angle (Cutoff Angle)

Principle: Calculate the dot product between the LightDir vector and the SpotDir vector, and compare it with the light cut angle value

data structure

struct SpotLight {<!-- -->
    vec3 position;
    vec3 direction; //direction

    vec3 ambient; //ambient intensity
    vec3 diffuse; //diffuse intensity
    vec3 specular; //specular intensity

    float cutOff; //The light cutting angle of the radius. Objects outside this angle will not be illuminated by this spotlight.
    float outerCutOff;//larger light cutting angle, used for gradients
};

Calculate lighting

//Calculate spotlight color
vec3 CalcSpotLight(SpotLight light)
{<!-- -->
    //Point in the direction of the spotlight
    vec3 lightDir = normalize(light.position - positionWS);

    //Do dot multiplication with the direction of the light source (reverse to ensure the direction is consistent)
    float theta = dot(-lightDir, normalize(light.direction));
    
    //Cosine value comparison for performance
    //The closer the cosine value is to 1.0, the smaller its angle is
    if (theta > light.cutOff)
    {<!-- -->
//Texture
        vec3 diffuseCol = texture(material.diffuse, uv).rgb;
        vec3 specularCol = texture(material.specular, uv).rgb;

        //Ambient light
        vec3 ambient = light.ambient * diffuseCol;

        //diffuse reflection
        vec3 normalDir = normalize(normalWS);
        vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;

        //High light reflection
        vec3 viewDir = normalize(cameraPos - positionWS);
        vec3 halfDir = normalize(viewDir + lightDir);
        vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;

        vec3 result = (ambient + diffuse + specular);

        //To create a spotlight that looks smooth around the edges
        // It is necessary to simulate the spotlight with an inner cone (Inner Cone) and an outer cone (Outer Cone)
        float intensity = clamp((theta - light.cutOff) / (light.cutOff - light.outerCutOff), 0.0, 1.0);
        result *= intensity;

        return vec3(result);
    }
    else
    {<!-- -->
        //Not within the area, currently black
        return vec3(0.0);
    }
}

Declare these in a shader to superimpose all lights

void main()
{<!-- -->
    vec3 dirLightResult = CalcDirLight(dirLight);

    vec3 result = dirLightResult;
    for(int i = 0; i < NR_POINT_LIGHTS; i + + )
    {<!-- -->
        vec3 pointLightResult = CalcPointLight(pointLights[i]);
        result + = pointLightResult;
    }
    
    vec3 spotLightResult = CalcSpotLight(spotLight);
    result + = spotLightResult;

    FragColor = vec4(result * objectColor, 1.0);
}

The effect is as shown in the figure: