Example 30

Example 30

This example implements a handful of lighting algorithms with vertex and fragment shaders. Press 't' to toggle between the different lighting modes, 'r' and 'e' to increase the resolution of the sphere, 'w' to toggle wireframe mode, and 'L' to control the position of the light source instead of the camera. By default, the shaders are disabled and the program uses flat shading (GL_FLAT). Pressing 't' or 'T' toggles through the other modes, including Gourad shading (per-vertex lighting, interpolated in the rasterizer across triangles), Phong shader (per pixel lighting, computed in the fragment shader using surface normal and direction-to-light vectors that were interpolated in the rasterizer), and Cel shading (same as Phong shading, but with the intensity stepped).

ex_30/fragment_shader_lighting.png

Source Code

main.cpp:

// Alexandri Zavodny
// CSE 40166: Computer Graphics, Fall 2010
// Example: Using GLSL shaders to implement a few different lighting modes


#include <math.h>
#include <GL/glew.h>
#include <GL/glut.h>
#include <stdio.h>
#include <string.h>

#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <vector>

using namespace std;


// GLOBAL VARIABLES ////////////////////////////////////////////////////////////

static size_t windowWidth = 640;
static size_t windowHeight = 480;
static float aspectRatio;

GLint leftMouseButton, rightMouseButton;    //status of the mouse buttons
int mouseX = 0, mouseY = 0;                 //last known X and Y of the mouse

float cameraTheta, cameraPhi, cameraRadius; //camera position in spherical coordinates
float x, y, z;                              //camera position in cartesian coordinates

float lightTheta, lightPhi, lightRadius;    //light position in spherical coordinates
float lightX, lightY, lightZ;               //light position in cartesian coordinates
bool controllingLight = false;
bool showLight = true;
bool useWireframe = false;

int numStacks = 8, numSlices = 8;


//some global variables to keep track of our rendering states...
enum usageModes {                           //a nice way of keeping track of our
    LIGHTING_MODEL_FLAT,                  //current rendering mode.
    LIGHTING_MODEL_GOURAD,
    LIGHTING_MODEL_PHONG,
    LIGHTING_MODEL_CEL,
};
enum usageModes currentLightingModel = LIGHTING_MODEL_FLAT;


//and some more global variables for our shaders...
char *vertexShaderString;
char *fragmentShaderString;
GLuint vertexShaderHandle, fragmentShaderHandle, shaderProgramHandle;
GLuint modeLocation, ambientLocation;




// recomputeLightOrientation() /////////////////////////////////////////////////
//
// This function updates the light's position in cartesian coordinates based 
//  on its position in spherical coordinates. Should be called every time 
//  lightTheta, lightPhi, or lightRadius is updated. 
//
////////////////////////////////////////////////////////////////////////////////
void recomputeLightOrientation()
{
    lightX = lightRadius * sinf(lightTheta)*sinf(lightPhi);
    lightZ = lightRadius * -cosf(lightTheta)*sinf(lightPhi);
    lightY = lightRadius * -cosf(lightPhi);
    glutPostRedisplay();
}

// recomputeOrientation() //////////////////////////////////////////////////////
//
// This function updates the camera's position in cartesian coordinates based 
//  on its position in spherical coordinates. Should be called every time 
//  cameraTheta, cameraPhi, or cameraRadius is updated. 
//
////////////////////////////////////////////////////////////////////////////////
void recomputeOrientation()
{
    x = cameraRadius * sinf(cameraTheta)*sinf(cameraPhi);
    z = cameraRadius * -cosf(cameraTheta)*sinf(cameraPhi);
    y = cameraRadius * -cosf(cameraPhi);
    glutPostRedisplay();
}

// resizeWindow() //////////////////////////////////////////////////////////////
//
//  GLUT callback for window resizing. Resets GL_PROJECTION matrix and viewport.
//
////////////////////////////////////////////////////////////////////////////////
void resizeWindow(int w, int h)
{
    aspectRatio = w / (float)h;

    windowWidth = w;
    windowHeight = h;

    //update the viewport to fill the window
    glViewport(0, 0, w, h);

    //update the projection matrix with the new window properties
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0,aspectRatio,0.1,100000);
}



// mouseCallback() /////////////////////////////////////////////////////////////
//
//  GLUT callback for mouse clicks. We save the state of the mouse button
//      when this is called so that we can check the status of the mouse
//      buttons inside the motion callback (whether they are up or down).
//
////////////////////////////////////////////////////////////////////////////////
void mouseCallback(int button, int state, int thisX, int thisY)
{
    //update the left and right mouse button states, if applicable
    if(button == GLUT_LEFT_BUTTON)
        leftMouseButton = state;
    else if(button == GLUT_RIGHT_BUTTON)
        rightMouseButton = state;
    
    //and update the last seen X and Y coordinates of the mouse
    mouseX = thisX;
    mouseY = thisY;
}

// mouseMotion() ///////////////////////////////////////////////////////////////
//
//  GLUT callback for mouse movement. We update cameraPhi, cameraTheta, and/or
//      cameraRadius based on how much the user has moved the mouse in the
//      X or Y directions (in screen space) and whether they have held down
//      the left or right mouse buttons. If the user hasn't held down any
//      buttons, the function just updates the last seen mouse X and Y coords.
//
////////////////////////////////////////////////////////////////////////////////
void mouseMotion(int x, int y)
{
    if(leftMouseButton == GLUT_DOWN)
    {
        (controllingLight ? lightTheta : cameraTheta) += (x - mouseX)*0.005;
        (controllingLight ? lightPhi : cameraPhi)   += (y - mouseY)*0.005;

        // make sure that phi stays within the range (0, M_PI)
        if(cameraPhi <= 0)
            cameraPhi = 0+0.001;
        if(cameraPhi >= M_PI)
            cameraPhi = M_PI-0.001;

        //you could make sure you only do these checks for the currently active
        //control mode, but... eh
        if(lightPhi <= 0)
            lightPhi = 0+0.001;
        if(lightPhi >= M_PI)
            lightPhi = M_PI-0.001;
        
        
        if(controllingLight)
            recomputeLightOrientation();
        else 
            recomputeOrientation();     //update camera (x,y,z) based on (radius,theta,phi)
    } else if(rightMouseButton == GLUT_DOWN) {
        double totalChangeSq = (x - mouseX) + (y - mouseY);
        
        cameraRadius += totalChangeSq*0.01;
        
        //limit the camera radius to some reasonable values so the user can't get lost
        if(cameraRadius < 2.0) 
            cameraRadius = 2.0;
        if(cameraRadius > 10.0) 
            cameraRadius = 10.0;

        recomputeOrientation();     //update camera (x,y,z) based on (radius,theta,phi)
    }
    mouseX = x;
    mouseY = y;
}



// initScene() /////////////////////////////////////////////////////////////////
//
//  A basic scene initialization function; should be called once after the
//      OpenGL context has been created. Doesn't need to be called further.
//
////////////////////////////////////////////////////////////////////////////////
void initScene() 
{
    glEnable(GL_DEPTH_TEST);

    float lightCol[4] = { 1, 1, 1, 1};
    float ambientCol[4] = {0.1, 0.1, 0.1, 1.0};
    glLightfv(GL_LIGHT0,GL_DIFFUSE,lightCol);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientCol);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    float ambientMaterial[4] = {0.0,0.0,0.0,1.0};
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambientMaterial);

    //glShadeModel(GL_SMOOTH);
    glShadeModel(GL_FLAT);
}


// drawLight() /////////////////////////////////////////////////////////////////
//
//  Draws a little representation of the light in 3D based on where it's located;
//      just helps the user debug how the object that's being lit should look.
//
////////////////////////////////////////////////////////////////////////////////
void drawLight()
{
    glPushAttrib(GL_LIGHTING_BIT);
    glPushAttrib(GL_TEXTURE_BIT);
    glDisable(GL_LIGHTING);
    glDisable(GL_TEXTURE_2D);

    glPushMatrix();
        glTranslatef(lightX, lightY, lightZ);
        glColor3f(1,1,0);
        glutWireSphere(0.3f, 8, 8);
    glPopMatrix();

    glPopAttrib();
    glPopAttrib();
}

// renderScene() ///////////////////////////////////////////////////////////////
//
//  GLUT callback for scene rendering. Sets up the modelview matrix, renders
//      a teapot to the back buffer, and switches the back buffer with the
//      front buffer (what the user sees).
//
////////////////////////////////////////////////////////////////////////////////
void renderScene(void) 
{
    //clear the render buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //update the modelview matrix based on the camera's position
    glMatrixMode(GL_MODELVIEW);         //make sure we aren't changing the projection matrix!
    glLoadIdentity();
    gluLookAt(x, y, z,                  //camera is located at (x,y,z)
                0, 0, 0,                //camera is looking at (0,0,0)
                0.0f,1.0f,0.0f);        //up vector is (0,1,0) (positive Y)

    //update the light position after we set the modelview matrix;
    //lights get transformed by the modelview matrix just like every other point!
    float lPosition[4] = { 0, 0, 0, 1 };
    lPosition[0] = lightX;
    lPosition[1] = lightY;
    lPosition[2] = lightZ;
    glLightfv(GL_LIGHT0,GL_POSITION,lPosition);


    glUseProgram(0);

    //draw a simple representation of the light, too
    if(showLight)
        drawLight();


    glColor3f(0.8f, 0.8f, 0.8f);
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);

    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    if(currentLightingModel== LIGHTING_MODEL_FLAT)
    {
        glUseProgram(0);
        glShadeModel(GL_FLAT);
    } else {
        glUseProgram(shaderProgramHandle);
        glUniform1i(modeLocation, currentLightingModel == LIGHTING_MODEL_PHONG ? 1 : 
                                    currentLightingModel == LIGHTING_MODEL_CEL ? 2 :
                                    0);
        glUniform3f(ambientLocation, 0.2f, 0.2f, 0.2f);
    }

    //draw the sphere!
    glRotatef(90,1,0,0);
    if(useWireframe)
        glutWireSphere(1.0f, numStacks, numSlices);
    else
        glutSolidSphere(1.0f, numStacks, numSlices);


    char *windowTitle = new char[256];
    sprintf(windowTitle, "Current usage mode: %s.", currentLightingModel == LIGHTING_MODEL_FLAT ? 
                                                        "flat shading" : 
                                                    currentLightingModel == LIGHTING_MODEL_GOURAD ?
                                                        "gourad shading" :
                                                    currentLightingModel == LIGHTING_MODEL_PHONG ?
                                                        "phong shading" :
                                                        "cel shading");
    glutSetWindowTitle(windowTitle);

    //push the back buffer to the screen
    glutSwapBuffers();
}


// normalKeys() ////////////////////////////////////////////////////////////////
//
//  GLUT keyboard callback.
//
////////////////////////////////////////////////////////////////////////////////
void normalKeys(unsigned char key, int x, int y) 
{
    if(key == 'q' || key == 'Q')
        exit(0);

    //toggle forwards through the lighting modes!
    if(key == 't')
    {
        if(currentLightingModel == LIGHTING_MODEL_FLAT)
            currentLightingModel = LIGHTING_MODEL_GOURAD;
        else if(currentLightingModel == LIGHTING_MODEL_GOURAD)
            currentLightingModel = LIGHTING_MODEL_PHONG;
        else if(currentLightingModel == LIGHTING_MODEL_PHONG)
            currentLightingModel = LIGHTING_MODEL_CEL;
        else if(currentLightingModel == LIGHTING_MODEL_CEL)
            currentLightingModel = LIGHTING_MODEL_FLAT;
    }
    
    //toggle backwards through the lighting modes!
    if(key == 'T')
    {
        if(currentLightingModel == LIGHTING_MODEL_FLAT)
            currentLightingModel = LIGHTING_MODEL_CEL;
        else if(currentLightingModel == LIGHTING_MODEL_CEL)
            currentLightingModel = LIGHTING_MODEL_PHONG;
        else if(currentLightingModel == LIGHTING_MODEL_PHONG)
            currentLightingModel = LIGHTING_MODEL_GOURAD;
        else
            currentLightingModel = LIGHTING_MODEL_FLAT;
    }


    //press 'l' to toggle control of the light
    if(key == 'l' || key == 'L')
        controllingLight = !controllingLight;

    //turn off the visualization of the light position
    if(key == 's' || key == 'S')
        showLight = !showLight;

    //turns wireframe on and off...
    if(key == 'w' || key == 'W')
        useWireframe = !useWireframe;
    
    if(key == 'e' || key == 'E')
        numStacks += 1;
    if(key == 'd' || key == 'D')
        numStacks -= 1;
    if(key == 'r' || key == 'R')
        numSlices += 1;
    if(key == 'f' || key == 'F')
        numSlices -= 1;

    if(numStacks < 3) numStacks = 3;
    if(numStacks > 64) numStacks = 64;
    if(numSlices < 3) numSlices = 3;
    if(numSlices > 64) numSlices = 64;
}

// readTextFile() //////////////////////////////////////////////////////////////
//
//  Reads in a text file as a single string. Used to aid in shader loading.
//
////////////////////////////////////////////////////////////////////////////////
void readTextFile(string filename, char* &output)
{
    string buf = string("");
    string line;

    ifstream in(filename.c_str());
    while(getline(in, line))
        buf += line + "\n";
    output = new char[buf.length()+1];
    strncpy(output, buf.c_str(), buf.length());
    output[buf.length()] = '\0';

    in.close();
}

// setupShaders() //////////////////////////////////////////////////////////////
//
//  A simple helper subrouting that performs the necessary steps to enable shaders
//      and bind them appropriately. Note because we're using global variables,
//      everything is relatively hard-coded, including filenames, and none of
//      the information are passed to the function as parameters.
//
////////////////////////////////////////////////////////////////////////////////
void setupShaders()
{
    readTextFile("lighting.vert", vertexShaderString);
    readTextFile("lighting.frag", fragmentShaderString);

    vertexShaderHandle = glCreateShader(GL_VERTEX_SHADER);
    fragmentShaderHandle = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(vertexShaderHandle, 1, (const char**)&vertexShaderString, NULL);
    glShaderSource(fragmentShaderHandle, 1, (const char**)&fragmentShaderString, NULL);

    //free up that memory cause we're awesome programmers.
    delete [] vertexShaderString;
    delete [] fragmentShaderString;

    glCompileShader(vertexShaderHandle);
    glCompileShader(fragmentShaderHandle);

    shaderProgramHandle = glCreateProgram();

    glAttachShader(shaderProgramHandle, vertexShaderHandle);
    glAttachShader(shaderProgramHandle, fragmentShaderHandle);

    glLinkProgram(shaderProgramHandle);
    glUseProgram(shaderProgramHandle);

    modeLocation = glGetUniformLocation(shaderProgramHandle, "mode");
    ambientLocation = glGetUniformLocation(shaderProgramHandle, "ambient");
}

    

// main() //////////////////////////////////////////////////////////////////////
//
//  Program entry point. Does not process command line arguments.
//
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char **argv)
{
    //create a double-buffered GLUT window at (50,50) with predefined windowsize
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(50,50);
    glutInitWindowSize(windowWidth,windowHeight);
    glutCreateWindow("let there be... per-pixel lighting");


    //check for GLEW support...
    glewInit();
    if(!glewIsSupported("GL_VERSION_2_0"))
    {
        fprintf(stderr, "Error! OpenGL 2.0 not supported on this machine.\n");
        exit(1);
    }



    //give the camera a 'pretty' starting point!
    cameraRadius = 7.0f;
    cameraTheta = 2.80;
    cameraPhi = 2.0;
    recomputeOrientation();

    //give the light a decent starting point...
    lightRadius = 3.0f;
    lightTheta = 3.4;
    lightPhi = 2.0;
    recomputeLightOrientation();

    //register callback functions...
    glutKeyboardFunc(normalKeys);
    glutDisplayFunc(renderScene);
    glutIdleFunc(renderScene);
    glutReshapeFunc(resizeWindow);
    glutMouseFunc(mouseCallback);
    glutMotionFunc(mouseMotion);

    //do some basic OpenGL setup
    initScene();


    //aaaand setup the shaders...
    setupShaders();


    //and enter the GLUT loop, never to exit.
    glutMainLoop();

    return(0);
}

Files