OpenGL - How to properly add lighting to a scene using the Blinn-Phong shading model?

693 Views Asked by At

Recently, I have been trying to add lighting to a simple OpenGL scene using the Blinn-Phong shading model as described in this website.

I tried to follow the tutorial as closely as possible. However, the lighting seems off, especially on the side faces of the cube as the light source begins to move across the front.

I believe it would have something to do with the positions of the Normals not being in the right place due to rotation on the model matrix or having done something wrong in the lighting shader, however, I am not sure whether either of those is really the cause.

Here is the source code, by the way:

#include <glad/glad.h>

#include <SFML/Graphics.hpp>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <iostream>
#include <cstdlib>
#include <cmath>

// Vertex shader for the light source cube
const std::string source_vert_shader = R"(
    #version 330 core

    layout (location = 0) in vec3 vertPos;

    uniform mat4 proj, view, model;

    void main() {
        gl_Position = proj * view * model * vec4(vertPos, 1);
    }
)";

// Fragment shader for the light source cube
const std::string source_frag_shader = R"(
    #version 330 core

    out vec4 FragColor;

    void main() {
        FragColor = vec4(1);
    }
)";

// Vertex shader for the cube
const std::string cube_vert_shader = R"(
    #version 330 core

    layout (location = 0) in vec3 vertPos;
    layout (location = 1) in vec3 vertNorm;

    uniform mat4 proj, view, model;

    out vec3 fragPos;
    out vec3 interNorm;

    void main() {
        fragPos = vec3(model * vec4(vertPos, 1));
        gl_Position = proj * view * vec4(fragPos, 1);
        interNorm = mat3(transpose(inverse(model))) * vertNorm;
    }
)";

// Fragment shader for the cube
const std::string cube_frag_shader = R"(
    #version 330 core

    in vec3 fragPos;
    in vec3 interNorm;

    out vec4 FragColor;

    uniform vec3 viewPos;
    uniform vec3 lightPos;
    uniform vec3 objectColor;

    const float pi = 3.14159265;
    const float shininess = 16;

    void main() {
        vec3 normal = normalize(interNorm);
        vec3 lightDir = normalize(lightPos - fragPos);
        float dist = length(lightPos - fragPos);
        float attenuation = 1 / (dist * dist);

        // Ambient light effect
        const float ambientStrength = 0.05;
        vec3 ambient = ambientStrength * objectColor;

        // Diffuse light effect
        float diff = max(dot(normal, lightDir), 0);
        vec3 diffuse = attenuation * diff * objectColor;

        // Specular light effect
        vec3 specular = vec3(0);
        if (diff != 0) {
            const float energy_conservation = (8 + shininess) / (8 * pi);
            vec3 viewDir = normalize(viewPos - fragPos);
            vec3 halfwayDir = normalize(lightDir + viewDir);
            float spec = energy_conservation * pow(max(dot(normal, halfwayDir), 0), shininess);
            specular = attenuation * spec * vec3(0.3);
        }

        const float gamma = 2.2;

        // Apply the different lighting techniques of the Phong shading model and finally apply gamma correction
        FragColor = vec4(pow(ambient + diffuse + specular, vec3(1 / gamma)), 1);
    }
)";

int main() {
    // Initialize the window
    sf::RenderWindow window(
        sf::VideoMode(1365, 768), "Lighting", sf::Style::Default,
        sf::ContextSettings(24, 8, 4, 3, 3, sf::ContextSettings::Core, true));

    // Initialize OpenGL functions
    gladLoadGLLoader(reinterpret_cast<GLADloadproc>(sf::Context::getFunction));

    // Specify the viewport of the scene
    glViewport(0, 0, window.getSize().x, window.getSize().y);

    // Enable depth testing
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    // Enable blending
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Load the shaders into the application
    sf::Shader shader, source_shader;
    (void)shader.loadFromMemory(cube_vert_shader, cube_frag_shader);
    (void)source_shader.loadFromMemory(source_vert_shader, source_frag_shader);

    // Define the vertices of the cube and the light source cube
    float vertices[] = {
        // Vertices           Normals
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
         0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
         0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
    };

    // Attach the vertices in the vertices array to the VAO and the VBO
    GLuint vao, vbo;

    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);

    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), nullptr);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), reinterpret_cast<void*>(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    // The same VBO can be used to render the light source cube
    GLuint source_vao;

    glGenVertexArrays(1, &source_vao);
    glBindVertexArray(source_vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), nullptr);
    glEnableVertexAttribArray(0);

    // Projection matrix
    auto proj = glm::perspective(glm::radians(45.0f), static_cast<GLfloat>(window.getSize().x) / window.getSize().y, 0.1f, 100.0f);

    glm::vec3 view_pos(0.0f, 0.0f, -5.0f);

    // View/camera matrix
    glm::mat4 view(1.0f);
    view = glm::translate(view, view_pos);
    view = glm::rotate(view, glm::radians(45.0f), glm::vec3(1.0f, 1.0f, 1.0f));

    // Model matrix
    glm::mat4 model(1.0f);
    //model = glm::rotate(model, glm::radians(45.0f), glm::vec3(1.0f, 1.0f, 0.0f));

    // For the cube in the center
    shader.setUniform("proj", sf::Glsl::Mat4(glm::value_ptr(proj)));
    shader.setUniform("view", sf::Glsl::Mat4(glm::value_ptr(view)));
    shader.setUniform("model", sf::Glsl::Mat4(glm::value_ptr(model)));
    shader.setUniform("viewPos", sf::Glsl::Vec3(view_pos.x, view_pos.y, view_pos.z));
    shader.setUniform("objectColor", sf::Glsl::Vec3(1.0f, 0.3f, 1.0f));

    // For the light source cube
    source_shader.setUniform("proj", sf::Glsl::Mat4(glm::value_ptr(proj)));
    source_shader.setUniform("view", sf::Glsl::Mat4(glm::value_ptr(view)));

    sf::Clock clock;
    sf::Event evt{};
    while (window.isOpen()) {
        while (window.pollEvent(evt)) {
            if (evt.type == sf::Event::Closed) {
                // When window is closed, destroy the VAO and the VBO
                glDeleteBuffers(1, &vbo);
                glDeleteVertexArrays(1, &vao);
                window.close();
            }
            if (evt.type == sf::Event::Resized)
                // Update the viewport as the window is resized
                glViewport(0, 0, evt.size.width, evt.size.height);
        }
        // Clear the screen with a color
        glClearColor(0.8f, 0.2f, 0.6f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Calculate an angular factor based on the elapsed time
        auto const angular_factor = glm::radians(45.0f) * clock.getElapsedTime().asSeconds();

        sf::Shader::bind(&shader);
        // Makes the light source move in circles around the cube in the center
        glm::vec3 light_pos(
            6.0f * std::sin(angular_factor),
            0.0f,
            6.0f * std::cos(angular_factor)
        );
        shader.setUniform("lightPos", sf::Glsl::Vec3(light_pos.x, light_pos.y, light_pos.z));
        // Draw the cube
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 36);

        sf::Shader::bind(&source_shader);
        model = glm::identity<glm::mat4>();
        model = glm::scale(model, glm::vec3(0.3f, 0.3f, 0.3f));
        model = glm::translate(model, light_pos);
        source_shader.setUniform("model", sf::Glsl::Mat4(glm::value_ptr(model)));
        // Draw the light source cube
        glBindVertexArray(source_vao);
        glDrawArrays(GL_TRIANGLES, 0, 36);

        sf::Shader::bind(nullptr);

        // Swap the window's buffers
        window.display();
    }
}
0

There are 0 best solutions below