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.

OpenGL

  • Bibliothek mit ca. 670 Grafikbefehlen
  • Funktionsnamen beginnen alle mit "gl", z.B. glClear(), glBegin()
  • Grafikbefehle werden an die Grafikkarte (bzw. an ihren Treiber) weitergegeben und auf der Hardware ausgeführt
  • OpenGL erlaubt damit sehr viel schnellere Darstellung von interaktiven 3D Grafiken als mit reiner CPU Programmierung

OpenGL

  • Die Grafikbefehle werden vom Grafikkartentreiber umgesetzt und sind damit unabhängig von
    • der Grafikkarten-Hardware
    • dem Betriebssystem
    • dem verwendeten Fenster-Manager
  • Grafikbefehle sind einigermaßen hardwarenah und ausreichend, um die Kernfunktionalität zu erreichen
  • Verschiedene Bibliotheken basieren auf OpenGL und erlauben Programmierung auf höherem Abstraktionsniveau

OpenGL Versionen

  • Seit seiner Einführung (1992) wurde OpenGL ständig erweitert, um neue Funktionalitäten der Grafikkarten zu unterstützen
    • OpenGL 1.0 (1992), OpenGL 1.1 (1997), OpenGL 1.2 (1998), OpenGL 1.3 (2001), OpenGL 1.4 (2002), OpenGL 1.5 (2003)
    • OpenGL 2.0 (2004), OpenGL 2.1 (2006)
    • OpenGL 3.0 (2008), OpenGL 3.1 (2009), OpenGL 3.2 (2009), OpenGL 3.3 (2010)
    • OpenGL 4.0 (2010), OpenGL 4.1 (2010), OpenGL 4.2 (2011), OpenGL 4.3 (2012), OpenGL 4.4 (2013), OpenGL 4.5 (2014), OpenGL 4.6 (2017)
  • In dieser Vorlesung wird OpenGL 2 und in späteren Kapiteln OpenGL Version ≥ 3.1 verwendet
  • Seit Version 3.1 wird die sogenannte "Fixed-Function-Pipeline" nicht mehr unterstützt, d.h. es müssen immer "Shader" implementiert werden
  • Dies erschwert den Einstieg. Daher verwendet diese Vorlesung die "Fixed-Function-Pipeline" (OpenGL 2) bis zum Kapitel 9 "GLSL Shading Language"

OpenGL ES und WebGL

  • OpenGL ES ist eine im Funktionsumfang reduzierte Version von OpenGL
  • "ES" steht für "Enbedded Subsystem", d.h. die Zielplattform sind eingebettete Systeme, wie Mobiltelefone, Fernseher, Tablets, etc.
    • OpenGL ES 1.0 (2003): ähnlich wie OpenGL 1.3 (Fixed-Function-Pipeline)
    • OpenGL ES 1.1 (2004): ähnlich wie OpenGL 1.5 (abwärtskompatibel)
    • OpenGL ES 2.0 (2007): ähnlich wie OpenGL 2.0 (allerdings keine Fixed-Function-Pipeline, immer Shader, nicht abwärtskompatibel)
    • OpenGL ES 3.0 (2012): ähnlich wie OpenGL 3.3 (keine Geometrie-Shader)
    • OpenGL ES 3.1 (2014): ähnlich wie OpenGL 4.3
    • OpenGL ES 3.2 (2015): ähnlich wie OpenGL 4.3
  • OpenGL ES wird zur Hardware-unterstützte Grafikausgabe auf vielen Smartphones verwendet (z.B. Apple's iPhone oder Android-basierte Geräte)
  • WebGL basiert auf OpenGL ES 2.0 (bzw. WebGL 2.0 auf OpenGL ES 3.0) und erlaubt 3D Grafik in Webseiten (mittlerweile von fast allen Browsern unterstützt)

OpenGL Utility Library (GLU)

  • OpenGL Utility Library (GLU) war Teil jeder OpenGL Implementierung bis Version 3.0
  • GLU erweitert OpenGL um einige Funktionen (ca. 50) mit höherem Abstraktionsniveau
  • Funktionen für das Zeichnen von Kugeln, Zylindern oder Kreisen, Kamera-Funktionen, Textur-Funktionen, usw.
  • In den aktuellen OpenGL Versionen wird GLU nicht mehr unterstützt
  • Die Funktionalität muss entweder selbst implementiert oder durch externe Bibliotheken bereit gestellt werden (siehe z.B. glm-Bibliothek)

C/C++ Include Files

  • OpenGL definiert seine Schnittstelle durch C-Funktionen
  • Für GL und GLU Funktionen
    #include <GL/gl.h>
    #include <GL/glu.h>
  • Erweiterungen
    #include <GL/glext.h>
  • Nur OpenGL 3.1 Funktionen (keine Fixed-Function-Pipeline)
    #include <GL3/gl3.h>
    #include <GL3/gl3ext.h>

OpenGL Utility Toolkit (GLUT)

  • ist ein möglicher Fenster-Manager für C/C++ Programme
  • ist nicht Teil einer Standard-Installation und muss typischerweise zusätzlich installiert werden (freeglut)
  • stellt den Render-Kontext für OpenGL zur Verfügung, in dem OpenGL-Funktionen ausgeführt werden können
  • behandelt Tastatur- und Mauseingaben
  • stellt plattform-unabhängige Schnittstelle bereit
  • wird eingebunden durch
    #include <GL/freeglut.h>
    (Gl/gl.h und GL/glu.h werden automatisch eingebunden)

OpenGL Utility Toolkit (GLUT) als Fenster-Manager

#include <GL/freeglut.h> // we use glut as window manager

class Renderer {
public:
  void init() {...}
  void resize(int w, int h) {...}
  void display() {...}
  void dispose() {...}
};

// static objects and callbacks
static Renderer *renderer;

static void glutResize(int w, int h) 
{ 
  renderer->resize(w, h); 
}

static void glutDisplay() 
{ 
  renderer->display(); 
  glutSwapBuffers(); 
}

static void glutClose()
{ 
  renderer->dispose(); 
}

int main(int argc, char **argv) {
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  glutInitWindowPosition(100,100);
  glutInitWindowSize(320, 320);
  glutCreateWindow("WindowTitle");
  glutDisplayFunc(glutDisplay);
  glutReshapeFunc(glutResize);
  glutCloseFunc(glutClose);
  renderer = new Renderer;
  renderer->init();
  glutMainLoop();
}

Qt als Fenster-Manager

  • Wie schon in Teil 2, Kapitel 3, "GUIs mit C++ und Qt" besprochen, ist Qt eine weitverbreitete Klassenbibliothek zur plattformübergreifenden Entwicklung von GUIs (Qt Webseite)
  • Qt erlaubt auch die Verwendung von OpenGL
  • Dazu muss die Klasse QOpenGLWidget überschrieben werden
  • Ebenfalls muss die entsprechende QMake-Projekt-Datei um "QT += widgets opengl openglwidgets" ergänzt werden
    QT += widgets opengl openglwidgets
    TARGET = ApplicationName
    SOURCES += main.cpp Widget1.cpp Widget2.cpp
    HEADERS += Widget1.h Widget2.h
    

Qt als Fenster-Manager

#include <QApplication>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_2_0>

class Renderer : protected QOpenGLFunctions_2_0 {
public:
  void init() {
    initializeOpenGLFunctions();
    ...
  }
  void resize(int w, int h) {...}
  void display() {...}
  void dispose() {...}
};

class MyWidget : public QOpenGLWidget {
private:
    Renderer *renderer;
public:
  MyWidget(QWidget *parent = NULL) : QOpenGLWidget(parent) {
    this->setWindowTitle("OpenGL with Qt");
    this->resize(320, 320);
    renderer = new Renderer();
  }
  ~MyWidget() { 
     makeCurrent();
     renderer->dispose();
     doneCurrent();
     delete renderer; 
   }
protected:
  void initializeGL() { renderer->init(); }
  void resizeGL(int w, int h){ renderer->resize(w, h); }
  void paintGL() { renderer->display(); }
};

int main (int argc, char* argv[]) {
  QApplication appl(argc, argv);
  MyWidget widget;  // create a widget
  widget.show(); //show the widget and its children
  return appl.exec(); // execute the application
}

OpenGL und Java

  • Java kann ebenfalls in Kombination mit OpenGL verwendet werden
  • Dazu wird in dieser Vorlesung JOGL (Java Binding for the OpenGL API) verwendet (JOGL Webseite)
  • Die OpenGL Funktionsaufrufe weisen die gleiche Syntax auf wie in C
  • JOGL muss zusätzlich zum JDK installiert werden (Installationsanleitung)

OpenGL und Java

Kompilieren: (In einer Shell)

javac -classpath "jar/gluegen-rt.jar;jar/jogl-all.jar" sourcefile.java

Ausführen:

java -classpath "jar/gluegen-rt.jar;jar/jogl-all.jar;." sourcefile

Bei der aktuellen JDK Version (Java 19) wird folgender zusätzlicher Kommandozeilenparameter benötigt:

java --add-exports java.desktop/sun.awt=ALL-UNNAMED 
     -classpath "jar/gluegen-rt.jar;jar/jogl-all.jar;." sourcefile

Classpath in Linux:

-classpath "jar/gluegen-rt.jar:jar/jogl-all.jar:."

Java als Fenster-Manager

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import com.jogamp.opengl.*;
import com.jogamp.opengl.awt.GLCanvas;
import com.jogamp.opengl.util.FPSAnimator;
import static com.jogamp.opengl.GL.*;  
import static com.jogamp.opengl.GL2.*;


class Renderer {
  public void init(GLAutoDrawable d) {...}
  public void resize(GLAutoDrawable d, int w, int h) {...}
  public void display(GLAutoDrawable d) {...}
  public void dispose(GLAutoDrawable d) {...}
}

class MyGui extends JFrame implements GLEventListener{
  private Renderer renderer;
 
  public void createGUI() {
    setTitle("JoglFirstTriangle");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   
    GLProfile glp = GLProfile.getDefault();
    GLCapabilities caps = new GLCapabilities(glp);
    GLCanvas canvas = new GLCanvas(caps);
    setSize(320, 320);
    getContentPane().add(canvas);
    final FPSAnimator ani = new FPSAnimator(canvas, 60, true);
    canvas.addGLEventListener(this);
    setVisible(true);
    renderer = new Renderer();
    ani.start();
  }

  @Override
  public void init(GLAutoDrawable d) { 
    renderer.init(d); 
  }

  @Override
  public void reshape(GLAutoDrawable d, int x, int y, 
                      int width, int height) { 
    renderer.resize(d, width, height); 
  }

  @Override
  public void display(GLAutoDrawable d) { 
     renderer.display(d); 
  }

  @Override
  public void dispose(GLAutoDrawable d) { 
    renderer.dispose(d);
  }
}

public class JoglFirstTriangle {
  public static void main(String[] args) {
    System.setProperty("sun.java2d.uiScale", "1.0");
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}

Das erste OpenGL Programm: Zeichnen eines Dreiecks

  • Nachdem nun die verschiedenen Fenster-Manager (GLUT, Qt, Java) eingeführt wurden, soll nun zunächst ein Dreieck mit OpenGL auf den Bildschirm gezeichnet werden
  • Im Folgenden werden immer nur die Funktionen init(), resize(), display() und dispose() der Klasse Renderer betrachtet, die komplett unabhängig von dem verwendeten Fenster-Manager sind

Das erste OpenGL Programm: Zeichnen eines Dreiecks

class Renderer {
public:
  void init() {}
  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);
    glLoadIdentity();
    glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
    glColor3f(1.0f, 1.0f, 1.0f);
    glBegin(GL_TRIANGLES);
    glVertex3f(-0.5f, -0.5f, 0.0f); // vertex on the left
    glVertex3f( 0.5f, -0.5f, 0.0f); // vertex on the right
    glVertex3f( 0.0f,  0.5f, 0.0f); // vertex at the top of the triangle
    glEnd();
  }
  void dispose() {}
};

Das erste OpenGL Programm: Zeichnen eines Dreiecks

opengl_triangle

OpenGL als Zustandsmachine

  • Beim Zeichnen befindet sich OpenGL in bestimmten Zuständen, die das Verhalten und die Ausgabe beeinflussen
  • Z.B. setzt der Befehl glColor(...) die aktuelle Zeichenfarbe
  • Dieser Zustand bleibt gesetzt bis er durch einen anderen Grafikbefehl verändert wird, also z.B. bis zum nächsten Aufruf von glColor(...)

OpenGL als Zustandsmachine

  • OpenGL kann bestimmte Zustandskombinationen mittels glPushAttrib() auf einen Stack legen und später mittels glPopAttrib() wieder herstellen
... // draw something here
glPushAttrib(GL_LINE_BIT);    
  glEnable(GL_LINE_SMOOTH);       
  glEnable(GL_LINE_STIPPLE); 
glPushAttrib(GL_COLOR_BUFFER_BIT);
  glEnable(GL_BLEND);
  glDisable(GL_DITHER);
... // drawing something special here
glPopAttrib(); // restore GL_COLOR_BUFFER_BIT
glPopAttrib(); // restore GL_LINE_BIT
... // draw more here

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)

OpenGL-Pipeline

  • In den folgenden Folien werden die einzelnen Module der OpenGL-Pipeline erläutert
  • Dies dient zunächst nur dazu, eine Übersicht zu bekommen
  • Details folgen in späteren Teilen der Vorlesung

Framebuffer

  • Der Framebuffer ist die "Leinwand" auf der OpenGL zeichnet
  • Der Framebuffer ist eine Rastergrafik, d.h. er hat eine bestimmte Breite und Höhe und enthält Pixel, die auf einem festen Raster angeordnet sind
  • Später werden wir sehen, dass OpenGL in mehrere Framebuffer gleichzeitig zeichnen kann

Vertex Daten

  • Ein Vertex ist ein Stützpunkt eines Grafikobjekts, z.B. Linie, Dreieck, Polygon
  • Mit glVertex(...) werden die Stützpunkte einzeln übergeben
    glBegin(GL_TRIANGLES);
    glVertex3f(-0.5f, -0.5f, 0.0f); // vertex on the left
    glVertex3f( 0.5f, -0.5f, 0.0f); // vertex on the right
    glVertex3f( 0.0f,  0.5f, 0.0f); // vertex at the top of the triangle
    glEnd();
  • Später werden in dieser Vorlesung auch Techniken beschrieben, mit denen große Speicherbereiche mit Vertex-Daten an die OpenGL-Pipeline übergeben werden können

Per-Vertex Operationen

  • In diesem Schritt werden für jeden Vertex eine Reihe von Operationen durchgeführt
  • Bei Verwendung der Fixed-Function Pipeline sind dies z.B.
    • Die Transformation der Stützpunkte vom globalen Welt- ins Kamerakoordinatensystem und die Projektion in die Kamerabildebene
    • Generierung von Normalen oder Texturkoordinaten und deren Transformation
    • Berechnung einer Vertex Farbe bei gegebener Beleuchtung
  • Bei der Verwendung von Shadern, wird dieser Schritt mittels eines so genannten Vertex-Shaders implementiert

Primitive Assembly & Per-Vertex Post-Processing

  • Beim Zusammenbauen von Primitiven werden die Vertex Daten unterschiedlich verwendet. Dies ist abhängig von dem Argument von glBegin(...)
prim_types

Primitive Assembly & Per-Vertex Post-Processing

  • In diesem Schritt werden ebenfalls 3D-Clipping Operationen durchgeführt. Durch das Clipping können zusätzliche Stützpunkte erzeugt werden
  • Danach wird eine perspektivische Division durchgeführt, die die perspektivische 2D-Abbildung der Objekte realisiert
  • Nicht sichtbare Primitive können entfernt werden
  • Anschließend werden die 2D-Koordinaten entsprechend der gewählten Bildauflösung und -position skaliert und/oder verschoben
  • Die erzeugten Primitive kennen nun ihre 2D-Koordinaten (reelle Zahlen) im Framebuffer und werden an den 2D-Rasterisierer übergeben

Pixel Daten

  • Mit OpenGL können ebenfalls Rastergrafiken verarbeitet werden
  • Außerdem werden Rasterbilder häufig als Texturen ("Farbtapete") verwendet
  • Es ist ebenfalls möglich, ein Bild aus dem Framebuffer als Eingabe für einen zweiten Rendering-Durchgang zu verwenden

Pixel Operationen

  • Pixel Daten werden vom Hauptspeicher des Computer in ein bestimmtes OpenGL-Speicherformat konvertiert
  • Es können Bildmanipulationen durchgeführt werden, wie Vergrößern, Verkleinern, Umdrehen des Bildes, Farbwerte manipulieren, Filter anwenden, etc.
  • Viele diese Operationen sind Teil der Fixed-Function-Pipeline und werden heutzutage in der Regel stattdessen durch Textur- und Framebuffer-Objekte realisiert.

Zusammenbauen von Texturen (Texture Assembly)

  • Pixel Daten können ebenfalls als Texturen verwendet werden
  • Texturen werden in Textur-Objekten gespeichert, die mit einer ID (Kennung) ausgestattet sind und so referenziert und wiederverwendet werden können
  • D.h. die Pixel Daten müssen nur bei der Erzeugung der Textur vom Hauptspeicher des Computers auf den Grafikkartenspeicher transferiert werden und stehen anschließend dort zur Verfügung
  • Die Grafikkarten-Hardware unterstützt den schnellen Zugriff auf Texturen

Rasterisierer

  • Der Rasterisierer konvertiert die prozessierten Vertex- oder Pixel-Daten in so genannte Fragmente
  • Die Rasterkonvertierung wird auch auf modernen Grafikkarten (mit programmierbaren Shader-Einheiten) noch durch dedizierte Hardware realisiert
  • Jedes Fragment kennt seinen interpolierten Farbwert, Tiefenwert und evtl. weitere interpolierte Attribute, wie Z.B. seine Textur-Koordinate oder Oberflächennormale

Per-Fragment Operationen

  • Bevor die Fragment-Daten als Pixelwerte im Framebuffer landen, werden noch eine Reihe von Operationen für jedes Fragment ausgeführt
  • Bei Verwendung der Fixed-Function Pipeline sind dies z.B.
    • Texturierung
    • Farbgenerierung oder -manipulation
    • Nebelerzeugung (Fog)
  • Bei der Verwendung von Shadern, kann dieser Schritt mittels eines sogenannten Fragment-Shaders implementiert werden

Per-Fragment Operationen

  • Das resultierende Fragment durchläuft dann noch einige weitere (optionale) Verarbeitungsschritte bis die Information im Framebuffer landet:
  1. Scissor-Test
  2. Alpha-Test
  3. Depth-Test
  4. Stencil-Test
  5. Blending
  6. Dithering
  7. Logische Operationen

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)