JMU
Using Shaders
In "Modern" OpenGL


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Review
This Lecture
Loading Shaders
Loading Shaders (cont.)

The Specification

openglexamples/modern/fileUtilities.h
        #ifndef FILE_UTILITIES__H
#define FILE_UTILITIES_H
#include <string>
using namespace std;

/**
 * /file
 * Functions for working with files.
 */

/**
 * Read a file into a stringstream.
 *
 * @param fileName  The name of the file to read
 * @return          A string containing the contents of the file
 */
string readFile(const char* fileName);

#endif
        
Loading Shaders (cont.)

The Implementation

openglexamples/modern/fileUtilities.cpp
        #include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include "fileUtilities.h"


string readFile(const char* fileName) {
  string   contents;
  ifstream stream(fileName, ios::in);
  
  if(stream.is_open()){
    stringstream sstr;
    sstr << stream.rdbuf();
    contents = sstr.str();
    stream.close();
  }
  return contents;
}
        
Compiling Shaders
Compiling Shaders (cont.)

The Specification

openglexamples/modern/shaderUtilities.h (Fragment: buildShader)
        /**
 * Build a GLSL shader.
 *
 */
GLuint buildShader(const char* fileName, GLenum shaderType);
        
Compiling Shaders (cont.)

The Implementation

openglexamples/modern/shaderUtilities.cpp (Fragment: buildShader)
        GLuint buildShader(const char* fileName, GLenum shaderType) {
  // Create the handle/name/ID
  GLuint id = glCreateShader(shaderType);

  // Read the shader code from the file
  string code = readFile(fileName);
  if (code.size() == 0) return 0;

  // Compile the shader
  char const* codePointer = code.c_str();
  glShaderSource(id, 1, &codePointer , NULL);
  glCompileShader(id);

  // Check the shader
  GLint   ok = GL_FALSE;
  GLsizei messageLength = 0;
  glGetShaderiv(id, GL_COMPILE_STATUS, &ok);
  if (!ok) {
    glGetShaderiv(id, GL_INFO_LOG_LENGTH, &messageLength);
    GLchar errorMessage[messageLength + 1];
    glGetShaderInfoLog(id, messageLength, NULL, &errorMessage[0]);
    printf("%s\n", &errorMessage[0]);
  }
  return id;
}
        
Building Programs
Building Programs (cont.)

The Specification

openglexamples/modern/shaderUtilities.h (Fragment: buildProgram)
        /**
 * Build a GLSL proram.
 *
 * @param vertexFileName   The name of the file containg the vertex shader
 * @param fragmentFileName The name of the file containg the fragment shader
 */
GLuint buildProgram(const char* vertexFileName, const char* fragmentFileName);
        
Building Programs (cont.)

The Implementation

openglexamples/modern/shaderUtilities.cpp (Fragment: buildProgram)
        GLuint buildProgram(const char* vertexFile,
                    const char* fragmentFile){
  // Read and compile the shaders
  GLuint vertexShaderID = buildShader(vertexFile, GL_VERTEX_SHADER);
  if (vertexShaderID == 0) return 0;
  
  GLuint fragmentShaderID = buildShader(fragmentFile, GL_FRAGMENT_SHADER);
  if (fragmentShaderID == 0) return 0;

  // Link the program
  GLuint programID = glCreateProgram();
  glAttachShader(programID, vertexShaderID);
  glAttachShader(programID, fragmentShaderID);
  glLinkProgram(programID);

  // Check the program
  GLint   ok = GL_FALSE;
  GLsizei messageLength = 0;
  glGetProgramiv(programID, GL_LINK_STATUS, &ok);
  if (!ok) {
    glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &messageLength);
    GLchar errorMessage[messageLength + 1];
    glGetProgramInfoLog(programID, messageLength, NULL, &errorMessage[0]);
    printf("%s\n", &errorMessage[0]);
  }

  // Cleanup
  glDetachShader(programID, vertexShaderID);
  glDetachShader(programID, fragmentShaderID);
        
  glDeleteShader(vertexShaderID);
  glDeleteShader(fragmentShaderID);

  return programID;
}
        
The Application that Invokes the OpenGL Drawing Commands
  1. Initialize the environment (e.g., GLUT, GLEW, and OpenGL)
  2. Read the shaders and create the program
  3. Create the transformation matrices, lights, etc...
  4. Create the vertex stream
  5. Create the vertex buffer object
  6. In the display loop, make the data available to the shaders and invoke the drawing commands
The Complete Application (cont.)

Initializing the Environment

openglexamples/modern/model.cpp (Fragment: environment)
          // Initialize GLUT
  glutInit(&argc, argv);

  // Create the GLUT window
  width =  640;
  height = 480;
  window = createWindow("Using Shaders", width, height, 0, 0);

  // Initialize GLEW
  glewExperimental = GL_TRUE; 
  if (glewInit() != GLEW_OK) {
    printf("Failed to initialize GLEW\n");
    return -1;
  }

  // Register the callbacks
  glutCloseFunc(onClose);
  glutDisplayFunc(display);
        
The Complete Application (cont.)

Read the Shaders and Create the Program

openglexamples/modern/model.cpp (Fragment: program)
          // Process the command line arguments
  if (argc <= 1) {
    programID = buildProgram("lighting.vert", "lighting.frag");
  } else {
    programID = buildProgram(argv[1], argv[2]);
  }
  if (programID == 0) {
    printf("Problem creating the shader program\n");
    return -2;
  }
        
The Complete Application (cont.)

Create the Transformation Matrices, Lights etc...

openglexamples/modern/model.cpp (Fragment: init)
        /**
 * Initilization tasks.
 */
void init() {
  // Create a vertex array object (VAO)
  glGenVertexArrays(1, &vertexArrayID);
  glBindVertexArray(vertexArrayID);

  // Set the clear color to JMU purple
  glClearColor(0.2706f, 0.0000f, 0.5176f, 0.000f);

  // Get a handle for the uniform named "MVP" for the given program ID
  mvpMatrixID = glGetUniformLocation(programID, "MVP");

  // Define the projection matrix
  P = glm::ortho(-1.5f,1.5f,-1.5f,1.5f,0.0f,5.0f);
  
  // Define the camera matrix
  V = glm::lookAt(
    glm::vec3(0,-1,-2), // Camera is at (0,1,-2), in world space
    glm::vec3(0,0,0),   // and looks at the origin
    glm::vec3(0,-1,0)   // Head is down
  );
  
  // Define the model matrix
  M = glm::mat4(1.0f); // The identity matrix

  MVP = P * V * M; // Remember: matrix multiplication is "backwards"

  // Get a handle for the uniform named "V"
  viewMatrixID = glGetUniformLocation(programID, "V");

  // Get a handle for the uniform named "M"
  modelMatrixID = glGetUniformLocation(programID, "M");

  // Get a handle for the uniform named "LightPosition_worldspace"
  lightID = glGetUniformLocation(programID, "lightPosition_worldspace");

  // Define the position of the light
  lightPos = glm::vec3(4.0, 4.0, 4.0);

  // Enable depth testing
  glEnable(GL_DEPTH_TEST);
  
  // Accept fragment if it closer to the camera than the former one
  glDepthFunc(GL_LESS);

  // Cull each triangle whose normal is not towards the camera
  glEnable(GL_CULL_FACE);  
}
        
The Complete Application (cont.)

Create the Vertex Stream

openglexamples/modern/trifileUtilities.cpp
        #include <algorithm>
#include "trifileUtilities.h"


Vertex* read(const char* fileName, int& size)
{
  char                s[80];   
  double              x, y, z, nx, ny, nz;
  double              minX, maxX, minY, maxY, minZ, maxZ;
  FILE*               in;
  int                 br, bg, bb, fr, fg, fb, triangles;

  minX = minY = minZ =  10000000.0;
  maxX = maxY = maxZ = -10000000.0;
   
  in = fopen(fileName, "r");
   
  // Read the number of triangles
  fscanf(in, "%d", &triangles);

  // The number of vertices (3 per triangle)
  size = triangles * 3;
   
  Vertex* vertices = new Vertex[size];

  // Read the triangles
  for (int t=0; t<triangles; t++) {
    // The word "Triangle"
    fscanf(in, "%s",s);

    // Front face R,G,B and back face R,G,B
    fscanf(in, "%d %d %d %d %d %d",&fr,&fg,&fb,&br,&bg,&bb);

    // The three vertices
    for (int v=0; v<3; v++){
      int index = t*3 + v;

        
      fscanf(in, "%lf %lf %lf %lf %lf %lf", &x, &y, &z, &nx, &ny, &nz);
      vertices[index].position[0] = x;
      vertices[index].position[1] = y;
      vertices[index].position[2] = z;

      vertices[index].normal[0] = nx;
      vertices[index].normal[1] = ny;
      vertices[index].normal[2] = nz;

      if      (x < minX) minX = x;
      else if (x > maxX) maxX = x;

      if      (y < minY) minY = y;
      else if (y > maxY) maxY = y;

      if      (z < minZ) minZ = z;
      else if (z > maxZ) maxZ = z;
         
      vertices[index].color[0] = (GLfloat)fr / 255.;
      vertices[index].color[1] = (GLfloat)fg / 255.;
      vertices[index].color[2] = (GLfloat)fb / 255.;
    }
  }

  fclose(in);

  // Translate and scale
  double scale = std::min(2.0 / (maxX - minX), 2.0 / (maxY - minY));
   
  double tx = -(maxX + minX)/2.0;
  double ty = -(maxY + minY)/2.0;
   
  for (int t=0; t<triangles; t++) {
    for (int v=0; v<3; v++){
      int index = t*3 + v;
       
      vertices[index].position[0] = (vertices[index].position[0] + tx) * scale;
      vertices[index].position[1] = (vertices[index].position[1] + ty) * scale;
      vertices[index].position[2] = vertices[index].position[2] * scale;
    }
  }
   
  return &vertices[0];
}

        
The Complete Application (cont.)

Create the Vertex Buffer Object

openglexamples/modern/model.cpp (Fragment: vbo)
          // Create the VBO
  glGenBuffers(1, &vertexBufferID);
  glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
  glBufferData(GL_ARRAY_BUFFER, size * sizeof(Vertex),
               vertices, GL_STATIC_DRAW);
        
The Complete Application (cont.)

The Display Loop

openglexamples/modern/model.cpp (Fragment: display)
        /**
 * The display callback
 */
void display() {
  glClear(GL_COLOR_BUFFER_BIT);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Use the appropriate shaders
  glUseProgram(programID);
  
  // Define uniforms for the currently bound shader
  glUniformMatrix4fv(mvpMatrixID, 1, GL_FALSE, &MVP[0][0]);
  glUniformMatrix4fv(modelMatrixID, 1, GL_FALSE, &M[0][0]);
  glUniformMatrix4fv(viewMatrixID, 1, GL_FALSE, &V[0][0]);
  glUniform3f(lightID, lightPos.x, lightPos.y, lightPos.z);
  
  // Describe the positions in the vertex array buffer
  glEnableVertexAttribArray(0);                                   
  glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);                    
  glVertexAttribPointer(                                          
      0,               // attribute identifier (used in the shader)
      3,               // size                             
      GL_FLOAT,        // type                             
      GL_FALSE,        // normalized or not?                      
      sizeof(Vertex),  // stride
      reinterpret_cast<void*>(offsetof(Vertex, position)) // offset      
  );                                                              

  // Describe the colors in the vertex array buffer
  glEnableVertexAttribArray(1);
  glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
  glVertexAttribPointer(
    1,
    3,
    GL_FLOAT,
    GL_FALSE,
    sizeof(Vertex),       
    reinterpret_cast<void*>(offsetof(Vertex, color))      
  );

  // Describe the normals in the vertex array buffer
  glEnableVertexAttribArray(2);
  glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
  glVertexAttribPointer(
     2,
     3,
     GL_FLOAT,
     GL_FALSE,
     sizeof(Vertex),       
     reinterpret_cast<void*>(offsetof(Vertex, normal))      
  );
  
  // Draw the triangles
  glDrawArrays(GL_TRIANGLES, 0, size);

  // Move the off-screen buffer to the screen
  glFlush();
}