Benutzer:Coolcat
Inhaltsverzeichnis
Komplettüberarbeitung des Shader-Artikels
Die traditionelle Funktionspipeline der OpenGL ist eine feste Pipeline, auf die man nur beschränkt durch Statechanges Einfluß nehmen kann. Man hat also an sehr vielen Stellen starre Vorgaben die nur minimal anpassbar sind. So sind z.B. Farbberechnungen oder die Beleuchtung fest definiert und nur wenige ihrer Attribute können variiert werden. Neuere Algorithmen erfordern aber eine viel höhere Flexibiltät. Einzelne Komponenten der Pipeline können durch kleine Programme, sog. Shader ersetzt werden. Bevor man sich mit Shadern beschäftigt hat sollte man die Grundlagen von OpenGL und den Aufbau der Renderingspipeline vollständig verstanden haben.
Es gibt verschiedene Shader-Versionen, das sogenannte ShaderModel, kurz SM. Man sollte immer darauf achten welches SM die eigene Grafikkarte unterstützt. Hier ein paar grobe Richtwerte:
Hersteller | Chip | ShaderModel |
---|---|---|
ATI | Radeon 9800 Pro | 2.0 |
Nvidia | GeForce 9800 GT | 4.0 |
Folgendes Bild zeigt den Aufbau der festen Renderingpipeline. Mit roten Kästen sind die Teile markiert die sich durch eigene kleine Programme, also Shader, ersetzen lassen. Weiter unten im Artikel werden diese Teile dann im einzelnen erklärt. Die im Bild als optional markierten Teile sind standardmäßig abgeschaltet und nur auf neuester Grafikhardware verfügbar.
Der Scissor- und Stencil-Test ist im Bild nach dem Pixelshader angeordnet. Auf den meisten Diagrammen im Internet ist dies ebenfalls so, daher haben wir uns entschieden diese hier genauso zu machen. Höchstwahrscheinlich ist der Scissor-Test aber direkt im Rasterizer implementiert und auch der Stencil-Test kommt vermutlich aus Performancegründen vor dem Pixelshader. Möglicherweise ist dies auch auf verschiedenen Chips unterschiedlich implementiert. Für das Verständnis der Shader sind diese Implementierungsdetails jedoch unerheblich.
Vertexshader
Der Vertexshader erlaubt uns jeden Vertex einzeln zu modifizieren. Wir müssen die Modelview-Transformation sowie die Perspective-Transformation selbst übernehmen. Wollen wir klassisches Gouraud Shading müssen wir dies auch selbst übernehmen.
Beispiel: Heightmap-Terrain
Angenommen wir benötigen für jeden Vertex eine Position, eine Normale und zwei Sets von Texturkoordinaten, also insgesamt 10 Floats bzw. 40 Bytes. Bei einer Heightmapgröße von 4096x4096 benötigen wir also 640 Mb Speicher. Dies ist also nicht wirklich sinnvoll, insbesondere da viele Grafikkarten nur 512 oder gar 256 Mb Speicher haben. Dies ist also mit der festen Funktionspipeline nicht praktikabel zu lösen.
Gewöhnlich rendert man das Terrain nicht am Stück sondern in Blöcken von z.B. 64x64 Quads. Wir verwenden nun einen kleinen Vertexbuffer der 65x65 minimale Vertices enthält: Jeweils nur eine 2D-Position. Die Höhe des jeweiligen Vertex wird im Vertexshader aus der Heightmap-Textur ausgelesen und an die entsprechende Koordinate geschrieben. Auch die Vertexnormalen und Texturkoordinaten berechnet man direkt im Shader. Letzlich benötigen wir nur den vernachlässigbar kleinen Vertex-, den ebenfalls sehr kleinen Indexbuffer und natürlich die Heightmaptextur. Angenommen wir verwenden 16bit Graustufen dann hat diese Textur eine Größe von 32 Mb. Dies sollte auch auf älteren Grafikkarten im Rahmen des machbaren sein.
Beispiel: Meshanimation
Will man ein Meshanimieren muss man normalerweise jeden Teil des Meshes einzelnen rendern. Im Vertexshader kann man jeden Teil des Meshes einzeln transformieren oder auch zwischen den Teilen interpolieren. Diese Methode erfordert natürlich eine Markierung der einzelnen Meshteile. Hier eignet sich beispielsweise ein Farbkanal der Vertexfarbe.
Geometryshader
Der Geometryshader ist Teil von Shader Model 4.0 und somit nur auf neueren Grafikkarten verfügbar. Zudem gehört er wirklich zu den fortgeschrittenen Dingen in der Computergrafik. Als Anfänger sollte man sich also vielleicht erstmal mit dem Vertexshader zufriedengeben.
Der Geometryshader kann vom Prinzip die gleichen Dinge tun wie der Vertexshader, hat aber diverse Vorteile:
- verarbeitet man z.B. Dreiecke, kann man ein Dreieck als ganzes Verarbeiten. Man kann also gleichzeitig auf alle 3 Vertices des Dreiecks zu greifen.
- es gibt neben den bekannten GL_POINTS, GL_LINES und GL_TRIANGLES neue Input-Geometrietypen, z.B.: GL_TRIANGLES_ADJACENCY_EXT und GL_TRIANGLE_STRIP_ADJACENCY_EXT. Diese erlauben es nicht nur auf das aktuelle Dreieck zuzugreifen, sondern auch auf die benachbarten Dreiecke. Bei GL_TRIANGLES_ADJACENCY_EXT bekommt man dann zum Beispiel ein Array der Größe 6 für jedes Attribut des Vertex. Diese Adjazenz-Informationen müssen natürlich auch bereits so im Vertexbuffer gegeben sein.
- der Geometryshader stellt neue Funktionen bereit mit denen man neue Primitive zusammenbauen kann. Es gibt drei mögliche Output-Geometrietypen: GL_POINTS, GL_LINE_STRIP und GL_TRIANGLE_STRIP
- alle Primitive die den Geometryshader verlassen muss man selbst erzeugen. Man kann also insbesondere auch einfach keine Primitive erzeugen.
Kommen wir zu den Nachteilen der aktuellen Implementierung:
- Anzahl der Output-Vertices und der Geometrietyp müssen im voraus festgelegt werden. Egal ob die Vertices erzeugt werden oder nicht wird der dafür nötige Speicher reserviert. Aus diesem Grund ist die Performance eines Geometryshaders sehr stark von dieser einmal vom Programmierer konstant festgelegten Anzahl Output-Vertices abhängig. [1]
- Der Geometryshader eignet sich nur für kleine Modifikationen, nicht für Heavy-Output Algorithmen.
Beispiel: Shadow-Volumes
Shadow-Volumes haben den Nachteil das man zusätzliche Geometrie für die Seiten des Volumes erzeugen muss. Dafür gibt es verschiedene Wege.
- Man kann die zusätzliche Geometrie auf der CPU erzeugen und in drei Passes (1. Frontfaces, 2. Backfaces extrudiert, 3. Seiten) rendern. Dies ist offensichtlich ineffizient.
- jede Kante im Mesh durch zwei degenerierte Dreiecke ersetzen. Im Vertexshader kann man dann Backfacing-Polygone extrudieren. Dies erhöht die Vertexanzahl um den Faktor 2. Die Anzahl der Dreiecke wird um den Faktor 4 erhöht.
- der Geometryshader erlaubt es nun die zwei benötigten Dreiecke einfach dann zu erzeugen wenn man sie benötigt. Da man zusätzlich Adjazenz-Informationen benötigt, vergrößert sich auch hier der Vertexbuffer um den Faktor 2. Allerdings bleibt die Anzahl der Dreiecke konstant und es ist nicht notwendig degenerierte Dreiecke zu rendern.
Transform-Feedback
Fragmentshader (auch Pixelshader)
Quellen
[1] NVIDIA GeForce 8 and 9 Series GPU Programming Guide, insbesondere Abschnitt 4.6