Shader 101: Clip Planes

Seit einigen Jahren wird die sogenannte Fixed Function Pipeline der Grafikkarten immer weiter abgebaut. Für die, die nicht wissen, was die Fixed Function Pipeline (FFP) ist: Es handelt sich dabei um mehr oder weniger fest in Silikon gebrannte Shader. Im Grunde genommen ist die FFP der Vorgänger der Shader. Früher musste alles, was mit 3D-Grafik in einer Grafikkarte passiert, in der FFP vorgenommen werden. Das ging von der Transformation bis hin zur Beleuchtung. Der Entwickler konnte dabei nur einzelne Teile der FFP ein- oder ausschalten und mit Parametern beeinflussen, nicht aber frei programmieren, was erst mit aufkommen der Shader möglich wurde.

Ein interessanter Aspekt der FFP war, daß diese 4 Clipping-Planes aufnehmen konnte und damit alles was ausserhalb dieser Planes lag clippen konnte. Dies sparte Rechenzeit in der Grafikkarte. In diesem Artikel möchte ich nun zeigen, wie man exakt dieses Feature in XNA in einem Shader nachbilden kann.

Zu Anfang möchte ich eine grundsätzliche Frage beantworten: Wozu überhaupt Clipping? Das ist sehr einfach zu erklären. Beim Clipping wird – etwas bildlich dargestellt – Grafik aus der Szene entfernt, die nicht notwendig ist. Alles was nicht gezeichnet wird, spart selbstverständlich Rechenzeit und damit hilft uns Clipping, die Last der Grafikkarte gering zu halten und Platz zu schaffen für tolle Effekte etc.

Dabei gibt es unterschiedliche Arten von Clipping bzw. auch von Culling, die teilweise auf der CPU, teilweise auf der GPU ausgeführt werden. Clipping und Culling sind im Sinne dieses Artikels sehr eng verwandt, wobei es schon ein paar Unterschiede gibt. Ein paar Beispiele sind:

  • View Frustum Culling
  • Occlusion Culling
  • Horizon Culling
  • Bounding Volume Culling (z.B. mittels Octree, Quadtree, BVH, BSP, etc.)
  • Scissor Test
  • Z-Test
  • Stencil-Test

Wichtig ist auch zu verstehen, wann was passiert und wo das jeweilige Clipping bzw. Culling greift, denn nur so kann man einschätzen, was man spart. Alle Algorithmen, die auf der CPU ausgeführt werden, passieren dabei natürlich vor der Grafikkarte und es werden hauptsächlich Draw-Calls gespart. Dies führt zu einer verminderten Last im Treiber und in DirectX bzw. damit auch XNA. Die negative Seite ist jedoch, dass die Last auf der CPU höher wird.

Danach geht es zur Grafikkarte, wo mehrere Stufen durchlaufen werden. Hier mal eine kleine Grafik, die ich eigentlich für einen anderen Artikel erstellt hatte, aber die hier auch ganz gut passt.

Das Shader-Clipping wird in der Regel im Vertex-Shader vorbereitet und im Pixel-Shader wird dann der jeweilige Pixel entweder gerendert oder aber auch entfernt.

Danach erfolgt der sogenannte Scissor-Test, der Z-Test (falls es einen Depthbuffer gibt), der Stencil-Test und abschliessend die sogenannte Write-Mask.

Was dies alles im Detail ist, möchte ich in diesem Artikel nicht beschreiben, da dies für das Verständnis von Clip-Planes nicht weiter von Bedeutung ist. Ich plane aber, noch weitere Artikel über die Bedeutung dieser Tests zum Culling und Clipping veröffentlichen.

Die wichtigste Information die man aus dieser Grafik entnehmen kann ist jedenfalls, dass das Clipping sehr früh in der Abarbeitung durch die Grafikkarte erfolgt. Alles was dahinter liegt spart man sich im Clipping-Fall. Dies spart insbesondere Lesezugriffe auf den Z-Buffer, aber auch Schreibzugriffe auf das Render-Target bzw. den Backbuffer. Es spart also – etwas plakativ dargestellt – Füllrate und entlastet den Speicherbus der Grafikkarte.

Das Plane-Clipping (welches so auch beim View Frustum Culling durchgeführt wird, aber dort mit 6 Planes gleichzeitig) kann man sich wie folgt vorstellen.

Das blaue Drahtgittermodell eines Würfels stellt dabei eine Geometrie dar, die wir zeichnen und clippen möchten. Die rote Fläche ist eine Clipping-Plane. Diese hat zwar normalerweise eine unendliche Ausdehnung, in diesem Fall habe ich diese aber endlich dargestellt, damit die Grafik deutlicher wird. Die grünen Linien sind die Schnittkanten. Zu beachten ist, dass an den Schnittkanten keine neue Fläche entsteht. Es wird einfach nur alles was auf einer Seite der Plane ist gerendert und was auf der anderen Seite der Plane ist, wird nicht gerendert. Dies würde in diesem Beispiel dazu führen, dass man – entsprechende Kamera-Position vorausgesetzt – in den Würfel hinein schauen kann.

Der eigentliche Clipping-Test ist nun sehr einfach. Wir berechnen einfach die Entfernung der aktuellen Vertex-Position zur Clip-Plane und übergeben diese an den Pixel-Shader. Dort wird diese Entfernung ganz einfach an die Clip-Funktion übergeben und der Pixel wird damit verworfen, oder eben nicht. Hier wird natürlich auch direkt ein weiteres Einsatzgebiet dieser Funktion deutlich und es wird klar, wie mächtig und vielseitig diese Funktion eigentlich ist.

Selbstverständlich müssen wir darauf achten, dass die Plane sich im selben Koordinaten-System befindet, wie das Objekt. Entweder beide im Objekt-Space oder beide im World-Space. Dies macht einen Unterschied, da man auf die jeweilige Transformation im Vertex-Shader achten muss.

Im Shader sieht das dann ungefähr wie folgt aus:

float4x4 World;
float4x4 View;
float4x4 Projection;

float4 ClippingPlane;

void vs(inout float4 position : POSITION0, out float4 planeDistance : TEXCOORD0)
{
    float4 worldPosition = mul(position, World);
    float4 viewPosition = mul(worldPosition, View);
    position = mul(viewPosition, Projection);

    planeDistance = dot(position, mul(ClippingPlane, Projection));
}

float4 ps(float4 planeDistance : TEXCOORD0) : COLOR0
{
    clip(planeDistance.x);

    // ab hier erfolgt der ganz normale Pixel-Shader
}

Diese Technik ist relativ einfach anzuwenden und es sollte keine größeren Probleme bereiten, dies in eigenen Spielen zu verwenden.

Anmerken möchte ich allerdings auch, dass diese Technik kein Allheilmittel für hohe Performance ist, sondern man genau abwägen muss, ob man den Overhead für das Clipping sinnvoll investieren kann, oder ob man sich diesen besser spart.

Eine Übersicht über alle Artikel der Reihe Shader 101 findet ihr im Einstiegsartikel: Shader 101: Grundlagen.

Advertisements

Veröffentlicht am 26.04.2011 in Shader, XNA, XNA 3.1, XNA 4.0 und mit , , , , getaggt. Setze ein Lesezeichen auf den Permalink. Ein Kommentar.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: