Search code examples
c++openglopengl-compat

OpenGL: Updating shading with several movable lights


In this code, I'm trying to shade a surface properly based on the position of a light which can be moved. So when you move the light, the surface updates. In addition, I'm looking to have two lights of different colors both shading the same surface. Unfortunately, the surface color remains static.

What I'd like:

1) Have the surface update when the light is moved, and a vector that will use both colors(I'm not 100% on how to do this).

2) Have the lights and normals remain a static color regardless of shading/light.

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef MAC
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

//Camera variables
int xangle = -270;
int yangle = 0;

//Control Modes (Rotate mode by default)
int mode = 0;
int lightmode = 0;

//Player Position (Y offset so it would not be straddling the grid)
float cubeX = 0;
float cubeY = 0.5;
float cubeZ = 0;

//Vertex arrays for surface
float surfaceX [12][12];
float surfaceY [12][12];
float surfaceZ [12][12];

//Surface Normal arrays
float Nx[11][11];
float Ny[11][11];
float Nz[11][11];

//Color arrays
float R[11][11];
float G[11][11];
float B[11][11];

//Material properties
float Ka = 0.2;
float Kd = 0.4;
float Ks = 0.4;
float Kp = 0.5;

//Light position and color variables
float Light1x = 0;
float Light1y = 5;
float Light1z = 0;

float Light1r = 1;
float Light1g = 0;
float Light1b = 0;

float Light2x = -5;
float Light2y = 5;
float Light2z = -5;

float Light2r = 0;
float Light2g = 1;
float Light2b = 0;



//Random number generator
float RandomNumber(float Min, float Max)
{
    return ((float(rand()) / float(RAND_MAX)) * (Max - Min)) + Min;
}

//---------------------------------------
// Initialize material properties
//---------------------------------------
void init_material(float Ka, float Kd, float Ks, float Kp,
                   float Mr, float Mg, float Mb)
{
   // Material variables
   float ambient[] = { Ka * Mr, Ka * Mg, Ka * Mb, 1.0 };
   float diffuse[] = { Kd * Mr, Kd * Mg, Kd * Mb, 1.0 };
   float specular[] = { Ks * Mr, Ks * Mg, Ks * Mb, 1.0 };

   // Initialize material
   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
   glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, Kp);
}

//---------------------------------------
// Initialize light source
//---------------------------------------
void init_light(int light_source, float Lx, float Ly, float Lz,
                float Lr, float Lg, float Lb)
{
   // Light variables
   float light_position[] = { Lx, Ly, Lz, 0.0 };
   float light_color[] = { Lr, Lg, Lb, 1.0 };

   // Initialize light source
   glEnable(GL_LIGHTING);
   glEnable(light_source);
   glLightfv(light_source, GL_POSITION, light_position);
   glLightfv(light_source, GL_AMBIENT, light_color);
   glLightfv(light_source, GL_DIFFUSE, light_color);
   glLightfv(light_source, GL_SPECULAR, light_color);
   glLightf(light_source, GL_CONSTANT_ATTENUATION, 1.0);
   glLightf(light_source, GL_LINEAR_ATTENUATION, 0.0);
   glLightf(light_source, GL_QUADRATIC_ATTENUATION, 0.0);
   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
}

//---------------------------------------
// Initialize surface 
//---------------------------------------
void init_surface()
{
    //Initialize X, select column  
    for (int i = 0; i < 12; i++) 
    {
        //Select row
        //Surface is +1 so the far right normal will be generated correctly     
        for (int j = 0; j < 12; j++)
        {   
            //-5 to compensate for negative coordinate values
            surfaceX[i][j] = i-5;
            //Generate random surface height
            surfaceY[i][j] = RandomNumber(5, 7) - 5;
            //surfaceY[i][j] = 0;
            surfaceZ[i][j] = j-5;
        }
    }
}

void define_normals()
{
    //Define surface normals
    for (int i = 0; i < 11; i++)
    {
        for (int j = 0; j < 11; j++)
        {
            //Get two tangent vectors
            float Ix = surfaceX[i+1][j] - surfaceX[i][j];
            float Iy = surfaceY[i+1][j] - surfaceY[i][j];
            float Iz = surfaceZ[i+1][j] - surfaceZ[i][j];
            float Jx = surfaceX[i][j+1] - surfaceX[i][j];
            float Jy = surfaceY[i][j+1] - surfaceY[i][j];
            float Jz = surfaceZ[i][j+1] - surfaceZ[i][j];

            //Do cross product, inverted for upward normals
            Nx[i][j] = - Iy * Jz + Iz * Jy;
            Ny[i][j] = - Iz * Jx + Ix * Jz;
            Nz[i][j] = - Ix * Jy + Iy * Jx;

            //Original vectors
            //Nx[i][j] = Iy * Jz - Iz * Jy;
            //Ny[i][j] = Iz * Jx - Ix * Jz;
            //Nz[i][j] = Ix * Jy - Iy * Jx;

            float length = sqrt( 
                Nx[i][j] * Nx[i][j] + 
                Ny[i][j] * Ny[i][j] + 
                Nz[i][j] * Nz[i][j]);
            if (length > 0)
            {
                Nx[i][j] /= length;
                Ny[j][j] /= length;
                Nz[i][j] /= length;
            }
        }   
    } 
}

void calc_color()
{
    for (int i = 0; i < 10; i++)
    {           
        for (int j = 0; j < 10; j++)
        {
            //Calculate light vector
            //Light position, hardcoded for now 0,1,1
            float Lx = Light1x - surfaceX[i][j]; 
            float Ly = Light1y - surfaceY[i][j];
            float Lz = Light1z - surfaceZ[i][j];

            //std::cout << "Lx: " << Lx << std::endl;   
            //std::cout << "Ly: " << Ly << std::endl;
            //std::cout << "Lz: " << Lz << std::endl;

            //Grab surface normals
            //These are Nx,Ny,Nz due to compiler issues
            float Na = Nx[i][j];
            float Nb = Ny[i][j];
            float Nc = Nz[i][j];

            //std::cout << "Na: " << Na << std::endl;   
            //std::cout << "Nb: " << Nb << std::endl;   
            //std::cout << "Nc: " << Nc << std::endl;

            //Do cross product
            float Color = (Na * Lx) + (Nb * Ly) + (Nc * Lz);
            //std::cout << "Color: " << Color << std::endl;

            R[i][j] = Color;
            G[i][j] = Color;
            B[i][j] = Color;    
        }
    }
}

//---------------------------------------
// Init function for OpenGL
//---------------------------------------
void init()
{
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //Viewing Window Modified
    glOrtho(-7.0, 7.0, -7.0, 7.0, -7.0, 7.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    //Rotates camera
    //glRotatef(30.0, 1.0, 1.0, 1.0);
    glEnable(GL_DEPTH_TEST);

    //Project 3 code
    init_surface();
    define_normals();

    //Shading code
    glShadeModel(GL_SMOOTH);
    glEnable(GL_NORMALIZE);

    //X,Y,Z - R,G,B
    init_light(GL_LIGHT1, Light1x, Light1y, Light1z, Light1r, Light1g, Light1b);
    //init_light(GL_LIGHT2, Light2x, Light2y, Light2z, Light2r, Light2g, Light2b);
    //init_light(GL_LIGHT2, 0, 1, 0, 0.5, 0.5, 0.5);


}

void keyboard(unsigned char key, int x, int y)
{

///TODO: allow user to change color of light

    //Controls
    //Toggle Mode  
    if (key == 'q')
    {
        if(mode == 0)
        {       
            mode = 1;
            std::cout << "Switched to Light mode (" << mode << ")" << std::endl;    
        }
        else if(mode == 1)
        {   
            mode = 0;
            std::cout << "Switched to Rotate mode (" << mode << ")" << std::endl;
        }
    }
    //Toggle light control
    else if (key == 'e')
    {
        if(lightmode == 0)
        {       
            lightmode = 1;
            std::cout << "Switched to controlling light 2 (" << lightmode << ")" << std::endl;  
        }

        else if(lightmode == 1)
        {       
            lightmode = 0;
            std::cout << "Switched to controlling light 1 (" << lightmode << ")" << std::endl;  
        }
    }

    ////Rotate Camera (mode 0)
    //Up & Down
    else if (key == 's' && mode == 0)
        xangle += 5;
    else if (key == 'w' && mode == 0)
    xangle -= 5;

    //Left & Right
    else if (key == 'a' && mode == 0) 
    yangle -= 5;
    else if (key == 'd' && mode == 0) 
    yangle += 5;

    ////Move Light (mode 1)
    //Forward & Back
    else if (key == 'w' && mode == 1) 
    {
        if (lightmode == 0) 
        {       
            Light1z = Light1z - 1;
            //init_surface();           
            //define_normals();         
            calc_color();
            glutPostRedisplay();

        }
        else if (lightmode == 1)
        Light2z = Light2z - 1;
        //init_surface();

    }

    else if (key == 's' && mode == 1)
    {
        if (lightmode == 0)
        Light1z = Light1z + 1;

        else if (lightmode == 1)
        Light2z = Light2z + 1;
    }

    //Strafe
    else if (key == 'd' && mode == 1)
    {
        if (lightmode == 0)
        Light1x = Light1x + 1;

        else if (lightmode == 1)
        Light2x = Light2x + 1;  
    }
    else if (key == 'a' && mode == 1)
    {
        if (lightmode == 0)     
        Light1x = Light1x - 1;
        else if (lightmode == 1)
        Light2x = Light2x - 1;

    }   

    //Up & Down (Cube offset by +0.5 in Y)
    else if (key == 'z' && mode == 1)
    {
        if (lightmode == 0)
        Light1y = Light1y + 1;
        else if (lightmode == 1)
        Light2y = Light2y + 1;
    }
    else if (key == 'x' && mode == 1)
    {
        if (lightmode == 0)
        Light1y = Light1y - 1;
        else if (lightmode == 1)
        Light2y = Light2y - 1;
    }

    //Redraw objects
    glutPostRedisplay();
}


//---------------------------------------
// Display callback for OpenGL
//---------------------------------------
void display()
{       
        // Clear the screen
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //Rotation Code
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(xangle, 1.0, 0.0, 0.0);
    glRotatef(yangle, 0.0, 1.0, 0.0);

    //Light Code
    init_material(Ka, Kd, Ks, 100 * Kp, 0.8, 0.6, 0.4); 

    //Color Code
    calc_color();

    //Draw the squares, select column  
    for (int i = 0; i <= 9; i++)
    {
        //Select row        
        for (int j = 0; j <= 9; j++)
        {   
            glBegin(GL_POLYGON); 

            //Surface starts at top left
            //Counter clockwise

            glColor3f(R[i][j], G[i][j], B[i][j]);
            glNormal3f(Nx[i][j], Ny[i][j], Nz[i][j]);
            glVertex3f(surfaceX[i][j], surfaceY[i][j], surfaceZ[i][j]);

            glColor3f(R[i][j+1], G[i][j+1], B[i][j+1]);
            glNormal3f(Nx[i][j+1], Ny[i][j+1], Nz[i][j+1]);
            glVertex3f(surfaceX[i][j+1], surfaceY[i][j+1], surfaceZ[i][j+1]);

            glColor3f(R[i+1][j+1], G[i+1][j+1], B[i+1][j+1]);
            glNormal3f(Nx[i+1][j+1], Ny[i+1][j+1], Nz[i+1][j+1]);
            glVertex3f(surfaceX[i+1][j+1], surfaceY[i+1][j+1], surfaceZ[i+1][j+1]);

            glColor3f(R[i+1][j], G[i+1][j], B[i+1][j]);
            glNormal3f(Nx[i+1][j], Ny[i+1][j], Nz[i+1][j]);
            glVertex3f(surfaceX[i+1][j], surfaceY[i+1][j], surfaceZ[i+1][j]);

            glEnd();
        }
    }

    //Draw the normals
    for (int i = 0; i <= 10; i++)
    {
        for (int j = 0; j <= 10; j++)
        {

            glBegin(GL_LINES);
            //glColor3f(0.0, 1.0, 1.0);
            float length = 1;
            glVertex3f(surfaceX[i][j], surfaceY[i][j], surfaceZ[i][j]);
                glVertex3f(surfaceX[i][j]+length*Nx[i][j], 
                surfaceY[i][j]+length*Ny[i][j], 
                surfaceZ[i][j]+length*Nz[i][j]);
            glEnd();
        }
    }

    //Marking location of lights
    glPointSize(10);
    glBegin(GL_POINTS);
    glColor3f(Light1r, Light1g, Light1b);
    glVertex3f(Light1x, Light1y, Light1z);      
    glEnd();

    glPointSize(10);
    glBegin(GL_POINTS);
    glColor3f(Light2r, Light2g, Light2b);
    glVertex3f(Light2x, Light2y, Light2z);      
    glEnd();

    //+Z = Moving TOWARD camera in opengl
    //Origin point for reference
    glPointSize(10);
    glColor3f(1.0, 1.0, 0.0);
    glBegin(GL_POINTS);
    glVertex3f(0, 0, 0);        
    glEnd();

    //Assign Color of Lines
    float R = 1;
    float G = 1;
    float B = 1;
    glBegin(GL_LINES);
    glColor3f(R, G, B);

    ////Drawing the grid
    //Vertical lines
    for (int i = 0; i < 11; i++)
    {
        int b = -5 + i;

        glVertex3f(b, 0, -5);
        glVertex3f(b, 0, 5);
    }

    //Horizontal lines
    for (int i = 0; i < 11; i++)
    {
        int b = -5 + i;
        glVertex3f(-5,0,b);
        glVertex3f(5,0,b);

    }

    glEnd();
    glFlush();  
}

//---------------------------------------
// Main program
//---------------------------------------
int main(int argc, char *argv[])
{
    srand(time(NULL));

    //Print Instructions
    std::cout << "Project 3 Controls: " << std::endl;
    std::cout << "q switches control mode" << std::endl;
    std::cout << "w,a,s,d for camera rotation" << std::endl;


    //Required
    glutInit(&argc, argv);
    //Window will default to a different size without
    glutInitWindowSize(500, 500);
    //Window will default to a different position without
    glutInitWindowPosition(250, 250);
    //
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH);
    //Required
    glutCreateWindow("Project 3");
    //Required, calls display function
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);

    //Required
    init();
    glutMainLoop();



   return 0;
}

Solution

  • 1) Have the surface update when the light is moved

    You missed to update the position of the light, when the light was moved. Set the light position at the begin of the function display.
    Note, when the light position is set by glLightfv(GL_LIGHT0, GL_POSITION, pos), then pos is multiplied by the current model view matrix.
    So the light has to be set after the model view matrix was "cleared" by glLoadIdentity:

    void display()
    {       
        // Clear the screen
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        //Rotation Code
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    
        // init light
        init_light(GL_LIGHT1, Light1x, Light1y, Light1z, Light1r, Light1g, Light1b);
        init_light(GL_LIGHT2, Light2x, Light2y, Light2z, Light2r, Light2g, Light2b);
    
        glRotatef(xangle, 1.0, 0.0, 0.0);
        glRotatef(yangle, 0.0, 1.0, 0.0);
    
        // [...]
    
    }
    

    2) Have the lights and normals remain a static color regardless of shading/light.

    Enable lighting before drawing the surface, but disable lighting before drawing the lines and points:

    void display()
    {
    
        // [...]
    
        // switch on lighting
        glEnable(GL_LIGHTING);
    
        //Draw the squares, select column  
        for (int i = 0; i <= 9; i++)
        {
            // [...]
        }
    
        // switch off lighting
        glDisable(GL_LIGHTING);
    
        //Draw the normals
        for (int i = 0; i <= 10; i++)
        {
            // [...]
        }
    
        // [...]
    
    }