Example 29

Example 29

This shader program works in conjunction with multitexturing to perturb the normals of an object and give it a much more sophisticated look. The normal map for this example was created from the given brick texture, using a function in the GIMP, an open-source photo manipulation program. Each pixel of the normal map corresponds to the (x,y,z) normal of the image at that point -- but those coordinates are given in tangent space, a frame of reference defined with respect to the surface itself (and invariant to the surface's position, rotation, or scaling in world space). Accordingly, we have to transform the direction to the light source into this tangent space in the vertex shader before the lighting computation is done. Press 't' to toggle between OpenGL texturing and normal mapping, and press 'L' to toggle between controlling the light position and controlling the camera position (both implemented as arcballs.)

ex_29/normal_mapping.png

Source Code

main.cpp:

// Alexandri Zavodny
// CSE 40166: Computer Graphics, Fall 2010
// Example: Using GLSL shaders to perform simple texturing with normal mapping


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


//some global variables for our textures
unsigned char *brickTexData, *brickBumpTexData;
GLuint brickTexHandle, brickBumpTexHandle;
int brickTexWidth, brickTexHeight;  //we're just going to assume these are the same 'k


//some global variables to keep track of our rendering states...
enum usageModes {                           //a nice way of keeping track of our
    USAGEMODE_GL_TEXTURES,
    USAGEMODE_GLSL_NORMAL_MAPPING,
};
enum usageModes currentUsageMode = USAGEMODE_GL_TEXTURES;


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

GLint colorTexSamplerLocation, bumpTexSamplerLocation, heightTexSamplerLocation;
GLint tangentLocation, binormalLocation;



const int NUM_TRIANGLES = 2;
const int NUM_VERTICES = 4;

float vertices[12] = 
    { -2.5f,  1.6125f, 0.0f,
       2.5f,  1.6125f, 0.0f,
       2.5f, -1.6125f, 0.0f,
      -2.5f, -1.6125f, 0.0f };

float triangles[6] =
    { 0.0f, 1.0f, 2.0f,
      0.0f, 2.0f, 3.0f };


float triangleNormals[6] = 
    { 0.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 1.0f };

float vertexNormals[12] = 
    { 0.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 1.0f };

float textureCoords[8] = 
    { 0.0f, 0.0f,
      1.0f, 0.0f,
      1.0f, 1.0f,
      0.0f, 1.0f };

float vertexTangents[12], vertexBinormals[12];


// readPPM() ///////////////////////////////////////////////////////////////////
//
//  This function reads a PPM, returning true if the function succeeds and
//      false if it fails. If it succeeds, the variables imageWidth and 
//      imageHeight will hold the width and height of the read image, respectively.
//
//  Returns the image as an unsigned character array containing 
//      imageWidth*imageHeight*3 entries (for that many bytes of storage).
//
//  NOTE: this function expects imageData to be UNALLOCATED, and will allocate 
//      memory itself. If the function fails (returns false), imageData
//      will be set to NULL and any allocated memory will be automatically deallocated.
//
////////////////////////////////////////////////////////////////////////////////
bool readPPM(string filename, int &imageWidth, int &imageHeight, unsigned char* &imageData)
{
    FILE *fp = fopen(filename.c_str(), "r");
    int temp, maxValue;
    fscanf(fp, "P%d", &temp);
    if(temp != 3)
    {
        fprintf(stderr, "Error: PPM file is not of correct format! (Must be P3, is P%d.)\n", temp);
        fclose(fp);
        return false;
    }

    //got the file header right...
    fscanf(fp, "%d", &imageWidth);
    fscanf(fp, "%d", &imageHeight);
    fscanf(fp, "%d", &maxValue);

    //now that we know how big it is, allocate the buffer...
    imageData = new unsigned char[imageWidth*imageHeight*3];
    if(!imageData)
    {
        fprintf(stderr, "Error: couldn't allocate image memory. Dimensions: %d x %d.\n", imageWidth, imageHeight);
        fclose(fp);
        return false;
    }

    //and read the data in.
    for(int j = 0; j < imageHeight; j++)
    {
        for(int i = 0; i < imageWidth; i++)
        {
            fscanf(fp, "%d", &imageData[(j*imageWidth+i)*3+0]);
            fscanf(fp, "%d", &imageData[(j*imageWidth+i)*3+1]);
            fscanf(fp, "%d", &imageData[(j*imageWidth+i)*3+2]);
        }
    }
    
    fclose(fp);
    return true;
}

// registerOpenGLTexture() /////////////////////////////////////////////////////
//
//  Attempts to register an image buffer with OpenGL. Sets the texture handle
//      appropriately upon success, and uses a number of default texturing
//      values. This function only provides the basics: just sets up this image
//      as an unrepeated 2D texture.
//
////////////////////////////////////////////////////////////////////////////////
bool registerOpenGLTexture(unsigned char *imageData, int texWidth, int texHeight, GLuint &texHandle)
{
    //first off, get a real texture handle.
    glGenTextures(1, &texHandle);

    //make sure that future texture functions affect this handle
    glBindTexture(GL_TEXTURE_2D, texHandle);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    //actually transfer the texture to the GPU!
    glTexImage2D(GL_TEXTURE_2D,                 //still working with 2D textures, obv.
                    0,                          //not using mipmapping, so this is the highest level.
                    GL_RGB,                     //we're going to provide OpenGL with R, G, and B components...
                    texWidth,                   //...of this width...
                    texHeight,                  //...and this height.
                    0,                          //give it a border of 0 pixels (none)
                    GL_RGB,                     //and store it, internally, as RGB (OpenGL will convert to floats between 0.0 and 1.0f)
                    GL_UNSIGNED_BYTE,           //this is the format our data is in, and finally,
                    imageData);                 //there's the data!

    return true;
}



// 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.3, 0.3, 0.3, 1.0};
    glLightfv(GL_LIGHT0,GL_DIFFUSE,lightCol);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientCol);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);


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

    glActiveTextureARB(GL_TEXTURE0_ARB);
    glDisable(GL_TEXTURE_2D);
    glActiveTextureARB(GL_TEXTURE1_ARB);
    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
    drawLight();


    if(currentUsageMode == USAGEMODE_GL_TEXTURES)
    {
        glActiveTextureARB(GL_TEXTURE0_ARB);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, brickTexHandle);

        glActiveTextureARB(GL_TEXTURE1_ARB);
        glDisable(GL_TEXTURE_2D);
    } else {
        //these are in backwards order just so that the current one is 0 when all's done.
        glActiveTextureARB(GL_TEXTURE1_ARB);
        glDisable(GL_TEXTURE_2D);

        glActiveTextureARB(GL_TEXTURE0_ARB);
        glDisable(GL_TEXTURE_2D);
    }
    if(glGetError() != GL_NO_ERROR)
        printf("omg what\n");



    if(currentUsageMode == USAGEMODE_GLSL_NORMAL_MAPPING)
    {
        glUseProgram(shaderProgramHandle);
    } else {
        glUseProgram(0);
    }


    //aaaaand draw the triangles with all of their associated information.
    glColor3f(1,1,1);
    glBegin(GL_TRIANGLES);
    for(int i = 0; i < NUM_TRIANGLES; i++)
    {
        int vIdx1, vIdx2, vIdx3;
        vIdx1 = triangles[i*3+0];
        vIdx2 = triangles[i*3+1];
        vIdx3 = triangles[i*3+2];

        glNormal3f(vertexNormals[vIdx1*3+0], vertexNormals[vIdx1*3+1], vertexNormals[vIdx1*3+2]);
        glMultiTexCoord2fARB(GL_TEXTURE0_ARB, textureCoords[vIdx1*2+0], textureCoords[vIdx1*2+1]);
        glMultiTexCoord2fARB(GL_TEXTURE1_ARB, textureCoords[vIdx1*2+0], textureCoords[vIdx1*2+1]);
        glVertexAttrib3f(tangentLocation, vertexTangents[vIdx1*3+0], vertexTangents[vIdx1*3+1], vertexTangents[vIdx1*3+2]);
        glVertexAttrib3f(binormalLocation, vertexBinormals[vIdx1*3+0], vertexBinormals[vIdx1*3+1], vertexBinormals[vIdx1*3+2]);
        glVertex3f(vertices[vIdx1*3+0], vertices[vIdx1*3+1], vertices[vIdx1*3+2]);

        glNormal3f(vertexNormals[vIdx2*3+0], vertexNormals[vIdx2*3+1], vertexNormals[vIdx2*3+2]);
        glMultiTexCoord2fARB(GL_TEXTURE0_ARB, textureCoords[vIdx2*2+0], textureCoords[vIdx2*2+1]);
        glMultiTexCoord2fARB(GL_TEXTURE1_ARB, textureCoords[vIdx2*2+0], textureCoords[vIdx2*2+1]);
        glVertexAttrib3f(tangentLocation, vertexTangents[vIdx2*3+0], vertexTangents[vIdx2*3+1], vertexTangents[vIdx2*3+2]);
        glVertexAttrib3f(binormalLocation, vertexBinormals[vIdx2*3+0], vertexBinormals[vIdx2*3+1], vertexBinormals[vIdx2*3+2]);
        glVertex3f(vertices[vIdx2*3+0], vertices[vIdx2*3+1], vertices[vIdx2*3+2]);

        glNormal3f(vertexNormals[vIdx3*3+0], vertexNormals[vIdx3*3+1], vertexNormals[vIdx3*3+2]);
        glMultiTexCoord2fARB(GL_TEXTURE0_ARB, textureCoords[vIdx3*2+0], textureCoords[vIdx3*2+1]);
        glMultiTexCoord2fARB(GL_TEXTURE1_ARB, textureCoords[vIdx3*2+0], textureCoords[vIdx3*2+1]);
        glVertexAttrib3f(tangentLocation, vertexTangents[vIdx3*3+0], vertexTangents[vIdx3*3+1], vertexTangents[vIdx3*3+2]);
        glVertexAttrib3f(binormalLocation, vertexBinormals[vIdx3*3+0], vertexBinormals[vIdx3*3+1], vertexBinormals[vIdx3*3+2]);
        glVertex3f(vertices[vIdx3*3+0], vertices[vIdx3*3+1], vertices[vIdx3*3+2]);
    }
    glEnd();

    char *windowTitle = new char[256];
    sprintf(windowTitle, "Current usage mode: %s.", currentUsageMode == USAGEMODE_GL_TEXTURES ? 
                                                        "OpenGL Texturing" : 
                                                        "Normal Mapping with GLSL" );
    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);

    if(key == 't')
    {
        if(currentUsageMode == USAGEMODE_GL_TEXTURES)
            currentUsageMode = USAGEMODE_GLSL_NORMAL_MAPPING;
        else
            currentUsageMode = USAGEMODE_GL_TEXTURES;
    }

    if(key == 'T')
    {
        if(currentUsageMode == USAGEMODE_GL_TEXTURES)
            currentUsageMode = USAGEMODE_GLSL_NORMAL_MAPPING;
        else
            currentUsageMode = USAGEMODE_GL_TEXTURES;
    }


    if(key == 'l' || key == 'L')
    {
        controllingLight = !controllingLight;
    }
}

// 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("normalMapping.vert", vertexShaderString);
    readTextFile("normalMapping.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);

    colorTexSamplerLocation =   glGetUniformLocation(shaderProgramHandle, "colorTex");
    bumpTexSamplerLocation =    glGetUniformLocation(shaderProgramHandle, "bumpTex");
    heightTexSamplerLocation =  glGetUniformLocation(shaderProgramHandle, "heightTex");
    tangentLocation =           glGetAttribLocation(shaderProgramHandle, "tangent");
    binormalLocation =          glGetAttribLocation(shaderProgramHandle, "binormal");
}

// computeTangentsAndBinormals() ///////////////////////////////////////////////
//
//  This function precomputes the tangent and binormals for each vertex of
//      our model; they're needed in order to appropriately deform the normals
//      of the model based on its normalmap. Together with the surface normal,
//      they comprise the frame of the tangent space matrix.
//
////////////////////////////////////////////////////////////////////////////////
void computeTangentsAndBinormals()
{
    float *triangleTangents = new float[NUM_TRIANGLES*3];
    float *triangleBinormals = new float[NUM_TRIANGLES*3];

    int *accumPerVertex = new int[NUM_VERTICES];
    for(int i = 0; i < NUM_VERTICES; i++) 
    {
        accumPerVertex[i] = 0;
        vertexTangents[i*3+0] = vertexTangents[i*3+1] = vertexTangents[i*3+2] = 0.0f;
        vertexBinormals[i*3+0] = vertexBinormals[i*3+1] = vertexBinormals[i*3+2] = 0.0f;
    }

    //first compute the triangle tangents and binormals, and then use
    //those to interpolate those across the vertices. ok, why lie.
    //when we find one for a triangle, we'll accumulate it over each vertex.
    for(int i = 0; i < NUM_TRIANGLES; i++)
    {
        int vIdx1, vIdx2, vIdx3;
        vIdx1 = triangles[i*3+0];
        vIdx2 = triangles[i*3+1];
        vIdx3 = triangles[i*3+2];
        
        //just gonna use a formula.
        float dc21x, dc31x, dc21y, dc31y, dv21x, dv21y, dv21z, dv31x, dv31y, dv31z;
        dc21x = textureCoords[vIdx2*2+0] - textureCoords[vIdx1*2+0];
        dc31x = textureCoords[vIdx3*2+0] - textureCoords[vIdx1*2+0];
        dc21y = textureCoords[vIdx2*2+1] - textureCoords[vIdx1*2+1];
        dc31y = textureCoords[vIdx3*2+1] - textureCoords[vIdx1*2+1];

        dv21x = vertices[vIdx2*3+0] - vertices[vIdx1*3+0];
        dv21y = vertices[vIdx2*3+1] - vertices[vIdx1*3+1];
        dv21z = vertices[vIdx2*3+2] - vertices[vIdx1*3+2];
        dv31x = vertices[vIdx3*3+0] - vertices[vIdx1*3+0];
        dv31y = vertices[vIdx3*3+1] - vertices[vIdx1*3+1];
        dv31z = vertices[vIdx3*3+2] - vertices[vIdx1*3+2];
        float denom = 1.0f / (dc21x * dc31y - dc31x * dc21y);

        accumPerVertex[vIdx1]++;
        accumPerVertex[vIdx2]++;
        accumPerVertex[vIdx3]++;

        triangleTangents[i*3+0] = denom * (dc31y * dv21x - dc21y * dv31x);
        triangleTangents[i*3+1] = denom * (dc31y * dv21y - dc21y * dv31y);
        triangleTangents[i*3+2] = denom * (dc31y * dv21z - dc21y * dv31z);
        float mag = sqrt(triangleTangents[i*3+0]*triangleTangents[i*3+0] +
                            triangleTangents[i*3+1]*triangleTangents[i*3+1] +
                            triangleTangents[i*3+2]*triangleTangents[i*3+2]);
        triangleTangents[i*3+0] /= mag;
        triangleTangents[i*3+1] /= mag;
        triangleTangents[i*3+2] /= mag;

        vertexTangents[vIdx1*3+0] += triangleTangents[i*3+0];
        vertexTangents[vIdx1*3+1] += triangleTangents[i*3+1];
        vertexTangents[vIdx1*3+2] += triangleTangents[i*3+2];
        vertexTangents[vIdx2*3+0] += triangleTangents[i*3+0];
        vertexTangents[vIdx2*3+1] += triangleTangents[i*3+1];
        vertexTangents[vIdx2*3+2] += triangleTangents[i*3+2];
        vertexTangents[vIdx3*3+0] += triangleTangents[i*3+0];
        vertexTangents[vIdx3*3+1] += triangleTangents[i*3+1];
        vertexTangents[vIdx3*3+2] += triangleTangents[i*3+2];


        triangleBinormals[i*3+0] = denom * (-dc31x * dv21x + dc21x * dv31x);
        triangleBinormals[i*3+1] = denom * (-dc31x * dv21y + dc21x * dv31y);
        triangleBinormals[i*3+2] = denom * (-dc31x * dv21z + dc21x * dv31z);
        mag = sqrt(triangleBinormals[i*3+0]*triangleBinormals[i*3+0] +
                    triangleBinormals[i*3+1]*triangleBinormals[i*3+1] +
                    triangleBinormals[i*3+2]*triangleBinormals[i*3+2]);
        triangleBinormals[i*3+0] /= mag;
        triangleBinormals[i*3+1] /= mag;
        triangleTangents[i*3+2] /= mag;
        vertexBinormals[vIdx1*3+0] += triangleBinormals[i*3+0];
        vertexBinormals[vIdx1*3+1] += triangleBinormals[i*3+1];
        vertexBinormals[vIdx1*3+2] += triangleBinormals[i*3+2];
        vertexBinormals[vIdx2*3+0] += triangleBinormals[i*3+0];
        vertexBinormals[vIdx2*3+1] += triangleBinormals[i*3+1];
        vertexBinormals[vIdx2*3+2] += triangleBinormals[i*3+2];
        vertexBinormals[vIdx3*3+0] += triangleBinormals[i*3+0];
        vertexBinormals[vIdx3*3+1] += triangleBinormals[i*3+1];
        vertexBinormals[vIdx3*3+2] += triangleBinormals[i*3+2];
    }


    //now go normalize them.
    for(int i = 0; i < NUM_VERTICES; i++)
    {
        vertexTangents[i*3+0] /= (float)accumPerVertex[i];
        vertexTangents[i*3+1] /= (float)accumPerVertex[i];
        vertexTangents[i*3+2] /= (float)accumPerVertex[i];

        vertexBinormals[i*3+0] /= (float)accumPerVertex[i];
        vertexBinormals[i*3+1] /= (float)accumPerVertex[i];
        vertexBinormals[i*3+2] /= (float)accumPerVertex[i];
    }
    
    delete [] accumPerVertex;
    delete [] triangleTangents;
    delete [] triangleBinormals;
}


// 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("it's teatime");


    //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();


    //load in the texture and normal map from disk...
    readPPM("brick.ppm", brickTexWidth, brickTexHeight, brickTexData);
    readPPM("brick_bump.ppm", brickTexWidth, brickTexHeight, brickBumpTexData);


    //we'll need to use multitexturing to pass both the normal map and the regular
    //object's texture to the GPU. simply, we switch between texture units
    //with the glActiveTextureARB functions, and then proceed normally within each one.
    glActiveTextureARB(GL_TEXTURE0_ARB);
    registerOpenGLTexture(brickTexData, brickTexWidth, brickTexHeight, brickTexHandle);

    glActiveTextureARB(GL_TEXTURE1_ARB);
    registerOpenGLTexture(brickBumpTexData, brickTexWidth, brickTexHeight, brickBumpTexHandle);

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

    //give these shaders the handles to our textures? 
    //documentation on this is vague, but I believe the numer we're supposed to
    //pass in is the number of the texture unit; i.e. a value of 0 says that the
    //sampler corresponds to whatever is bound to GL_TEXTURE0_ARB, and so on...
    glUniform1i(colorTexSamplerLocation,    0);
    glUniform1i(bumpTexSamplerLocation,     1);


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

    return(0);
}

Files