Steuerungstasten

nächste Folie (auch Enter oder Spacebar).
vorherige Folie
 d  schaltet das Zeichnen auf Folien ein/aus
 p  wechselt zwischen Druck- und Präsentationsansicht
CTRL  +  vergrößert die Folien
CTRL  -  verkleinert die Folien
CTRL  0  setzt die Größenänderung zurück

Das Weiterschalten der Folien kann ebenfalls durch das Klicken auf den rechten bzw. linken Folienrand erfolgen.

Notation

Typ Schriftart Beispiele
Variablen (Skalare) kursiv $a, b, x, y$
Funktionen aufrecht $\mathrm{f}, \mathrm{g}(x), \mathrm{max}(x)$
Vektoren fett, Elemente zeilenweise $\mathbf{a}, \mathbf{b}= \begin{pmatrix}x\\y\end{pmatrix} = (x, y)^\top,$ $\mathbf{B}=(x, y, z)^\top$
Matrizen Schreibmaschine $\mathtt{A}, \mathtt{B}= \begin{bmatrix}a & b\\c & d\end{bmatrix}$
Mengen kalligrafisch $\mathcal{A}, B=\{a, b\}, b \in \mathcal{B}$
Zahlenbereiche, Koordinatenräume doppelt gestrichen $\mathbb{N}, \mathbb{Z}, \mathbb{R}^2, \mathbb{R}^3$

OpenGL Shading Language

  • Die OpenGL Shading Language (GLSL) erlaubt, benutzerdefinierte Programme für Vertex- und Fragment-Prozessoren zu schreiben
  • Die benutzerdefinierten GLSL Programme ersetzen dabei Teile der OpenGL Pipeline, die bisher von der Fixed-Function Pipeline ausgeführt wurden
  • Dabei werden Teile der Per-Vertex-Operationen durch einen benutzerdefinierten Vertex-Shader und Teile der Per-Pixel-Operationen durch einen benutzerdefinierten Fragment-Shader ersetzt

Wiederholung: OpenGL-Pipeline

openglpipeline
openglpipeline
openglpipeline
openglpipeline
openglpipeline
openglpipeline
openglpipeline
Quelle: basierend auf Mark Segal, Kurt Akeley, The OpenGL Graphics System: A Specification Version 2.0, 2004, Figure 2.1. Block diagram of the GL (modifiziert)

Vertex-Shader

pervertex_fixedfunction_vs_shader

Fragment-Shader

perfragment_fixedfunction_vs_shader
Quelle: basierend auf Mark Segal, Kurt Akeley, The OpenGL Graphics System: A Specification Version 2.0, 2004, Abb. 3.1 und 4.1. (modifiziert)

Parallele Verarbeitung

  • Die Daten werden in den Vertex- und Fragment-Shadern parallel verarbeitet
  • Heutige GPUs besitzen bis zu 2600 Recheneinheiten, die auf den Daten parallel Berechnungen ausführen können
  • Für alle Daten wird parallel der exakt gleiche Shader-Code ausgeführt ("Stream Processing")
  • Die parallele Verarbeitung ist möglich, da die Operationen genau ein definiertes Ziel haben (entweder ein transformiertes Vertex oder Fragment), welches ohne Konflikte geschrieben werden kann

Erzeugung von Shader-Programmen in OpenGL

  • Die Shader-Programme werden zur Laufzeit kompiliert und an die Grafikkarte übergeben
  • Zur Erzeugung eines Shader-Programms, das Vertex- und Fragment-Shader enthalten kann, wird folgender Befehl verwendet
    progID = glCreateProgram();
  • Erzeugen, Zuweisen und Kompilieren von Shader-Code eines Vertex- und eines Fragment-Shaders erfolgt mit:
    vertID = glCreateShader(GL_VERTEX_SHADER);
    fragID = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(vertID, 1, &vertShaderSrcCodeString, &vStrLength);
    glShaderSource(fragID, 1, &fragShaderSrcCodeString, &fStrLength);
    glCompileShader(vertID);
    glCompileShader(fragID);
  • Danach werden die Shader dem Programm zugewiesen und ein Executable erzeugt:
    glAttachShader(progID, vertID);
    glAttachShader(progID, fragID);
    glLinkProgram(progID);
    

Verwenden von Shader-Programmen in OpenGL

  • Zur Aktivierung eines Shader-Programms wird folgender Befehl verwendet
    glUseProgram(progID);
  • Dabei verhält sich OpenGL wieder wie eine Zustandsmaschine, d.h. alle nachfolgenden Renderfunktionen verwenden das aktuell aktivierte Shader-Programm
  • Das heißt auch, dass zur Laufzeit zwischen verschiedenen Shader-Programmen gewechelt werden kann, was in der Praxis auch häufig vorkommt. Typischerweise werden z.B. verschiedene Shader eingesetzt, um verschiedene Materialien zu simulieren.

Löschen von Shader-Objekten

  • Zum Löschen werden folgende Funktionen eingesetzt
    glDeleteProgram(progID);
    glDeleteShader(vertID);
    glDeleteShader(fragID);

Ein- und Ausgabe eines Vertex-Shaders

vertexshader_inout
Quelle: basierend auf 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 (modifiziert)

Ein- und Ausgabe eines Fragment-Shaders

fragmentshader_inout
Quelle: basierend auf 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 (modifiziert)

GLSL Syntax

  • Die Syntax von GLSL orientiert sich stark an C, mit einigen Elementen von C++
  • Datentypen für Gleitkommazahlen:
    float, vec2, vec3, vec4, mat2, mat3, mat4
  • Ganzzahlige Datentypen:
    int, ivec2, ivec3, ivec4
  • Boolsche Datentypen:
    bool, bvec2, bvec3, bvec4
  • Sampler Datentypen für den Zugriff auf Texturen:
    sampler2D, sampler3D, samplerCube, ...

GLSL Syntax

  • Strukturen, wie aus C bekannt, sind erlaubt:
    struct Vertex { 
      float val1; 
      int val2; 
      bool val3;
    };
  • Felder (Arrays), wie aus C bekannt, sind auch möglich
    float a[16];
  • Datentypen müssen explizit konvertiert werden
    float a = float(1);

GLSL Syntax

  • Vieles wie in C:
    • Konditionen: if, if()else
    • Scheifen: for, while, do{}while()
    • Abbruch: return, break, continue
  • aber nicht alles ist erlaubt:
    • Keine Pointer
    • keine Strings
    • Keine unsigned, byte, short, long-Typen, Kein union
    • Kein switch()case

Konstruktion und Verwendung von Vektoren

// 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)

Konstruktion und Verwendung von Matrizen

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

Beispiel: Ein erstes Dreieck mit GLSL

opengl_firstshader

Beispiel: Ein erstes Dreieck mit 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);
  }
};

Beispiel: Ein erstes Dreieck mit 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);
    }

Übergabe von Vertex Attributen

  • Per-Vertex Attribute können an einen Vertex-Shader mittels in-Variablen übergeben werden
  • Hierbei kann unterschieden werden in spezielle in-Variablen, die automatisch erzeugt werden, und benutzerdefinierte in-Variablen, die von der Applikation übergeben werden müssen
  • Die Vertex-Daten werden dabei mittels Vertex Buffer Objects (VBOs) an die Grafikkarte übergeben

Übergabe von Vertex Attributen

Um Daten eines VBOs für die benutzerdefinierten in -Variablen zu verwenden, wird häufig wie folgt vorgegangen

  • Mittels glGetAttribLocation wird ein Lokalisierungskennzeichner für eine in-Variable abgefragt. Heißt die Variable im Shader-Code beispielsweise "inputColor"
    int colorLoc = glGetAttribLocation(progID, "inputColor");
  • Die Funktion glVertexAttribPointer(colorLoc, size, type, normalized, stride, offset) definiert dann die Zuordnung zwischen in-Variable und VBO-Daten
  • Abschließend muss das Attribut noch aktiviert werden mittels
    glEnableVertexAttribArray(colorLoc);
  • Bei einem anschießenden Aufruf von glDrawArray bzw. glDrawElements mit aktiviertem VBO werden die in-Variable des Vertex-Shaders dann für jedes gezeichnete Vertex entsprechend gefüllt

Daten Ein- und Ausgabe im Fragment-Shader

  • Benutzerdefinierte out-Variablen des Vertex-Shaders werden im Rasterisierer interpoliert und die interpolierten Werte als in-Variablen im Fragment-Shader zur Verfügung gestellt
  • Die out-Variablen des Fragmentshaders werden zum Framebuffer geschickt
  • Soll z.B. die out-Variable "outputColor" in den ersten Color-Buffer des Framebuffers geschrieben werden, kann dies erreicht werden durch
    glBindFragDataLocation(progID, 0, "outputColor");

Verwendung von Uniform Variablen

  • Neben in-Variablen sind uniform-Variablen eine weitere Möglichkeit, um Daten an einen Shader zu übergeben
  • Im Unterschied zu den in-Variablen, die Vertex Attribute beschreiben, beinhalten die uniform-Variablen Daten, die für alle gezeichneten Stützpunte eines glDrawArray bzw. glDrawElements-Aufrufs konstant sind

Verwendung von Uniform Variablen

  • Der Lokalisierungskennzeichner einer uniform-Variablen kann abgefragt werden durch
    paraLoc = glGetUniformLocation(progID, "parameter");
  • Anschießend kann die uniform-Variable folgendermaßen gesetzt werden
    glUniform1i(paraLoc, 123);
  • bzw. wenn es z.B. um einen float-Vektor handelt
    glUniform3fv(threeVectorLoc, 1, threeVector);
  • und bei einer Matrix
    glUniformMatrix4fv(modelviewLoc, 1, transpose, modelview);

Beispiel: Übergeben der Transformationsmatrizen

opengl_shaderuniform

Beispiel: Übergeben der Transformationsmatrizen

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

Beispiel: Übergeben der Transformationsmatrizen

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

Beispiel: Übergeben der Transformationsmatrizen

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

Transformation von Oberflächen-Normalen

  • Die Modelview-Matrix transformiert Stützpunkte vom lokalen Koordinatensystem ins Kamerakoordinatensystem
  • Zur späteren Anwendung von Beleuchtungsmodellen muss die Normaleninformation eines Stützpunktes ebenfalls vom lokalen Koordinatensystem ins Kamerakoordinatensystem transformiert werden
  • Bisher hat sich die Fixed-Function-Pipeline um die Transformation der Normalen gekümmert, bei Verwendung eines Shaders ist es nun ebenfalls Aufgabe des Programmierers

Transformation von Oberflächen-Normalen

  • Bei der Transformation von Normalen ist zu beachten, dass es sich bei einer Normalen um einen Einheitsvektor handelt, der bei der Transformation mit der Modelview-Matrix $\mathtt{T}_{\mathrm{\small modelview}}$ nicht transliert oder skaliert, sondern lediglich rotiert werden soll
  • Problem: Wie kann die Transformationsmatrix $\mathtt{T}_{\mathrm{\small normal}}$ der Normalen aus $\mathtt{T}_{\mathrm{\small modelview}}$ berechnet werden?
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}}$
Kamerakoordinatensystem
Lokales Koordinatensystem

Transformation von Oberflächen-Normalen

  • Beobachtung: Im lokalen Koordinatensystem ist die Normale $\mathbf{N}=(n_x,n_y,n_z,0)^{\top}$ orthogonal zur Tangente $\mathbf{M}=(m_x,m_y,m_z,0)^{\top}$. Durch die Transformation mit $\mathtt{T}_{\mathrm{\small modelview}}$ wird die Normale falsch abgebildt, aber die Tangente zeigt weiterhin in die richtige Richtung
  • Vorgehen: Vor der Transformation gilt $\mathbf{N}^{\top}\mathbf{M} = 0$ und diese Beziehung soll nach der Transformation erhalten bleiben, daher:
    $\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}$

  • D.h. für $(\mathtt{T}_{\mathrm{\small normal}})^{\top} \mathtt{T}_{\mathrm{\small modelview}}=\mathtt{I}_{4 \times 4}$ ist die Bedingung erfüllt, damit folgt
    $\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}$

Beispiel: Transformation der Oberflächen-Normalen

opengl_shadernormaltrans

Beispiel: Transformation der Oberflächen-Normalen

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

Beispiel: Transformation der Oberflächen-Normalen

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

Beispiel: Transformation der Oberflächen-Normalen

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

Zugriff auf Texturen im Shader

  • Auf Texturen kann sowohl im Vertex- als auch im Fragment-Shader zugegriffen werden
  • Dazu muss im Shader eine uniform-Variable vom Typ sampler definiert werden, z.B. bei einer 2D Textur:
    uniform sampler2D myTexture;
  • Mit der Funktion texture(...) erfolgt dann der Zugriff auf eine Texturkoordinate $(s,t)$:
    texture(myTexture, vec2(s,t))

Bereitstellen von Texturen

  • Texturen werden in der Anwendung, wie in Kapitel 7 besprochen, mit glGenTexture und glBindTexture erzeugt, Parameter mit glTexParameter gesetzt und die Texturdaten mittels glTexImage übergeben
  • Um die Textur im Shader bereitzustellen, muss zunächst der Lokalisationskennzeichner der sampler-Variable ermittelt werden
    texLoc = glGetUniformLocation(progID, "myTexture");
  • Dann wird eine Textureinheit ausgewählt (z.B. hier die nullte Unit), die Textur aktiviert und die gewählte Textureinheit an die sampler-Variable übergeben:
    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
    

Beispiel: Zugriff auf Texturen im Shader

opengl_shadertexture

Beispiel: Zugriff auf Texturen im 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;
  }
};

Beispiel: Zugriff auf Texturen im 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);
    }

Beispiel: Zugriff auf Texturen im 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);
    }
    

Beispiel: Zugriff auf Texturen im Shader

  • Fragment Shader Alternative: Was macht dieser 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);
    
    }
    

Beispiel: Zugriff auf Texturen im Shader

  • Fragment Shader Alternative 2: Was macht dieser 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);
        }
      }
    }

Early Fragment Tests

perfragment_fixedfunction_vs_shader
Standard Pipeline
  • Ein Fragment durchläuft eine Reihe von Test bevor es in den Framebuffer geschrieben wird
  • Die Tests werden laut OpenGL-Standard erst nach dem Ausführen des Fragment-Shaders ausgeführt
  • GPUs können zur Optimierung den Test vorziehen, wenn sich dadurch das Verhalten der Pipeline nicht ändert
  • Wird jedoch z.B. im Fragment-Shader der Tiefenwert gl_FragDepth verändert, kann der Depth-Test nicht vorgezogen werden
  • Ab GLSL Version 4.2 können Early Fragment Tests erzwungen werden mit:
    #version 420
    layout(early_fragment_tests) in;
Quelle: basierend auf Mark Segal, Kurt Akeley, The OpenGL Graphics System: A Specification Version 2.0, 2004, Abb. 3.1 und 4.1. (modifiziert)

Gibt es Fragen?

questions

Anregungen oder Verbesserungsvorschläge können auch gerne per E-mail an mich gesendet werden: Kontakt

Weitere Vorlesungsfolien

Folien auf Englisch (Slides in English)