• Keine Ergebnisse gefunden

Kontakt und Updates

2.3 Direct3D-Grundlagen

2.3.6 Oberflächen

Wer schon mit früheren DirectX-Versionen gearbeitet hat, wird sich bestimmt noch an Di-rectDraw – die 2D-Grafikkomponente von DirectX – und den Begriff der Oberfläche erin-nern. Entgegen der Meinung vieler Programmierer, die auf neuere Versionen umgestiegen sind, existieren solche Oberflächen in DirectX immer noch – sie gehören nun aber zu Di-rect3D.

Eine Oberfläche ist ein Speicherbereich, der ein zweidimensionales Bild beliebiger Größe dar-stellt. Oberflächen gehören zu den Direct3D-Ressourcen, was uns erlaubt, sie – falls nötig – im Speicher der Grafikkarte unterzubringen.

Oberflächen werden durch die Schnittstelle IDirect3DSurface9 vertreten.

2.3.6.1 Zweck von Oberflächen

Einzelne Oberflächen werden recht selten erstellt – meistens sind sie Teil von Texturen. Eine Textur ist ein Bild mit verschiedenen Detailstufen (wovon jede in Form einer Oberfläche exis-tiert), welches sich über 3D-Primitiven legen lässt, um den Eindruck bestimmter Oberflächen-beschaffenheiten wie Holz, Metall oder Haut zu erwecken.

2.3.6.2 Oberflächenformate

Wie viele Bits oder Bytes ein Bildpunkt einer Oberfläche an Speicher benötigt und wie diese Daten letztendlich zu einer Farbinformation zusammenzusetzen sind, bestimmt das Oberflä-chenformat. Direct3D definiert für die verschiedenen Formate eine enum-Aufzählung namens

D3DFORMAT mit recht vielen Einträgen. Es ist wichtig zu wissen, dass nicht jede Grafikkarte alle Formate unterstützt und manche vielleicht auch nur für bestimmte Zwecke (zum Beispiel nur als Textur und nicht als Bildpuffer). Was im Bereich des Möglichen liegt, findet man ebenfalls mit der Schnittstelle IDirect3D9 heraus, was wir später genau besprechen werden.

Tabelle 2.4 Die wichtigsten Oberflächenformate Formatbezeichner Beschreibung

D3DFMT_R8G8B8 24-Bit-RGB-Format: 8 Bits für jede Farbkomponente D3DFMT_A8R8G8B8 8 Bits für Alpha und 8 Bits für jede Farbkomponente D3DFMT_X8R8G8B8 8 Bits unbenutzt und 8 Bits für jede Farbkomponente D3DFMT_R5G6B5 16-Bit-RGB-Format: 5 Bits für Rot und Blau, 6 Bits für Grün

Formatbezeichner Beschreibung

D3DFMT_A1R5G5B5 1 Bit für Alpha und jeweils 5 Bits für die Farbkomponenten D3DFMT_X1R5G5B5 1 Bit unbenutzt und 5 Bits für jede Farbkomponente D3DFMT_A4R4G4B4 Jeweils 4 Bits für Alpha, Rot, Grün und Blau

D3DFMT_A2R10G10B10 2 Bits für Alpha und jeweils 10 Bits für jede Farbkomponente D3DFMT_R3G3B2 8-Bit-RGB-Format für Texturen – 3 Bits für Rot/Grün, 2 Bits für Blau D3DFMT_A16B16G16R16 Neu in DirectX 9: ein 64-Bit-Oberflächenformat

D3DFMT_A32R32G32B32F Ebenfalls neu: 32 Bits pro Komponente als float-Werte

Die Werte für die Farbkomponenten liegen im Speicher in der Reihenfolge vor, in der sie im Formatbezeichner genannt werden. Bei D3DFMT_R8G8B8 kommen also als Erstes 8 Bits für Rot, gefolgt von 8 Bits für Grün und 8 Bits für Blau. Das ist wichtig zu wissen, wenn man direkt auf Oberflächen zeichnen möchte, indem man ihren Speicherbereich manipuliert.

2.3.6.3 Der Bildpuffer (Back-Buffer)

Wenn eine 3D-Szene gezeichnet wird, passiert das Schritt für Schritt, Dreieck für Dreieck, nicht alles wird auf einmal gezeichnet. Doch wie kommt es dann, dass man auf dem Bild-schirm von diesem Aufbau nichts mitbekommt? Geht das wirklich so schnell, dass man nicht das geringste Flackern bemerkt, oder gibt es da einen Trick?

Man zeichnet nicht direkt „auf den Bildschirm“ – also nicht direkt in den sichtbaren Videospeicher der Grafikkarte, welchen sie zum Monitor schickt, sondern in einen abgeschotteten, für den Benut-zer unsichtbaren Speicherbereich. Dieser Speicherbereich nennt sich Back-Buffer oder Bildpuffer und befindet sich im Speicher der Grafikkarte, damit Zugriff auf ihn schneller erfolgen kann.

Der Bildpuffer ist eine typische Direct3D-Oberfläche, das heißt, dass er auch gesperrt werden kann, um direkten Zeichenzugriff für 2D-Grafik zu erhalten (wie man das von DirectDraw-Oberflächen gewohnt war).

Zu Beginn des Zeichenvorgangs wird der Bildpuffer auf eine frei definierbare Farbe gesetzt – entweder der ganze Puffer oder nur rechteckige Teilbereiche. In einem Weltraumspiel würde man wohl am ehesten Schwarz verwenden. Alle danach folgenden Zeichenbefehle schreiben dann in den Bildpuffer und bauen allmählich das komplette Bild auf. In Direct3D gibt es sogar die Möglichkeit, die Ausgabeoberfläche frei festzulegen, um beispielsweise auf eine Textur oder eine andere Oberfläche zu rendern.

Am Ende der Szene wird der Bildpuffer sichtbar gemacht. Läuft die Anwendung in einem Fenster, wird er dort hinein kopiert und auf die Fenstergröße angepasst (das Bild wird ge-staucht oder gestreckt, falls der Bildpuffer nicht die Größe des Fensters hat). Der Bildpuffer muss im Fenstermodus dem Format des aktuellen Videomodus angepasst sein (Ausnahmen gibt es nur, wenn die Hardware eine automatische Farbkonvertierung unterstützt). Läuft das System zum Beispiel gerade auf 16-Bit-R5G6B5-Grafik, sollte der Bildpuffer ebenfalls dieses Format haben. Im Vollbildmodus können wir selbst das Bildpufferformat bestimmen, der Vi-deomodus wird dann automatisch angepasst.

Vertikale Strahlsynchronisation

Wie Sie vielleicht wissen, baut ein Monitor sein Bild mit Hilfe von Elektronenstrahlen auf, welche die Mattscheibe in verschiedenen Farben zum Leuchten anregen, während sie sie von oben nach unten zeilenweise abtasten. Die Bildwiederholfrequenz bestimmt, wie oft das Bild

pro Sekunde neu aufgebaut wird. Je niedriger die Frequenz, desto stärker „flimmert“ das Bild.

Bei 120 Hz – wo man kein Flimmern mehr wahrnimmt – sind es genau 120 Mal pro Sekunde.

Nun stellen Sie sich folgende Situation vor: Eine 3D-Szene wurde gerade fertig gerendert und wartet nun im Bildpuffer darauf, sichtbar gemacht zu werden (durch Kopieren in den sichtba-ren Videospeicher). Nun beginnt der PC mit dem Kopiervorgang, wenn der Bildschirm sein Bild gerade erst bis zur Mitte aufgebaut hat. Das Bild ist nun so schnell kopiert, dass es prak-tisch den Bildschirm überholt hat, und die obere Hälfte zeigt noch das alte Bild, während die untere bereits das neue Bild, welches frisch aus dem Bildpuffer kommt, zeigt. Fand zwischen diesen beiden Bildern eine signifikante Bewegung statt, wird das Bild „abgehackt“ erscheinen – ein unschöner Effekt, den man vermeiden sollte. Fällt Ihnen eine Lösung ein?

Man wartet mit dem Kopieren bis zum vertikalen Strahlrücklauf. Das ist der Moment, in dem der Bildschirm das aktuelle Bild gerade fertig aufgebaut hat und der Elektronenstrahl von unten nach oben in die gegenüberliegende Ecke zurückgefahren wird (der Strahl wird mit Hilfe zweier Elekt-romagneten durch die Lorentz-Kraft vertikal und horizontal abgelenkt). Da das einige Zeit in An-spruch nimmt, kann das Bild währenddessen sicher in den sichtbaren Videospeicher transferiert werden. Vor dem Rücklauf sendet der Monitor ein Signal an die Grafikkarte. Diese Technik nennt man vertikale Strahlsynchronisation.

2.3.6.4 Der Z- und der Stencil-Buffer

Kommen wir nun zu zwei weiteren wichtigen Oberflächen: dem Z-Buffer und dem Stencil-Buffer. In Wirklichkeit handelt es sich zwar um eine einzige Oberfläche, aber es ist einfacher, sie sich getrennt vorzustellen.

Der Z-Buffer

Stellen Sie sich eine 3D-Szene mit sehr vielen herumschwirrenden Objekten vor. Manche sind weit vom Beobachter entfernt, manche nicht. Nun soll die Szene gezeichnet werden, doch da taucht ein Problem auf: Wie erreicht man es, dass nahe Objekte vor fernen Objekten erschei-nen, sie also verdecken? Am einfachsten scheint es zunächst, die Objekte nach ihrer Entfer-nung zu sortieren und sie dann von hinten nach vorne zu zeichnen, so dass die vorderen die hinteren Objekte überdecken. An sich ein guter Ansatz – aber was, wenn sich zwei Objekte überschneiden? „Man sortiert dann nicht mehr die Objekte, sondern die Dreiecke, aus denen sie bestehen!“ könnte man argumentieren. Was passiert jedoch, wenn sich nun auch noch zwei Dreiecke schneiden? Sollte man sie dann entlang ihrer Schnittlinie in kleinere Teildreiecke aufteilen?

Das läge natürlich im Bereich des Möglichen, und wenn man Lust hat, auf jedes Bild eine oder zwei Sekunden zu warten, kann man es auch so machen.

Es ist hier nötig, eine Ebene tiefer zu gehen: nicht die Dreiecke sortieren, sondern die Pixel! Man darf nicht vergessen, dass auch nach der Projektion noch eine Tiefe vorhanden ist. Es wird ein neuer Speicherbereich angelegt, der die Tiefe jedes Pixels speichert, sein Name: Z-Buffer.

Nun wird nicht mehr jeder Pixel „bedenkenlos“ gezeichnet, sondern man vergleicht erst seine Tie-fe mit dem Wert aus dem Z-BufTie-fer an den Koordinaten des Pixels, also mit der TieTie-fe eines eventu-ell vorher schon an dieser Steventu-elle geschriebenen Pixels. Wenn die Tiefe des neuen Pixels kleiner (oder gleich) ist als die bereits gespeicherte, dann bedeutet das, dass der Pixel näher am Beobach-ter ist und damit Vorrang hat. Seine Tiefe wird nun im Z-Buffer gespeichert. Liegt er jedoch tiefer als der bereits gespeicherte Pixel, kann er gar nicht sichtbar sein und wird daher verworfen, also nicht gezeichnet.

Zu Beginn der Szene setzt man den gesamten Z-Buffer auf den Maximalwert, so dass die ersten ankommenden Pixel immer sichtbar sind (es sei denn, sie liegen hinter der hinteren Clipping-Ebene) und dann langsam die z-Grenze herabsetzen.

Obwohl der Z-Buffer keine Farben sondern Zahlen im Bereich von 0 bis 1 speichert (0: mini-male Entfernung; 1: maximini-male Entfernung), ist er trotzdem eine Oberfläche. Es gibt verschie-dene Oberflächenformate für Z-Buffer. Dabei gilt: Je mehr Bits, desto höher ist die Genauig-keit. Z-Buffer können für gewöhnlich nicht gesperrt werden. Es gibt zwar eine Ausnahme, je-doch ist sie unmittelbar mit einem Performanceverlust verbunden.

Tabelle 2.5 Z-Buffer-Oberflächenformate

Formatbezeichner Beschreibung

D3DFMT_D32 32-Bit-Z-Buffer-Format

D3DFMT_D24X8 32-Bit-Z-Buffer-Format, wobei aber nur 24 Bits als Z-Buffer verwendet wer-den (die restlichen 8 Bits bleiben unbenutzt)

D3DFMT_D16 16-Bit-Z-Buffer-Format

D3DFMT_D16_LOCKABLE 16-Bit-Z-Buffer, der gesperrt werden kann (direkter Zugriff durch die Anwen-dung), langsamer und nicht von jeder Hardware unterstützt

D3DFMT_D32F_LOCKABLE Neu in DirectX 9: 32 Bits pro Pixel, aber als Fließkommazahl. Dieses Format ist – wie D3DFMT_D16_LOCKABLE – sperrbar.

Das Verhalten des Z-Buffers wird unter anderem durch die Vergleichsfunktion bestimmt. Sie legt fest, wann ein Pixel sichtbar sein soll und wann nicht. Normalerweise verwendet man die Vergleichsfunktion kleiner oder gleich, so dass die Objekte in der korrekten Reihenfolge von vorne nach hinten erscheinen, denn nur Pixel, deren Tiefe kleiner oder gleich der des bereits im Z-Buffer vorhandenen ist, werden gezeichnet (und ihre Tiefe wird zum neuen Z-Buffer-Wert dieses Pixels).

Leider ist die Genauigkeit im Z-Buffer nicht überall gleich groß. In kurzen Entfernungen ist die Genauigkeit sehr hoch, meistens zu hoch, so dass weiter entfernte Objekte oft nur fehler-haft dargestellt werden können. Es hilft, in der Projektionsmatrix möglichst große Werte für die nahe Clipping-Ebene und möglichst kleine für die ferne Clipping-Ebene zu verwenden, wobei die Veränderung der nahen Ebene die größten Auswirkungen hat. Allgemein sollte der Bereich zwischen den beiden Ebenen so klein wie möglich gehalten werden, denn wenn bei-spielsweise ein 16-Bit-Z-Buffer (in dem die Tiefe nur einen von 216 = 65536 Werte annehmen kann) auf 100 Einheiten Tiefe verteilt wird, ist er natürlich genauer, als wenn er auf 1000 heiten aufgeteilt werden muss. Setzt man die nahe Clipping-Ebene beispielsweise auf 5 Ein-heiten, dann ist ein Objekt, das nur 4 Einheiten vor der Kamera positioniert ist, nicht sichtbar.

Man sollte dann also dafür sorgen, dass der Spieler nicht zu nah an Wände oder Ähnliches he-rankommt, da er sonst einfach hindurchsehen kann. Einen kleinen Rechner für die Genauigkeit eines Z-Buffers mit beliebiger Anzahl von Bits finden Sie unter folgender Adresse:

http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html.

Um das Problem mit der Genauigkeit etwas zu lindern, kann man den Z-Buffer auch ein wenig umfunktionieren: Der W-Buffer speichert nicht die Tiefe jedes Pixels in Form seiner z-Koordinate, sondern in Form seiner w-z-Koordinate, die bei der Projektion herauskommt. Die w-Werte verteilen sich nämlich ein wenig besser in der Tiefe als die z-Werte. Unglücklicher-weise wird der W-Buffer nicht von jeder Hardware unterstützt. 24- oder 32-Bit-Z-Buffer rei-chen jedoch auch meistens aus, wenn man sie gewissenhaft einsetzt.

Der Stencil-Buffer

Der Stencil-Buffer (Schablonenpuffer) kann als Puffer gesehen werden, der für jeden Pixel ei-nen zusätzlichen Wert – eine recht beschränkte Zahl wie zum Beispiel eiei-nen 4-Bit-Wert – speichert. Durch das Zeichnen von Dreiecken kann der Stencil-Buffer gefüllt werden, oder

man verwendet seinen Inhalt, um bestimmte Pixel zu maskieren (beispielsweise um sie nicht zu zeichnen oder sie durch eine andere Farbe zu ersetzen).

Beispiel

Man möchte den Umriss eines Objekts besonders hervorheben. Dazu maskiert man mit Hilfe des Stencil-Buffers diejenigen Pixel, die das Objekt umgeben. Anschließend zeichnet man ein großes rotes Viereck über das gesamte Bild und stellt den Stencil-Buffer so ein, dass die rote Farbe nur auf vorher maskierte Pixel gezeichnet wird.

Auch hier gibt es unzählige Optionen, die man festlegen kann – wir werden sie uns später ge-nauer ansehen. Erst einmal reicht es, wenn man weiß, dass es so etwas wie den Stencil-Buffer überhaupt gibt.

Wie bereits gesagt sind Z- und Stencil-Buffer nicht unabhängig voneinander. In der Tat befin-den sie sich in ein und demselben Puffer. Die zuvor aufgezählten Z-Buffer-Formate sehen kei-nen Speicherplatz für eikei-nen Stencil-Buffer vor. Dafür gibt es nämlich die folgenden Formate:

Tabelle 2.6 Stencil- und Z-Buffer-Formate Formatbezeichner Beschreibung

D3DFMT_D24S8 24 Bits für den Z-Buffer und 8 Bits für den Stencil-Buffer D3DFMT_D24X4S4 24-Bit-Z-Buffer und 4-Bit-Stencil-Buffer (4 Bits ungenutzt) D3DFMT_D15S1 15-Bit-Z-Buffer und nur ein Bit für den Stencil-Buffer

D3DFMT_D24FS8 Neu in DirectX 9: 24 Bits für den Z-Buffer als Fließkommazahl und 8 Bits für den Stencil-Buffer

Es sollte schon vorher bekannt sein, ob die Anwendung einen Stencil-Buffer benötigt oder nicht. Falls ja, kommt nur eines dieser vier Formate in Betracht.