Search code examples
c++windowsopenglglfwlighting

Problem with specular lightning artifacts in OpenGL


The problem is specifically when i look at the corners of the room. When i do that the specular lightning gets brighter:

looking to a corner

...and when i look at a flat wall doesn't:

looking straight to a wall

Here i post the entire code:

#include <windows.h>
#include <iostream>
#include <fstream>
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm>

#define GLEW_STATIC
#include <glew.h>
#include <GL/glfw.h>

template <typename T>
T clamp(T value, T min, T max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

// CAMERA PROPERTIES
float posX = 0.0f, posY = 0.0f, posZ = 5.0f; // position
float rotX = 0.0f, rotY = 3.0f;              // rotation

// movement speed and mouse sensitivity
float speed = 0.05f;
const float mouseSensitivity = 0.005f;

// MOUSE INPUT TRACKING
double lastMouseX, lastMouseY;

// ROOM BOUNDARIES
float x_min = -5.0f, x_max = 5.0f;
float z_min = -5.0f, z_max = 5.0f;
float collision = 0.2f;

void checkCollision(float &posX, float &posZ) {
    if (posX < x_min + collision) posX = x_min + collision; // left
    if (posX > x_max - collision) posX = x_max - collision; // right
    if (posZ < z_min + collision) posZ = z_min + collision; // back
    if (posZ > z_max - collision) posZ = z_max - collision; // front
}

void drawLightMarker() {
    glDisable(GL_LIGHTING);
    glColor3f(1.0f, 1.0f, 0.0f);
    glPushMatrix();
    glTranslatef(0.0f, 0.5f, 0.0f);

    glBegin(GL_QUADS);

    // FRONT FACE
    glVertex3f(-0.1f, -0.1f, 0.1f);
    glVertex3f(0.1f, -0.1f, 0.1f);
    glVertex3f(0.1f, 0.1f, 0.1f);
    glVertex3f(-0.1f, 0.1f, 0.1f);

    // BACK FACE
    glVertex3f(-0.1f, -0.1f, -0.1f);
    glVertex3f(0.1f, -0.1f, -0.1f);
    glVertex3f(0.1f, 0.1f, -0.1f);
    glVertex3f(-0.1f, 0.1f, -0.1f);

    // LEFT FACE
    glVertex3f(-0.1f, -0.1f, -0.1f);
    glVertex3f(-0.1f, -0.1f, 0.1f);
    glVertex3f(-0.1f, 0.1f, 0.1f);
    glVertex3f(-0.1f, 0.1f, -0.1f);

    // RIGHT FACE
    glVertex3f(0.1f, -0.1f, -0.1f);
    glVertex3f(0.1f, -0.1f, 0.1f);
    glVertex3f(0.1f, 0.1f, 0.1f);
    glVertex3f(0.1f, 0.1f, -0.1f);

    // TOP FACE
    glVertex3f(-0.1f, 0.1f, 0.1f);
    glVertex3f(0.1f, 0.1f, 0.1f);
    glVertex3f(0.1f, 0.1f, 0.1f);
    glVertex3f(-0.1f, 0.1f, 0.1f);

    // BOTTOM FACE
    glVertex3f(-0.1f, -0.1f, -0.1f);
    glVertex3f(0.1f, -0.1f, -0.1f);
    glVertex3f(0.1f, -0.1f, 0.1f);
    glVertex3f(-0.1f, -0.1f, 0.1f);

    glEnd();
    glPopMatrix();
    glEnable(GL_LIGHTING);
}

// ********TO FIX********
void setupLightning() {
    // PROPERTIES
    GLfloat ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};
    GLfloat diffuse[] = {2.0f, 2.0f, 2.0f, 1.0f};
    GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
    GLfloat mat_specular[] = {0.5f, 0.5f, 0.5f, 1.0f};
    GLfloat shininess[] = {20.0f};

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

    glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, shininess);

    // ATTENUATION
    glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.5f);
    glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.1f);
    glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.01f);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_NORMALIZE);
}

void processInput(GLFWwindow *window) {
    // FORWARD/BACKWARD MOVEMENT
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
        posX += speed * sin(rotY);
        posZ += speed * cos(rotY);
    }

    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
        posX -= speed * sin(rotY);
        posZ -= speed * cos(rotY);
    }

    // LEFT/RIGHT STRAFING
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
        posX += speed * cos(rotY);
        posZ -= speed * sin(rotY);
    }

    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
        posX -= speed * cos(rotY);
        posZ += speed * sin(rotY);
    }

    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwDestroyWindow(window);
        glfwTerminate();
    }

    if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) speed = 0.5f;

    if (glfwGetKey(window, GLFW_KEY_BACKSPACE) == GLFW_PRESS) speed = 0.05f;
    //checkCollision(posX, posZ);
}

void mouseCallback(GLFWwindow* window, double xpos, double ypos) {
    // calculate mouse delta
    double deltaX = xpos - lastMouseX;
    double deltaY = ypos - lastMouseY;
    lastMouseX = xpos;
    lastMouseY = ypos;

    // UPDATE ROTATION
    rotY -= deltaX * mouseSensitivity; // Horizontal rotation
    rotX -= deltaY * mouseSensitivity; // Vertical rotation

    rotX = clamp(rotX, -1.5f, 1.5f);
}

void renderRoom() {
    glBegin(GL_QUADS);

    // FLOOR
    glColor3f(0.5f, 0.5f, 0.5f);
    glNormal3f(0.0f, 1.0f, 0.0f);

    glVertex3f(-5.0f, -1.0f, -5.0f);
    glVertex3f(5.0f, -1.0f, -5.0f);
    glVertex3f(5.0f, -1.0f, 5.0f);
    glVertex3f(-5.0f, -1.0f, 5.0f);

    // CEILING
    glColor3f(0.3f, 0.3f, 0.3f);
    glNormal3f(0.0f, -1.0f, 0.0f);

    glVertex3f(-5.0f, 1.0f, -5.0f);
    glVertex3f(5.0f, 1.0f, -5.0f);
    glVertex3f(5.0f, 1.0f, 5.0f);
    glVertex3f(-5.0f, 1.0f, 5.0f);

    // WALLS

    // back wall
    glColor3f(0.8f, 0.2f, 0.2f);
    glNormal3f(0.0f, 0.0f, 1.0f);
    glVertex3f(-5.0f, -1.0f, -5.0f);
    glVertex3f(5.0f, -1.0f, -5.0f);
    glVertex3f(5.0f, 1.0f, -5.0f);
    glVertex3f(-5.0f, 1.0f, -5.0f);

    // front wall
    glNormal3f(0.0f, 0.0f, -1.0f);
    glVertex3f(-5.0f, -1.0f, 5.0f);
    glVertex3f(5.0f, -1.0f, 5.0f);
    glVertex3f(5.0f, 1.0f, 5.0f);
    glVertex3f(-5.0f, 1.0f, 5.0f);

    // left wall
    glNormal3f(1.0f, 0.0f, 0.0f);
    glVertex3f(-5.0f, -1.0f, -5.0f);
    glVertex3f(-5.0f, -1.0f, 5.0f);
    glVertex3f(-5.0f, 1.0f, 5.0f);
    glVertex3f(-5.0f, 1.0f, -5.0f);

    // right wall
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glVertex3f(5.0f, -1.0f, -5.0f);
    glVertex3f(5.0f, -1.0f, 5.0f);
    glVertex3f(5.0f, 1.0f, 5.0f);
    glVertex3f(5.0f, 1.0f, -5.0f);

    glEnd();
}

GLFWwindow* initWindowFullScreen() {
    // INITIALIZE GLFW
    if (!glfwInit()) std::cerr << "Failed to initialize GLFW" << std::endl;

    GLFWmonitor* monitor = glfwGetPrimaryMonitor();
    if (!monitor) {
        glfwTerminate();
        std::cerr << "Failed to get primary monitor" << std::endl;
    }

    const GLFWvidmode* mode = glfwGetVideoMode(monitor);

    GLFWwindow* window = glfwCreateWindow(mode->width, mode->height, "3D Room full-screen", monitor, NULL);
    if (!window) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    glfwMakeContextCurrent(window);

    return window;
}

int main() {
    GLFWwindow* window = initWindowFullScreen();

    // MOUSE CALLBACK
    glfwSetCursorPosCallback(window, mouseCallback);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    glfwGetCursorPos(window, &lastMouseX, &lastMouseY);

    setupLightning();

    // MAIN LOOP
    while (!glfwWindowShouldClose(window)) {
        processInput(window);

        // RENDERING
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(70.0, 800.0 / 600.0, 0.1, 100.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        // APPLY CAMERA TRANSFORMATIONS
        float lookX = posX + sin(rotY) * cos(rotX);
        float lookY = posY + sin(rotX);
        float lookZ = posZ + cos(rotY) * cos(rotX);
        gluLookAt(posX, posY, posZ, lookX, lookY, lookZ, 0.0f, 1.0f, 0.0f);

        GLfloat lightPos[] = {0.0f, 0.5f, 0.0f, 1.0f};
        glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

        renderRoom();
        drawLightMarker();

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}

I tried to modify the specular, the ambient, diffuse lightning but nothing.


Solution

  • In the smooth shading mode which you use, fragments of triangle are interpolated based on properties of vertices.

    So, suppose you have a triangle:

    v0 --- v1
    |   /
    v2
    

    now specular contribution of lighting effect is computed per v0, v1, v2. When triangle is being rendered, all area which fills the triangle is interpolated. To see specular contribution, light ray needs to hit some vertices v0, v1, v2. In your scene it is hard to be satisfied because position of light is (0,0,0) and each face of box has 4 vertices where length of side is 10.

    --------------- seen from top along Y axis towards negative values
    |
    |
    |     (L)        -> L is position of light
    |
    |
    (Y)------------
    

    Y is position of camera. Specular effect is based on angle between view direction and reflection vector when light ray hits box's face. Taking left face of box as seen above, you need some more vertices along the distance between L and Y.

    Fixed

    The view above was created when floor is rendered as 100 quads in range [-5,5] in both X and Z axis. So dx and dy offsets are 1. And left face is rendered with dz = 1 and dy = 0.2 offsets (it is also 100 quads).

    So as conclusion, you need to make geometry denser, to see lighting effects. On the floor specular effect is visible. On left face we see diffuse effect.

    The code for generating the floor:

    // FLOOR
    glColor3f(0.5f, 0.5f, 0.5f);
    glNormal3f(0.0f, 1.0f, 0.0f);
    
    for (float z = -5; z < 5; z += 1)
        for (float x = -5; x < 5; x += 1)
        {
            glVertex3f(x, -1.0f, z);
            glVertex3f(x+1, -1.0f, z);
            glVertex3f(x+1, -1.0f, z+1);
            glVertex3f(x, -1.0f, z+1);
        }