Meine Werkzeuge
Namensräume
Varianten

Vier Methoden zur Erzeugung eines Fullscreen Quad

Aus indiedev
Wechseln zu: Navigation, Suche
Tutorial
Vier Methoden zur Erzeugung eines Fullscreen Quad
Autor Roland "Glatzemann" Rosenkranz
Programmier­sprache HLSL
Kategorie HLSL
Diskussion Thread im Forum
Lizenz indiedev article license


In diesem Artikel möchte ich eine einfache und extrem effektive Methode zur Darstellung eines Rechteckes, das den gesamten Bildschirm bedeckt, vorstellen. Dieses auch Full Screen Quad genannte Rechteck wird für eine Reihe von Effekten im Rahmen der Grafikprogrammierung verwendet. Es kommt hauptsächlich im Rahmen des Deferred Rendering und bei Post Processing Effekten zum Einsatz. Ziel dieses Rechteckes ist es ein möglichst günstiges Objekt zu zeichnen, welches dazu führt, daß ein Pixel Shader für jeden einzelnen sichtbaren Bildpunkt auf dem Bildschirm ausgeführt wird.

Inhaltsverzeichnis

Naive Methode

Bei der naiven Methode wird einfach ein Rechteck aus zwei Dreiecken erzeugt. Dreiecke deswegen, weil dies das Grundprimitive der Grafikkarten ist und man daraus einfach ein Rechteck erzeugen kann. Man erzeugt zunächst einen Vertex Buffer, welcher aus sechs Vertices besteht. Ein einfacher Vertex Shader, der einfach die Positionen im Clip Space durchreicht ist mehr als genug. Der Pixel Shader variiert je nach Anwendungsfall natürlich.

FullScreenQuadNaiv.png

Die sechs Vertices sind als rote Punkte markiert.

Die naive Methode hat drei Nachteile:

  1. Zwei Vertices werden doppelt berechnet. Einmal für das erste Dreieck und einmal für das zweite Dreieck. Ohne Index Buffer funktioniert nämlich der Vertex Cache nicht.
  2. Einige Bildpunkte werden doppelt berechnet. Dies sind die Bildpunkte, an denen die beiden Dreiecke auf dem Bildschirm aneinander stoßen. Dies hängt damit zusammen, daß die GPU der Grafikkarte Pixel in Blöcken bearbeitet. Dies können 2x2 Pixel gleichzeitig sein, aber auch bei einigen Modellen bis zu 16x16 Pixeln. Am Rand in der Bildschirmmitte werden die Bildpunkte also doppelt berechnet, da es dafür keinen Cache gibt (und auch nicht geben kann).
  3. Ein Vertex Buffer muss erzeugt und zum Programmende freigegeben (also verwaltet) werden.

Die naive Methode kann einfach durch Verwendung eines Index Buffers optimiert werden.

"Indiziertes Rechteck" Methode

Bei dieser Methode müssen lediglich vier Vertices für die Eckpunkte des Bildschirms erzeugt werden. Zusätzlich erzeugen wir einen Index Buffer mit dessen Hilfe zwei Dreiecke erzeugt werden, die dann das Vollbild Rechteck erzeugen. Die beiden doppelt verwendeten Vertices müssen bei dieser Methode dank des Vertex Cache nur einmalig berechnet werden.

FullScreenQuadIndiziert.png

Die vier Vertices sind in rot markiert und die sechs Indices in grün.

Diese Methode hat zwei Nachteile:

  1. Es werden immer noch einige Bildpunkte doppelt berechnet. Der Grund ist - wie in der vorherigen Methode - immer noch die Blockarbeitsweise der Grafikkarte.
  2. Hier müssen sogar zwei Buffer - Index- und Vertex Buffer - erzeugt und verwaltet werden.

Resourcenschonende Rechteck Methode

Mit Verwendung von HLSL Vertex Shadern ab Version 4 ist es möglich auf den Vertex Index zuzugreifen. Damit ist es einfach möglich - auch ohne Verwendung von Tesselation oder Geometry Shadern - die notwendigen Vertices im Vertex Shader zu berechnen. Für diese Methode ist lediglich ein Draw Call und ein passender Vertex Shader notwendig. Der Pixel Shader natürlich auch, aber um den anzusteuern treiben wir den ganzen Aufwand ja erst.


struct VSQuadOut{
	float4 position : SV_Position;
	float2 texcoord: TexCoord;
};

VSQuadOut main(uint VertexID: SV_VertexID)
{
  VSQuadOut Out;
  Out.texcoord = float2((VertexID << 1) & 2, VertexID & 2);
  Out.position = float4(Out.texcoord * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
  return Out;
}

Um zu zeichnen muss lediglich ein leerer Vertex- und Index-Buffer gesetzt werden und sechs Vertices gerendert werden (mittels Draw-Aufruf).

Aber auch die resourcenschonende Methode hat Nachteile:

  1. Bei dieser Methode werden zwei Vertices wieder doppelt berechnet.
  2. Und auch einige Bildpunkte werden wegen der Blockarbeitsweise der GPU immer noch doppelt berechnet.

Optimale Methode

Die optimale Methode hat nur Vorteile. Bei dieser Methode wird weder ein Vertex- noch ein Index Buffer benötigt. Hier werden auch keine Vertices doppelt berechnet und auch keine Pixelblöcke müssen mehrfach berechnet werden. Dies erreichen wir ganz einfach durch Einsatz eines einzelnen Dreiecks.

Ein Dreieck? Ihr fragt euch sicherlich nun, wie man mit einem einzelnen Dreieck den gesamten Bildschirm abdecken kann. Das ist ganz einfach: Das Dreieck wird einfach so groß gemacht, daß es den gesamten Bildschirm abdeckt und dabei an den Rändern übersteht.

FullScreenTriangle.png

Der pfiffige Leser wird nun sicherlich direkt einen Einwand haben: Wenn ein Dreieck so groß ist, daß es über die Bildschirmränder hinausragt, dann werden doch etliche Bildpunkte zuviel berechnet. Dies klingt logisch, ist aber glücklicherweise nicht so und das ist auch der Grund, warum diese Methode die optimale Methode ist.

Eine Grundfunktionalität von modernen Grafikkarten und GPUs ist das sogenannte Clipping. Um möglichst viel Arbeit zu sparen werden Dreiecke am Bildschirmrand von der Hardware geclippt und nur die sichtbaren Bildpunkte berechnet. Dieser Vorgang ist, da er für jedes Dreieck in Hardware ausgeführt werden muss, kostenlos. Die Hardware macht das einfach.

Wir benötigen also einen passenden Vertex Shader, der die Eckpunkte des Dreiecks bereitstellt und einen Draw-Call um drei Vertices zu zeichnen. Mehr ist nicht notwendig um für jeden Bildschirmpunkt einmal den Pixel Shader auszuführen.


struct VSQuadOut{
	float4 position : SV_Position;
	float2 texcoord: TexCoord;
};

VSQuadOut main(uint VertexID: SV_VertexID){// ouputs a full screen quad with tex coords
	VSQuadOut Output;
	Output.texcoord = float2((VertexID << 1) & 2, VertexID & 2);
	Output.position = float4(Output.texcoord * float2(2, -2) + float2(-1, 1), 0, 1);
	return Output;
}

Auch hier muss mit einem nicht gesetzten Vertex- und Index-Buffer einfach ein Draw-Call mit drei Vertices ausgeführt werden.

Das Ende

In diesem kurzen Artikel habe ich vier Methoden zur Erzeugung eines Vollbild-Rechtecks gezeigt. Diese wurden basierend auf den Nachteilen immer weiter optimiert, bis zum Schluss eine optimale Version herausgekommen ist. Dieses Verfahren wurde bereits in etlichen Spielen wie beispielsweise Battlefield 3 angewendet und hat somit bewiesen, daß es funktioniert.

Navigation
Tutorials und Artikel
Community Project
Werkzeuge