OpenGL: Как сделать свет неподвижным при перемещении камеры?

Я пытаюсь визуализировать простую (размером 10x3x10) комнату, используя OpenGL и GLUT, написанную на C.

Я хотел бы создать простой стационарный прожектор, при этом я могу перемещаться по комнате.

(Конечная цель - сделать прожектор, идущий от лампы на потолке)

Проблема в том, что освещение меняется, когда я перемещаю камеру.

Я вызываю следующую функцию из 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);
}

Функция, которую я передаю 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();
}

И функция initLight():

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);
}

Мне сказали, что эту проблему можно решить правильным расположением вызовов glPushMatrix() и glPopMatrix().

Куда бы я их ни поместил, свет либо продолжает меняться, когда я двигаю камеру, либо света нет вообще. Я просто не могу этого понять.

Что было бы правильным решением здесь?

Заранее спасибо.

EDIT: я переместил вызов initLight() после вызова gluLookAt(), как предложил @derhass ниже. Свет все еще меняется, когда я двигаюсь.

Ниже приведены два скриншота.

На первом даже света не видно. Когда я поворачиваю немного вправо, он появляется. введите здесь описание изображения введите здесь описание изображения

EDIT2:

Полный (урезанный) исходный код:

#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;
}

person justanoob    schedule 18.12.2016    source источник
comment
Как насчет того, чтобы камера и источник света находились в одной системе отсчета, чтобы получить желаемый эффект?   -  person Luis Colorado    schedule 19.12.2016
comment
@LuisColorado Спасибо за ваш ответ. Как я мог этого добиться?   -  person justanoob    schedule 19.12.2016


Ответы (1)


Ваш источник света находится неподвижно, но направлен на камеру.

Фиксированная функция OpenGL выполняет все расчеты освещения в пространстве глаза. В тот момент, когда вы указываете GL_POSITION источника света, он преобразуется в соответствии с текущей матрицей modelView во время вызова, чтобы получить положение глаза в пространстве. Так как modelView настроен на идентичность, он всегда будет устанавливать свет в указанное место в пространстве глаз - независимо от того, где вы позже разместите камеру.

Чтобы это исправить, просто переместите initLight() после gluLookAt.

Тем не мение. Вы вообще не должны этого делать. Вы используете устаревшие функции OpenGL, которые вам не следует использовать уже десятилетие. В современном GL эта функциональность полностью удалена. Так что, если вы изучаете OpenGL в 2016 году, сделайте себе одолжение и забудьте об этом старом хламе и просто изучайте шейдеры напрямую.

person derhass    schedule 19.12.2016
comment
Спасибо за ваш ответ. К сожалению, перемещение initLight() после вызова gluLookAt() не работает. Смотрите мой отредактированный вопрос для скриншотов. Это задание, и я должен использовать эти устаревшие функции. Если у вас есть другие идеи, почему свет все еще меняется при перемещении, сообщите мне. Спасибо. - person justanoob; 19.12.2016
comment
Ну не понятно, что мы там видим на самом деле. Как смоделирована комната? Как устанавливаются нормали? Освещение не зависит от положения просмотра — зеркальный компонент всегда зависит от вида. С затенением по Гуро и достаточно низким уровнем тесселяции на гранях результат может быть даже правильным. Чего я не знаю, так это откуда берется это неравномерное сглаживание. Что это за реализация GL? - person derhass; 19.12.2016
comment
Я добавил ссылку pastebin на исходный код в конце вопроса. Урезал его до минимума, чтобы проблема была воспроизводимой. Пожалуйста, посмотрите, если у вас есть время. Спасибо. Я использую реализацию OpenGL по умолчанию на моем компьютере с Debian. - person justanoob; 19.12.2016
comment
Не ссылайтесь на внешний код. Поместите соответствующую часть непосредственно в вопрос. Однако ваши нормали полностью сломаны в этой ссылке. Вы никогда не получите надлежащее освещение, не предоставив надлежащие нормали. - person derhass; 19.12.2016
comment
О, этих вызовов glNormal не должно было быть в функции renderWalls(). Отредактировал вопрос с правильным исходным кодом. С нормалами все еще проблема? Если это так, пожалуйста, укажите мне в правильном направлении. Спасибо. - person justanoob; 19.12.2016
comment
Да, с нормалями огромная проблема - вы их не поставляете. Из-за модели конечного автомата OpenGL у вас будет постоянная нормаль для всех этих вершин, что не может работать. Я не знаю, почему ты думаешь, что можешь уйти без нормальных людей. Возможно, вы думаете, что glEnable(GL_AUTO_NORMAL) вам поможет, но это не так. Эта функция относится только к функциональности оценщика GL, функция, которая еще более неясна, чем устаревшая GL, которую вы используете, и совершенно не имеет отношения к коду, который вы вставили. - person derhass; 19.12.2016