Control Keys

move to next slide (also Enter or Spacebar).
move to previous slide.
 d  enable/disable drawing on slides
 p  toggles between print and presentation view
CTRL  +  zoom in
CTRL  -  zoom out
CTRL  0  reset zoom

Slides can also be advanced by clicking on the left or right border of the slide.

Notation

Type Font Examples
Variables (scalars) italics $a, b, x, y$
Functions upright $\mathrm{f}, \mathrm{g}(x), \mathrm{max}(x)$
Vectors bold, elements row-wise $\mathbf{a}, \mathbf{b}= \begin{pmatrix}x\\y\end{pmatrix} = (x, y)^\top,$ $\mathbf{B}=(x, y, z)^\top$
Matrices Typewriter $\mathtt{A}, \mathtt{B}= \begin{bmatrix}a & b\\c & d\end{bmatrix}$
Sets calligraphic $\mathcal{A}, B=\{a, b\}, b \in \mathcal{B}$
Number systems, Coordinate spaces double-struck $\mathbb{N}, \mathbb{Z}, \mathbb{R}^2, \mathbb{R}^3$

OpenGL Shading Language

  • The OpenGL Shading Language (GLSL) allows writing custom programs for vertex and fragment processors
  • The custom GLSL programs thereby replace parts of the OpenGL pipeline that were previously performed by the fixed-function pipeline
  • The per-vertex-operations are partially replaced by a vertex shader and the per-pixel-operations by a fragment shader

Reiteration: OpenGL-Pipeline

openglpipeline
openglpipeline
openglpipeline
openglpipeline
openglpipeline
openglpipeline
openglpipeline
Source: based on Mark Segal, Kurt Akeley, The OpenGL Graphics System: A Specification Version 2.0, 2004, Figure 2.1. Block diagram of the GL (modified)

Vertex Shader

pervertex_fixedfunction_vs_shader

Fragment Shader

perfragment_fixedfunction_vs_shader
Source: based on Mark Segal, Kurt Akeley, The OpenGL Graphics System: A Specification Version 2.0, 2004, Abb. 3.1 und 4.1. (modified)

Parallel Processing

  • In the vertex and fragment shaders the data is processed in parallel
  • Today's GPUs have up to 2600 processing units that can perform parallel calculations on the data
  • For all data exactly the same shader code is executed in parallel ("Stream Processing")
  • The parallel processing is possible because the operations have a defined goal (either a transformed vertex or fragment), which can be written without conflicts

Creation of Shader Programs in OpenGL

  • The shader programs are compiled at runtime and passed to the graphics card
  • To create a shader program that may contain vertex and fragment shaders, the following command is used
    progID = glCreateProgram();
  • Creating, assigning, and compiling of shader code for a vertex and fragment Shader is done with:
    vertID = glCreateShader(GL_VERTEX_SHADER);
    fragID = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(vertID, 1, &vertShaderSrcCodeString, &vStrLength);
    glShaderSource(fragID, 1, &fragShaderSrcCodeString, &fStrLength);
    glCompileShader(vertID);
    glCompileShader(fragID);
  • Then the shaders are assigned to the program and an executable is generated:
    glAttachShader(progID, vertID);
    glAttachShader(progID, fragID);
    glLinkProgram(progID);
    

Using Shader Programs in OpenGL

  • To activate a shader program the following command is used
    glUseProgram(progID);
  • In this case, OpenGL behaves again as a state-machine,i.e., all subsequent rendering function are using the currently active shader program
  • This also means that swapping between different shader programs at runtime is possible (and often occurs in practice). For example, different shaders are used to simulate different materials.

Deleting Shader Objects

  • For deletion the following functions are used:
    glDeleteProgram(progID);
    glDeleteShader(vertID);
    glDeleteShader(fragID);

Input and Output of a Vertex Shader

vertexshader_inout
Source: based on Randi J. Rost, Bill Licea-Kane. OpenGL Shading Language, Third Edition, 2010 sowie
John Kessenich (Dave Baldwin, Randi Rost). The OpenGL Shading Language
Language Version: 1.40, Chapter 7, 2009 (modified)

Input and Output of a Fragment Shader

fragmentshader_inout
Source: based on Randi J. Rost, Bill Licea-Kane. OpenGL Shading Language, Third Edition, 2010 sowie
John Kessenich (Dave Baldwin, Randi Rost). The OpenGL Shading Language
Language Version: 1.40, Chapter 7, 2009 (modified)

GLSL Syntax

  • The syntax of GLSL is very similar to C (with some elements of C++)
  • Data types for floating point numbers:
    float, vec2, vec3, vec4, mat2, mat3, mat4
  • Integer data types:
    int, ivec2, ivec3, ivec4
  • Boolean data types:
    bool, bvec2, bvec3, bvec4
  • Sampler data types for accessing textures:
    sampler2D, sampler3D, samplerCube, ...

GLSL Syntax

  • Struct statements (as known from C) are permitted:
    struct Vertex { 
      float val1; 
      int val2; 
      bool val3;
    };
  • Arrays (as known from C) are also possible:
    float a[16];
  • Data types must be explicitly converted
    float a = float(1);

GLSL Syntax

  • Much as in C:
    • Conditions: if, if()else
    • Loops: for, while, do{}while()
    • Termination: return, break, continue
  • But not everything is allowed:
    • No pointers
    • No strings
    • No unsigned, byte, short, long types, no union
    • No switch()case

Construction and Usage of Vectors

// construction
vec2 a = vec2(1.0, 2.0);
vec3 b = vec3(1.0,-1.0, 1.0);
vec4 c = vec4(1.0, 2.0, 3.0, 4.0);
vec4 allOne = vec4(1.0); 
vec4 d = vec4(a, a);
vec4 e = vec4(b, 1.0);
// accessing elements
float f = b[1]; // is -1.0
float g = b.y;  // is -1.0 as well
//conversion 
d = c.rgba;  // stays a 4-vector
b = c.xyz;   // convert to 3-vector
a = c.xy;    // convert to 2-vector
e = c.xxyy;  // e = (1.0, 1.0, 2.0, 2.0)  "swizzling"
e.xz = vec2(0.5, 0.6); // e = (0.5, 1.0, 0.6, 2.0)

Construction and Usage of Matrices

// construction
mat4 a = mat4(0.0, 0.1, 0.2, 0.3,   // column major order:
              0.4, 0.5, 0.6, 0.7,   // transposed of the
              0.8, 0.9, 1.0, 1.1,   // usual interpretation
              1.2, 1.3, 1.4, 1.5); 
mat3 b = mat3(1.0); // identity matrix
// conversion
mat3 c = mat3(a);   // upper 3x3 matrix
vec4 d = a[1];      // d = (0.4, 0.5, 0.6, 0.7)
// operations
vec4 e = a * vec4(1.0);
mat4 f = 2.0 * a - mat4(1.0);

Example: A First Triangle with GLSL

opengl_firstshader

Example: A First Triangle with GLSL

class Renderer {

private:
  struct Vertex
  {
    float position[3];
    float color[4];
  };

private:
  enum {Triangle, numVAOs};
  enum {TriangleAll, numVBOs};
  GLuint vaoID[numVAOs];
  GLuint bufID[numVBOs];
  int triangleVertNo;
  GLuint progID;
  GLuint vertID;
  GLuint fragID;
  GLint vertexLoc;
  GLint colorLoc;

public:
  // constructor
  Renderer() : triangleVertNo(0), progID(0), vertID(0),
               fragID(0), vertexLoc(-1), colorLoc(-1) {}
  //destructor
  ~Renderer() {
    glDeleteVertexArrays(numVAOs, vaoID);
    glDeleteBuffers(numVBOs, bufID);
    glDeleteProgram(progID);
    glDeleteShader(vertID);
    glDeleteShader(fragID);
  }

public:
  void init() {
    initExtensions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);

    setupShaders();

    // create a Vertex Array Objects (VAO)
    glGenVertexArrays(numVAOs, vaoID);

    // generate a Vertex Buffer Object (VBO)
    glGenBuffers(numVBOs, bufID);

    // binding the Triangle VAO
    glBindVertexArray(vaoID[Triangle]);

    float triangleVertexData[] = {
       0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
      -0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
       0.5f,-0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
    };
    triangleVertNo = 3;

    glBindBuffer(GL_ARRAY_BUFFER, bufID[TriangleAll]);
    glBufferData(GL_ARRAY_BUFFER, triangleVertNo*sizeof(Vertex),
                 triangleVertexData, GL_STATIC_DRAW);

    int stride = sizeof(Vertex);
    char *offset = (char*)NULL;

    // position
    if(vertexLoc != -1) {
      glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, 
                            GL_FALSE, stride, offset);
      glEnableVertexAttribArray(vertexLoc);
    }

    // color
    if(colorLoc != -1) {
      offset = (char*)NULL + 3*sizeof(float);
      glVertexAttribPointer(colorLoc, 4, GL_FLOAT, 
                            GL_FALSE, stride, offset);
      glEnableVertexAttribArray(colorLoc);
    }
  }

  void setupShaders() {

    // create shader
    vertID = glCreateShader(GL_VERTEX_SHADER);
    fragID = glCreateShader(GL_FRAGMENT_SHADER);

    // load shader source from file
    std::string vs = loadShaderSrc("./first.vert");
    const char* vss = vs.c_str();

    std::string fs = loadShaderSrc("./first.frag");
    const char* fss = fs.c_str();

    // specify shader source
    glShaderSource(vertID, 1, &(vss), NULL);
    glShaderSource(fragID, 1, &(fss), NULL);

    // compile the shader
    glCompileShader(vertID);
    glCompileShader(fragID);

    // check for errors
    printShaderInfoLog(vertID);
    printShaderInfoLog(fragID);

    // create program and attach shaders
    progID = glCreateProgram();
    glAttachShader(progID, vertID);
    glAttachShader(progID, fragID);

    // "outColor" is a user-provided OUT variable
    // of the fragment shader.
    // Its output is bound to the first color buffer
    // in the framebuffer
    glBindFragDataLocation(progID, 0, "outputColor");

    // link the program
    glLinkProgram(progID);
    // output error messages
    printProgramInfoLog(progID);

    // "inputPosition" and "inputColor" are user-provided
    // IN variables of the vertex shader.
    // Their locations are stored to be used later with
    // glEnableVertexAttribArray()
    vertexLoc = glGetAttribLocation(progID,"inputPosition");
    colorLoc = glGetAttribLocation(progID, "inputColor");

  }

  void resize(int w, int h) {
    glViewport(0, 0, w, h);
  }

  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(progID);

    // bind Triangle VAO
    glBindVertexArray(vaoID[Triangle]);
    // render data
    glDrawArrays(GL_TRIANGLES, 0, triangleVertNo);
  }
  
private:
  void printShaderInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetShaderInfoLog(obj, infoLogLength, 
                         &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  void printProgramInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetProgramInfoLog(obj, infoLogLength, 
                          &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  std::string loadShaderSrc(const std::string& filename) {
    std::ifstream is(filename);
    if (is.is_open()) {
      std::stringstream buffer;
      buffer << is.rdbuf();
      return buffer.str();
    }
    cerr << "Unable to open file " << filename << endl;
    exit(1);
  }
};

Example: A First Triangle with GLSL (Shader Code)

  • Vertex Shader:
    #version 140
    in vec3 inputPosition;
    in vec4 inputColor;
    out vec3 forFragColor;
    void main(){
        forFragColor = inputColor.rgb;
        gl_Position =  vec4(inputPosition, 1.0);
    }
  • Fragment Shader:
    #version 140
    in vec3 forFragColor;
    out vec4 outputColor;
    void main() {
        outputColor = vec4(forFragColor,1.0);
    }

Passing of Vertex Attributes

  • Per-Vertex attributes can be passed to a vertex shader using in variables
  • Distinctions can be made regarding special in variables, which are automatically generated, and user-defined in variables, which must be passed by the application
  • The vertex data is transferred to the graphics card via Vertex Buffer Objects (VBOs)

Passing of Vertex Attributes

To use the data of VBOs for the used-defined in variables, the usual procedure will be as follows:

  • With glGetAttribLocation a location identifier for an in variable is queried. For example, when the variable in the shader code is called "inputColor"
    int colorLoc = glGetAttribLocation(progID, "inputColor");
  • The function glVertexAttribPointer(colorLoc, size, type, normalized, stride, offset) then defines the mapping between an in variable and VBO attribute
  • Finally, the attribute needs to be activated by
    glEnableVertexAttribArray(colorLoc);
  • In a subsequent call to glDrawArray or glDrawElements with activated VBO the in variable of the vertex shader is then filled with the attribute data for each drawn vertex

Data Input and Output in the Fragment Shader

  • User-defined out variables of the vertex shader are interpolated in the rasterizer and the interpolated values are supplied as in variables to the fragment shader
  • The out variables of the fragment shader will be sent to the framebuffer
  • For example, if the out variable "outputColor" should be written into the first color buffer of the framebuffer, this can be achieved by
    glBindFragDataLocation(progID, 0, "outputColor");

Uniform Variables

  • In addition to in variables, uniform variables are another way to pass data to a shader
  • In contrast to the in variables that contain the vertex attributes, the uniform variables include data that remains constant for all vertices during a call to glDrawArray or glDrawElements

Uniform Variables

  • The location identifier of a uniform variable can be queried by
    paraLoc = glGetUniformLocation(progID, "parameter");
  • Afterwards, the uniform variable can be set with
    glUniform1i(paraLoc, 123);
  • or, if the variable is a float-vector
    glUniform3fv(threeVectorLoc, 1, threeVector);
  • and in case of a matrix
    glUniformMatrix4fv(modelviewLoc, 1, transpose, modelview);

Example: Passing the Transformation Matrices As Uniforms

opengl_shaderuniform

Example: Passing the Transformation Matrices As Uniforms

class Renderer {

private:
  struct Vertex {
    float position[3];
    float color[4];
  };

public:
  float t;

private:
  enum {Triangle, numVAOs};
  enum {TriangleAll, numVBOs};
  GLuint vaoID[numVAOs];
  GLuint bufID[numVBOs];
  int triangleVertNo;
  GLuint progID;
  GLuint vertID;
  GLuint fragID;
  GLuint vertexLoc;
  GLuint colorLoc;
  GLuint projectionLoc;
  GLuint modelviewLoc;
  float projection[16];  // projection matrix
  float modelview[16];  // modelview matrix

public:
  // constructor
  Renderer() : t(0.0f), triangleVertNo(0), progID(0), vertID(0), fragID(0),
               vertexLoc(-1), colorLoc(-1), projectionLoc(-1), modelviewLoc(-1)
               {}
  //destructor
  ~Renderer() {
    glDeleteVertexArrays(numVAOs, vaoID);
    glDeleteBuffers(numVBOs, bufID);
    glDeleteProgram(progID);
    glDeleteShader(vertID);
    glDeleteShader(fragID);
  }

public:
  void init() {
    initExtensions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);

    setupShaders();

    // create a Vertex Array Objects (VAO)
    glGenVertexArrays(numVAOs, vaoID);

    // generate a Vertex Buffer Object (VBO)
    glGenBuffers(numVBOs, bufID);

    // binding the Triangle VAO
    glBindVertexArray(vaoID[Triangle]);

    float triangleVertexData[] = {
       0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
      -0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
       0.5f,-0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
    };
    triangleVertNo = 3;

    glBindBuffer(GL_ARRAY_BUFFER, bufID[TriangleAll]);
    glBufferData(GL_ARRAY_BUFFER, triangleVertNo*sizeof(Vertex),
                 triangleVertexData, GL_STATIC_DRAW);

    int stride = sizeof(Vertex);
    char *offset = (char*)NULL;

    // position
    if(vertexLoc != -1) {
      glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, 
      GL_FALSE, stride, offset);
      glEnableVertexAttribArray(vertexLoc);
    }

    // color
    if(colorLoc != -1) {
      offset = (char*)NULL + 3*sizeof(float);
      glVertexAttribPointer(colorLoc, 4, GL_FLOAT, 
      GL_FALSE, stride, offset);
      glEnableVertexAttribArray(colorLoc);
    }
  }

  void setupShaders() {

    // create shader
    vertID = glCreateShader(GL_VERTEX_SHADER);
    fragID = glCreateShader(GL_FRAGMENT_SHADER);

    // load shader source from file
    std::string vs = loadShaderSrc("./uniform.vert");
    const char* vss = vs.c_str();

    std::string fs = loadShaderSrc("./uniform.frag");
    const char* fss = fs.c_str();

    // specify shader source
    glShaderSource(vertID, 1, &(vss), NULL);
    glShaderSource(fragID, 1, &(fss), NULL);

    // compile the shader
    glCompileShader(vertID);
    glCompileShader(fragID);

    // check for errors
    printShaderInfoLog(vertID);
    printShaderInfoLog(fragID);

    // create program and attach shaders
    progID = glCreateProgram();
    glAttachShader(progID, vertID);
    glAttachShader(progID, fragID);

    // "outColor" is a user-provided OUT variable
    // of the fragment shader.
    // Its output is bound to the first color buffer
    // in the framebuffer
    glBindFragDataLocation(progID, 0, "outputColor");

    // link the program
    glLinkProgram(progID);
    // output error messages
    printProgramInfoLog(progID);

    // "inputPosition" and "inputColor" are user-provided
    // IN variables of the vertex shader.
    // Their locations are stored to be used later with
    // glEnableVertexAttribArray()
    vertexLoc = glGetAttribLocation(progID,"inputPosition");
    colorLoc = glGetAttribLocation(progID, "inputColor");

    // "projection" and "modelview" are user-provided
    // UNIFORM variables of the vertex shader.
    // Their locations are stored to be used later
    projectionLoc = glGetUniformLocation(progID, "projection");
    modelviewLoc = glGetUniformLocation(progID, "modelview");

  }

  void resize(int w, int h) {
    glViewport(0, 0, w, h);

    // this function replaces gluPerspective
    mat4Perspective(projection, 45.0f, (float)w/(float)h, 0.5f, 4.0f);
    //mat4Print(projection);
  }

  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // camera orbits in the y=2 plane
    // and looks at the origin
    // mat4LookAt replaces gluLookAt
    double rad = M_PI / 180.0f * t;
    mat4LookAt(modelview,
               2.0f*cos(rad), 2.0f, 2.0f*sin(rad), // eye
               0.0f, 0.0f, 0.0f, // look at
               0.0f, 1.0f, 0.0f); // up
  
    glUseProgram(progID);
    // load the current projection and modelview matrix into the
    // corresponding UNIFORM variables of the shader
    glUniformMatrix4fv(projectionLoc, 1, false, projection);
    glUniformMatrix4fv(modelviewLoc, 1, false, modelview);

    // bind Triangle VAO
    glBindVertexArray(vaoID[Triangle]);
    // render data
    glDrawArrays(GL_TRIANGLES, 0, triangleVertNo);
  }
private:
  void printShaderInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetShaderInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  void printProgramInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetProgramInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  std::string loadShaderSrc(const std::string& filename) {
    std::ifstream is(filename);
    if (is.is_open()) {
      std::stringstream buffer;
      buffer << is.rdbuf();
      return buffer.str();
    }
    cerr << "Unable to open file " << filename << endl;
    exit(1);
  }

  // the following functions are some matrix and vector helpers
  // they work for this demo but in general it is recommended
  // to use more advanced matrix libraries,
  // e.g. OpenGL Mathematics (GLM)
  float vec3Dot( float *a, float *b) {
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
  }

  void vec3Cross( float *a, float *b, float *res) {
    res[0] = a[1] * b[2]  -  b[1] * a[2];
    res[1] = a[2] * b[0]  -  b[2] * a[0];
    res[2] = a[0] * b[1]  -  b[0] * a[1];
  }

  void vec3Normalize(float *a) {
    float mag = sqrt(a[0] * a[0]  +  a[1] * a[1]  +  a[2] * a[2]);
    a[0] /= mag; a[1] /= mag; a[2] /= mag;
  }

  void mat4Identity( float *a) {
    for (int i = 0; i < 16; ++i) a[i] = 0.0f;
    for (int i = 0; i < 4; ++i) a[i + i * 4] = 1.0f;
  }

  void mat4Multiply(float *a, float *b, float *res) {
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        res[j*4 + i] = 0.0f;
        for (int k = 0; k < 4; ++k) {
          res[j*4 + i] += a[k*4 + i] * b[j*4 + k];
        }
      }
    }
  }

  void mat4Perspective(float *a, float fov, float aspect, float zNear, float zFar) {
    float f = 1.0f / float(tan (fov/2.0f * (M_PI / 180.0f)));
    mat4Identity(a);
    a[0] = f / aspect;
    a[1 * 4 + 1] = f;
    a[2 * 4 + 2] = (zFar + zNear)  / (zNear - zFar);
    a[3 * 4 + 2] = (2.0f * zFar * zNear) / (zNear - zFar);
    a[2 * 4 + 3] = -1.0f;
    a[3 * 4 + 3] = 0.0f;
  }

  void mat4LookAt(float *viewMatrix,
                  float eyeX, float eyeY, float eyeZ,
                  float centerX, float centerY, float centerZ,
                  float upX, float upY, float upZ) {

    float dir[3], right[3], up[3], eye[3];
    up[0]=upX; up[1]=upY; up[2]=upZ;
    eye[0]=eyeX; eye[1]=eyeY; eye[2]=eyeZ;

    dir[0]=centerX-eyeX; dir[1]=centerY-eyeY; dir[2]=centerZ-eyeZ;
    vec3Normalize(dir);
    vec3Cross(dir,up,right);
    vec3Normalize(right);
    vec3Cross(right,dir,up);
    vec3Normalize(up);
    // first row
    viewMatrix[0]  = right[0];
    viewMatrix[4]  = right[1];
    viewMatrix[8]  = right[2];
    viewMatrix[12] = -vec3Dot(right, eye);
    // second row
    viewMatrix[1]  = up[0];
    viewMatrix[5]  = up[1];
    viewMatrix[9]  = up[2];
    viewMatrix[13] = -vec3Dot(up, eye);
    // third row
    viewMatrix[2]  = -dir[0];
    viewMatrix[6]  = -dir[1];
    viewMatrix[10] = -dir[2];
    viewMatrix[14] =  vec3Dot(dir, eye);
    // forth row
    viewMatrix[3]  = 0.0f;
    viewMatrix[7]  = 0.0f;
    viewMatrix[11] = 0.0f;
    viewMatrix[15] = 1.0f;
  }
  void mat4Print(float* a) {
    // opengl uses column major order
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        cout << a[j * 4 + i] << " ";
      }
      cout << endl;
    }
  }
};

Example: Passing the Transformation Matrices As Uniforms

  • Vertex Shader:
    #version 140
    in vec3 inputPosition;
    in vec4 inputColor;
    uniform mat4 projection;
    uniform mat4 modelview;
    out vec3 forFragColor;
     
    void main(){
        forFragColor = inputColor.rgb;
        gl_Position =  projection * modelview * vec4(inputPosition, 1.0);
    }

Example: Passing the Transformation Matrices As Uniforms

  • Fragment Shader:
    #version 140
    in vec3 forFragColor;
    out vec4 outputColor;
     
    void main() {
        outputColor = vec4(forFragColor,1.0);
    }

Transformation of Surface Normals

  • The modelview matrix transforms vertices from the local coordinate system to the camera coordinate system
  • A later application of illumination models requires that the surface normal of each vertex is also transformed from the local coordinate system to the camera coordinate system
  • So far, the fixed-function pipeline has taken care of the transformation of normals. With shaders, this is now also a task of the programmer.

Transformation of Surface Normals

  • When transforming surface normals, it should be considered that a normal is a unit vector that is neither translated nor scaled during a transformation with the modelview matrix $\mathtt{T}_{\mathrm{\small modelview}}$. It is only rotated.
  • Problem: How can the transformation matrix $\mathtt{T}_{\mathrm{\small normal}}$ of the normal be calculated from the modelview matrix $\mathtt{T}_{\mathrm{\small modelview}}$ ?
local2cam2
$\mathbf{V}_1$
$\mathbf{V}_2$
$\tilde{\mathbf{V}}_2$
$\tilde{\mathbf{V}}_1$
$\mathtt{T}_{\mathrm{\small modelview}}$
$\mathbf{N}$
$\tilde{\mathbf{N}}$
$\tilde{\mathbf{N}}'= \mathtt{T}_{\mathrm{\small normal}} \mathbf{N}$
$\mathbf{M}$
$\tilde{\mathbf{M}}$
Camera coordinate system
Local coordinate system

Transformation of Surface Normals

  • Observation: In the local coordinate system the normal $\mathbf{N}=(n_x,n_y,n_z,0)^{\top}$ is orthogonal to the tangent $\mathbf{M}=(m_x,m_y,m_z,0)^{\top}$. Because of the transformation with $\mathtt{T}_{\mathrm{\small modelview}}$ the normal is mapped wrongly, but the tangent is still pointing in the right direction
  • Approach: Before the transformation, it holds that $\mathbf{N}^{\top}\mathbf{M} = 0$ and this relationship should be maintained after the transformation, therefore:
    $\begin{align} \tilde{\mathbf{N}}'^{\top} \tilde{\mathbf{M}} &\stackrel{!}{=} 0 \\ (\mathtt{T}_{\mathrm{\small normal}} \mathbf{N})^{\top} ( \mathtt{T}_{\mathrm{\small modelview}} \mathbf{M} ) &= 0\\ \mathbf{N}^{\top} (\mathtt{T}_{\mathrm{\small normal}})^{\top} \mathtt{T}_{\mathrm{\small modelview}} \mathbf{M} &= 0 \end{align}$

  • Thus, for $(\mathtt{T}_{\mathrm{\small normal}})^{\top} \mathtt{T}_{\mathrm{\small modelview}}=\mathtt{I}_{4 \times 4}$ the condition is satisfied and it follows that
    $\begin{align} (\mathtt{T}_{\mathrm{\small normal}})^{\top} \mathtt{T}_{\mathrm{\small modelview}} &=\mathtt{I}_{4 \times 4}\\ (\mathtt{T}_{\mathrm{\small normal}})^{\top} &= (\mathtt{T}_{\mathrm{\small modelview}})^{-1}\\ \mathtt{T}_{\mathrm{\small normal}} &= (\mathtt{T}_{\mathrm{\small modelview}})^{-\top}\\ \end{align}$

Example: Transformation of Surface Normals

opengl_shadernormaltrans

Example: Transformation of Surface Normals

class Renderer {

private:
  struct Vertex {
    float position[3];
    float texCoord[2];
    float normal[3];
  };

public:
  float t;
  int modeVal;
private:
  enum {Scene, numVAOs};
  enum {SceneAll, numVBOs};
  GLuint vaoID[numVAOs];
  GLuint bufID[numVBOs];
  int sceneVertNo;
  GLuint progID;
  GLuint vertID;
  GLuint fragID;
  GLint vertexLoc;
  GLint texCoordLoc;
  GLint normalLoc;
  GLint projectionLoc;
  GLint modelviewLoc;
  GLint normalMatrixLoc;
  GLint modeLoc;
  float projection[16];  // projection matrix
  float modelview[16];  // modelview matrix

public:
  // constructor
  Renderer() : t(0.0), modeVal(1), sceneVertNo(0), progID(0), 
               vertID(0), fragID(0),
               vertexLoc(-1), texCoordLoc(-1), normalLoc(-1),
               projectionLoc(-1), modelviewLoc(-1), 
               normalMatrixLoc(-1), modeLoc(-1)
               {}
  //destructor
  ~Renderer() {
    glDeleteVertexArrays(numVAOs, vaoID);
    glDeleteBuffers(numVBOs, bufID);
    glDeleteProgram(progID);
    glDeleteShader(vertID);
    glDeleteShader(fragID);
  }

public:
  void init() {
    initExtensions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);

    setupShaders();

    // create a Vertex Array Objects (VAO)
    glGenVertexArrays(numVAOs, vaoID);

    // generate a Vertex Buffer Object (VBO)
    glGenBuffers(numVBOs, bufID);

    // binding the pyramid VAO
    glBindVertexArray(vaoID[Scene]);

    std::vector <float> data;
    int perVertexFloats = (3+2+3);
    loadVertexData(string("teapot.vbo"), data, perVertexFloats);

    sceneVertNo = int(data.size()) / perVertexFloats;

    glBindBuffer(GL_ARRAY_BUFFER, bufID[SceneAll]);
    glBufferData(GL_ARRAY_BUFFER, sceneVertNo*sizeof(Vertex),
                 &data[0], GL_STATIC_DRAW);

    int stride = sizeof(Vertex);
    char *offset = (char*)NULL;

    // position
    if(vertexLoc != -1) {
      glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, GL_FALSE, 
                            stride, offset);
      glEnableVertexAttribArray(vertexLoc);
    }

    // texCoord
    if(texCoordLoc != -1) {
      offset = (char*)NULL + 3*sizeof(float);
      glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 
                            stride, offset);
      glEnableVertexAttribArray(texCoordLoc);
    }

    // normal
    if(normalLoc != -1) {
      offset = (char*)NULL + (3+2)*sizeof(float);
      glVertexAttribPointer(normalLoc, 3, GL_FLOAT, GL_FALSE, 
                            stride, offset);
      glEnableVertexAttribArray(normalLoc);
    }

  }

  void setupShaders() {

    // create shader
    vertID = glCreateShader(GL_VERTEX_SHADER);
    fragID = glCreateShader(GL_FRAGMENT_SHADER);

    // load shader source from file
    std::string vs = loadShaderSrc("./pass.vert");
    const char* vss = vs.c_str();
    std::string fs = loadShaderSrc("./pass.frag");
    const char* fss = fs.c_str();

    // specify shader source
    glShaderSource(vertID, 1, &(vss), NULL);
    glShaderSource(fragID, 1, &(fss), NULL);

    // compile the shader
    glCompileShader(vertID);
    glCompileShader(fragID);

    // check for errors
    printShaderInfoLog(vertID);
    printShaderInfoLog(fragID);

    // create program and attach shaders
    progID = glCreateProgram();
    glAttachShader(progID, vertID);
    glAttachShader(progID, fragID);

    // "outColor" is a user-provided OUT variable
    // of the fragment shader.
    // Its output is bound to the first color buffer
    // in the framebuffer
    glBindFragDataLocation(progID, 0, "outputColor");

    // link the program
    glLinkProgram(progID);
    // output error messages
    printProgramInfoLog(progID);

    // retrieve the location of the IN variables of the vertex shader.
    vertexLoc = glGetAttribLocation(progID,"inputPosition");
    texCoordLoc = glGetAttribLocation(progID,"inputTexCoord");
    normalLoc = glGetAttribLocation(progID, "inputNormal");

    // retrieve the location of the UNIFORM variables of the vertex shader.
    projectionLoc = glGetUniformLocation(progID, "projection");
    modelviewLoc = glGetUniformLocation(progID, "modelview");
    normalMatrixLoc = glGetUniformLocation(progID, "normalMat");
    modeLoc = glGetUniformLocation(progID, "mode");
  }

  void resize(int w, int h) {
    glViewport(0, 0, w, h);

    // this function replaces gluPerspective
    mat4Perspective(projection, 30.0f, (float)w/(float)h, 0.5f, 4.0f);
    // mat4Print(projection);
  }

  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // camera orbits in the z=2 plane
    // and looks at the origin
    // mat4LookAt replaces gluLookAt
    double rad = M_PI / 180.0f * t;
    mat4LookAt(modelview,
               1.5f*cos(rad), 1.5f*sin(rad), 1.5f, // eye
               0.0f, 0.0f, 0.0f, // look at
               0.0f, 0.0f, 1.0f); // up


    float modelviewInv[16], normalmatrix[16];
    mat4Invert(modelview, modelviewInv);
    mat4Transpose(modelviewInv, normalmatrix);

    glUseProgram(progID);
    // load the current projection and modelview matrix into the
    // corresponding UNIFORM variables of the shader
    glUniformMatrix4fv(projectionLoc, 1, false, projection);
    glUniformMatrix4fv(modelviewLoc, 1, false, modelview);
    glUniformMatrix4fv(normalMatrixLoc, 1, false, normalmatrix);
    glUniform1i(modeLoc, modeVal);

    // bind Triangle VAO
    glBindVertexArray(vaoID[Scene]);
    // render data
    glDrawArrays(GL_TRIANGLES, 0, sceneVertNo);
  }
private:
  void printShaderInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetShaderInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  void printProgramInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetProgramInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  std::string loadShaderSrc(const std::string& filename) {
    std::ifstream is(filename);
    if (is.is_open()) {
      std::stringstream buffer;
      buffer << is.rdbuf();
      return buffer.str();
    }
    cerr << "Unable to open file " << filename << endl;
    exit(1);
  }

  bool loadVertexData(std::string &filename, std::vector<float> &data, 
                      unsigned perVertexFloats) 
  {
    // read vertex data from file
    ifstream input(filename.c_str());
    if(!input) {
      QMessageBox msgBox;
      msgBox.setText("Can not find vertex data file");
      msgBox.exec();
      return false;
    }

    int numFloats;
    double vertData;
    if(input >> numFloats) {
      if(numFloats > 0) {
        data.resize(numFloats);
        int i = 0;
        while(input >> vertData && i < numFloats) {
          // store it in the vector
          data[i] = float(vertData);
          i++;
        }
        if(i != numFloats || numFloats % perVertexFloats) return false;
      }
    }else{
      return false;
    }
    return true;
  }

  // the following functions are some matrix and vector helpers,
  // which work for this demo but in general it is recommended
  // to use more advanced matrix libraries,
  // e.g. OpenGL Mathematics (GLM)
  float vec3Dot( float *a, float *b) {
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
  }

  void vec3Cross( float *a, float *b, float *res) {
    res[0] = a[1] * b[2]  -  b[1] * a[2];
    res[1] = a[2] * b[0]  -  b[2] * a[0];
    res[2] = a[0] * b[1]  -  b[0] * a[1];
  }

  void vec3Normalize(float *a) {
    float mag = sqrt(a[0] * a[0]  +  a[1] * a[1]  +  a[2] * a[2]);
    a[0] /= mag; a[1] /= mag; a[2] /= mag;
  }

  void mat4Identity( float *a) {
    for (int i = 0; i < 16; ++i) a[i] = 0.0f;
    for (int i = 0; i < 4; ++i) a[i + i * 4] = 1.0f;
  }

  void mat4Multiply(float *a, float *b, float *res) {
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        res[j*4 + i] = 0.0f;
        for (int k = 0; k < 4; ++k) {
          res[j*4 + i] += a[k*4 + i] * b[j*4 + k];
        }
      }
    }
  }

  void mat4Perspective(float *a, float fov, float aspect, 
                       float zNear, float zFar) {
    float f = 1.0f / float(tan (fov/2.0f * (M_PI / 180.0f)));
    mat4Identity(a);
    a[0] = f / aspect;
    a[1 * 4 + 1] = f;
    a[2 * 4 + 2] = (zFar + zNear)  / (zNear - zFar);
    a[3 * 4 + 2] = (2.0f * zFar * zNear) / (zNear - zFar);
    a[2 * 4 + 3] = -1.0f;
    a[3 * 4 + 3] = 0.0f;
  }

  void mat4LookAt(float *viewMatrix,
                  float eyeX, float eyeY, float eyeZ,
                  float centerX, float centerY, float centerZ,
                  float upX, float upY, float upZ) {

    float dir[3], right[3], up[3], eye[3];
    up[0]=upX; up[1]=upY; up[2]=upZ;
    eye[0]=eyeX; eye[1]=eyeY; eye[2]=eyeZ;

    dir[0]=centerX-eyeX; dir[1]=centerY-eyeY; dir[2]=centerZ-eyeZ;
    vec3Normalize(dir);
    vec3Cross(dir,up,right);
    vec3Normalize(right);
    vec3Cross(right,dir,up);
    vec3Normalize(up);
    // first row
    viewMatrix[0]  = right[0];
    viewMatrix[4]  = right[1];
    viewMatrix[8]  = right[2];
    viewMatrix[12] = -vec3Dot(right, eye);
    // second row
    viewMatrix[1]  = up[0];
    viewMatrix[5]  = up[1];
    viewMatrix[9]  = up[2];
    viewMatrix[13] = -vec3Dot(up, eye);
    // third row
    viewMatrix[2]  = -dir[0];
    viewMatrix[6]  = -dir[1];
    viewMatrix[10] = -dir[2];
    viewMatrix[14] =  vec3Dot(dir, eye);
    // forth row
    viewMatrix[3]  = 0.0f;
    viewMatrix[7]  = 0.0f;
    viewMatrix[11] = 0.0f;
    viewMatrix[15] = 1.0f;
  }

  void mat4Print(float* a) {
    // opengl uses column major order
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        cout << a[j * 4 + i] << " ";
      }
      cout << endl;
    }
  }

  void mat4Transpose(float* a, float *transposed) {
    int t = 0;
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        transposed[t++] = a[j * 4 + i];
      }
    }
  }

  bool mat4Invert(float* m, float *inverse) {
    float inv[16];
    inv[0] = m[5]*m[10]*m[15]-m[5]*m[11]*m[14]-m[9]*m[6]*m[15]+
             m[9]*m[7]*m[14]+m[13]*m[6]*m[11]-m[13]*m[7]*m[10];
    inv[4] = -m[4]*m[10]*m[15]+m[4]*m[11]*m[14]+m[8]*m[6]*m[15]-
             m[8]*m[7]*m[14]-m[12]*m[6]*m[11]+m[12]*m[7]*m[10];
    inv[8] = m[4]*m[9]*m[15]-m[4]*m[11]*m[13]-m[8]*m[5]*m[15]+
             m[8]*m[7]*m[13]+m[12]*m[5]*m[11]-m[12]*m[7]*m[9];
    inv[12]= -m[4]*m[9]*m[14]+m[4]*m[10]*m[13]+m[8]*m[5]*m[14]-
             m[8]*m[6]*m[13]-m[12]*m[5]*m[10]+m[12]*m[6]*m[9];
    inv[1] = -m[1]*m[10]*m[15]+m[1]*m[11]*m[14]+m[9]*m[2]*m[15]-
             m[9]*m[3]*m[14]-m[13]*m[2]*m[11]+m[13]*m[3]*m[10];
    inv[5] = m[0]*m[10]*m[15]-m[0]*m[11]*m[14]-m[8]*m[2]*m[15]+
             m[8]*m[3]*m[14]+m[12]*m[2]*m[11]-m[12]*m[3]*m[10];
    inv[9] = -m[0]*m[9]*m[15]+m[0]*m[11]*m[13]+m[8]*m[1]*m[15]-
             m[8]*m[3]*m[13]-m[12]*m[1]*m[11]+m[12]*m[3]*m[9];
    inv[13]= m[0]*m[9]*m[14]-m[0]*m[10]*m[13]-m[8]*m[1]*m[14]+
             m[8]*m[2]*m[13]+m[12]*m[1]*m[10]-m[12]*m[2]*m[9];
    inv[2] = m[1]*m[6]*m[15]-m[1]*m[7]*m[14]-m[5]*m[2]*m[15]+
             m[5]*m[3]*m[14]+m[13]*m[2]*m[7]-m[13]*m[3]*m[6];
    inv[6] = -m[0]*m[6]*m[15]+m[0]*m[7]*m[14]+m[4]*m[2]*m[15]-
             m[4]*m[3]*m[14]-m[12]*m[2]*m[7]+m[12]*m[3]*m[6];
    inv[10]= m[0]*m[5]*m[15]-m[0]*m[7]*m[13]-m[4]*m[1]*m[15]+
             m[4]*m[3]*m[13]+m[12]*m[1]*m[7]-m[12]*m[3]*m[5];
    inv[14]= -m[0]*m[5]*m[14]+m[0]*m[6]*m[13]+m[4]*m[1]*m[14]-
             m[4]*m[2]*m[13]-m[12]*m[1]*m[6]+m[12]*m[2]*m[5];
    inv[3] = -m[1]*m[6]*m[11]+m[1]*m[7]*m[10]+m[5]*m[2]*m[11]-
             m[5]*m[3]*m[10]-m[9]*m[2]*m[7]+m[9]*m[3]*m[6];
    inv[7] = m[0]*m[6]*m[11]-m[0]*m[7]*m[10]-m[4]*m[2]*m[11]+
             m[4]*m[3]*m[10]+m[8]*m[2]*m[7]-m[8]*m[3]*m[6];
    inv[11]= -m[0]*m[5]*m[11]+m[0]*m[7]*m[9]+m[4]*m[1]*m[11]-
             m[4]*m[3]*m[9]-m[8]*m[1]*m[7]+m[8]*m[3]*m[5];
    inv[15]= m[0]*m[5]*m[10]-m[0]*m[6]*m[9]-m[4]*m[1]*m[10]+
             m[4]*m[2]*m[9]+m[8]*m[1]*m[6]-m[8]*m[2]*m[5];

    float det = m[0]*inv[0]+m[1]*inv[4]+m[2]*inv[8]+m[3]*inv[12];
    if (det == 0) return false;
    det = 1.0f / det;
    for (int i = 0; i < 16; i++) inverse[i] = inv[i] * det;
    return true;
  }
}; 

Example: Transformation of Surface Normals

  • Vertex Shader:
    #version 140
    in vec3 inputPosition; 
    in vec2 inputTexCoord; 
    in vec3 inputNormal;
    uniform mat4 projection, modelview, normalMat; 
    uniform int mode;
    out vec4 forFragColor;
    void main(){
        gl_Position = projection * modelview * vec4(inputPosition, 1.0);
        vec4 normal = normalMat * vec4(inputNormal, 0.0);
        if(mode == 1) forFragColor = normal;
        if(mode == 2) forFragColor = vec4(inputNormal, 1.0);
        if(mode == 3) forFragColor = gl_Position;
        if(mode == 4) forFragColor = vec4(inputPosition, 1.0);
        if(mode == 5) forFragColor = vec4(inputTexCoord, 0.0, 1.0);
    }

Example: Transformation of Surface Normals

  • Fragment Shader:
    #version 140
    in vec4 forFragColor;
    out vec4 outputColor;
    
    void main() {
        outputColor = forFragColor;
    }

Accessing Textures in the Shader

  • Textures can be accessed both in vertex and fragment shaders
  • To this end, a uniform variable of the type sampler must be defined: For example, in case of a 2D texture:
    uniform sampler2D myTexture;
  • With the function texture(...) the color value at the texture coordinate $(s,t)$ is read:
    texture(myTexture, vec2(s,t))

Providing Textures

  • Textures are generated in the application with glGenTexture and glBindTexture, parameters are set with glTexParameter, and the texture data is passed with glTexImage (as discussed in Chapter 7)
  • To provide the texture in the shader, the location identifier of the sampler variable must be queried
    texLoc = glGetUniformLocation(progID, "myTexture");
  • Then a texture unit is selected (e.g. here the zeroth unit), the texture is activated, and the number of the selected texture unit is passed to the sampler variable:
    glActiveTexture(GL_TEXTURE0);    // activate texture unit 0
    glBindTexture(GL_TEXTURE_2D, texID); // bind texture
    glUniform1i(texLoc, 0);  // inform the shader to use texture unit 0
    

Example: Accessing Textures in the Shader

opengl_shadertexture

Example: Accessing Textures in the Shader

class Renderer {

private:
  struct Vertex {
    float position[3];
    float color[4];
    float texCoord[2];
    float normal[3];
  };

public:
  float t;

private:
  enum {Pyramid, numVAOs};
  enum {PyramidAll, numVBOs};
  GLuint vaoID[numVAOs];
  GLuint bufID[numVBOs];
  int pyramidVertNo;
  GLuint texID;
  GLuint progID;
  GLuint vertID;
  GLuint fragID;
  GLint vertexLoc;
  GLint colorLoc;
  GLint texCoordLoc;
  GLint normalLoc;
  GLint projectionLoc;
  GLint modelviewLoc;
  GLint texLoc;
  float projection[16];  // projection matrix
  float modelview[16];  // modelview matrix

public:
  // constructor
  Renderer() : t(0.0), pyramidVertNo(0), texID(0), progID(0), vertID(0), fragID(0),
               vertexLoc(-1), colorLoc(-1), texCoordLoc(-1), normalLoc(-1),
               projectionLoc(-1), modelviewLoc(-1), texLoc(-1)
               {}
  //destructor
  ~Renderer() {
    glDeleteVertexArrays(numVAOs, vaoID);
    glDeleteBuffers(numVBOs, bufID);
    glDeleteProgram(progID);
    glDeleteShader(vertID);
    glDeleteShader(fragID);
  }

public:
  void init() {
    initExtensions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);

    setupShaders();

    // create a Vertex Array Objects (VAO)
    glGenVertexArrays(numVAOs, vaoID);

    // generate a Vertex Buffer Object (VBO)
    glGenBuffers(numVBOs, bufID);

    // bind the pyramid VAO
    glBindVertexArray(vaoID[Pyramid]);

    float pyramidVertexData[] = {
       0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.0000f,-0.9701f, 0.2425f,
      -0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0000f,-0.9701f, 0.2425f,
       0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0000f,-0.9701f, 0.2425f,
       0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.9701f, 0.0000f, 0.2425f,
       0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.9701f, 0.0000f, 0.2425f,
       0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.9701f, 0.0000f, 0.2425f,
       0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, 1.0f, 0.0000f, 0.9701f, 0.2425f,
       0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0000f, 0.9701f, 0.2425f,
      -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0000f, 0.9701f, 0.2425f,
       0.0f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f,-0.9701f, 0.0000f, 0.2425f,
      -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,-0.9701f, 0.0000f, 0.2425f,
      -0.5f,-0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,-0.9701f, 0.0000f, 0.2425f
    };

    pyramidVertNo = 12;

    glBindBuffer(GL_ARRAY_BUFFER, bufID[PyramidAll]);
    glBufferData(GL_ARRAY_BUFFER, pyramidVertNo*sizeof(Vertex),
                 pyramidVertexData, GL_STATIC_DRAW);

    int stride = sizeof(Vertex);
    char *offset = (char*)NULL;

    // position
    if(vertexLoc != -1) {
      glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, GL_FALSE, stride, offset);
      glEnableVertexAttribArray(vertexLoc);
    }

    // color
    if(colorLoc != -1) {
      offset = (char*)NULL + 3*sizeof(float);
      glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, stride, offset);
      glEnableVertexAttribArray(colorLoc);
    }

    // texCoord
    if(texCoordLoc != -1) {
      offset = (char*)NULL + (3+4)*sizeof(float);
      glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, stride, offset);
      glEnableVertexAttribArray(texCoordLoc);
    }

    // normal
    if(normalLoc != -1) {
      offset = (char*)NULL + (3+4+2)*sizeof(float);
      glVertexAttribPointer(normalLoc, 3, GL_FLOAT, GL_FALSE, stride, offset);
      glEnableVertexAttribArray(normalLoc);
    }

    std::string fileName("./checkerboard.ppm");
    texID = loadTexture(fileName);

  }

  void setupShaders() {

    // create shader
    vertID = glCreateShader(GL_VERTEX_SHADER);
    fragID = glCreateShader(GL_FRAGMENT_SHADER);

    // load shader source from file
     std::string vs = loadShaderSrc("./texture.vert");
     const char* vss = vs.c_str();
     std::string fs = loadShaderSrc("./texture.frag");
     const char* fss = fs.c_str();

     // specify shader source
     glShaderSource(vertID, 1, &(vss), NULL);
     glShaderSource(fragID, 1, &(fss), NULL);

    // compile the shader
    glCompileShader(vertID);
    glCompileShader(fragID);

    // check for errors
    printShaderInfoLog(vertID);
    printShaderInfoLog(fragID);

    // create program and attach shaders
    progID = glCreateProgram();
    glAttachShader(progID, vertID);
    glAttachShader(progID, fragID);

    // "outColor" is a user-provided OUT variable
    // of the fragment shader.
    // Its output is bound to the first color buffer
    // in the framebuffer
    glBindFragDataLocation(progID, 0, "outputColor");

    // link the program
    glLinkProgram(progID);
    // output error messages
    printProgramInfoLog(progID);

    // retrieve the location of the IN variables of the vertex shader.
    vertexLoc = glGetAttribLocation(progID,"inputPosition");
    colorLoc = glGetAttribLocation(progID, "inputColor");
    texCoordLoc = glGetAttribLocation(progID,"inputTexCoord");
    normalLoc = glGetAttribLocation(progID, "inputNormal");

    // retrieve the location of the UNIFORM variables of the vertex shader.
    projectionLoc = glGetUniformLocation(progID, "projection");
    modelviewLoc = glGetUniformLocation(progID, "modelview");
    texLoc = glGetUniformLocation(progID, "myTexture");

  }

  void resize(int w, int h) {
    glViewport(0, 0, w, h);

    // this function replaces gluPerspective
    mat4Perspective(projection, 30.0f, (float)w/(float)h, 1.0f, 10.0f);
    // mat4Print(projection);
  }

  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // camera orbits in the z=5 plane
    // and looks at the origin
    // mat4LookAt replaces gluLookAt
    double rad = M_PI / 180.0f * t;
    mat4LookAt(modelview,
               5.0f*cos(rad), 5.0f*sin(rad), 5.0f, // eye
               0.0f, 0.0f, 0.5f, // look at
               0.0f, 0.0f, 1.0f); // up

    glUseProgram(progID);
    // load the current projection and modelview matrix into the
    // corresponding UNIFORM variables of the shader
    glUniformMatrix4fv(projectionLoc, 1, false, projection);
    glUniformMatrix4fv(modelviewLoc, 1, false, modelview);


    // activate texture unit 0
    glActiveTexture(GL_TEXTURE0);
    // bind texture
    glBindTexture(GL_TEXTURE_2D, texID);
    // inform the shader to use texture unit 0
    glUniform1i(texLoc, 0);

    // bind pyramid VAO
    glBindVertexArray(vaoID[Pyramid]);
    // render data
    glDrawArrays(GL_TRIANGLES, 0, pyramidVertNo);
  }
private:
  void printShaderInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetShaderInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  void printProgramInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetProgramInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  std::string loadShaderSrc(const std::string& filename) {
    std::ifstream is(filename);
    if (is.is_open()) {
      std::stringstream buffer;
      buffer << is.rdbuf();
      return buffer.str();
    }
    cerr << "Unable to open file " << filename << endl;
    exit(1);
  }

  // the following functions are some matrix and vector helpers
  // they work for this demo but in general it is recommended
  // to use more advanced matrix libraries,
  // e.g. OpenGL Mathematics (GLM)
  float vec3Dot( float *a, float *b) {
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
  }

  void vec3Cross( float *a, float *b, float *res) {
    res[0] = a[1] * b[2]  -  b[1] * a[2];
    res[1] = a[2] * b[0]  -  b[2] * a[0];
    res[2] = a[0] * b[1]  -  b[0] * a[1];
  }

  void vec3Normalize(float *a) {
    float mag = sqrt(a[0] * a[0]  +  a[1] * a[1]  +  a[2] * a[2]);
    a[0] /= mag; a[1] /= mag; a[2] /= mag;
  }

  void mat4Identity( float *a) {
    for (int i = 0; i < 16; ++i) a[i] = 0.0f;
    for (int i = 0; i < 4; ++i) a[i + i * 4] = 1.0f;
  }

  void mat4Multiply(float *a, float *b, float *res) {
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        res[j*4 + i] = 0.0f;
        for (int k = 0; k < 4; ++k) {
          res[j*4 + i] += a[k*4 + i] * b[j*4 + k];
        }
      }
    }
  }

  void mat4Perspective(float *a, float fov, float aspect, 
                       float zNear, float zFar) 
  {
    float f = 1.0f / float(tan (fov/2.0f * (M_PI / 180.0f)));
    mat4Identity(a);
    a[0] = f / aspect;
    a[1 * 4 + 1] = f;
    a[2 * 4 + 2] = (zFar + zNear)  / (zNear - zFar);
    a[3 * 4 + 2] = (2.0f * zFar * zNear) / (zNear - zFar);
    a[2 * 4 + 3] = -1.0f;
    a[3 * 4 + 3] = 0.0f;
  }

  void mat4LookAt(float *viewMatrix,
                  float eyeX, float eyeY, float eyeZ,
                  float centerX, float centerY, float centerZ,
                  float upX, float upY, float upZ) {

    float dir[3], right[3], up[3], eye[3];
    up[0]=upX; up[1]=upY; up[2]=upZ;
    eye[0]=eyeX; eye[1]=eyeY; eye[2]=eyeZ;

    dir[0]=centerX-eyeX; dir[1]=centerY-eyeY; dir[2]=centerZ-eyeZ;
    vec3Normalize(dir);
    vec3Cross(dir,up,right);
    vec3Normalize(right);
    vec3Cross(right,dir,up);
    vec3Normalize(up);
    // first row
    viewMatrix[0]  = right[0];
    viewMatrix[4]  = right[1];
    viewMatrix[8]  = right[2];
    viewMatrix[12] = -vec3Dot(right, eye);
    // second row
    viewMatrix[1]  = up[0];
    viewMatrix[5]  = up[1];
    viewMatrix[9]  = up[2];
    viewMatrix[13] = -vec3Dot(up, eye);
    // third row
    viewMatrix[2]  = -dir[0];
    viewMatrix[6]  = -dir[1];
    viewMatrix[10] = -dir[2];
    viewMatrix[14] =  vec3Dot(dir, eye);
    // forth row
    viewMatrix[3]  = 0.0;
    viewMatrix[7]  = 0.0;
    viewMatrix[11] = 0.0;
    viewMatrix[15] = 1.0f;
  }
  void mat4Print(float* a) {
    // opengl uses column major order
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        cout << a[j * 4 + i] << " ";
      }
      cout << endl;
    }
  }

  // returns a valid textureID on success, otherwise 0
  GLuint loadTexture(std::string &filename) {

    unsigned width;
    unsigned height;
    int level = 0;
    int border = 0;
    std::vector<unsigned char> imgData;

    // load image data
    if(!loadPPMImageFlipped(filename, width, height, imgData)) return 0;

    // data is aligned in byte order
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    //request textureID
    GLuint textureID;
    glGenTextures( 1, &textureID);

    // bind texture
    glBindTexture( GL_TEXTURE_2D, textureID);

    //define how to filter the texture (important but ignore for now)
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    //texture colors should replace the original color values
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //GL_MODULATE

    // specify the 2D texture map
    glTexImage2D(GL_TEXTURE_2D, level, GL_RGB, width, height, border, 
                 GL_RGB, GL_UNSIGNED_BYTE, &imgData[0]);

    // return unique texture identifier
    return textureID;
  }


  bool loadPPMImageFlipped(std::string &filename, unsigned &width, 
                 unsigned &height, std::vector<unsigned char> &imgData) 
  {

    ifstream input(filename.c_str(), ifstream::in | ifstream::binary);
    if(!input) { // cast istream to bool to see if something went wrong
      QMessageBox msgBox;
      msgBox.setText(QString("Can not find texture data file ")
                     +  QString(filename.c_str()));
      msgBox.exec();
      return false;
    }
    input.unsetf(std::ios_base::skipws);

    string line;
    input >> line >> std::ws;
    if (line != "P6") {
      QMessageBox msgBox;
      msgBox.setText("File is not PPM P6 raw format");
      msgBox.exec();
      return false;
    }

    width = 0;
    height = 0;
    unsigned depth = 0;
    unsigned readItems = 0;
    unsigned char lastCharBeforeBinary;

    while (readItems < 3) {
      input >> std::ws;
      if(input.peek() != '#') {
        if (readItems == 0) input >> width;
        if (readItems == 1) input >> height;
        if (readItems == 2) input >> depth >> lastCharBeforeBinary;
        readItems++;
      }else{ // skip comments
        std::getline(input, line);
      }
    }

    if(depth >= 256) {
      QMessageBox msgBox;
      msgBox.setText("Only 8-bit PPM format is supported");
      msgBox.exec();
      return false;
    }

    unsigned byteCount = width * height * 3;
    imgData.resize(byteCount);
    input.read((char*)&imgData[0], byteCount*sizeof(unsigned char));

    // vertically flip the image because the image origin
    // in OpenGL is the lower-left corner
    unsigned char tmpData;
    for(unsigned y=0; y < height / 2; y++) {
      int sourceIndex = y * width * 3;
      int targetIndex = (height-1-y) * width *3;
      for(unsigned x=0; x < width*3; x++) {
          tmpData = imgData[targetIndex];
          imgData[targetIndex] = imgData[sourceIndex];
          imgData[sourceIndex] = tmpData;
          sourceIndex++;
          targetIndex++;
      }
    }

    return true;
  }
};

Example: Accessing Textures in the Shader

  • Vertex Shader:
    #version 140
    in vec3 inputPosition;
    in vec4 inputColor;
    in vec2 inputTexCoord;
    in vec3 inputNormal;
    uniform mat4 projection, modelview;
    out vec3 forFragColor;
    out vec2 forFragTexCoord;
    
    void main(){
        forFragColor = inputColor.rgb;
        forFragTexCoord = inputTexCoord;
        gl_Position =  projection * modelview * vec4(inputPosition, 1.0);
    }

Example: Accessing Textures in the Shader

  • Fragment Shader:
    #version 140
    in vec3 forFragColor;
    in vec2 forFragTexCoord;
    out vec4 outputColor;
    uniform sampler2D myTexture;
    
    void main() {
        vec3 textureColor = vec3( texture(myTexture, forFragTexCoord) );
        outputColor = vec4(forFragColor*textureColor,1.0);
    }
    

Example: Accessing Textures in the Shader

  • Fragment shader alternative: What is the functionality of this code?
    #version 140
    in vec3 forFragColor;
    in vec2 forFragTexCoord;
    out vec4 outputColor;
    uniform sampler2D myTexture;
    
    void main() {
        vec3 textureColor = vec3( texture(myTexture, forFragTexCoord) );
        outputColor = vec4(forFragColor*textureColor,1.0);
    
        if(forFragTexCoord.x > 0.5) outputColor = vec4(1.0, 0.0, 0.0, 1.0);
    
    }
    

Example: Accessing Textures in the Shader

  • Fragment shader alternative 2: What is the functionality of this code?
    #version 140
    in vec3 forFragColor;
    in vec2 forFragTexCoord;
    out vec4 outputColor;
    uniform sampler2D myTexture;
    const vec2 texSize = vec2(256.0,256.0);
    void main() {
      vec3 tC = vec3( texture(myTexture, forFragTexCoord) );
      vec3 tC2 = vec3( texture(myTexture, forFragTexCoord + vec2(1.0/texSize.x, 0.0) ) );
      vec3 tC3 = vec3( texture(myTexture, forFragTexCoord + vec2(0.0, 1.0/texSize.y) ) );
      if(abs(tC.x - tC2.x) > 0.01) {
        outputColor = vec4(1.0,1.0,1.0,1.0);
      }else {
        if(abs(tC.x - tC3.x) > 0.01) {
          outputColor = vec4(1.0,1.0,0.0,1.0);
        }else {
          outputColor = vec4(forFragColor*tC,1.0);
        }
      }
    }

Are there any questions?

questions

Please notify me by e-mail if you have questions, suggestions for improvement, or found typos: Contact

More lecture slides

Slides in German (Folien auf Deutsch)