© DrHitch/Shutterstock.com
Das Vulkan-API Teil 3

1 3-D-Modelle und Texturen


Egal, ob im Rahmen der Programmentwicklung bereits die neue Vulkan-Schnittstelle [1], [2] zum Einsatz kommt oder man noch immer auf das althergebrachte OpenGL vertraut: Funktionen zum Laden einer Textur oder eines 3-D-Modells wird man in beiden Fällen vergeblich suchen. Aus diesem Grund setzen wir uns in diesem Kapitel mit der Frage auseinander, wie sich die für die Darstellung einer 3-D-Szene benötigten Daten unabhängig vom jeweils verwendeten API innerhalb einer Grafikanwendung handhaben lassen.

In den vorangegangenen shortcuts haben wir unser Augenmerk stets auf einen jeweils anderen Aspekt der Vulkan-Programmierung gelegt. Nachdem wir uns zunächst mit den grundlegenden Funktionsprinzipien des neuen Grafik-API auseinandergesetzt haben (Initialisierungsschritte, das Zusammenspiel zwischen einem Command-Buffer und einem Queue-Objekt, Swap Chains, Bufferobjekte und Texturen, Speicherverwaltung, Pipeline- und Renderpass-Objekte, Descriptor Sets, Synchronisierungsmechanismen usw.), drehte sich im folgenden Kapitel schließlich alles um das Multi-Thread-basierte Rendering und Ressourcenmanagement. Im letzten shortcut haben wir darüber hinaus mit dem Entwurf eines einfachen Frameworks (Programmgerüst) [3] für unsere zukünftigen Vulkan-Demoanwendungen begonnen und uns mit den Grundlagen der GLSL-Programmierung (Verwendung von Vertex-, Fragment- sowie Compute-Shader-Programmen) vertraut gemacht. Heute nun werden wir uns mit der Frage auseinandersetzen, wie sich die für die Darstellung einer 3-D-Szene benötigten Modelle und Texturen unabhängig vom jeweils verwendeten Grafik-API innerhalb einer 3-D-Anwendung handhaben lassen. Wie schon gesagt, spielt es in diesem Zusammenhang überhaupt keine Rolle, ob man bereits die neue Vulkan-Schnittstelle verwendet oder weiterhin auf das althergebrachte OpenGL vertraut; Funktionen zum Laden einer Textur oder eines 3-D-Modells sind in beiden Fällen schlichtweg nicht vorhanden.

3-D-Modelle damals und heute

An der Vorgehensweise, wie man ein 3-D-Modell (bzw. die zugrunde liegenden Modellteile) konstruiert und die zugehörigen Geometriedaten abspeichert, hat sich seit den 80er- und 90er-Jahren des letzten Jahrtausends, in denen die ersten 3-D-Spiele wie Elite, Frontier: Elite 2 oder Star Wars: X-Wing das Licht der Welt erblickten, nicht wirklich viel verändert. Auch wenn es zwischen den aktuellen Spieletiteln und den Entwicklungen von einst nur noch sehr wenige Gemeinsamkeiten gibt, setzt sich die Oberfläche eines 3-D-Modells nach wie vor aus einer mehr oder weniger großen Anzahl von Dreiecksflächen zusammen, deren Eckpunkte man als Vertices bezeichnet. Für die Beschreibung und Darstellung eines 3-D-Modells sind nun zwei Arten von Informationen erforderlich, die sich getrennt voneinander abspeichern bzw. im Rahmen einer Grafikanwendung mithilfe von zwei separaten Bufferobjekten verwalten lassen. Innerhalb eines Vertexbuffers werden die Geometriedaten sämtlicher Dreieckseckpunkte relativ zum Modellmittelpunkt gespeichert. Hierzu zählen neben den Vertexpositionen und den Texturkoordinaten auch die für die Beleuchtungsberechnungen unverzichtbaren Normalenvektoren, mit deren Hilfe sich die räumliche Orientierung der einzelnen Dreiecksflächen beschreiben lässt. Die Indizes der an den einzelnen Dreiecksflächen beteiligten Vertices werden hingegen in einem zweiten Array, dem so genannten Indexbuffer, gespeichert. Die Vertices mit den Indices 0, 1, 2 definieren Dreieck 1, die Vertices mit den Indices 3, 4, 5 definieren Dreieck 2 usw. Anhand des in Abbildung 1.1 illustrierten Vertex Quads – hierbei handelt es sich um ein besonders einfaches 3-D-Modell, das häufig im Zusammenhang mit der Billboard-Darstellung zum Einsatz kommt – lässt sich das hinter den Vertex- und Indexbufferobjekten stehende Konzept besonders anschaulich nachvollziehen.

image

Abbildung 1.1: Texturiertes Vertex Quad

Grafikprimitiven (Primitive Topologies)

Die Interpretation der in einem Indexbuffer gespeicherten Daten als eine Liste von Dreiecken, so geschehen bei dem zuvor betrachteten Vertex Quad, stellt jedoch nur eine von mehreren Deutungsmöglichkeiten dar. Die Zusatzinformation, wie die Indexbufferdaten beim Rendering zu interpretieren sind, wird gemeinhin als Grafikprimitive oder als Primitive Topology bezeichnet und muss im Zuge der Initialisierung einer Rendering-Pipeline (eines VkPipeline-Objekts) festgelegt werden. Welche Primitiven wir in diesem Zusammenhang verwenden können, lässt sich anhand von Listing 1.1 und Abbildung 1.2 nachvollziehen. Während man bei der überwiegenden Mehrzahl der 3-D-Modelle für gewöhnlich auf die Dreieckslisten-Primitive (VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) zurückgreift und in den zugehörigen Indexbufferobjekten jeweils eine Liste der darzustellenden Dreiecke abspeichert, kommt beim Rendering von einzelnen Punkten, Linien oder Linienzügen hingegen die VK_PRIMITIVE_TOPOLOGY_POINT_LIST- bzw. die VK_PRIMITIVE_TOPOLOGY_LINE_LIST-Primitive zum Einsatz.

typedef enum VkPrimitiveTopology {
VK_PRIMITIVE_TOPOLOGY_POINT_LIST = 0,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST = 1,
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP = 2,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST = 3,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP = 4,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN = 5,
[...]
} VkPrimitiveTopology;

Listing 1.1: Grafikprimitiven

image

Abbildung 1.2: Grafikprimitiven

Vertexformate

Die verwendete Grafikprimitive stellt jedoch nicht die einzige Art von Information dar, die für die korrekte Darstellung eines 3-D-Modells erforderlich ist. Um zu verhindern, dass die in einem Vertex-Shader-Programm durchgeführten Berechnungen zu unerwünschten Ergebnissen führen, müssen innerhalb des hierbei verwendeten Vertexbufferobjekts die Attribute aller an einem 3-D-Modell beteiligten Vertices (Vertexattribute: Texturkoordinaten, Normale, Position usw.) in einer genau definierten Reihenfolge (in einem genau definierten Format) abgespeichert worden sein. Anhand der Listings 1.2 bis 1.6 können Sie sich einen Überblick über die wichtigsten Vertexformate verschaffen, die zum jetzigen Zeitpunkt von unserem Vulkan-Framework unterstützt werden.

Auf das in Listing 1.2 gezeigte CSimpleTexturedScreenSpaceVertex-Format können wir beispielsweise immer dann zurückgreifen, wenn die Vertices wie bei einem Projected Grid (Wasserdarstellung, Terrain-Rendering) oder bei einem Screen-Space-Vertex-Quad (Textausgabe, Darstellung von GUI-Elementen, Hilfsmittel bei der Durchführung von Post-Processing-Berechnungen) bereits im Verlauf der Vertexbufferinitialisierung in den Projektionsraum (Bildraum) transformiert worden sind.

struct CSimpleTexturedScreenSpaceVertex
{
float x, y, z, w;
float tu, tv;
};

Listing 1.2: Vertexformat für bereits in den Bildraum transformierte Vertices

Vertexattribute der CSimpleTexturedScreenSpaceVertex-Struktur:

  • Bildraumposition
  • Texturkoordinaten

Das in Listing 1.3 gezeigte CTexturedVertex-Format lässt sich hingegen immer dann verwenden, wenn bei der Darstellung eines 3-D-Modells auf die Durchführung von Echtzeitbeleuchtungsberechnungen verzichtet werden kann.

struct CTexturedVertex
{
float x, y, z;
float tu, tv, textureID;
};

Listing 1.3: Vertexformat für texturierbare, bereits beleuchtete 3-D-Modelle

Vertexattribute der CTexturedVertex-Struktur:

  • Vertexposition relativ zum Modellmittelpunkt (Modellkoordinaten)
  • Texturkoordinaten
  • Index der zu verwendenden Textur

Sofern die Beleuchtung eines 3-D-Modells lediglich im Verlauf der Vertex-Shader-Berechnungen erfolgen soll, bietet sich die Verwendung des in Listing 1.4 gezeigten CTexturedVertexWithNormal-Formats an.

struct CTexturedVertexWithNormal
{
float PosX, PosY, PosZ;
float NormalX, NormalY, NormalZ;
float tu, tv, textureID;
};

Listing 1.4: Vertexformat für texturierbare 3-D-Modelle (vertexbasierte Beleuchtung)

Vertexattribute der CTexturedVertexWithNormal-Struktur:

  • Vertexposition relativ zum Modellmittelpunkt (Modellkoordinaten)
  • Normalenvektor
  • Texturkoordinaten
  • Index der zu verwendenden Textur

Soll bei einem 3-D-Modell die Beleuchtungssimulation beispielsweise im Rahmen des Deferred Lightings (Per-Pixel-Beleuchtung innerhalb des Fragment Shaders) erfolgen, müssen wir hingegen auf das in Listing 1.5 gezeigte CTexturedVertexWithNormal_NM-Format zurückgreifen (Hinweis: Die Abkürzung NM steht für Normal Mapping!).

struct CTexturedVertexWithNormal_NM
{
float PosX, PosY, PosZ;

/* Die nachfolgenden neun Variablen repräsentieren die Elemente
der Normal-Map-Transformationsmatrix (Texture Space -> Model
Space): */
float NormalX, NormalY, NormalZ;
float Perpendicular1X, Perpendicular1Y, Perpendicular1Z;
float Perpendicular2X, Perpendicular2Y, Perpendicular2Z;

float tu, tv, textureID;
};

Listing 1.5: Vertexformat für texturierbare 3-D-Modelle (pixelgenaue Beleuchtung)

Vertexattribute der CTexturedVertexWithNormal_NM-Struktur:

  • Vertexposition relativ zum Modellmittelpunkt (Modellkoordinaten)
  • 3x3-Rotationsmatrix, mit deren Hilfe sich die in einer Normal Map gespeicherten Normalenvektoren in das Modellkoordinatensystem transformieren lassen
  • Texturkoordinaten
  • Index der zu verwendenden Textur

Die letzten beiden Vertexformate (Listing 1.6), auf die wir an dieser Stelle zu sprechen kommen, lassen sich bei der Darstellung eines skelettal animierten 3-D-Modells verwenden. Auf das CTexturedAnimatedVertexWithNormal_NM-Format greifen wir beispielsweise immer dann zurück, wenn bei einer solchen Animation lediglich ein einzelnes Animationsskelett bzw. ein einzelner Satz von Transformationsmatrizen zum Einsatz kommen soll.

struct CTexturedAnimatedVertexWithNormal_NM
{
/* Vertexposition relativ zu demjenigen Gelenk (Joint), das
für die Animation verantwortlich ist: */
float PosX, PosY, PosZ;

/* Die nachfolgenden neun Variablen repräsentieren die Elemente der
Normal-Map-Transformationsmatrix (Texture Space -> Model Space): */
float NormalX, NormalY, NormalZ;
float Perpendicular1X, Perpendicular1Y, Perpendicular1Z;
float Perpendicular2X, Perpendicular2Y, Perpendicular2Z;

/* Über den jointID-Parameter wird der Vertex an einen Joint gebunden.
jointID speichert den Index der Transformationsmatrix, die bei der
Positions- bzw. Normalenvektor-Transformation verwendet werden soll: */
float tu, tv, jointID, textureID;
};

struct CTexturedAnimatedVertexWithNormal_NM_2Skeletons
{
/* Vertexposition relativ zu demjenigen Gelenk (Joint), das
für die Animation verantwortlich ist: */
float PosX_Skeleton1, PosY_Skeleton1, PosZ_Skeleton1;
float PosX_Skeleton2, PosY_Skeleton2, PosZ_Skeleton2;

/* Die nachfolgenden neun Variablen repräsentieren die Elemente der
Normal-Map-Transformationsmatrix (Texture Space -> Model Space): */
float NormalX, NormalY, NormalZ;
float Perpendicular1X, Perpendicular1Y, Perpendicular1Z;
float Perpendicular2X, Perpendicular2Y, Perpendicular2Z;

/* Über den jointID-Parameter wird der Vertex an einen Joint gebunden.
jointID speichert den Index der Transformationsmatrix, die bei der
Positions- bzw. Normalenvektor-Transformation verwendet werden soll: */
float tu, tv, jointID, textureID;
};

Listing 1.6: Vertexformate für animierbare 3-D-
Modelle (pixelgenaue Beleuchtung)

Vertexattribute der CTexturedAnimatedVertexWithNormal_NM-Struktur:

  • Vertexposition relativ zu dem Gelenk, das für die Animation verantwortlich ist
  • 3x3-Rotationsmatrix, mit deren Hilfe sich die in einer Normal Map gespeicherten Normalenvektoren in das Modellkoordinatensystem transformieren lassen
  • Texturkoordinaten
  • Index des für die Animation verantwortlichen Gelenks
  • Index der zu verwendenden Textur

Wenn man ein 3-D-Objekt hingegen mit mehreren, leicht unterschiedlich modellierten Animationsskeletten animiert – man verwendet in diesem Fall mehrere Sätze von Transformationsmatrizen – und im Anschluss daran die animierten Vertexpositionen durch Mittelwertbildung bestimmt, dann wirken die daraus resultierenden Bewegungsabläufe sehr viel natürlicher, als das bei einer Bewegungssimulation mit einem einzelnen Animationsskelett der Fall wäre. Greift man nun bei der Animation eines 3-D-Modells beispielsweise auf zwei Animationsskelette zurück, dann bietet sich die Verwendung des CTexturedAnimatedVertexWithNormal_NM_2Skeletons-Formats an.

Vertexattribute der CTexturedAnimatedVertexWithNormal_NM_2Skeletons-Struktur:

  • Vertexposition relativ zum für die Animation verantwortlichen Gelenk (Animationsskelett 1)
  • Vertexposition relativ zum für die Animation verantwortlichen Gelenk ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang