// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: Using GLSL shaders to implement a few different lighting modes #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; 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); }