This shader program simulates a 'film grain' by applying a random amount of noise to each pixel on the screen. The scene is rendered as normal, without the shader enabled, and then a quad is rendered over the entire screen with the shader enabled. Feel free to experiment with different noise functions and weighting values for a better look -- this example just uses white noise.
main.cpp:
// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: load and execute some simple shaders. #include <math.h> #include <GL/glew.h> #include <GL/glut.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fstream> #include <iostream> #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 bool useShader = false; char *vertexShaderString; char *fragmentShaderString; GLuint vertexShaderHandle, fragmentShaderHandle, shaderProgramHandle; GLint timeLocation; vector<float> vertices; vector<float> vertexNormals; vector<int> faceVerts; vector<int> faceVertNormals; GLuint objectDisplayList; // tokenizeString() //////////////////////////////////////////////////////////// // // Helper function to break a string into a series of tokens. // //////////////////////////////////////////////////////////////////////////////// vector<string> tokenizeString(string input, string delimiters) { if(input.size() == 0) return vector<string>(); vector<string> retVec = vector<string>(); size_t oldR = 0, r = 0; //strip all delimiter characters from the front and end of the input string. string strippedInput; int lowerValidIndex = 0, upperValidIndex = input.size() - 1; while(lowerValidIndex < input.size() && delimiters.find_first_of(input.at(lowerValidIndex), 0) != string::npos) lowerValidIndex++; while(upperValidIndex >= 0 && delimiters.find_first_of(input.at(upperValidIndex), 0) != string::npos) upperValidIndex--; //if the lowest valid index is higher than the highest valid index, they're all delimiters! return nothing. if(lowerValidIndex >= input.size() || upperValidIndex < 0 || lowerValidIndex >= upperValidIndex) return vector<string>(); //remove the delimiters from the beginning and end of the string, if any. strippedInput = input.substr(lowerValidIndex, upperValidIndex-lowerValidIndex+1); //search for each instance of a delimiter character, and create a new token spanning //from the last valid character up to the delimiter character. while((r = strippedInput.find_first_of(delimiters, oldR)) != string::npos) { if(oldR != r) //but watch out for multiple consecutive delimiters! retVec.push_back(strippedInput.substr(oldR, r-oldR)); oldR = r+1; } if(r != 0) retVec.push_back(strippedInput.substr(oldR, r-oldR)); return retVec; } // loadOBJ() /////////////////////////////////////////////////////////////////// // // Simple function to load an OBJ file. Doesn't support anything fancy; just // vertices, normals, and faces. // //////////////////////////////////////////////////////////////////////////////// void loadOBJ(string filename) { string line; ifstream in(filename.c_str()); while(getline(in, line)) { vector<string> tokens = tokenizeString(line, " /"); if(tokens.size() < 4) continue; if(!tokens.at(0).compare("v")) { vertices.push_back(atof(tokens.at(1).c_str())); vertices.push_back(atof(tokens.at(2).c_str())); vertices.push_back(atof(tokens.at(3).c_str())); } else if(!tokens.at(0).compare("vn")) { vertexNormals.push_back(atof(tokens.at(1).c_str())); vertexNormals.push_back(atof(tokens.at(2).c_str())); vertexNormals.push_back(atof(tokens.at(3).c_str())); } else if(!tokens.at(0).compare("f")) { faceVerts.push_back(atoi(tokens.at(1).c_str())); faceVerts.push_back(atoi(tokens.at(3).c_str())); faceVerts.push_back(atoi(tokens.at(5).c_str())); faceVertNormals.push_back(atoi(tokens.at(2).c_str())); faceVertNormals.push_back(atoi(tokens.at(4).c_str())); faceVertNormals.push_back(atoi(tokens.at(6).c_str())); } } printf("We have %d vertices, %d vertex normals, and %d faces.\n", vertices.size()/3, vertexNormals.size()/3, faceVerts.size()/3); in.close(); } // createOBJDisplayList() ////////////////////////////////////////////////////// // // Simple helper function to create a display list for the loaded OBJ file. // //////////////////////////////////////////////////////////////////////////////// void createOBJDisplayList() { objectDisplayList = glGenLists(1); glNewList(objectDisplayList, GL_COMPILE); //glColor3f(1,1,1); glColor3f(0.8,0.8,0.8); glBegin(GL_TRIANGLES); for(int i = 0; i < faceVerts.size()/3; i++) { int idx1 = faceVerts.at(i*3+0)-1; int idx2 = faceVerts.at(i*3+1)-1; int idx3 = faceVerts.at(i*3+2)-1; int nIdx1 = faceVertNormals.at(i*3+0)-1; int nIdx2 = faceVertNormals.at(i*3+1)-1; int nIdx3 = faceVertNormals.at(i*3+2)-1; glNormal3f(vertexNormals.at(nIdx1*3+0), vertexNormals.at(nIdx1*3+1), vertexNormals.at(nIdx1*3+2)); glVertex3f(vertices.at(idx1*3+0), vertices.at(idx1*3+1), vertices.at(idx1*3+2)); glNormal3f(vertexNormals.at(nIdx2*3+0), vertexNormals.at(nIdx2*3+1), vertexNormals.at(nIdx2*3+2)); glVertex3f(vertices.at(idx2*3+0), vertices.at(idx2*3+1), vertices.at(idx2*3+2)); glNormal3f(vertexNormals.at(nIdx3*3+0), vertexNormals.at(nIdx3*3+1), vertexNormals.at(nIdx3*3+2)); glVertex3f(vertices.at(idx3*3+0), vertices.at(idx3*3+1), vertices.at(idx3*3+2)); } glEnd(); glEndList(); } // 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(); } // 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) { cameraTheta += (x - mouseX)*0.005; 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; 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.0f, 1.0f, 1.0f, 1.0f}; 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); glShadeModel(GL_SMOOTH); //glShadeModel(GL_FLAT); } // 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) { //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) float lPosition[4] = { 10, 10, 10, 1 }; glLightfv(GL_LIGHT0,GL_POSITION,lPosition); //clear the render buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //glDisable(GL_CULL_FACE); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); //draw some cubes floating around the model glPushMatrix(); glRotatef(glutGet(GLUT_ELAPSED_TIME)/50.0f,0,1,0); glUseProgram(0); glPushMatrix(); glTranslatef(2,0,2); glutSolidCube(0.5f); glPopMatrix(); glPushMatrix(); glTranslatef(-2,0,2); glutSolidCube(0.5f); glPopMatrix(); glPushMatrix(); glTranslatef(-2, 0, -2); glutSolidCube(0.5f); glPopMatrix(); glPushMatrix(); glTranslatef(2,0,-2); glutSolidCube(0.5f); glPopMatrix(); glPopMatrix(); //render the model normally, first. glUseProgram(0); GLfloat ambMat[4] = { 1.0f, 1.0f, 1.0f, 1.0f}; GLfloat diffMat[4] = { 1.0f, 1.0f, 1.0f, 1.0f}; glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambMat); glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffMat); glEnable(GL_NORMALIZE); glPushMatrix(); glTranslatef(0.0f,-1.5f,0.0f); glScalef(0.001,0.001,0.001); glCallList(objectDisplayList); glPopMatrix(); //then, render a screen-aligned quad and enable the shader. glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(-1,1,-1,1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glUseProgram(shaderProgramHandle); //don't forget to update the time variable! glUniform1f(timeLocation, glutGet(GLUT_ELAPSED_TIME)/1000.0f); glDisable(GL_CULL_FACE); glBegin(GL_QUADS); glVertex2f(-1,-1); glVertex2f( 1,-1); glVertex2f( 1, 1); glVertex2f(-1, 1); glEnd(); glEnable(GL_CULL_FACE); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); //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 == 'c' || key == 'C') useShader = !useShader; } // 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("noise.vert", vertexShaderString); readTextFile("noise.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); timeLocation = glGetUniformLocation(shaderProgramHandle, "time"); } void timer(int val) { glutTimerFunc(1000.0 / 24.0, timer, 0); glutPostRedisplay(); } // 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("just like a movie!"); 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(); //register callback functions... glutKeyboardFunc(normalKeys); glutDisplayFunc(renderScene); glutReshapeFunc(resizeWindow); glutMouseFunc(mouseCallback); glutMotionFunc(mouseMotion); //do some basic OpenGL setup initScene(); setupShaders(); loadOBJ("venus.obj"); createOBJDisplayList(); glutTimerFunc(1000.0 / 24.0, timer, 0); //and enter the GLUT loop, never to exit. glutMainLoop(); return(0); }