// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: Using GLSL shaders to perform simple texturing with normal mapping #include #include #include #include #include #include #include #include #include #include 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); }