Double Buffering ist ein Konzept aus dem Rendering, bei dem mit 2 Framebuffern gearbeitet wird.
Dabei kommt das aktuelle Bild, das der Monitor zeigt immer aus dem Front Buffer. Währenddessen kann das nächste Bild im Back Buffer gezeichnet werden. Um das fertig gezeichnete Bild schließlich anzuzeigen, wird ein Buffer Swap ausgeführt, der die beiden Puffer tauscht.
Die Baryzentrische Inteprolation ist eine Interpolation auf einem Dreieck, also zwischen 3 Koordinaten.
Gegeben sind die Werte , , an den Dreieckspunkten, zwischen denen interpoliert werden soll und die baryzentrischen Koordinaten, an denen interpoliert wird.
In OpenGL kann man Vertex Shader und Fragment Shader definieren. Diese werden im Setup zu einem Shader Programm zusammengefasst, welches beim Draw-Call in der Render-Pipeline automatisch ausgeführt wird. Dafür müssen einige Sachen vorbereitet werden:
Schlägt das Kompilieren fehl sollten die Shader und Programme gelöscht werden.
Shader können auch direkt nach dem erfolgreichem Linken gelöscht werden. Davor sollten sie noch mit glDetachShader vom Programm gelöst werden.
Das Shader Programm sollte dann natürlich erst gelöscht werden, wenn es nicht mehr zum Rendern benötigt wird.
Beispiel
// 1. Shader Program erstellenGLuint shader_prog = glCreateProgram();// 2.1. Shader erstellenGLuint vert_shader = glCreateShader(GL_VERTEX_SHADER);// 2.2. Shader-Quellcode in den Shader ladenconst char *vert_source_cstr; // Sei hier mal Definiert und NullterminiertglShaderSource(vert_shader, 1, &vert_source_cstr, 0);// 2.3. Shader kompilierenglCompileShader(vert_shader);// Check ob es Kompilierfehler gabGLint result = GL_FALSE;glGetShaderiv(vert_shader, GL_COMPILE_STATUS, &result);if (result != GL_TRUE) { cerr << "Failed to compile shader" << endl; // Aufräumen glDeleteShader(vert_shader); glDeleteProgram(shader_prog); shader_prog = -1; exit(-1);}// 2.4. Shader an das Program anheftenglAttachShader(shader_prog, vert_shader);// (2.1. bis 2.4. analog nochmal für den Fragment Shader)// 3. Shader Program linkenglLinkProgram(shader_prog);// Check auf Linkerfehlerresult = GL_FALSE;glGetProgramiv(shader_prog, GL_LINK_STATUS, &result);if (result != GL_TRUE) { cerr << "Failed to link shaders" << endl; // Aufräumen glDeleteShader(vert_shader); glDeleteShader(frag_shader); glDeleteProgram(shader_prog); shader_prog = -1; exit(-1);}// Alles lief durch! Wir können den Shader jetzt verwenden:glUseProgram(shader_prog);
GL_ELEMENT_ARRAY_BUFFER - Zum Speichern von Indizes (von z.B. Indexed Face Sets)
GLuint buffer - Die Adresse des Buffers.
Beispiel
// Buffer an den Port bindenglBindBuffer(GL_ARRAY_BUFFER , vbo);// Jetzt können Daten an den Port geschickt werden, die dann im Buffer landen// Port wieder entbindenglBindBuffer(GL_ARRAY_BUFFER , 0);
In OpenGL besteht die Möglichkeit, mehrere Attribute pro Vertex zu verarbeiten. Um ein Attribut von einem Buffer in den Shader zu streamen, muss zuerst wieder das Buffer Object an einen Binding Point gebunden werden. Anschließend kann ein Input-Stream definiert werden, der dann implizit von dem gebundenen Buffer liest. Danach kann der gebundene Buffer wieder gelöst werden. In der Praxis geht das so:
Falls wir mit Indexed Face Sets arbeiten, müssen wir dieses noch an den Binding Point GL_ELEMENT_ARRAY_BUFFER hängen. Dadurch ist in OpenGL automatisch der Index-Stream definiert und die Primitive werden später richtig konstruiert.
Beispiel
// vbo1 als ersten Shader-Input definierenglBindBuffer(GL_ARRAY_BUFFER, vbo1);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);// vbo2 als zweiten Shader-Input definierenglBindBuffer(GL_ARRAY_BUFFER, vbo2);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribPointer - Definiert einen Input Stream für den Vertex Shader. Als Stream-Quelle wird dabei der Buffer verwendet, der zur Zeit des Aufrufs an GL_ARRAY_BUFFER hängt.
Den Großteil (Clipping, Rasterisieren, …) erledigt OpenGL von selbst, allerdings können wir uns mit Shadern in bestimmten Abschnitten in bestimmte Phasen einklinken:
Fragment Shader durchlaufen alle “Fragmente” (also einfach gesagt alle rasterisierten Pixel). Hier können wir den Pixeln z.B. eine Farbe geben oder Lichteffekte hinzufügen.
Beispiel
// vbo1 als ersten Shader-Input definierenglBindBuffer(GL_ARRAY_BUFFER, vbo1);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);// vbo2 als zweiten Shader-Input definierenglBindBuffer(GL_ARRAY_BUFFER, vbo2);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);// ibo als Indizies nochmal binden, sofern nicht schon gemachtglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);// Draw Call!glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);// Bindungen lösenglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);glBindBuffer(GL_ARRAY_BUFFER, 0);
GL_TRIANGLE_STRIP - Dreiecks-Strip. Dabei werden neue Punkte mit den zwei letzten Punkten aus dem vorherigem Dreieck verbunden. Eignet sich gut für zusammenhängende Flächen.
GL_QUADS- Quadrate
GLsizei count - Anzahl der Verticies die genutzt werden sollen
Damit OpenGL auf der GPU rechnen kann, müssen wir alle nötigen Daten (z.B. die Positionen der einzelnen Vertices oder das Indexed Face Set) bereitstellen. Die Daten speichern wir in einem Buffer Object. Das ist einfach Speicher auf der GPU. Auf den haben wir allerdings keinen direkten Zugriff, sondern nur über einen Buffer Binding Point. Diesen Binding Point können wir mit dem Buffer verbinden, um dann Daten über den Zielport an den Buffer zu speichern. Konkret geht das so:
Die Daten über Ziel an gebundenen Buffer senden mit glBufferData
Beispiel
// Die seien hier mal schon definiertvector<vec3> vertices;vector<unsigned> indices;// 1. Buffer anlegenGLuint vbo;GLuint ibo;glGenBuffers(1, &vbo);glGenBuffers(1, &ibo);// 2. Buffer an ein Ziel bindenglBind(GL_ARRAY_BUFFER, vbo); // 3. Daten über Ziel an Buffer sendenglBufferData( GL_ARRAY_BUFFER, vertices.size() * sizeof(vec3), &vertices[0], GL_STATIC_DRAW); // Bindung lösenglBind(GL_ARRAY_BUFFER, 0); // Analog für den Index BufferglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);glBufferData( GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned), &indices[0], GL_STATIC_DRAW);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // Bindung lösen
Wir haben gesehen, wie man in OpenGL rasterisiert. Dieses Vorgehen funktioniert gut wenn man nur wenige Meshes hat. Das Problem: Tatsächlich arbeitet man ja mit einigen hunderten Meshes. Und da jeder dieser Aufrufe ein Call der CPU auf die GPU, ist, dauert das. Oft ist dann nicht mehr die GPU das Bottleneck sondern die CPU, die extrem viele Calls an die GPU senden muss.
Beispiel
// Für jedes der hunderten Meshes muss das passierenfor (int i = 0; i < meshes.size(); i++) { // Inputs binden glBindBuffer(...) glEnableVertexAttribArray(...) glVertexAttribPointer(...) glBindBuffer(...) glEnableVertexAttribArray(...) glVertexAttribPointer(...) glBindBuffer(...) // Draw Call! glDrawElements(...); // Bindungen lösen glBindBuffer(..., 0); glBindBuffer(..., 0);}
Lösung
In OpenGL löst man dieses Problem mit sogenannten Vertex Array Objects. Ein VAO kann man sich als “Recorder” vorstellen, das solange es gebunden ist aufzeichnet, welche Vertex-Input-Streams definiert werden. Dadurch müssen beim Draw-Call die einzelnen Vertex-Inputs nicht jedes mal neu definiert werden, sondern lediglich der VAO gebunden. Der lädt die Konfiguration dann automatisch. In der Praxis sieht das dann so aus:
glAttachShader(shader_prog, vert_shader);glAttachShader(shader_prog, frag_shader);// Stream an Index 0 wird mit "local_vertex" verknüpftglBindAttribLocation(shader_prog, 0, "local_vertex");glLinkProgram(shader_prog);