In dieser Serie möchte ich auf Aspekte der 3D-Spieleprogrammierung eingehen. Inhalte dieser Serie werden Matrizen, Vertices, Indices, Vertex-Buffer, Index-Buffer sein. Außerdem wird diese Serie von einer HLSL-Serie begleitet, die sinnvoll ineinander verzahnt sind.

In diesem Kapitel möchte ich erstmal alle Begriffe erläutern.

Eine Matrix

Was ist eine Matrix?

Eine Matrix kennen viele aus der Schule. In MonoGame ist es nichts anderes. Mathematisch gesehen ist ein Vektor nichts anderes als eine Matrix mit einer Spalte. Matrizen können dabei beliebig viele Spalten und Zeilen haben. Es werden also Zahlen in waagerechten und senkrechten Reihen eingeordnet. Die Matrizen, denen wir jedoch begegnen werden, besitzen genau vier Spalten und vier Zeilen – gefüllt mit float-Werten.

Eine Matrix könnte beispielsweise so aussehen:

(0.0f  0.1f  0.2f  0.3f)
(0.0f  0.1f  0.2f  0.3f)
(0.0f  0.1f  0.2f  0.3f)
(0.0f  0.1f  0.2f  0.3f)

Wozu Matrizen in Spielen?

Um zu erklären, warum Matrizen ein wichtiger Bestandteil der Spieleentwicklung sind, muss ich etwas weiter ausholen. Wenn wir an dreidimensionale Spiele denken, kommen uns bestimmt sehr viele Verschiedene in den Sinn. Doch eines haben diese Spiele gemeinsam: nämlich, dass sie in den meisten Fällen auf einem gewöhnlichen Bildschirm gespielt werden. Doch der Bildschirm ist eine zweidimensionale Fläche, ohne wirklich Tiefe. Alles, was wir als Tiefe wahrnehmen, sind nichts weiter als Sinnestäuschungen durch Effekte wie „weiter entfernte Objekte sind kleiner“ (Projektion) oder der Möglichkeit, Objekte zu drehen und von verschiedenen Seiten zu beobachten (Transformation).

All diese Operationen sind zwar auch ohne Matrizen möglich, doch die Verwendung erlaubt es Berechnungen einzusparen, was zu einem spürbaren Performancegewinn bei sehr vielen Objekten führt.

Der Vertex

Der Begriff Vertex kommt nicht nur in der IT-Welt vor, sondern auch in der Medizin oder Astronomie. Das Wort kommt dabei aus dem Lateinischen und bedeutet übersetzt Scheitel.

Ein Vertex in der Spieleentwicklung definiert eine Struktur, welche z.B. einen Punkt in einem Raum (Vector3) beinhaltet. Er kann jedoch auch wetere Informationen speichern, welche genau sehen wir später.

Stellen wir uns einen Würfel im Spiel vor: Der Würfel hat genau acht Ecken. Diese Ecken haben eine Position in der Welt. Die Vertices wären dann die Ecken des Würfels. Allerdings brauchen wir statt acht insgesamt 36 Vertices. Der Grund dafür ist, dass unsere Grafikkarte Dreicke berechnet. Das bedeutet, alle Objekte in Spielen aus ganz vielen Dreiecken modelliert wurden. Eigentlich könnte man ein Objekt auch mit Vierecken formen. Aber ein Viereck kann immer in zwei Dreiecke unterteilt werden. Dreiecke hingegen kann man nur in Dreicke teilen. Somit ist das Dreieck das simpelste Polygon, um Flächen/Objekte zu formen.

Stellen wir uns also den Würfel aus einer Zusammensetzung von 12 Dreiecken mit je drei Vertices vor. Eine Fläche des Würfels würde also so aussehen:

vertices_flaeche

Der Index

Der Begriff Index kommt ebenfalls aus dem Lateinischen und bedeutet „Verzeichnis“. Den Begriff werdet ihr aber auch aus dem Matheunterricht kennen. Der Plural kann Indexe oder Indices sein, wobei zweiteres sicherlich verbreiterter ist.

Ein Verzeichnis ist an sich eine Liste von Elementen, wobei jedem Element ein eindeutiger Schlüssel zugeordnet ist. Genau, wie auf der Speisekarte die verschiedenen Gerichte durchnummeriert sind und ihr bei der Bestellung „die Nummer 34“ sagen könnt.

In unseren Spielen kann so ein Verzeichnis ebenfalls nützlich sein. Denken wir an den Würfel mit den 36 Vertices zurück. Viele der Dreiecke benutzen den gleichen Vertex als Ecke. So können wir nur acht Vertices definieren und zusammen mit einer Indexliste an die GPU senden.

Wenn wir das praktisch umsetzen, wird das Prinzip klarer.

Der Vertex- und Index-Buffer

Die Berechnungen der Vertices erfolgen entweder zur Laufzeit auf der CPU oder sind in einer Modelldatei aus einem Modellierungsprogramm wie Blender. In beiden Fällen wären die Millionen von Vertices und Indices in unserem Arbeitsspeicher. Doch „gezeichnet“ werden sie von unserer Grafikkarte und das im besten Fall mit mehr als 60 Bildern pro Sekunde. Das bedeutet, dass 60 mal pro Sekunde die Daten aus unserem Arbeitsspeicher (RAM) an die Grafikkarte gesendet werden. Hier entsteht Zeitverlust. Deshalb haben dedizierte Grafikkarten einen eigenen Speicher (Videospeicher oder VRAM genannt). Wenn wir unsere Daten einmalig an die Grafikkarte senden und sie dann jederzeit die Daten aus dem eigenen Speicher viel schneller abrufen kann, klingt nach einer plausibleren Lösung.

Der Vertex-Buffer und Index-Buffer sind Speicherbereiche. Diese liegen optimalerweise im Speicher der Grafikkarte. In einigen Fällen landen unsere Vertices aber im RAM. Zum Beispiel wenn der VRAM zu voll ist oder das System schlicht und ergreifend keinen eigenen Grafikspeicher besitzt. Smartphones oder Konsolen sind gängige Beispiele für Systeme ohne eigenen VRAM. Dies handled MonoGame für uns, weshalb es uns bei der Programmierung an sich egal sein kann, wo die Daten nun wirklich liegen. Allerdings sollte man durch die Nutzung von Hardware-Buffern auf einer Konsole nicht den selben Performancegewinn erhoffen wie auf einem Gaming-PC.

An dieser Stelle möchte ich dieses Kapitel nun auch abschließen. In den nächsten Kapiteln werden die hier genannten Sachen mit MonoGame praktisch angewandt.