Search code examples
copenglglut

OpenGL: How to make light stationary when moving the camera around?


I'm trying to render a simple (10x3x10 in size) room using OpenGL and GLUT, written in C.

I'd like to create a simple statinary spotlight, while i can move around in the room.

(The ultimate goal is to make a spotlight coming from a lamp at the ceiling)

The problem is the light changes when i move the camera around.

I'm calling the following function from main():

void init() {
    glEnable(GL_TEXTURE_2D);

    loadTexture(); // loading some textures using SOIL
    loadModels(); // loading some OBJ models

    glShadeModel(GL_SMOOTH);
    glEnable(GL_NORMALIZE);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glClearDepth(1);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LEQUAL);
    glClearColor(0, 0, 0, 1);
}

The function i pass to glutDisplayFunc():

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    initLight();

    gluLookAt(x, 1.5f, z, x + vX, 1.5f, z + vZ, 0.0f, 1.5f, 0.0f);

    // ...
    // drawing the ground, ceiling and the walls
    // no matrix transformations here, just simple glVertex () calls
    // ...

    glPushMatrix();

    // drawing a table
    glTranslatef(5, 0.842843, 5);
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    draw_model(&modelTable);

    // drawing some chairs
    glTranslatef(0, -0.312053, chair1Position);
    glBindTexture(GL_TEXTURE_2D, texture[2]);
    draw_model(&modelChair1);
    glTranslatef(0, 0, chair2Position);
    draw_model(&modelChair2);

    glPopMatrix();

    glutSwapBuffers();
}

And the initLight() function:

void initLight() {

    // i would like the light to originate from an upper corner
    // directed to the opposing lower corner (across the room basically)

    GLfloat lightPosition[] = { 10, 3, 0, 1 };
    GLfloat lightDirection[] = { 0, 0, 10, 0 };
    GLfloat ambientLight[] = { 0.3, 0.3, 0.3, 1 };
    GLfloat diffuseLight[] = { 1, 1, 1, 1 };

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 180);
    glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 64);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lightDirection);
}

I was told this problem could be solved by the proper positioning of the glPushMatrix() and glPopMatrix() calls.

Wherever i put them, the light either keeps changing when i'm moving the camera or i got no light at all. I just cannot figure it out.

What would be a proper solution here?

Thanks in advance.

EDIT: I've moved the initLight() call to after the gluLookAt() call as @derhass suggested below. The light is still changing when i'm moving around.

There are two screenshots below.

On the first one the light is not even visible. When i turn slightly to the right, it appears. enter image description here enter image description here

EDIT2:

The full (stripped down) source code:

#include <GL/glut.h>
#include <SOIL/SOIL.h>
#include <math.h>

GLfloat lightPosition[] = { 10, 3, 0, 1 };
GLfloat lightDirection[] = { 0, 0, 10, 0 };
GLfloat diffuseLight[] = { 0, 1, 0, 1 };
GLfloat ambientLight[] = { 0.2, 0.2, 0.2, 1 };

float x = 1.0f, z = 5.0f;
float vX = 1.0f, vZ = 0.0f;
float angle = 1.5f;

int windowWidth = 1024;
int windowHeight = 768;

char* textureFiles[13] = { "floortexture.png", "tabletexture.png",
        "chairtexture.png", "orange.png", "helptexture.png",
        "fridgetexture.png", "oven.png", "yellow.png", "dishwasher.png",
        "metallic.png", "cabinet.png", "wood.png", "cabinet2.png" };
GLuint texture[13];

void loadTexture() {
    for (int i = 0; i < 13; i++) {
        texture[i] = SOIL_load_OGL_texture(textureFiles[i], SOIL_LOAD_RGBA,
                SOIL_CREATE_NEW_ID, 0);

        glBindTexture(GL_TEXTURE_2D, texture[i]);

        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
}

void init() {
    glEnable(GL_TEXTURE_2D);

    loadTexture();

    glShadeModel(GL_SMOOTH);
    glEnable(GL_NORMALIZE);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_COLOR_MATERIAL);
    glClearDepth(1);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glClearColor(0, 0, 0, 1);
}

void processSpecialKeys(int key, int xx, int yy) {

    float moveFraction = 0.2f;
    float angleFraction = 0.1f;

    switch (key) {
    case GLUT_KEY_LEFT:
        angle -= angleFraction;
        vX = sin(angle);
        vZ = -cos(angle);
        break;
    case GLUT_KEY_RIGHT:
        angle += angleFraction;
        vX = sin(angle);
        vZ = -cos(angle);
        break;
    case GLUT_KEY_UP:
        x += vX * moveFraction;
        z += vZ * moveFraction;
        if (x < 1) {
            x = 1;
        }
        if (z < 1) {
            z = 1;
        }
        if (x > 9) {
            x = 9;
        }
        if (z > 9) {
            z = 9;
        }
        break;
    case GLUT_KEY_DOWN:
        x -= vX * moveFraction;
        z -= vZ * moveFraction;
        if (x < 1) {
            x = 1;
        }
        if (z < 1) {
            z = 1;
        }
        if (x > 9) {
            x = 9;
        }
        if (z > 9) {
            z = 9;
        }
        break;
    }
}

void initLight() {
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 180);
    glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 128);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lightDirection);
}

void renderWalls() {
    // floor
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glBegin(GL_QUADS);

    glTexCoord2f(0, 0);
    glVertex3f(0.0f, 0.0f, 0.0f);

    glTexCoord2f(0, 8);
    glVertex3f(0.0f, 0.0f, 10.0f);

    glTexCoord2f(8, 8);
    glVertex3f(10.0f, 0.0f, 10.0f);

    glTexCoord2f(8, 0);
    glVertex3f(10.0f, 0.0f, 0.0f);

    glEnd();

    // walls
    glBindTexture(GL_TEXTURE_2D, texture[3]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    // first wall
    glBegin(GL_QUADS);

    glTexCoord2f(0, 0);
    glVertex3f(0.0f, 0.0f, 0.0f);

    glTexCoord2f(0, 1);
    glVertex3f(0.0f, 3.0f, 0.0f);

    glTexCoord2f(1, 1);
    glVertex3f(10.0f, 3.0f, 0.0f);

    glTexCoord2f(1, 0);
    glVertex3f(10.0f, 0.0f, 0.0f);

    glEnd();

    // second wall
    glBegin(GL_QUADS);

    glTexCoord2f(0, 0);
    glVertex3f(10.0f, 0.0f, 0.0f);

    glTexCoord2f(0, 1);
    glVertex3f(10.0f, 3.0f, 0.0f);

    glTexCoord2f(1, 1);
    glVertex3f(10.0f, 3.0f, 10.0f);

    glTexCoord2f(1, 0);
    glVertex3f(10.0f, 0.0f, 10.0f);

    glEnd();

    // third wall
    glBegin(GL_QUADS);

    glTexCoord2f(0, 0);
    glVertex3f(10.0f, 0.0f, 10.0f);

    glTexCoord2f(0, 1);
    glVertex3f(10.0f, 3.0f, 10.0f);

    glTexCoord2f(1, 1);
    glVertex3f(0.0f, 3.0f, 10.0f);

    glTexCoord2f(1, 0);
    glVertex3f(0.0f, 0.0f, 10.0f);

    glEnd();

    // fourth wall
    glBegin(GL_QUADS);

    glTexCoord2f(0, 0);
    glVertex3f(0.0f, 0.0f, 0.0f);

    glTexCoord2f(0, 1);
    glVertex3f(0.0f, 3.0f, 0.0f);

    glTexCoord2f(1, 1);
    glVertex3f(0.0f, 3.0f, 10.0f);

    glTexCoord2f(1, 0);
    glVertex3f(0.0f, 0.0f, 10.0f);

    glEnd();

    // ceiling
    glBindTexture(GL_TEXTURE_2D, texture[7]);
    glBegin(GL_QUADS);

    glTexCoord2f(0, 0);
    glVertex3f(0.0f, 3.0f, 0.0f);

    glTexCoord2f(0, 1);
    glVertex3f(10.0f, 3.0f, 0.0f);

    glTexCoord2f(1, 1);
    glVertex3f(10.0f, 3.0f, 10.0f);

    glTexCoord2f(1, 0);
    glVertex3f(0.0f, 3.0f, 10.0f);

    glEnd();
}

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    gluLookAt(x, 1.5f, z, x + vX, 1.5f, z + vZ, 0.0f, 1.5f, 0.0f);
    initLight();

    renderWalls();

    glutSwapBuffers();
}

void changeWindowSize(int width, int height) {
    if (height == 0) {
        height = 1;
    }

    float ratio = 1.0 * width / height;

    windowWidth = width;
    windowHeight = height;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glViewport(0, 0, width, height);

    gluPerspective(45.0f, ratio, 0.1f, 100.0f);

    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowPosition(0, 0);
    glutInitWindowSize(windowWidth, windowHeight);
    glutCreateWindow("Kitchen");

    init();

    glutDisplayFunc(display);
    glutReshapeFunc(changeWindowSize);
    glutIdleFunc(display);
    glutSpecialFunc(processSpecialKeys);

    glutMainLoop();

    return 0;
}

Solution

  • Your light is stationary - but to the camera.

    Fixed-function OpenGL does all the lighting calculations in eye space. At the moment you specify the GL_POSITION of a light, it is transformed according to the current modelView matrix at the time of the call, to get the eye space position. Since modelView is set to identity, it will always set the light to the given eye-space location - no matter where you later place the camera.

    To fix this, just move initLight() after the gluLookAt.

    However. You should not be doing this at all. You are using deprecated OpenGL features which you shouldn't use since a decade now. In modern GL, that functionality is completely removed. So if you are learning OpenGL in 2016, do yourself a favour and forget about that old cruft, and just learn shaders directly.