Example 27

Example 27

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.

ex_27/film_grain.png

Source Code

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

Files