Computer Graphics Charakter-Animation

Transcription

Computer Graphics Charakter-Animation
Hochschule Fulda – University of Applied Sciences
Computer Graphics
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
Autoren:
Michael Genau
Andreas Gärtner
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Eigenständigkeitserklärung
Wir versichern hiermit, dass wir unsere Teilbereiche der Ausarbeitung selbstständig verfasst und keine
anderen als die angegebenen Hilfsmittel benutzt haben. Die Stellen, die anderen Werken dem Wortlaut
oder dem Sinn nach entnommen wurden, haben wir in jedem einzelnen Fall durch die Angabe der
Quelle kenntlich gemacht.
____________
Ort, Datum
_________________________________
Unterschrift (Michael Genau)
____________
Ort, Datum
___________________________________
Unterschrift (Andreas Gärtner)
Anmerkungen 1
Wie zuvor abgesprochen überschreite ich, Michael Genau, den normalen Umfang einer Ausarbeitung
von bis zu 25 Seiten. Des Weiteren wird, wie abgesprochen, in der Implementierung des Teils DirectX
C++ eingesetzt. Dies ist darin begründet, dass für die im DirectX-SDK angebotenen Funktionen der
Bibliothek D3DX, beispielsweise zum Laden von 3D Objekten aus X-Dateien, die Hierarchien oder
benutzerdefinierte Templates enthalten, Strukturen erweitert und zugleich deren Methoden
überschrieben werden müssen.
Anmerkungen 2
Wie zuvor abgesprochen konnte ich, Andreas Gärtner, die Implementierung des Skinning mit OpenGL
aufgrund von Zeitmangel nicht mehr durchführen. Da das 3DS Dateiformat keine Bones speichert,
hätte ich ein neues Dateiformat und einen dafür geeigneten Loader suchen müssen. Die Durchführung
der anderen Aufgaben (siehe Aufwandsabschätzung) dauerte bereits bis Anfang Januar.
Seite |I
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Inhaltsverzeichnis
Eigenständigkeitserklärung
Anmerkungen 1
Anmerkungen 2
I
I
I
1. Einleitung
1
1
1
1.1. Motivation
1.2. Zielsetzung
2. Theoretische Grundlagen (Verfasst von Michael Genau)
2.1. Einführung in die 3D Animation
2.2. Verfahren zur Interpolation
2.3. Datei-Formate für 3D-Objekte
2.3.1. Das X-Dateiformat
2.3.2. Das 3DS-Dateiformat (Verfasst von Andreas Gärtner)
2.4. Vergleich zwischen Animationen mit der CPU und der GPU
2.5. 3D-Bibliotheken und ihre Shader-Sprachen
2.5.1. Shader im Allgemeinen
2.5.2. DirectX
2.5.2.1. Entwicklungsgeschichte von DirectX
2.5.2.2. DirectX-Graphics
2.5.2.3. HLSL
2.5.3. OpenGL (Verfasst von Andreas Gärtner)
2.5.3.1. GLSL
2.6. Zusammenfassung
2
2
3
4
4
5
7
7
7
8
8
9
9
11
11
13
3. Konzeption (Verfasst von Michael Genau)
14
3.1. Die Bedeutung der Zeit
3.2. Konzeption der DirectX-Anwendungen
3.2.1. Einrichtung der Arbeitsumgebung
14
3.2.2. DirectX Graphics und D3DX
15
3.2.2.1. Konzept zur Implementierung der Animationstechnik Skinning
15
3.2.2.2. Konzept zur Implementierung der Animationstechnik Tweening
17
3.2.3. Animation Blending
18
3.2.4. DirectX-Shader (HLSL)
3.2.4.1. Einführung in HLSL
3.2.4.2. Integration Eines Vertexshaders in eine Anwendung
3.2.4.3. Konzept zur Implementierung einer Charakter-Animation auf Basis eines
Shaders
22
3.2.4.4. Effekt Dateien – eine Erweiterung zur Implementierung von Shadern 22
3.3. Konzeption der OpenGL-Anwendungen (Verfasst von Andreas Gärtner) 24
3.3.1. Einrichtung der Arbeitsumgebung
24
3.3.2. Kompilieren und Starten der Anwendungen
25
3.3.3. Animationen mit GLSL
3.3.3.1. Datentypen und Funktionen
25
3.3.3.2. Integration von Shadern in ein Programm
27
3.4. Zusammenfassung
14
14
18
18
19
25
29
S e i t e | II
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
4. Realisierung der Konzeption
30
4.1. Modellierung und Animation des Charakters
30
(Verfasst von Michael Genau)
4.1.1. Modellierung
4.1.2. Animation
4.2. Implementierung der DirectX-Anwendungen
(Verfasst von Michael Genau)
4.2.1. Aufbau einer Windows-Applikation
4.2.2. Initialisierung der benötigten DirectX-Komponenten
4.2.2.1. DirectX-Graphics
4.2.2.2. DirectInput
4.2.3. Laden von 3D-Objekten aus einer X-Datei
4.2.4. Zeitmessung
4.2.5. CPU-basierte Charakter-Animation durch Tweening
4.2.6. GPU-basierte Charakter-Animation durch Tweening mit einem HLSLVertexshader (Effekt-Datei)
4.2.7. Repräsentation der Animationen in einer X-Datei
4.2.8. Erweiterung des X-Dateiformats für Morphing und Key-Frames
4.2.9. Charakter-Animation durch Skinning
4.2.9.1. Laden des Meshs und Aufbau der benötigten Datenstrukturen
4.2.9.2. Animation des Charakters
4.2.9.3. Rendern des Charakters mit einem HLSL Vertexshader
4.3. Implementierung der OpenGL-Anwendungen
(Verfasst von Andreas Gärtner)
4.3.1. Laden der 3d Objekte
4.3.2. Normalenberechnung des 3ds Loaders
4.3.3. Initialisierung von OpenGl und Glut
4.3.4. Morphing mit OpenGL
4.3.5. Morphing mit GLSL
4.3.5.1. Erweiterung der bestehenden Implementierung
4.3.5.2. Implementierung des Vertex Shaders
4.3.5.3. Implementierung des Fragment Shaders
4.4. Zusammenfassung
30
31
31
32
33
33
34
35
37
38
39
42
45
46
46
47
47
50
50
51
52
53
55
55
57
58
59
5. Fazit
61
6. Anwendungsdokumentation
62
62
62
62
6.1. Erläuterung der Ordnerstruktur
6.2. Verwendete Werkzeuge
6.3. Aufwandsabschätzungen
VI. Literaturverzeichnis
LXIV
VII. Abbildungsverzeichnis
VIII. Weitere Anlagen
LXV
LXVII
S e i t e | III
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
1. Einleitung
In der heutigen Zeit werden animierte Filme und Videospiele immer realistischer. Einen großen Anteil
daran tragen die Animationen der Charaktere bei. Daher wird in dieser Ausarbeitung das Thema der
Animation von 3D-Objekten und insbesondere von Charakteren behandelt.
1.1. Motivation
Sie haben sicher schon einmal eine Animation gesehen und waren davon gefesselt. Ein Grund hierfür
ist die immer geringer werdende Grenze zwischen Computergrafik und Realität. Besonders deutlich
wird dieser Prozess dann, wenn man sich die verschiedenen Generationen der Grafikkarten und die
dazugehörigen Computerspiele ansieht.
Als Motivation für diese Arbeit dienen daher die verschiedenen aktuellen Techniken und
Grafikbibliotheken, die für die Realisierung solcher Animationen zur Verfügung stehen. Begriffe wie
DirectX oder OpenGL sind hier sicher jedem bekannt. Doch immer, wenn man vor einer neuen
Aufgabe steht, muss man sich entscheiden, auf welche Art und Weise man das Vorhaben umsetzt. Um
diese Entscheidung zu erleichtern, ist es das Ziel dieser Arbeit die vorhandenen Techniken
miteinander zu vergleichen, was im nächsten Abschnitt detaillierter aufbereitet ist.
1.2. Zielsetzung
Das Ziel dieser Ausarbeitung ist es die vorhandenen Techniken zur Charakter-Animation gegenüber
zu stellen. Dabei sind zwei Grafikbibliotheken, nämlich DirectX und OpenGL, zu unterscheiden. Doch
neben dieser Differenzierung in zwei Grafikbibliotheken gilt es auch die jeweiligen CPU- und GPUbasierten Methoden zu erläutern. Im Vordergrund steht dabei jeweils die Realisierung einer
Animation. Wobei vor allem Performanceunterschiede zwischen den CPU- und den GPU-basierten
Methoden zu erwarten sind. Ein Ziel ist es daher, diese Unterschiede zu messen und somit am Ende
die Performance der Methoden zu vergleichen. Doch die Geschwindigkeitsunterschiede sind nicht das
einzige Maß, an denen die Techniken bewertet werden. Das zweite Kriterium ist die Komplexität,
denn nicht für jede Aufgabe benötigt man eine extrem hohe Performance, wenn man auf der anderen
Seite Vorteile wie eine geringere Komplexität und einen geringeren Programmierungsaufwand besitzt.
Neben diesen praktischen Zielen wird unter anderem auf die benötigte Theorie zur Animation
eingegangen. Hier sind vor allem Skinning und Tweening mittels Key-Frames von Bedeutung. Solche
Animationen werden typischerweise mit Hilfe von 3D-Modellierungsprogrammen wie Maya,
Lightwave3D oder 3DStudio Max erstellt. Um diese Modelle und Animationen in einer eigenen
Anwendung zu laden, gilt es wichtige Bestandteile der Dateiformate zu erläutern. Ein grundlegendes
Wissen über die verwendeten Formate ist für die Verarbeitung von großer Bedeutung. Ein weiterer
wichtiger Punkt in der Animation ist die Interpolation, daher werden hier zwei bedeutende Verfahren
vorgestellt.
Doch kann in dieser Ausarbeitung das Thema der Charakter-Animation nicht allumfassend behandelt
werden. Aus diesem Grund wird beispielsweise auf die Simulation von Kleidung, die die Charaktere
eventuell tragen, nicht eingegangen. Bei Interesse an solch fortgeschrittenen Techniken sind jedoch
die angegebenen Quellen zu empfehlen.
S e i t e | IV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
2. Theoretische Grundlagen
Das Kapitel 2 – Theoretische Grundlagen – beschreibt einführend Techniken, die bei 3D-Animationen
von Bedeutung sind und erläutert den dafür benötigten theoretischen Hintergrund. Auf Basis dieser
Grundlagen wird auf zwei Dateiformate für 3D-Objekte näher eingegangen. Des Weiteren werden,
wie bereits angesprochen, die Animationen auf DirectX und OpenGL basieren. Aus diesem Grund gilt
es diese zwei Bibliotheken mit samt den dazugehörigen Shader-Programmiersprachen und weitere
bedeutende Belange vorzustellen.
2.1. Einführung in die 3D Animation
In der Computer-Animation kann man mehrere Techniken zur Animation unterscheiden. In dieser
Ausarbeitung werden Tweening, auch als Morphing bekannt, und Skinning, auch als SkelettAnimation bekannt, behandelt. Bei beiden Techniken sind Key-Frames von großer Bedeutung.
Um Skinning zu verstehen ist grundlegendes Wissen über Bones notwendig. Bones dienen als Gerüst
für Skelette, um die Knochen der Charaktere zu simulieren. Als Gelenke, die diese Knochen
miteinander verbinden, dienen so genannten Joints. Die Verwendung von Skinning erleichtert die
Animation von Körperbewegungen ungemein. Dadurch, dass jeder Vertex einem oder mehreren
Bones zugewiesen werden kann, ist es nicht notwendig, die Vertices einzeln zu animieren. Ein Bone
oder Joint ist also ein Kontrollpunkt für eine Gruppe von Vertices. Wenn sich dieser bewegt, dann
bewegen sich alle zugewiesenen Vertices automatisch mit. Wie und in welcher Form hängt dabei von
den Parametern und eventuell hierarchisch untergeordneten Kontrollpunkten ab. Durch diese
Hierarchie entsteht demnach eine Baumstruktur, wobei die Wurzel als Root-Joint bezeichnet wird.
Eine Animation lässt sich hiermit beispielsweise erstellen, indem man in mehreren Key-Frames die
Bones derart manipuliert, so dass ein Walk-Cycle entsteht. Als praktisches Beispiel kann man sich hier
die Bewegung des Oberarmes vorstellen. Indem jemand seinen Oberarm bewegt, bewegt sich dessen
Unterarm automatisch mit. Wird jedoch lediglich der Unterarm bewegt, so bleibt der Oberarm ruhig.
Ein großer Vorteil der Animation mit Bones ist der Realismus bei der Animation von Charakteren.
Deutlich wird dieser Realismus vor allem dann, wenn man sich klar macht, dass ein Programm die
Bones beliebig manipulieren kann. Beispielsweise bei einer Kollision mit einem anderen Gegenstand
in der Szene oder bei einer Rag-Doll-Animation. Ein Nachteil, den dieses Verfahren jedoch besitzt ist
seine Komplexität. Diese erschwert es das Verfahren zu verstehen und zu implementieren.
Im vorherigen Abschnitt ist der Begriff Key-Frame verwendet wurden. Doch was ist ein Key-Frame
eigentlich? Ein Key-Frame ist ein Stützpunkt in einer Szene, an dessen Position beispielsweise ein
Bone manipuliert wird. Für jeden Key-Frame werden daher die Parameter der Objekte, also unter
anderem die Koordinaten und Rotationseigenschaften der Bones, gespeichert. Hierbei ist zwischen
einer kombinierten Matrix oder separaten Werten für die entsprechenden Parameter in der späteren
Betrachtung zu differenzieren. Dadurch ist man in der Lage zwischen jeweils zwei Key-Frames zu
interpolieren. Eine wichtige Frage, die an dieser Stelle zu klären ist, ist die Form der Interpolation. Die
einfachste Variante wäre es immer linear zu interpolieren. Dieses Vorgehen zerstört jedoch sehr
schnell den Eindruck der virtuellen Realität. Aus diesem Grund bieten 3D-Modellierungswerkzeuge
die Möglichkeit für Animationen mit Key-Frames Kurven für jede Achse anzugeben. Doch diese
Verfahren spielen unter anderem bei der Spieleentwicklung eine untergeordnete Rolle. Daher gibt es
verschiedene Algorithmen, um eine hohe Performance und Realismus zu vereinen. Sehr oft werden für
die Interpolation die Verfahren LEPR beziehungsweise SLERP verwendet. Diese beiden Verfahren
nutzen zur Berechnung Quaternionen und werden später vorgestellt.
Seite |V
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Die zweite Form der Charakter-Animation ist das Tweening. Hierbei handelt es sich ebenfalls um eine
Technik, die zwischen zwei Key-Frames interpoliert. Im Gegensatz zu Skinning werden allerdings
keine Bones oder ähnliches als Stützgerüst benötigt. Stattdessen sind in jedem Frame die gesamten
Objektdaten vorhanden, eben nur verschieden animiert. Man muss also die Vertices beziehungsweise
Polygone von Hand manipulieren, wodurch sich viele Möglichkeiten ergeben, die sich bei Skinning
nicht bieten. Ein Beispiel hierfür sind zwei Key-Frames, wobei im ersten eine Figur positioniert wurde
die keine besondere Mimik aufweist. Wenn man im zweiten Key-Frame dieser Person ein Lächeln
animiert, so kann man durch Tweening einen flüssigen Übergang zwischen einer Person die nicht
lächelt und einer Person die lächelt realisieren und umgekehrt.
Bisher wurden diese zwei Techniken nur getrennt voneinander betrachtet. Sie lassen sich aber
durchaus kombinieren, denn beide haben ihre Vorteile. Beispielsweise indem man Körperbewegungen
wie das Laufen mit Skinning und die Mimik mit Tweening realisiert. Ein weiteres Beispiel für
Tweening ist, wenn ein Charakter mutiert, in solch einer Situation stößt Skinning an seine Grenzen.
Man sieht demnach, dass beide Techniken Verwendung finden und man sich daher gründlich
überlegen muss, wie man was realisiert.
2.2. Verfahren zur Interpolation
Im vorherigen Kapitel wurde oftmals der Begriff Interpolation verwendet. Dieses Kapitel beschreibt,
was man darunter versteht und wie man solch ein Verfahren realisiert.
Eine Interpolation ist eine diskrete Approximation. Unter Approximation versteht man die
Bestimmung einer Ersatzfunktion
aus einer gegebenen Funktionsklasse , die von einer Funktion
möglichst wenig abweicht. Wobei der Approximationsfehler der Interpolation an endlich
vielen, fest vorgegebenen Stützstellen, zu null wird. (1)
Um die später betrachteten Verfahren zu verstehen ist an dieser Stelle ein wenig Mathematik
notwendig. Beispielsweise sind Vektoren und Matrizen in der 3D-Computergrafik von grundlegender
Bedeutung. Doch in diesem Abschnitt geht es um Quaternionen. W.R Hamilton erdachte sie im Jahr
1843 in Dublin (2). Dabei wurden sie als eine vierdimensionale Erweiterung der komplexen Zahlen
verwendet. In diesem Kontext werden sie jedoch zur Beschreibung von Rotationen in einem
dreidimensionalem Raum verwendet. Ein Quaternion besteht aus vier Zahlen, die Rotationen
repräsentieren. Dabei besteht ein Quaternion aus zwei Komponenten – einem Vektor (x, y, z) und
einem Skalar (w). Der Wert des Skalars beschreibt den Winkel der Rotation in der Form
2-1 Gimbal Lock. Quelle: (2)
.
An dieser Stelle kann man sich fragen
wofür man das alles benötigt – nicht
wahr? Dies tiefgründig hier zu
belegen sprengt den Rahmen der
Ausarbeitung. Der wichtigste Grund
ist, dass Quaternionen unempfindlich
gegenüber dem Phänomen Gimbal
Lock sind. Gimbal Lock bezeichnet
ein Problem bei Transformationen,
dass in bestimmten Situationen
auftritt, wenn man Eulerwinkel zur
Berechnung zugrunde legt. Dabei
entsteht
eine
Blockade
einer
Drehrichtung bei einer kardanischen
Aufhängung.
S e i t e | VI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Weitere Vorteile sind, dass durch die Beschreibung mittels Quaternionen weniger Speicherplatz als
bei der Verwendung von Matrizen benötigt wird und weichere Animationen realisiert werden können.
Nachteilig wirkt sich jedoch aus, dass man die Quaternionen in Matrizen konvertieren muss, bevor
man sie an die Grafikkarte sendet. In (2) wird mathematisch beschrieben, wie diese Transformationen
durchgeführt werden, so dass man sich hierfür eine eigene Klasse implementieren kann. Jedoch
existiert beispielsweise in DirectX solch eine Klasse schon.
Nach der Erklärung dieser Grundlagen verstanden, ist man auch in der
Lage den Hintergrund der Verfahren LERP und SLERP zur
Interpolation zu verstehen. LERP bezeichnet eine lineare Interpolation
und SLERP bezeichnet eine lineare kugelförmige Interpolation. Der
Vorteil von SLERP ist eine weichere Animation. Wobei SLERP bei
kurzen Distanzen unrealistisch wirkt, aus diesem Grund wird in
solchen Situationen LERP verwendet. Bei tiefgründigem Interesse an
diesen Verfahren, wobei auch das rechnen mit Quaternionen von
Bedeutung ist, ist die Quelle (2) zu empfehlen.
2-2 Visualisierung von
LERP und SLERP. Quelle:
(2)
2.3. Datei-Formate für 3D-Objekte
Im Laufe der Zeit sind viele Dateiformate entstanden. Beispielsweise das MD2 Format von id
Software, was unter anderem bei Quake 2 zum Einsatz kam oder MD3 für Quake 3. Ein weiteres
bekanntes Format ist das MDL-Format von Valve im Spiel Half-Life. In dieser Ausarbeitung werden
allerdings zwei andere Formate verwendet. Auf der einen Seite das X-Format in Verbindung mit
DirectX und auf der anderen Seite das 3DS-Format bei OpenGL. Daher werden diese beiden Formate
anschließend näher betrachtet. Zuerst gilt es jedoch zu klären, was in solch einer Datei alles
gespeichert wird. Grundsätzlich gilt es immer die Vertizes der 3D-Modelle zu speichern. Neben diesen
Daten sind auch Materialeigenschaften des Objektes oder von einzelnen Teilen des Objektes darin
enthalten. Des Weiteren können Informationen über Texturen, Beleuchtung oder auch Animationen
enthalten sein. Wie genau diese Daten in den verwendeten Formaten abgelegt sind wird in den beiden
folgenden Kapiteln erklärt.
2.3.1. Das X-Dateiformat
In diesem Kapitel wird das X-Format von Microsoft insofern erläutert, dass man es grundlegend
versteht und in der Lage sind sich ein Bild vom Aufbau zu machen. In der Anlage 8.1 ist eine solche
Datei abgebildet. (3)
Am Anfang der Datei steht der Header. Das erste Token identifiziert dabei die Datei als eine X-Datei.
Das zweite Token zeigt die Version der X-Datei an und bestimmt zugleich, dass die Datei als
Textformat vorliegt. Neben diesem Format existieren auch ein binäres sowie ein binäres
komprimiertes Format. Das letzte Token des Headers gibt an, ob die Datei in einer 32 oder 64 Bit
Version vorliegt. Nach dem Header folgen mehrere Chunks, die sich in Templates und Data Objects
unterteilen lassen.
Ein Chunk, engl. für Brocken oder auch Stück, ist eine Menge an Informationen. Chunks werden in
Formaten für 3D-Modelle sehr oft verwendet. Sie erleichtern es beispielsweise eigene Ladefunktionen
zu schreiben. Dabei besteht eine Datei aus einer Anzahl aneinandergereihter Chunks. Wobei jeder
Chunk aus einem Header und Daten besteht.
S e i t e | VII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Ein Data Object ist dabei immer eine Instanz eines Templates. In Folge dessen beschreiben die
Templates Informationen wie das Layout der 3D-Objekte, die in der X-Datei abgebildet sind. Das
erste Template in diesem Beispiel heißt Header, was einem Klassennamen gleichkommt. Es enthält
eine GUID und drei weitere DWORD Variablen. Die GUID (Global Unique Identification Number)
dient DirectX dazu das Template eindeutig zu identifizieren, wenn es geladen wurde. In Verbindung
mit diesem Template steht das Data Object Header. Dieses initialisiert die drei DWORD Variablen,
wobei alle Variablen in der korrekten Reihenfolge initialisiert werden müssen.
Das zweite Template in dieser Datei beschreibt einen Frame. Ein Frame ist ein spezielles Template,
es beschreibt keine Objektdaten sondern Hierarchien. Daher werden innerhalb dieses Templates
andere Template-Klassen referenziert. In diesem Beispiel sind es die Klassen
FrameTransformMatrix und Mesh. Dadurch, dass nur diese zwei Klassen referenziert sind, wird
sogleich eine Restriktion erstellt. Betrachtet man diese Templates genauer, so wird ersichtlich, dass
auf den Mesh zwei Transformationsmatrizen angewendet werden. Durch solche Hierarchien wird es
prinzipiell möglich, das im Kapitel 2.1 – Einführung in die 3D-Animation – angesprochene Beispiel
mit der Bewegung der Arme abzubilden.
Wenngleich die interne Repräsentation in Verbindung
mit Bones komplexer ist. Des Weiteren sind dem Mesh Materialeigenschaften zugewiesen, die an
dieser Stelle aber nicht weiter besprochen werden. Im Kapitel 4.2.7. wird auf die Repräsentation von
Animationen noch genauer eingegangen. Dieses Kapitel dient eher dazu einen Überblick zu schaffen.
DirectX bietet eine große Anzahl an standardmäßig vorhandenen Templates an. Einen Ausschnitt dazu
ist im Anhang 8.2 aufgeführt. Neben diesen Templates können aber auch eigene Definiert werden.
Hierzu enthält das DirectX-SDK ein Programm zur Generierung der GUIDs. Wobei solch eine
Erweiterung später noch vorgestellt wird. So viel zu diesem Format zur Abbildung von 3D-Objekten
und Animationen, im nächsten Kapitel wird ein weiteres Format beschrieben, da für OpenGL kein
Loader für das X-Format existiert.
2.3.2. Das 3DS-Dateiformat (Andreas Gärtner)
Als Dateiformat der Objekte im OpenGL Teil dieser Arbeit findet das 3DS Format Verwendung. Es ist
ein weit verbreitetes Format und man findet dementsprechend auch ausreichend viele Tutorials und
Loader dafür. Das 3DS Format ist ein binäres Dateiformat zur Speicherung der Daten einer 3D Szene.
Die komplette 3DS Datei wird aus Chunks aufgebaut. Wie schon zuvor erwähnt, kann man sich einen
Chunk als einen Datenblock vorstellen, in dem die Informationen zu Teilaspekten einer 3D Szene
gespeichert werden (siehe Glossar). Im 3DS Format gibt es beispielsweise einen Block mit dem
Namen „Keyframer Chunk“. Innerhalb dieses Blocks werden, wie der Name schon vermuten lässt,
sämtliche Informationen zu den deklarierten Keyframes innerhalb der 3D Szene beschrieben. Daneben
gibt es noch zahlreiche andere Chunks, zum Beispiel für die Koordinatenwerte der Vertices, den
Namen jedes Objekts, die Liste aller Polygone etc. Die wichtigsten Chunks, die im Rahmen dieser
Arbeit zum Darstellen des 3D Objekts in der OpenGL Anwendung relevant sind, werden im Praxisteil
dieser Ausarbeitung erläutert. Zuvor jedoch eine Anmerkung zur Struktur der Chunks. Diese ist nicht
linear, sondern hierarchisch aufgebaut. Es gibt Vater Kind Beziehungen, so das einige Chunks von
anderen einer höheren Ebene abhängig sind. Will man mit einem Parser die Daten eines Kind Chunks
auslesen, so müssen zunächst erst die Daten von sämtlichen übergeordneten Chunks ermittelt werden.
Die folgende Abbildung illustriert die Chunk Hierarchie einiger wichtiger Elemente:
Fehler: Referenz nicht gefunden
2-3 Hierarchie einiger 3ds chunk Elemente, Quelle (4)
Will man also beispielsweise alle Vertices eines Objekts auslesen, so erfolgt dies über das „Vertices
List“ Chunk. Um jedoch dorthin zu gelangen, müssen zuvor erst die Blöcke „Main Chunk“, „3D
Editor Chunk“, „Object Block“, sowie „Triangular Mesh“ in dieser Reihenfolge gelesen werden.
Enthält ein Chunk keine relevanten Informationen, und ist nur notwendig, um an ein Kindelement zu
S e i t e | VIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
gelangen, so kann anhand der ermittelten Länge des Chunks der Dateizeiger entsprechend
weiterbewegt werden. (siehe Praxisteil)
Sämtliche Parameter werden im Hexadezimalformat abgespeichert. Jeder Datenblock hat zunächst
einen Identifier. In diesem 2 Byte langen Datenfeld wird jeder Block durch den entsprechenden
Hexadezimalwert eindeutig identifiziert. Benötigt man also nicht alle Chunks für seine Anwendung, so
kann man anhand des Identifiers unter Zuhilfenahme der entsprechenden 3DS Dokumentation (es
existieren jedoch nur inoffizielle Dokumentationen, eine solche befindet sich auf dem Datenträger)
recht schnell sehen, ob der aktuelle Datenblock gelesen werden soll oder nicht. Als nächstes folgt das
4 Byte große Feld mit der Angabe der Chunk Länge. Dabei ergibt sich die Länge eines Datenblocks
nicht nur aus der Anzahl der Bytes, die zur Speicherung seiner Daten benötigt werden, sondern auch
aus der Länge aller abhängigen Kindblöcke. Diese beiden Größenangaben werden aufsummiert, um
die Gesamtlänge zu bilden. Im nächsten Feld befinden sich dann die eigentlichen Daten des
jeweiligen Chunks. Da diese von der Art des Chunks abhängig sind, gibt es hier keine feste Anzahl
von Bytes zur Speicherung, sondern die Länge des Datenfeldes ist variabel. Manche Chunks, wie der
Main Chunk beispielsweise, besitzen auch überhaupt keine Daten. Der Grund hierfür ist, dass die
Daten in den entsprechenden Kindblöcken hinterlegt sind. Im letzten Feld werden schließlich
sämtliche Kinder des jeweiligen Chunks aufgelistet, sofern vorhanden. Die nachfolgende Tabelle
illustriert dies noch einmal:
Offset
0
2
6
6+n
Länge
2
4
n
m
Beschreibung
Chunk identifier
Chunk Länge
Chunk Daten
Kind Chunks
2-4 Felder eines Chunks, Quelle: (4)
(4) (5)
2.4. Vergleich zwischen Animationen mit der CPU und der GPU
In diesem Kapitel geht es darum die Unterschiede herauszuarbeiten zwischen der CPU-basierten
Animation und der GPU-basierten (Shader) Animation von 3D-Objekten. Am Anfang der 3D-Grafik
blieb den Programmieren nichts anderes übrig, als die Animationen von der CPU berechnen zu lassen.
Als jedoch die ersten programmierbaren Grafikkarten erschienen, hat sich dieser Zustand geändert. In
den letzten Jahren haben sich die sogenannten Shader stark verbreitet und es sind Hochsprachen wie
HLSL und GLSL entstanden. Ein Vorteil den die Berechnung mit Hilfe der Grafikkarte bringt ist der
enorme Performancezuwachs. Der Nachteil ist jedoch, dass die Grafikkarte der Endanwender die
verwendeten Shader unterstützen muss. Dies wird vor allem daran deutlich, dann schon frühzeitig
nach dem Erscheinen des Shader Models 3.0 viele Spiele es auch gefordert haben. Hatte man also eine
ältere, dennoch aber leistungsstarke Grafikkarte mit Shader Model 2.0 Unterstützung, so kann man
diese Spiele nicht spielen.
Der deutliche Vorteil bei der Ausführung der Berechnungen auf dem Grafikprozessor liegt in dessen
Parallelität. Hierdurch werden sehr viele Rechenoperationen gleichzeitig ausgeführt, die bei einer
normalen CPU sequentiell abgearbeitet werden müssten.
Zusammenfassend lässt sich sagen, dass die Berechnungen der Animationen mittels der CPU zwar
nicht so Performant sind dafür jedoch prinzipiell auf jedem Computer ausgeführt werden können.
Doch dieser Vorteil ist in Zeitkritischen Bereichen nicht von all zu großer Bedeutung, so dass bei
Aufwändigeren Animationen die GPU bevorzugt werden sollte. Hier ist dann aber zu beachten, dass
möglichst viele Grafikkarten unterstützt werden. Als Stichwort sei an dieser Stelle auf Effektdateien
und verschiedene Techniken zur Realisierung der Effekte verwiesen oder auf eine separate CPUMethode, falls die Grafikkarte das gewünschte Feature nicht unterstützt.
S e i t e | IX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
2.5. 3D-Bibliotheken und ihre Shader-Sprachen
Wie bereits erwähnt, wird in dieser Arbeit die Charakter-Animation mit zwei verschiedenen
Grafikbibliotheken behandelt. In diesem Kapitel werden eben diese Bibliotheken und die
dazugehörigen Shader-Sprachen vorgestellt. Dies ist notwendig, da ein grundlegendes Verständnis
vorhanden sein muss, um die weiteren Kapitel zur Konzeption und Implementierung der Anwendung
zu verstehen.
2.5.1. Shader im Allgemeinen
Das Wort Shader ist leicht mit Shading und somit mit Schattieren zu verwechseln. Dies trifft aber
nicht die eigentliche Bedeutung. In OpenGL wird anstelle von Shader oft das Wort Programm
gebraucht, was die Sache besser beschreibt. Demnach ist ein Shader also ein Programm. In den
meisten Fällen ein relativ kleines, das einmal pro Vertex oder einmal pro Pixel aufgerufen wird. Man
unterscheidet demnach die Shader in Vertex- und Pixel-Shader(DirectX), auch FragmentShader(OpenGL) genannt. Vertex-Shader können auf jede Komponente eines Vertex zugreifen, dass
bedeutet man hat die volle Kontrolle über dessen Parameter. Analog hierzu kann man sich PixelShader vorstellen. Der Unterschied ist nur, dass diese Programme einmal pro Pixel aufgerufen werden
und eben jeweils nur auf diesen Pixel Zugriff haben. Das besondere an Shader-Programmen ist, dass
sie auf dem Grafikprozessor ausgeführt werden, was einen großen Geschwindigkeitsvorteil mit sich
bringt. Am Anfang der Shader-Entwicklung wurden diese Programme in speziellen ShaderAssembler-Sprachen geschrieben, doch mittlerweile wurden hierfür Hochsprachen und
Entwicklungsumgebungen veröffentlicht, die von der Architektur der Grafikkarte so abstrahieren, wie
beispielsweise Java von der CPU. Ein weiterer großer Vorteil neben der Geschwindigkeit ist die große
Flexibilität. (6)
Nach dieser Begriffsklärung wird in den nächsten Kapiteln noch genauer auf Shader und die
dazugehörigen Hochsprachen eingegangen.
2.5.2. DirectX
DirectX ist eine Sammlung von verschiedenen COM-basierten Bibliotheken, aus dem Hause
Microsoft, die hauptsächlich in der Spieleentwicklung aber auch ganz allgemein in der Entwicklung
von Multimediasoftware verwendet werden. Im Gegensatz zu OpenGL ist DirectX nicht nur für die
Grafik zuständig. Im Folgenden sind die enthaltenen Komponenten aufgelistet und mit einer kurzen
Beschreibung versehen.
 DirectX Graphics
• DirectX Graphics ermöglicht den direkten Zugriff auf die Grafikkarte und stellt eine API für 2D
und 3D Grafiken zur Verfügung.
 DirectX Audio
• DirectX Audio ist für den Sound verantwortlich. Hierzu zählen sowohl Musik als auch
Audioeffekte und 3D-Sound sowie eine Mikrofon-Unterstützung.
 DirectInput
• DirectInput ist für Eingabegeräte zuständig. Hierzu zählen alle Arten wie Mäuse, Tastaturen,
Lenkräder oder auch Game-Pads und Joy-Sticks mit Force-Feedback-Technik.
 DirectPlay
Seite |X
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
• DirectPlay vereinfacht die Nutzung von Netzwerken und Kabelverbindungen. Hierdurch lassen
sich recht komfortabel Multiplayeranwendungen entwickeln.
 DirectShow
• DirectShow ermöglicht die Wiedergabe aller Typen von Multimedia-Daten.
 DirectSetup
• DirectSetup dient dazu ein DirectX-Setup in eine Anwendung zu integrieren. Dieses kann der
Benutzer installieren, sollte seine Version nicht aktuell sein.
In dieser Ausarbeitung geht es hauptsächlich um DirectX Graphics, auch als Direct3D bekannt. In der
DirectX Version 8 wurden jedoch die Komponenten DirectDraw und Direct3D zu DirectX Graphics
vereint.
2.5.2.1. Entwicklungsgeschichte von DirectX
In den 1990er Jahren wurden Spiele entweder für Konsolen oder für das Betriebssystem DOS
entwickelt. Auch als grafische Oberflächen mit Windows 3.0 oder Windows 95 sich etablierten, blieb
dieser Zustand unverändert. Das lag daran, dass es noch kein DirectX für Windows gab und die
Entwickler keinen direkten Zugriff auf die Hardware hatten. Diesen direkten Zugriff bot DOS jedoch.
Der Nachteil liegt aber darin, dass man entweder nur Funktionen verwendet, die jede Hardware
ausführen kann oder separat für einzelne Hardware entwickelt, was den Aufwand extrem steigert. Aus
diesem Grund veröffentlichte Microsoft zuerst WinG und Wavemix. WinG war eine Sammlung von
Funktionen, die in Windows performanter war, als das normale GDI. Dieser Ansatz fand allerdings
kaum Beachtung, wodurch später das Game SDK veröffentlich wurde, welches später in DirectX SDK
umbenannt wurde. DirectX wurde im Jahr 1995 in der Version 1.0 veröffentlicht und hat seitdem eine
rasante Entwicklung durchgemacht. (7)
Momentan ist die Version 10.1 aktuell, Microsoft hat jedoch für das Jahr 2010 DirectX 11
angekündigt. Die größte Entwicklung hat nach der Veröffentlichung von DirectX 8 die Komponente
DirectX Graphics durchgemacht. Beispielsweise heißen bei DirectX 9.0c die Schnittstellen der
Komponente DirectInput noch genauso wie in der Version 8. Dies soll verdeutlichen, dass sich an
dieser Stelle nichts oder nur sehr geringfügig etwas geändert wurde. Auch wenn DirectX 10.1 im
Moment aktuell ist, wird in diesem Projekt DirectX 9.0c verwendet. Das ist darin begründet, da die
verwendete Hardware keine neuere Version unterstützt und nicht auf die Referenzimplementierung (in
Software) zurückgegriffen werden soll.
2.5.2.2. DirectX-Graphics
Im Teilbereich DirectX dieser Ausarbeitung liegt der Schwerpunkt auf der DirectX-Komponente
DirectX Graphics. Sie ermöglicht es sowohl 2D als auch 3D Grafiken zu zeichnen und stellt die
Schnittstelle zwischen Programm und Grafikkarte dar. Des Weiteren enthält sie eine sehr hilfreiche
Bibliothek namens D3DX. Sie bildet eine High-Level-API und beinhaltet nützliche Funktionen, die
die Arbeit mit der Low-Level-API Direct3D vereinfachen. Dazu zählen vor allem Funktionen, die
mathematische Aufgaben übernehmen oder auch Datenstrukturen.
Wie bereits einführend erwähnt, baut DirectX auf dem COM (Component Object Model) auf. Dies
erlaubt eine Vereinfachung der Organisation von großen Funktionsmengen. DirectX besitzt demnach
eine große Anzahl von Funktionen, Klassen, Methoden, Schnittstellen und Strukturen. Das COM
erlaubt es eine Schnittstelle parallel von mehreren Programmteilen oder Programmen zu nutzen. Aus
diesem Grund enthält jede COM-Schnittstelle einen Referenzzähler. Dieser ist notwendig, da ein
Programm die Schnittstelle nicht löschen darf, wenn andere Programme sie noch verwenden. Um
diese und andere Verwaltungsmethoden nicht für jede Schnittstelle implementieren zu müssen,
existiert die Schnittstelle IUnknown, von der alle anderen Schnittstellen abgeleitet sind.
S e i t e | XI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
Methode
WS 08/09
Zweck
AddRef
Release
Erhöhen des Referenzzählers um 1.
Freigeben der Schnittstelle, bzw. Verringern des Referenzzählers um 1
und Abbau der Schnittstelle beim Wert 0.
QueryInterface Anfordern eines anderen Schnittstellentype.
2-5 Beschreibung der Schnittstelle IUnknown. Quelle: (6)
Wie sie später sehen werden, wird eine DirectX-Anwendung in eine normale Windows-Anwendung
integriert. Solche Windows-Anwendungen nutzen aber niemals direkt den Grafik-Adapter, sondern
das GDI (Graphic Device Interface). DirectX-Anwendungen nutzen hingegen DirectX Graphics und
somit dessen Low-Level-API Direct3D und den HAL (Hardware Abstraction Layer). Hierdurch
werden so viele Befehle wie möglich direkt von der Grafikkarte ausgeführt. Ist der HAL nicht
verfügbar, so kann das Reference Device verwendet werden. Dies ist allerdings in Software
implementiert und daher vergleichsweise sehr langsam.
Dies war eine kurze Einführung zu DirectX-Graphics – mehr aber auch nicht. Wie man damit
praktisch arbeitet, lässt sich in den passenden Abschnitten des Kapitels 4 nachlesen.
2.5.2.3. HLSL
HLSL steht für High Level Shader Language und ist die Programmiersprache für Shader im Bereich
DirectX. Sie wurde mit DirectX 9 eingeführt, was die Shader-Programmierung stark vereinfachte. Des
Weiteren wurde mit DirectX 9 das Shader Model 3 eingeführt.
2.5.2.3.1. Shader in der DirectX-Renderpipeline
Um in die Shader-Programmierung einzusteigen, benötigt man zuerst ein Verständnis darüber, wie
und an welcher Stelle diese zum Tragen kommen. Aus diesem Grund betrachten wir zuerst die
Renderpipeline.
Fixed Function Pipeline
Grafikprimitive
Clipping
Culling
Vorverarbeitung
Vertex- und Pixelshader
Texturierung
Transformation
und Beleuchtung
Endverarbeitung
Bildschirmpixel
Pixelshader
Vertexshader
2-6 Skizze der DirectX Renderpipeline. Quelle: (11)
In der Abbildung 2.4 ist zu erkennen, dass man wahlweise Vertexshader und Pixelshader einsetzen
kann. Es gilt jedoch zu beachten, dass wenn man Vertexshader einsetzt, man sich um die
Transformation und Beleuchtung innerhalb des Vertexshaders kümmern muss. Ein Mischen ist nicht
möglich. Analog dazu verhält es sich bei den Pixelshadern. Die genannten Einschränkungen gelten
jedoch immer nur für ein Modell, beispielsweise einen Mesh oder eine Dreiecksliste. Außerdem kann
nach jedem Frame wieder zur jeweils anderen Variante gewechselt werden.
2.5.2.3.2. Vertexshader
In diesem Kapitel wird der Aufbau eines Vertexshaders betrachtet. Vereinfacht lässt sich ein
Vertexshader durch die Abbildung 2.5 darstellen.
Konstanten
Input
Vertexshader
Register Quelle: (11)
2-7 Funktionsweise eines Vertexshader.
Output
S e i t e | XII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Der Vertexshader bekommt Input Streams von Vertices, die nach einer bestimmten festgelegten
Deklaration aufgebaut sind. Darin können Informationen über Position, Normalen-Vektoren, diffuse
oder spekulare Farbe und weitere Informationen enthalten sein. Als Konstanten bezeichnet man die
Variablen, auf die der Shader nur lesend zugreifen kann. Sie werden vom Anwendungsprogramm aus
gesetzt. Im Register legt der Shader Zwischenergebnisse ab. Dieser Bereich wird jedoch von HLSL
verwaltet, demnach muss sich der Programmierer hierum nicht kümmern. Letztendlich ergeben die
Berechnungen des Vertexshaders einen Output. Hierbei kann es sich beispielsweise um geänderte
Geometriedaten oder Texturkoordinaten handeln.
2.5.2.3.3. Pixelshader
Ein Pixelshader ist grundsätzlich genauso wie ein Vertexshader aufgebaut. Jedoch ist der Kontext ein
anderer, da es sich in diesem Fall nicht mehr um Geometriedaten sondern um Pixel handelt. In der
eigentlichen Thematik dieser Ausarbeitung spielen die Pixel-Shader eine untergeordnete Rolle, daher
werden sie nicht detaillierter besprochen.
2.5.3. OpenGL (Andreas Gärtner)
OpenGL steht für Open Graphics Library und ist im Gegensatz zu DirectX eine plattformunabhängige
Programmierschnittstelle zur Entwicklung von 3D-Grafiken. OpenGL beschränkt sich jedoch im
Gegensatz zu DirectX auf Funktionen zum Darstellen von Objekten. Es existieren also keine
Komponenten, die für Audio oder Eingabegeräte zuständig sind. Die folgende Tabelle verdeutlicht die
Entwicklung der API.
Version
1.0
1.1
1.2
Zeitraum
Juli 1992
1997
März 1998
1.2.1
1.3
1.4
Oktober 1998
August 2001
Juli 2002
1.5
2.0
Juli 2003
September 2004
2.1
August 2006
3.0
August 2008
2-8 OpenGL Historie Quelle (8)
Technische Neuerungen
Erste Veröffentlichung
Vertex Arrays, Texture Objects, Polygon Offset
3D-Texturen, BGRA-, Packed-Pixelformat, Level-Of-DetailTexturen
Einführung der ARB-Erweiterungen, ARB-Multitexture
Komprimierte Texturen, Cube-Maps, Multitexturing
Tiefentexturen,
Automatische
MipMap-Erzeugung,
Nebelkoordinaten
Pufferobjekte, Occlusion Queries
Einführung der GL Shading Language (GLSL / GLslang)
Multiple Render Targets, variable Texturgrößen
Pixel Buffer Objects, OpenGL Shading Language 1.2, sRGBTexturen
OpenGL Shading Language 1.3, Codebasis aufgeräumt, die
Architektur nähert sich DirectX an, erstmals ein
weitestgehender Verzicht auf Abwärtskompatibilität
Der OpenGL-Standard wird seit 1992 von ARB verwaltet. ARB steht für Architecture Review Board.
Dies ist ein Konsortium zu dem Firmen wie NVidia, ATI, IBM und Intel zählen. Im Jahr 2003 hat
Microsoft, einer der Gründer, das Team verlassen.
Die Weiterentwicklung von OpenGL wird von herstellerspezifischen Erweiterungen geprägt. Das
ARB entwirft aus diesen Erweiterungen herstellerunabhängige ARB-Erweiterungen und fügt diese
zum Standard von OpenGL hinzu. Dieses Prinzip ermöglicht das Ziel der Plattformunabhängigkeit
weiter zu verfolgen.
Des Weiteren ist OpenGL von vielen ergänzenden Bibliotheken umgeben. So zum Beispiel die GLUBibliothek
zum
darstellen
komplexer
Objekte
oder
der
GLUT-Bibliothek
zur
betriebssystemabhängigen Fensterverwaltung.
S e i t e | XIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
2.5.3.1. GLSL
GLSL oder auch GLslang ist die Shader-Programmiersprache im Bereich OpenGL. Im Kapitel HLSL
wurden Vertex- und Pixelshader behandelt. Bei OpenGL spricht man im Gegensatz zu DirectX nicht
von Pixel- sondern von Fragmentshadern.
2.5.3.1.1. Shader in der OpenGL-Renderpipeline
Die Abbildung 2.7 zeigt vereinfacht die Renderpipeline von OpenGL. Die Vertex- beziehungsweise
Fragment-Shader können dabei den statischen Teil der Renderpipeline ersetzen. Zunächst wollen wir
die klassische Renderingpipeline betrachten und einige wichtige Elemente darin erläutern:
2-9 OpenGL Renderingpipeline, Quelle: (9)
 Mittels Darstellungslisten, auch Displaylisten genannt, können bestimmte Aufgaben, wie das
Zeichnen mehrerer Vertex Punkte, zwischengespeichert und erst zu einem späteren Zeitpunkt alle
auf einmal ausgeführt werden. Dies nennt man auch retained mode. Dadurch wird die Performance
der Anwendung deutlich erhöht, da nur einmal der entsprechende glCallList() Aufruf erfolgen
muss und das Netz nicht durch das unmittelbare Ausführen jeder einzelnen Anweisung belastet
wird (auch immediate mode genannt).
 Die Evaluatoren kommen vor allem im Zusammenhang mit Freiformkurven oder –flächen vor.
Kurven werden zum Beispiel meistens zuerst auf normale Vertexdaten approximiert und erst
danach gezeichnet. Die Evaluatoren dienen hierbei als Berechnungsmethode, um die Umwandlung
der Kurvengeometriedaten auf die Vertexdaten vorzunehmen.
 Die Transformations Primitive kümmert sich um Berechnungen wie die Verschiebung, Rotation
oder Skalierung eines Objekts. Sie kümmert sich zudem um die Ansichtstransformation. Darunter
vertseht man etwa die Positionierung der Kamera innerhalb einer Szene mit der gluLookAt()
Funktion.
 Die Beleuchtungs Primitive kümmert sich um die Berechnung von Licht innerhalb einer Szene.
 Die Clipping Primitive sorgt dafür, dass Vertexdaten, die sich außerhalb eines definierten Bereichs
befinden, nicht dargestellt werden.
 Im Rasterisierungsvorgang werden aus den zuvor angegebenen, geometrischen Informationen der
Grafikprimitive die konkreten Pixel im Ausgabefenster gezeichnet. Dadurch entstehen die so
genannten Fragmente. Ein Fragment ist eine quadratische Fläche, die von einem Pixel belegt wird.
 In OpenGL existiert eine Vielzahl von unterschiedlichen Operationen, die auf ein Fragment
angewendet werden können. Ein Beispiel einer Fragmentoperation ist der Alpha Test. Hierbei wird
S e i t e | XIV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
der Alpha Wert eines Fragments mit einem Referenzwert auf Baiss einer Vergleichsoperation (z.B.
GL_GREATER) verglichen. Voraussetzung hierfür ist, dass der RGBA Modus verwendet wird.
Erfüllt ein Fragment den Alphatest, so wird es zum Framebuffer durchgelassen, sonst verworfen.
Wo setzen nun die Shader an? Der Vertexshader kann an Stelle der normalen Transformations
Primitive eingesetzt werden. Dadurch müssen die entsprechenden Operationen, wie etwa die
Beleuchtung, selber implementiert werden. Einige Anweisungen innerhalb der statischen Pipeline,
beispielsweise „glEnableLighting“ haben keine Auswirkungen mehr, da der statische Teil der Pipeline
ersetzt wurde. Andere Operationen hingegen sind nach wie vor nutzbar. Die Verwendung von
„glVertex()“ schickt die Vertexdaten aber nun direkt an den Vertexshader und die Variable
„gl_Vertex“. Für die Shaderprogrammierung existieren zahlreiche solcher fest definierten Variablen
(so genannte built in Variablen).
Der Fragment Shader wiederum kann die normalen Fragmentoperationen ersetzen. Er entspricht dem
Pixel Shader unter DirectX. Der Programmierer kann dadurch etwa einen eigenen Alpha Test oder
Blending Algorithmus entwickeln. Eine genauere Erläuterung der Shaderprogrammierung erfolgt im
Praxisteil.
(9) (10)
2.6. Zusammenfassung
Nachdem in diesem Kapitel doch recht viele unterschiedliche Komponenten
Grafikprogrammierung angesprochen wurden ist eine Zusammenfassung angebracht.
der
Am Anfang des Kapitels wurde eine kurze Einführung in die 3D-Animation gegeben. Der
Schwerpunkt lag hier auf der Charakter-Animation mit Hilfe von Skinning und Tweening und den
damit verbundenen Key-Frames, die zur Interpolation benötigt werden. Des Weiteren wurde auf den
mathematischen Hintergrund für Interpolationen eingegangen und zwei bedeutende Verfahren wurden
erwähnt. Je nachdem mit welcher Anwendung man diese Objekte und Animationen erstellt lassen sich
die Dateien in verschiedene Formate exportieren. Doch nachdem man diese Dateien erstellt hat, stellt
sich die Frage danach, wie man sie in einem DirectX oder OpenGL Programm lädt, anzeigt und auf
die Animationen zugreift. Je nach Format liegen die Daten in anderer Form vor. Man benötigt also
jeweils einen spezifischen Loader. Hier stehen viele Möglichkeiten offen, entweder man schreibt einen
eigenen oder man verwendet einen vorhanden Loader. Um aber einen eigenen Loader zu
implementieren sind zwingend Kenntnisse über die Struktur der Formate notwendig. Aus diesem
Grund werden die zwei verwendeten Formate einführend angesprochen. Nachdem dieser Abschnitt
abgehandelt wurde folgt eine Diskussion über die Vor- und Nachteile der Realisierung der
Animationen mit der CPU bzw. der GPU. Hierzu lässt sich zusammenfassend sagen, dass die
Animation durch die GPU einen deutlichen Performancezuwachs erzielt. Jedoch muss die jeweilige
GPU des Endanwenders die verwendete Technik auch unterstützen. Das letzte und größte Kapitel
befasst sich mit den 3D-Grafikbibliotheken DirectX und OpenGL inklusive der dazugehörigen
Shader-Programmiersprachen. Bisher wurden allerdings nur theoretische Aspekte, wie die Einbettung
der Shader in die Renderpipeline oder die Shader-Arten, angesprochen. Wie Anwendungen davon
Gebrauch machen folgt in den nächsten zwei Kapiteln. Ob man für die Realisierung eines Projektes
nun besser DirectX oder OpenGL nimmt liegt im Auge des Betrachters. Grundsätzlich kann man mit
beiden großartige Animationen und Computerspiele erstellen.
S e i t e | XV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
3. Konzeption
Das dritte Kapitel beschreibt nach der Einführung und den grundlegenden Sachverhalten die Struktur
der Anwendung sowie die grundsätzliche Herangehensweise.
3.1. Die Bedeutung der Zeit
Im zweiten Kapitel wurde das Thema Interpolation und Key-Frames besprochen. Wie später zu sehen
ist, wird in jedem Durchlauf des Programms die Animation fortgeführt und die Szene gerendert.
Hierbei kann es zu Problemen kommen, wenn man die Zeit nicht berücksichtigt. Wenn man versucht
auf einem neuen Rechner ein altes Spiel zu spielen, dann steht man oft vor einem Problem, denn das
Spiel wird viel zu schnell laufen. Aus diesem Grund ist es wichtig die vergangene Zeit, seit dem die
Anwendung gestartet wurde, zu messen. Des Weiteren ist es von großer Bedeutung zu messen, wie
viel Zeit benötigt wird um die Szene einmal zu bewegen und zu rendern. Denn das Ergebnis dieser
Berechnungen fließt in die Animationen ein. Je mehr Zeit dabei vergeht, desto „schneller“ muss die
Animation also fortschreiten – und umgekehrt.
Daraus ergibt sich eine Differenz zwischen zwei Frames von:
Legt man nun eine Zeit fest, die normalerweise insgesamt für eine Animation vergehen sollte, so kann
man daraus den Faktor für den aktuellen Stand der Animationen berechnen.
Daraus ergibt sich:
Woraus folgt, dass am Anfang der Animation (zwischen zwei Key-Frames) der Faktor gleich 0 und
am Ende gleich 1 ist. Hierdurch ist gewährleistet, dass unabhängig von der Rechengeschwindigkeit
des einzelnen Systems die Animation die gleiche Zeitspanne benötigt.
3.2. Konzeption der DirectX-Anwendungen
Dieses Kapitel beschreibt die benötigten Belange im Bereich der DirectX-Entwicklung. Dazu zählen
unter anderem die Einrichtung der Entwicklungsumgebung und die Struktur der Anwendungen, sowie
die Konzeption der Integration der Shader in den Verarbeitungsablauf aber auch allgemeine
Erklärungen zu den verwendeten Techniken.
3.2.1. Einrichtung der Arbeitsumgebung
In diesem Projekt wird als Arbeitsumgebung für die DirectX-Anwendungen MS Visual Studio 2008
verwendet. Diese IDE ist über MSDNAA kostenfrei verfügbar. Des Weiteren wird das DirectX-SDK
benötigt, welches auf der Homepage von Microsoft als Download zur Verfügung steht. In diesem
Projekt wird die DirectX-SDK Version vom März 2008 verwendet. Diese Software sollte man
installieren, um die auf der CD im Anhang befindlichen Projekte zu bearbeiten.
S e i t e | XVI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Um eine Anwendung zu kompilieren und zu Linken müssen bestimmte Pfade gesetzt werden, damit
MS Visual Studio 2008 auf die benötigten Elemente zugreifen kann. Andernfalls scheitert das
Kompilieren beziehungsweise das Linken. Hierfür startet man MS Visual Studio 2008 und klickt auf
Extras, Optionen. Dort navigiert man zu den Einstellungen für C++ Verzeichnisse (Projekte und
Projektmappen – C++ Verzeichnisse). Wenn man dieses Menü öffnet, kann man die entsprechenden
Ordner für die Bibliotheksdateien und Include-Dateien angeben. Hier fügt man die Ordner des DirectX
SDKs ein.
Im nächsten Schritt erstellt man ein neues Projekt. Dabei Achten Sie darauf, dass Sie ein Win32Projekt erstellen. Im Anschluss daran öffnet man das Projekt-Menü und klickt dort auf Eigenschaften.
In dem daraufhin erscheinenden Fenster navigiert man sich anschließend zu den passenden LinkerEinstellungen durch. (Konfigurationseigenschaften->Linker->Eingabe). Öffnet man dieses Menü,
kann man zusätzliche Abhängigkeiten festlegen. In diesem Projekt wurden immer die folgenden
Abhängigkeiten dort eingetragen, an den man sich orientieren kann.
dinput8.lib, dxguid.lib, dxerr9.lib, dsound.lib, d3dx9.lib, winmm.lib,
d3d9.lib, kernel32.lib, user32.lib, comdlg32.lib, gdi32.lib, shell32.lib
3-10 Ein Beispiel für zusätzliche Linkeroptionen für DirectX-Anwendungen.
Wenn diese Schritte erfolgreich durchgeführt wurden, lassen sich eigene Anwendungen erstellen, die
DirectX-Komponenten verwenden.
3.2.2. DirectX Graphics und D3DX
Wie im zweiten Kapitel beschrieben handelt es sich bei DirectX Graphics um die DirectXKomponente, die für 2D und 3D Grafiken zuständig ist. In diesem Projekt wird überwiegend die
Bibliothek D3DX verwendet. Hierbei handelt es sich um eine High-Level-API für Direct3D. Es
existieren daher viele nützliche Funktionen und Datenstrukturen für mathematische Konstrukte oder
auch 3D-Modelle.
Eine dieser Datenstrukturen ist das Interface ID3DXMESH. Das ist eine Datenstruktur, die einen Mesh
– ein 3D Objekt – enthält. Ein solcher Mesh besteht prinzipiell aus vier Teilen, einem Vertexbuffer,
einem Indexbuffer, einem Attributbuffer und einer Attributtabelle. Um den Aufbau dieser Struktur zu
verstehen ist ein grundlegendes Wissen über Index- und Vertexbuffer notwendig. Doch wozu werden
diese zwei Buffer verwendet? Angenommen es wird ein normaler Würfel in eine 3D-Szene geladen,
dann besteht dieser aus acht Eckpunkten und vier Seitenflächen. Prinzipiell muss für jede Seite des
Würfels jeder Eckpunkt gespeichert werden. Würde man an dieser Stelle für jede Seite die Eckpunkte
(Vertices) separat speichern, dann führt das zu einer hohen Redundanz und Speicherbelastung. Aus
diesem Grund sind die einzelnen Vertices in einem Vertexbuffer abgelegt auf die über einen
Indexbuffer zugegriffen wird. Für jeden Eckpunkt existiert daher ein Eintrag im Indexbuffer, mit
dessen Hilfe sehr performant die Vertexdaten ausgelesen werden können. Der Attributbuffer und die
Attributtabelle teilen den Mesh in verschiedene Untermengen. Eine solche Menge (Subset) zeichnet
sich durch eine einheitliche Textur beziehungsweise ein einheitliches Material aus. Im Attributbuffer
befinden sich daher für jede Oberfläche Informationen über das Material, mit dem diese Oberfläche zu
belegen ist. (11)
Weitere Datenstrukturen die bei der Animation von Bedeutung sind, sind ID3DXSKININFO,
ID3DXMESHCONTAINER, ID3DXAnimationController, D3DXMATRIX oder auch D3DXVECTOR3.
Auf Basis dieser Elemente wird im nächsten Kapitel die Herangehensweise beschrieben, wie man eine
Animation in einer Anwendung umsetzt.
3.2.2.1. Konzept zur Implementierung der Animationstechnik Skinning
S e i t e | XVII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Die Grundlage von Skinning bilden die Vertices und Bones der Objekte. Zur Animation werden dabei
die Eigenschaften der verschiedenen Bones zu den jeweiligen Key-Frames ausgelesen. Des Weiteren
werden Informationen darüber benötigt welche Vertices, durch welche Bones, wie stark, beeinflusst
werden. Um das Objekt zu animieren muss im folgenden Schritt in Abhängigkeit der Zeit zwischen
den jeweiligen Key-Frames interpoliert werden.
Diese Beschreibung ist jedoch eher theoretisch und sehr wage. Aus diesem Grund beschreibt dieses
Kapitel die Vorgehensweise etwas genauer.
Fehler: Referenz nicht gefunden
3-11 Beispiel für eine Bone-Hierarchie.
Die Abbildung 3.2 zeigt eine Hierarchie von Bones. Dabei ist dem Oberarm der Unterarm
untergeordnet und diesem wiederum die Hand und so weiter. Auch wenn der Oberarm in dieser
Betrachtung in der Hierarchie am höchsten steht, so muss bei der Transformation auch dessen Lage in
der Szene berücksichtigt werden, dafür dient die World-Matrix. Unter der Annahme, dass für jeden
Bone die lokalen Eigenschaften im Verhältnis zu seinem Vater gespeichert sind, muss zu dessen
Transformation die gesamte Hierarchie herabgestiegen werden. Es gilt demnach die folgende Aussage:
Wenn F1 die Transformationsmatrix für T1, T0 ist und F2 für T2, T1 und so weiter, wobei F0 die
Transformationsmatrix für T0, World darstellt, dann lässt sich wie folgt die Szene in das WorldKoordinatensystem transformieren.
Eine zweite und sogleich effizientere Methode ist es aber einen Top-Down-Ansatz zu verfolgen.
Hierdurch wird die Redundanz stark verringert und somit Zeit eingespart. Erst am Ende, wenn alle
Einzelteile transformiert wurden, wird die World-Matrix angewendet.
Nach dieser Betrachtung stellt sich die Frage danach, wie man die Animation umsetzen kann. Zuvor
wurde die Datenstruktur eines Meshs beschrieben, in der bekanntlich die Vertices eines 3D-Modells
abgelegt sind. Des Weiteren wurde im zweiten Kapitel das Thema Interpolation angesprochen und
auch Quaternionen wurden erwähnt. Das alles ist an dieser Stelle nun von Bedeutung.
Je nachdem, welche Einstellungen man beim Export des Modells aus der Modellierungssoftware
vornimmt, sind verschiedene Daten in den Key-Frames gespeichert. Matrizen sind an dieser Stelle zur
Interpolation leider nicht geeignet, da man zwischen ihnen nicht direkt interpolieren kann (12).
Normalerweise verwendet man die Werte für Rotation, Skalierung und Translation unabhängig
voneinander und kombiniert diese zum Schluss wieder zu einer Matrix. Sollte jedoch eine kombinierte
Matrix vorliegen, so kann diese mit der Funktion D3DXMatrixDecompose in die entsprechenden
Bestandtele extrahieren. Je nachdem, ob eine besonders weiche Animation gewünscht ist oder nicht,
verwendet man zur Interpolation das Verfahren SLERP oder LERP (siehe Kapitel 2.2 – Verfahren zur
Interpolation). Wobei für eben diese beiden Verfahren im dreidimensionalen Raum in der D3DXBibliothek entsprechende Funktionen existieren (D3DXVec3Lerp, D3DXQuaternionSlep).
Eine Feinheit, die noch zu beachten ist, ist das die Vertices die ein Bone beeinflusst nicht relativ zu
dessen Koordinatensystem liegen. Daher müssen die Vertices zuvor mittels eines Offsets transformiert
werden.
S e i t e | XVIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Um flüssige Übergänge zwischen den Bestandteilen des zu animierenden Objekts herzustellen
verwendet man Vertex Blending. Dabei wird die Position der Vertices durch die Position und
Gewichtung der Bones beeinflusst, wobei in der Regel nicht mehr als vier Bone-Gewichte je Vertex
benötigt werden (13). Daraus ergibt sich die folgende Formel.
Analog dazu lassen sich auch die Normalen-Vektoren berechnen. Durch diese tiefgehende
Betrachtung sollte man in der Lage sein die spätere Implementierung zu verstehen und gegebenenfalls
selbst umzusetzen.
3.2.2.2. Konzept zur Implementierung der Animationstechnik Tweening
Im Gegensatz zu Skinning sind bei Tweening keine Bones notwendig. Trotzdem sind sie sehr hilfreich
um das Objekt innerhalb einer 3D-Modellierungssoftware zu animieren. Der Unterschied liegt jedoch
darin, dass die Berechnung der Animation innerhalb der Anwendung nicht auf Bones basiert, sondern
die Positionen der Vertices an sich zwischen den Key-Frames interpoliert werden. Daher gilt es in
diesem Kapitel zu erklären, wie solch ein Algorithmus strukturell aufgebaut ist.
Grundsätzlich müssen bei Tweening bestimme Bedingungen erfüllt sein. Es müssen in beiden
Instanzen, zwischen denen Interpoliert werden soll:
 die gleiche Anzahl an Vertices vorhanden sein
 und die Vertices müssen in der gleichen Reihenfolge nummeriert sein.
Da hierbei auf Bones und andere komplexe Gebilde verzichtet wird, reicht es aus die Vertices im
Laufe der Zeit vom Start- zum Endpunkt zu interpolieren und da die Vertices in Form von
dreidimensionalen Vektoren vorliegen, kann man dies durch einfache Multiplikation mit einem Skalar
und Additionen durchführen.
Am Anfang der Animation ist der Wert von s gleich null und am Ende eins, er wird also in
Abhängigkeit der Zeit erhöht. Viel mehr muss man hierbei auch nicht berechnen. Es sei denn, man
möchte die Normalen verwenden, dann müssen diese auch berechnet werden. Die Animation jedoch
ist durch diese Formel beschrieben.
3.2.3. Animation Blending
Bisher wurden Animationen jeweils separat betrachtet, das ist aber oft nicht sinnvoll, da so sehr
abgehakte Animationen entstehen können. Beispielsweise wenn ein Charakter anfangs geht und
danach sofort rennt. Ein anderes Beispiel für Animation Blending ist das Heben der Hand, dies kann
man sich in Computerspielen in vielen Situationen, wie das Zielen mit einer Waffe, vorstellen. Diese
Aktion kann der Charakter nun sowohl im Stehen als auch während des Laufens durchführen. Wenn
man in solchen Situationen jeweils einzelne Animationen erstellt, wächst die Anzahl derer
exponentiell und wird praktisch unbrauchbar. Aus diesem Grund gibt es Animation Blending, womit
man mehrere Animationen überlagert und somit die beschriebenen Probleme löst.
Auf eine detaillierte Beschreibung dieser Technik wird verzichtet. Dies hat den Grund, dass es
einerseits den Umfang dieser Ausarbeitung sprengen würde und andererseits in DirectX Funktionen
integriert sind, die diese Arbeit bei Skinning stark vereinfachen, wodurch keine eigenen Berechnungen
implementiert werden müssen.
S e i t e | XIX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
3.2.4. DirectX-Shader (HLSL)
Im zweiten Kapitel wurde bereits einführen erklärt, was HLSL ist und wie es funktioniert. An dieser
Stelle hingegen wird der Aufbau eines HLSL-Shaders erklärt und beschrieben wie ein Shader für
Charakter-Animationen eingesetzt werden kann. Aus diesem Grund folgt an dieser Stelle eine
Einführung in die Programmiersprache HLSL (11).
3.2.4.1. Einführung in HLSL
Die Programmiersprache HLSL ist stark an C angelehnt und gliedert sich in die folgenden Bereiche:
 Skalare Datentypen
• In HLSL gibt es bool, half, int, float und double als skalare Datentypen.
 Vektoren
• Notation eines Vektors: vector <type, size> instancName;
• Jedoch wird in der Regel eine verkürzte Schreibweise verwendet.
• Notation in kurzer Schreibweise: float4 myVector;
 Matrizen
• Allgemeine Notation: matrix <type, rows, columns>;
• Notation in kurzer Schreibweise: matrix3x2 myMatrix;
 Texturen und Sampler
• Sampler sind Datentypen, die dem Zugriff auf Texturen dienen.
• Beispiele hierfür sind texture2D myTextur; und sampler2D mySampler;.
 Annotationen
• Annotationen sind Zusatzinformationen, die beispielsweise einer Variablen hinzugefügt werden
können. Sie tragen in HLSL jedoch keine Semantik, können aber vom verarbeitenden
Programm ausgelesen und verarbeitet werden.
 Arrays
• Arrays können wie in C angelegt und verwendet werden.
• float array[19]; array[0] = 1.23;
 Strukturen
• Auch Strukturen können wie in C angelegt werden.
• struct
int
float4
myStruct{
index;
vector;
}
S e i t e | XX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
 Speicherklassen
• Ähnlich wie in C können Variablen lokal und global definiert werden. Des Weiteren gibt es die
Möglichkeit Schlüsselwörter wie const, extern, static, shared und uniform zu
verwenden.
 Operatoren
• In HLSL existieren wie in allen Hoch-Sprachen viele verschiedene Operatoren. In der Hilfe von
DirectX ist eine Tabelle mit deren Bedeutung und Abarbeitungsreihenfolge enthalten.
 Kontrollflusssteuerung
• Im Gegensatz zur CPU ist die GPU nicht für prozedurale Programmierung gedacht. Deshalb
sollte man wenn möglich darauf verzichten. Nichts desto trotz ist es manchmal notwendig,
daher gibt es Fallunterscheidungen mit if und else und auch Schleifen wie die for-Schleife.
Die Syntax ist dabei wieder wie in C.
 Vordefinierte Funktionen
• HLSL bietet viele vordefinierte Funktionen. Hierzu zählen unteranderem mathematische
Grundfunktionen, wie Sinus und Cosinus.
 Eigendefinierte Funktionen
• Da die GPU nicht für Unterprogrammaufrufe konzipiert wurde, kann sie auch keine echten
Unterprogramme ausführen. Es existiert auch kein Stack, auf dem die dafür benötigten Daten
abgelegt werden könnten. Jedoch kann man als Programmierer Funktionen wie in C schreiben,
diese werden jedoch immer als „Inline“ deklariert und somit im Quelltext an die entsprechenden
Stellen direkt eingefügt.
 Ein- und Ausgabesemantik
• Der Shader besitzt in der Regel Datenstrukturen für die Eingabe und die Ausgabe. Um
beispielsweise die Eingabedaten mit Informationen aus der Rendering-Pipeline zu füllen,
müssen die Elemente der Eingabe-Datenstruktur spezifiziert werden. Hierfür dienen Semantics
– Schlüsselwörter.
 Shader Hauptprogramm
• Ein Shader besitzt immer einen Einstiegspunkt, ähnlich einer main-Funktion in C.
Nach dieser kurzen und zugleich umfassenden Einführung in HLSL – der Hochsprache zur ShaderProgrammierung für DirectX - folgt im nächsten Schritt die Integration eines Shaders in eine
Anwendung. Im speziellen wird die Integration eines Vertexshaders erläutert, da Pixelshader in dieser
Ausarbeitung nur eine zweitrangige Rolle spielen.
3.2.4.2. Integration Eines Vertexshaders in eine Anwendung
Der erste Schritt um einen Vertexshader in eine Anwendung zu integrieren ist die Definition der
benötigten Komponenten innerhalb des Shaders. Dazu zählen globale Variablen, auch als Konstanten
bezeichnet, sowie die Eingangs- und Ausgangsdatenstruktur, die somit die Schnittstellen festlegen.
Dieses Beispiel definiert zwei Strukturen, die hier zufälligerweise gleich sind. Die Variable pos
beziehungswiese posNew deutet an, dass die Position des Vertex in irgendeiner Form geändert wird.
Die Variable text wird nicht verändert, sie ist jedoch notwendig, da sonst die Textur nicht in der
S e i t e | XXI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Ausgabe integriert wäre. Die Textur-Koordinaten werden also nur durch geleitet. Die Angaben nach
dem „:“ sind Semantics, dessen Bedeutung anhand des Namens meist selbsterklärend sind.
// globale Variablen
float4x4 globalVariable;
// Eingangsdatenstruktur
struct VS_INPUT{
float4 pos
: POSITION;
float2 text
: TEXCORD0;
}
// Definition einer 4 mal 4 Matrix.
// Position des Vertex im Raum.
// Texturkoordinate der Texturebene 0.
// Ausgangsdatenstruktur
struct VS_OUTPUT{
float4 posNew
: POSITION; // Transformierte Position des Vertex.
float2 text
: TEXCORD0; // Texturkoordinate der Texturebene 0.
}
3-12 Definition der Input- und Output-Strauktur eines Vertex-Shaders.
Um den Shader zu vervollständigen muss ein Einstiegspunkt hinzugefügt werden. Man benötigt also
noch eine vs_main-Funktion. Der Name ist allerdings frei definierbar und muss daher später beim
Laden des Shaders angegeben werden.
VS_OUTPUT vs_main (VS_INPUT in){
VS_OUTPUT out;
// Instanz der Vertex-Output-Datenstruktur.
…
// Bearbeitung des Inputs.
return out;
// Rückgabe des bearbeiteten Vertex.
}
3-13 Definition einer Main-Methode in einem Vertex-Shader.
An der Stelle “…” würde die eigentliche Bearbeitung der Vertices stattfinden. An dieser Stelle ist das
allerdings nicht von Bedeutung, da ja nur das Prinzip erläutert wird. Daher werden im Anschluss an
die Implementierung des Shaders auf der Anwendungsseite die benötigten Komponenten erstellt.
LPDIRECT3DVERTEXSHADER9 vertexShader; // Zeiger auf einen Vertexshader.
LPD3DXCONSTANTTABLE globalVariable;
// Zeiger auf eine Konstantentabelle.
3-14 Definition bedeutender DirectX-Variablen zur Verwendung von Vertex-Shadern.
Im Wesentlichen sind es diese zwei Variablen, sie dienen dazu auf den Shader beziehungsweise
dessen Konstanten zuzugreifen. Da der Shader-Quelltext in einer separaten Datei vorliegt, muss diese
Datei zuerst geladen werden, wie in Code-Ausschnitt 3.6 dargestellt.
Bevor man einen Shader innerhalb einer Anwendung lädt, empfiehlt es sich externe Compiler zum
Testen zu verwenden, um zum Beispiel Syntaxfehler vorher auszuschließen. Das Debuggen ist aber
auch innerhalb von MS Visual Studio möglich. Hierfür muss die Compiler-Option
D3DXSHADER_DEBUG gesetzt sein und D3DDEVTYPE_REF beim Erzeugen des Devices angegeben
werden. Um zu testen ob der jeweilige Rechner auf dem die Anwendung ausgeführt wird auch das
mindestens benötigte Shader-Model unterstützt, müssen die Caps des Grafikadapters ausgelesen
werden. Mehr hierzu im Abschnitt zur Initialisierung von DirectX im vierten Kapitel.
HRESULT D3DXCompileShaderFromFile(
LPCSTR pSrcFile,
// Zeiger auf einen String (Dateipfad).
CONST D3DXMACRO* pDefines, // Optional, Array von D3DXMACRO Strukturen.
LPD3DXINCLUDE pInclude,
// Optional, Zeiger auf ein ID3DXInclude
// Interface.
LPCSTR pFunctionName,
// Zeiger auf einen String (Main-Funktion).
LPCSTR pProfile,
// Zeiger auf ein Shader-Profil.
DWORD Flags,
// Kompilierungsoptionen
S e i t e | XXII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
LPD3DXBUFFER* ppShader,
// Gibt einen Buffer mit den kompilierten
// Shader zurück.
LPD3DXBUFFER* ppErrorMsgs, // Gibt einen Buffer mit den Fehlermeldungen
// zurück.
LPD3DXCONSTANTTABLE* ppConstantTable // Gibt ein ID3DXConstantTable
// Interface zurück.
);
3-15 DirectX-Funktion zum Laden eines Shaders aus einer Datei.
Der nächste Schritt in der Anwendung besteht darin den Vertexshader zu erzeugen. Hierfür stellt die
Schnittstelle IDirect3DDevice9 eine passende Funktion zur Verfügung.
HRESULT CreateVertexShader(
CONST DWORD * pFunction,
// Zeiger auf ein Array,
// mit Kompilerinformationen.
IDirect3DVertexShader9** ppShader // Zeiger einen resultierenden Shader.
);
3-16 DirectX-Funktion zum Erstellen eines vertex-Shaders.
Wenn hierbei kein Fehler aufgetreten ist, was anhand des Rückgabewertes überprüft werden kann,
lassen sich die globalen Variablen (Konstanten) setzen. Hierzu benötigt man allerdings zuvor die
Handles der Konstanten-Tabelle.
D3DXHANDLE GetConstantByName(
D3DXHANDLE hConstant, // Name in Bezug zur Vater-Struktur oder 0.
LPCSTR pName
// Name der Konstante.
);
3-17 DirectX-Funktion für den Zugriff auf die Konstanten eines Shaders.
Mit Hilfe dieser Handles werden die globalen Variablen der Konstanten-Tabelle gefüllt. Hierbei gibt
es für die verschiedenen Datentypen spezifische Methoden. In diesem Beispiel (float4x4) wird die
passende Methode für eine Matrix angewendet.
HRESULT SetMatrix(
LPDIRECT3DDEVICE9 pDevice, // Grafikkartenadapter
D3DXHANDLE hConstant,
// Zuvor erstelltes Handle.
CONST D3DXMATRIX* pMatrix // Die Matrix, die dem Shader zugeordnet wird.
);
3-18 DirectX-Funktion zum Setzen einer Konstanten (einer Matrix).
Im anschließenden und letzten Schritt muss der Vertexshader in den Render-Prozess integriert werden.
Die Schnittstelle IDirect3DDevice9 bietet dazu die folgende Funktion an.
HRESULT SetVertexShader(
IDirect3DVertexShader9* pShader // Der zuvor kompilierte Vertex-Shader.
);
3-19 DirectX-Funktion zum Setzen eines Vertex-Shaders im Rendervorgang.
Nach diesem Schritt folgen Aktionen wie das Setzen der Materialen und der Texturen für die
einzelnen Bestandteile des Meshs, wie sie sonst auch ausgeführt werden, wenn man ohne Shader
arbeitet. Zum Zeichnen des Meshs muss nur noch die anschließende Methode aufgerufen werden.
HRESULT DrawSubset(
DWORD AttribId // Spezifiziert das Subset des Meshs.
);
3-20 DirectX-Funktion zum Zeichnen eines Meshs.
Soviel zur Erklärung der Integration eines Vertexshaders, die genaue Implementierung des HLSL
Shaders zur Animation eines Charakters befindet sich im Anhang und auf der CD. Das Prinzip dieses
Shaders wird jedoch im nächsten Kapitel erläutert.
S e i t e | XXIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
3.2.4.3. Konzept zur Implementierung einer Charakter-Animation auf Basis eines Shaders
Dieses Kapitel beschreibt die Idee und somit die Funktionsweise, wie man mit einem Shader eine
Charakter-Animation berechnen kann. Dabei wird sowohl auf Tweening als auch auf Skinning
eingegangen. (14) (15)
Beim Tweening benötigt der Shader zu jedem Zeitpunkt die Daten der zwei Key-Frames zwischen
denen interpoliert werden soll. Unter Daten werden hier die Positionen und Normalen der Vertices
verstanden. Des Weiteren wird ein Faktor benötigt, der angibt wie weit die Animation fortgeschritten
ist. Wenn dieser Faktor am Anfang 0 und am Ende 1 ist, dann ergibt sich für die Animation die
folgende Berechnung.
Diese Berechnung ist unabhängig vom Shader, sie muss also auch bei den CPU-basierten Verfahren
ausgeführt werden. Daher ist es die gleiche Formel wie im Kapitel 3.2.2.2.
Im Anschluss daran muss noch die World-, View- und Projektionsmatrix darauf angewendet werden.
Diese kann man dem Shader als eine kombinierte Transformationsmatrix (Konstante) übergeben.
Des Weiteren kann man um die Performance zu verbessern die Berechnung von
auch in der
normalen Anwendung durchführen und das Ergebnis als eine weitere Konstante dem Shader
übergeben. So muss diese Berechnung nicht für jeden Vertex einzeln durchgeführt werden.
Beim Skinning werden anstatt der Vertexdaten zu den jeweiligen Key-Frames die Matrizen der Bones
und eine Liste der je Bone beeinflussten Vertices benötigt. Des Weiteren wird auch hier der oben
beschriebene Faktor zur Interpolation verwendet. Die Berechnung stützt sich aber auf eine Reihe von
Matrixmultiplikationen. Dabei müssen die Gewichtungen des jeweiligen Bones mit dessen
Transformationsmatrix multipliziert und anschließend kumuliert werden. Nach diesem Schritt ist auch
wieder die Transformation von World-, View- und Projektionsmatrix und gegebenenfalls weitere
Berechnungen durchzuführen.
Im Grunde genommen müssen die gleichen Schritte wie bei einer äquivalenten CPU-basierten
Methode durchgeführt werden. Der Unterschied liegt darin, dass dem Shader die benötigten Werte
mitgeteilt werden und der Shader einmal je Vertex aufgerufen wird. Ihn umgibt demnach eine
„unsichtbare Schleife“, die sonst von Hand programmiert werden muss.
Um eine effiziente und allgemeingültige Implementierung der Shader zu gewährleisten, muss dem
Shader bei der Tweening-Variante ein zweiter Stream zugewiesen werden. Es ist in diesem Fall nicht
sinnvoll, das komplette Objekte und somit unter anderem seine Vertexdaten als Konstanten zu
übergeben. Wie man einen solchen zweiten Stream zuweist, ist im Kapitel zur Implementierung eines
Vertex-Shaders für Tweening erklärt.
3.2.4.4. Effekt Dateien – eine Erweiterung zur Implementierung von Shadern
Neben der Möglichkeit einen einfachen Vertexshader zu schreiben gibt es die Möglichkeit EffektDateien zu erstellen. Diese Dateien kombinieren Vertex- und Pixelshader und bieten zusätzlich die
Möglichkeit, mehrere Techniken für einen Effekt in einer Datei zu kombinieren. Das bedeutet, dass je
nach Hardware des Anwendungssystems eine entsprechende Technik angewendet werden kann.
Hierfür bietet DirectX Methoden an, um zur Laufzeit zu testen ob ein System eine bestimmte Technik
unterstützt oder nicht. Für das Erstellen dieser Effekte gibt es zwei bekannte IDEs, FX Composer von
NVidia und RenderMonkey von AMD. Eine Beschreibung wird hier nicht weiter ausgeführt, denn das
würde den Rahmen sprengen. Jedoch wird in diesem Kapitel aufgezeigt, wie man mit solch einer
Effektdatei in einer Anwendung arbeitet, wie man sie lädt, wie man die Konstanten setzt und wie man
den Render-Prozess entsprechend steuert.
S e i t e | XXIV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Effekt-Dateien sind allerdings mehr als eine Kombination von Vertex- und Pixelshadern.
Beispielsweise kann eine Technik mehrere Passes (Durchläufe) enthalten. Des Weiteren kann man in
ihr Effekt-States festlegen, ähnlich wie die Render-States in der Fixed-Function-Pipeline. Daher ergibt
sich die Möglichkeit unabhängig von der späteren Anwendung komplette Shader zu entwickeln, wobei
die Anwendung diese auf sehr einfache Weise verwenden kann.
Im Folgenden wird die Integration einer solchen Datei beschrieben. Wie auch bei einem VertexShader, muss die Effekt-Datei zuerst geladen werden. Hierfür gibt es die Methode
D3DXCreateEffectFromFile(…).
HRESULT D3DXCreateEffectFromFile(
LPDIRECT3DDEVICE9 pDevice,
LPCTSTR pSrcFile,
CONST D3DXMACRO * pDefines,
LPD3DXINCLUDE pInclude,
DWORD Flags,
LPD3DXEFFECTPOOL pPool,
LPD3DXEFFECT * ppEffect,
//
//
//
//
//
//
//
//
//
//
//
//
Grafikkartenadapter
Zeiger auf einen String (Pfad).
Optional, Array von D3DXMACRO
Strukturen.
Zeiger auf ein ID3DXInclude
Interface.
D3DXSHADER und D3DXFX Flags.
Zeiger auf ein ID3DXEffectPool
Objekt.
Gibt den resultierenden Effekt
zurück.
Gibt Fehlermeldungen zurück.
LPD3DXBUFFER * ppCompilationErrors
);
3-21 DirectX-Funktion zum Laden einer Effektdatei.
Anschließend muss man die Handles erstellen, um auf die Konstanten des Effektes zuzugreifen. Das
Vorgehen ist dabei Äquivalent zum vorherigen Beispiel (GetConstantByName). Danach erstellt
man einen Handle um auf eine Technik des Effektes zuzugreifen.
D3DXHANDLE GetTechniqueByName(
LPCSTR pName
// Zeiger auf einen String (Name).
);
3-22 DirectX-Funktion für den Zugriff auf die Konstanten einer Effektdatei.
Jetzt kann der Render-Prozess beginnen, dazu gilt wie oberhalb auch, dass man mit Methoden wie
SetMatrix die Konstanten des Effektes mit Werten belegt. Anschließend wird die gewünschte
Technik aktiviert, deren Passes in einer Schleife durchlaufen und der Effekt zum Schluss beendet.
effect->SetTechnique(name);
effect->Begin(&anzPasses, 0);
//
//
//
//
//
//
//
Aktiviert eine best. Technik.
Ermittelt die Anzahl der
Durchläufe.
Durchlaufe alle Durchläufe.
Beginne Durchlauf pass.
Führe weitere Aufgaben aus.
Beende Durchlauf pass.
for(pass=0; pass<anzPasses; pass++){
effect->BeginPass(pass);
// …
effect->EndPass();
}
effect->End();
// Beende den Effekt.
3-23 Code-Ausschnitt zum Rendern eines Effekts.
Wie ersichtlich, ist der Render-Programmcode sehr allgemein gehalten und kann in gleicher Weise auf
„alle“ Objekte angewendet werden. Dies ist zumindest dann der Fall, wenn der Effekt die benötigten
Effekt-States setzt.
Dieses Kapitel dient als Grundlage für die Umsetzung des Teils DirectX in Kapitel vier. Im Kapitel
3.3. wird zuvor allerdings eine Grundlage zum Teil der OpenGL-Programmierung geschaffen.
3.3. Konzeption der OpenGL-Anwendungen
S e i t e | XXV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
3.3.1. Einrichtung der Arbeitsumgebung
Im Kapitel 3.2.1. wurde die Einrichtung der Arbeitsumgebung für DirectX beschrieben. Wenn Sie
diese Schritte durchgeführt haben, dann haben Sie Visual Studio 2008 bereits eingerichtet.
Grundsätzlich lassen sich damit OpenGL Anwendungen ausführen. Um jedoch ein geeignetes
Fenstersystem für die Ausgabe zur Verfügung zu haben, benötigen Sie für die OpenGL-Entwicklung
die GLUT-Bibliothek. In diesem Fall wurde die aktuelle Version 3.7.6 verwendet, die von Nate Robin
nach Windows portiert wurde und unter der folgenden URL zu beziehen ist:
http://www.xmission.com/~nate/glut.html
Im Anschluss daran, entpacken Sie das heruntergeladene Archiv. Sie finden im entpackten Archiv eine
Readme Datei, in der beschrieben wird, welche Dateien in welche Verzeichnisse kopiert werden
müssen. Für die Einbindung von GLUT in Visual Studio 2008 folgt hier aber auch nochmal eine kurze
Anleitung. Für 32 Bit Betriebssysteme kopieren sie bitte die folgenden Dateien in die angegebenen
Verzeichnisse:
 glut32.dll nach %WinDir%\System32,
 glut32.lib nach {VS Root}\VC\lib, und
 glut.h nach {VS Root}\VC\include\GL.
VS Root steht hierbei für den Stammordner, in dem Visual Studio 2008 installiert ist. Nachdem Sie
diese Schritte durchgeführt haben sollten Sie OpenGL Anwendungen unter Verwendung der GLUT
Bibliothek in Visual Studio 2008 kompilieren und ausführen können. Es sind keine zusätzlichen
Einstellungen im Programm nötig.
Für die Shadersprache GLSL gibt es ebenfalls eine Bibliothek, welche dem Programmierer mit
diversen Funktionen die Arbeit an Shaderprogrammen erleichtert. Dies ist die GLEW (GL extension
wrangler) Bibliothek, die sich unter http://glew.sourceforge.net/ befindet. Die darin enthaltenen
Dateien müssen wie folgt kopiert werden:
 glew32.dll nach %WinDir%\System32
 glew32.lib nach {VS Root}\VC\lib
 glew.h und wglew.h nach {VS Root}\VC\include\GL
Im jeweiligen Projekt, in dem glew benutzt werden soll, muss anschließend noch die glew32.lib als
zusätzliche Abhängigkeit eingetragen werden, sonst kommt es zu Fehlern im Link Prozess. Dazu
rechtsklicken Sie auf das Projekt, wählen den Reiter Eigenschaften und navigieren zu
Konfigurationseigenschaften->Linker->Eingabe->Zusätzliche Abhängigkeiten. Dort muss der Eintrag
„glew32.lib“ (ohne Anführungszeichen) eingetragen werden.
Falls Sie Windows Vista in der 64 Bit Version verwenden, so müssen die glut32.dll und die glew32.dll
Dateien statt nach System32 ins SysWOW64 Unterverzeichnis kopiert werden.
3.3.2. Kompilieren und Starten der Anwendungen
Im Rahmen dieser Ausarbeitung wurden zwei OpenGL Programme implementiert, die das Morphing
einmal mittels CPU- und einmal mittels Shaderberechnung realisieren. Die Programme befinden sich
auf dem Datenträger unter „Source-Code->OpenGL->„Morphing mittels CPU“ bzw. Morphing mittels
S e i t e | XXVI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Shadern“. Im jeweiligen Programmordner doppelklicken Sie auf die Solution Datei (Morphing mittels
CPU.sln/Morphing mittels Shadern.sln). Daraufhin öffnet sich das entsprechende Visual Studio
Projekt. Die oben beschriebene, zusätzliche Abhängigkeit der glew32.lib ist bereits gesetzt, so das ein
Druck auf die F5 Taste ausreicht, um das Programm auszuführen. Im Pfad „Source-Code->OpenGL>„Morphing mittels CPU-> Morphing mittels CPU“ bzw. „Source-Code->OpenGL->„Morphing
mittels Shadern -> Morphing mittels Shadern “ befinden sich einige Objektdateien im 3DS Format. In
den Zeilen 107-109 der Datei main.c sehen Sie, welche 3DS Dateien standardmäßig für die Animation
benutzt werden (ein menschlicher Kopf in zwei Positionen). Wollen Sie einige der übrigen Objekte
verwenden, so muss der entsprechende Dateiname bei der „ObjLoad()“ Funktion geändert werden.
Sollten eine oder beide Objektdateien nicht existieren, wird eine Fehlermeldung im Konsolenfenster
ausgegeben. Im Verzeichnis Quellen->links.txt“ ist ersichtlich, woher die verwendeten 3DS Dateien
stammen.
3.3.3. Animationen mit GLSL
Die Shadersprache GLSL ist, wie auch das Pendant HLSL für DirectX, an C angelehnt. GLSL
orientiert sich dabei besonders stark an ANSI C während HLSL auch viele aus C++ bekannte
Konstrukte enthält. Natürlich gibt es auch neue Elemente, die so nicht in C vorzufinden sind und
speziell auf den Bereich der 3D Grafik abgestimmt sind. Deshalb folgt hier zunächst eine Einführung
in die Syntax einiger wichtiger Elemente der Shadersprache:
3.3.3.1. Datentypen und Funktionen
Einige Datentypen wurden unverändert von C übernommen. Dazu gehören unter anderem:
 bool
 int
 float
Da im Grafikbereich viel mit Vektoren gerechnet wird, gibt es entsprechende Konstrukte in GLSL
dafür. Zum einen den Datentyp
 vecx
wobei x die Werte 2, 3 und 4 annehmen kann. Damit wird ein 2 bis 4 dimensionaler Vektor definiert,
dessen Werte Fließkommazahlen (float) sind. Neben dem Vektor für Fließkommazahlen gibt es noch
zwei weitere Vektortypen, für Integer und boolsche Werte, die ebenfalls 2 bis 4 dimensional sein
können. Die Datentypen dafür sind:
 bvecx (bool)
 ivecx(int)
Neben Vektoren wird im Bereich der Computergrafik auch viel mit Matrizen gerechnet. In GLSL gibt
es dafür folgende Datentypen:
 mat2
 mat3
 mat4
Diese spezifizieren eine 2x2, 3x3 und 4x4 Matrix mit float Werten. Speziell für den Zugriff auf
Texturen gibt es in GLSL eine Reihe von so genannten Sampler Datentypen, die dann benötigt
S e i t e | XXVII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
werden, wenn man auf konkrete Werte einer Textur zugreifen möchte. Für 1 bis 3 dimensionale
Texturen kann der Datentyp
 samplerxD mit x = 1 ,2 oder 3
eingesetzt werden. Zudem gibt es noch:
 samplerCube
 sampler1DShadow
 sampler2DShadow
für den Zugriff auf eine Cubemap und eine 1D bzw. 2D Tiefentextur mit Vergleichsoperationen.
GLSL unterstützt arrays auf die gleiche Weise wie in C mit einem Unterschied: Eine Vorbelegung mit
Werten bei der Initialisierung eines Arrays ist nicht möglich. Structs funktionieren analog zu C. Auch
Schleifen werden unterstützt nämlich die for, while und do while Schleife.
Nachdem nun einige Datentypen vorgestellt wurden, wird als nächstes auf einige Besonderheiten bei
der Deklaration von Variablen eingegangen, da es hier einige Unterschiede zu C gibt. Generell setzt
GLSL sehr stark auf Konstruktoren (nicht zu verwechseln mit Konstruktoren von Klassen in Java/C+
+) für die Initialisierung von Variablen und Typumwandlungen (typecast). Das folgende Beispiel
veranschaulicht dies:
Fehler: Referenz nicht gefunden
3-24 Besonderheiten bei der Initialisierung von GLSL Variablen, Quelle (16)
Für die Deklaration der Vektor und Matrizentypen mit Wertevorgabe werden ebenfalls die
Konstruktoren verwendet. Der jeweilige Konstruktor besitzt dabei den selben Namen wie die
Typendeklaration. (zum Beispiel vec4(float,float,float,float)) als Konstruktor für einen 4
dimensionalen Vektor. Zur Vereinfachung müssen im obigen Beispiel nicht zwingend 4 float Werte
eingetragen werden. Man kann beispielsweise auch einen 3 dimensionalen Vektor vec3 zur Belegung
der x, y und z- Komponente verwenden und nur die w Komponente numerisch spezifizieren:
Fehler: Referenz nicht gefunden
3-25 Nutzung des vec Datentyps, Quelle (17)
Einer Variablen kann noch ein Qualifier vorangestellt werden. Bedingung dafür ist, dass die Variable
global definiert wird. Neben dem schon aus C bekannten const, welches eine nur lesbare Variable
definiert, deren Wert unveränderlich ist, gibt es in GLSL unter anderem folgende Qualifier:
 uniform
Uniform Variablen bieten dem Programmierer die Möglichkeit, Daten von der Anwendung an
einen Shader zu übergeben. Diese Variablen können sowohl im Vertex- als auch im
Fragmentshader definiert werden. Für die Shader sind es nur lesbare Variablen. Uniform Variablen
werden für Werte benutzt, die sich innerhalb eines Grafik Primitivs nicht verändern, etwa der
Skalierungsfaktor einer Animation.
 attribute
Genau wie die uniform Variablen, dienen attribute Variabeln dazu, Daten von der Anwendung an
den Shader zu übermitteln. Im Gegensatz zu den uniform Variablen können attribute Variablen nur
im Vertexshader verwendet werden. Zudem gelten Sie nicht nur für ein ganzes Primitiv. So kann
theoretisch jeder einzelne Vertexpunkt an eine attribute Variable übergeben werden.
S e i t e | XXVIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
 varying
Diese Variablen sind für die Kommunikation zwischen Vertex- und Fragmentshader nötig. Sie
müssen in beiden Shadern deklariert werden, sind jedoch nur im Vertexshader änderbar. Der Sinn
von varying Variablen besteht darin, Daten vom Vertex- an den Fragmentshader zu geben. Zu
beachten ist noch, dass nur bestimmte Datentypen den Qualifier varying verwenden können,
konkret sind dies: float, vec2, vec3, vec4, mat2, mat3 und mat 4
(10) (16) (17) (18)
3.3.3.2. Integration von Shadern in ein Programm
Nachdem nun einige grundlegende Dinge zur Syntax von GLSL erläutert wurden, beschreibt dieses
Kapitel die Schritte, die nötig sind, um einen Shader in eine Anwendung zu integrieren. Der erste
Schritt zur Erstellung eines Shaders in OpenGL ist der Aufruf folgender Funktion:
Fehler: Referenz nicht gefunden
3-26 Erzeugung eines Shader Containers, Quelle: (18)
Mit dieser Funktion wird ein Shader Container erzeugt und ein Handler für den Container als GLuint
Variabel zurückgeliefert. Mittels der handler Variablen kann man in seinem OpenGl Programm mit
den Shadern arbeiten. Es können beliebig viele Shader (sowohl Vertex als auch Fragment) zu einem
OpenGL Programm hinzugefügt werden. Hierbei muss jedoch folgendes beachtet werden: Es darf für
jeden Shader Type nur eine main Funktion existieren, ganz gleich, wie viele Shader einer Art man
verwenden will.
Nachdem nun das Grundgerüst für die Shaderverwendung existiert, wird im nächsten Schritt der
Quellcode der Shaderprogramme in die Anwendung integriert. Shaderprogramme werden meistens in
externe Dateien abgelegt. Folglich muss zunächst eine Funktion zum Auslesen der Quelltexte
implementiert werden. Anschließend wird mit der folgenden Anweisung der zu verwendende Shader
Code in OpenGL in den zuvor definierten Shader Container geladen:
Fehler: Referenz nicht gefunden
3-27 Laden des Shader Codes in den Shader Container , Quelle: (18)
Nun wird der Shader Code kompiliert:
Fehler: Referenz nicht gefunden
3-28 Kompilieren des Shader Codes, Quelle: (18)
Innerhalb der nächsten Schrite werden alle kompilierten Shader in ein Programm gelinkt. Dazu wird
analog zum Shader Container zunächst ein Programm Container Objekt benötigt. Auch hier wird
wieder ein handler zurückgegeben:
Fehler: Referenz nicht gefunden
3-29 Erzeugung eines Programm Containers, Quelle: (18)
Anschließend werden die oben erzeugten Shader dem Programmcontainer hinzugefügt. Geht man von
dem Fall aus, dass ein Vertex- und ein Fragmentshader vorliegen, so wird die folgende Funktion
zweimal aufgerufen:
Fehler: Referenz nicht gefunden
3-30 Shader-Code zum Shader-Programm hinzufuegen, Quelle: (18)
Nun folgt der oben erwähnte Linkprozess:
S e i t e | XXIX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Fehler: Referenz nicht gefunden
3-31 Shader Programm binden , Quelle: (18)
Jetzt sind alle erforderlichen Schritte getan, um das Shader Programm in der OpenGL Anwendung zu
nutzen. Dazu muss noch ein finaler Funktionsaufruf getätigt werden:
Fehler: Referenz nicht gefunden
3-32 Shader Code auf Grafikkarte installieren , Quelle: (18)
Diese Funktion muss nur einmal implementiert werden. Sollte ein Shader Programm, dass bereits
durch die obige Funktion verwendet wird, neu gelinkt werden, so wird die obige Funktion automatisch
neu aufgerufen. Das grundlegende Konzept zur Animation mit einem Shader entspricht dem für HLSL
(siehe Kapitel 3.2.4.3).
(10) (16) (17) (18)
3.4. Zusammenfassung (Gemeinsam)
Dieses Kapitel stellte zunächst die Bedeutung der Zeitmessung bei Animationen dar. Anschließend
wurden wichtige Voraussetzungen zur Implementierung der Programme erörtert. Sowohl für DirectX,
als auch für OpenGL wurden die nötigen Schritte zur Einrichtung der Arbeitsumgebung aufgezeigt. Im
DirectX Teil folgte nun die Erläuterung der beiden Animationstechniken Morphing/Tweening und
Skinning und wie diese in einer Anwendung umzusetzen sind. Anschließend wurde die Shadersprache
HLSL erläutert, sowie notwendige Voraussetzungen zur Integration von Shadern in eine DirectX
Anwendung besprochen. Am Ende wurden noch Effekt Dateien vorgestellt, die unter anderem dazu
dienen, Vertex und Pixel Shader in einer gemeinsamen Datei zu entwickeln.
Im OpenGL Teil wurde die Shadersprache GLSL vorgestellt und ebenfalls gezeigt, wie Shader in eine
OpenGL Anwendung integriert werden. Dabei wurden auch wichtige Unterschiede der Shader
Sprache im Vergleich zur herkömmlichen C Syntax erläutert. Da das grundlegende Konzept zur
Realisierung der Animation identisch zu DirectX ist, wurde darauf im zweiten Teil nicht noch einmal
eingegangen.
S e i t e | XXX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
4. Realisierung der Konzeption
Nachdem die Konzeption der Anwendungen abgeschlossen ist, steht der nächste Schritt an – die
Implementierung der Animationen. Dieses Kapitel zeigt daher Möglichkeiten auf, wie man solche
Charakter-Animationen praktisch umsetzt.
4.1. Modellierung und Animation des Charakters
Der erste praktische Schritt ist die Erstellung des zu animierenden 3D-Objektes. Hierfür wird unter
anderem ein Charakter erstellt und in die entsprechenden Dateiformate, die in diesem Projekt für
DirectX beziehungsweise OpenGL benötigt werden, exportiert. Diesen Charakter gilt es einerseits mit
Hilfe von Bones zu animieren und auf der anderen Seite in mehreren Key-Frames so zu verformen,
dass Tweening als zweite Animationstechnik anwendbar ist.
4.1.1. Modellierung
Das Ziel, welches bei der Modellierung des Charakters verfolgt wird, ist es, mit möglichst wenigen
Polygonen einen hohen Detailgrad zu erreichen. Aus diesem Grund besteht der Körper nur aus einer
sehr geringen Anzahl von Polygonen, die durch Funktionen wie Bevel und Extrude aus einem
Zylinder geformt werden. Auf diese Art und Weise lassen sich alle Körperteile bis auf den Kopf
realisieren. Im Anschluss daran wird auf den Körper eine Funktion angewendet, die die wenigen
existierenden Polygone aufteilt und abrundet, wodurch eine „organische“ Figur entsteht. In
LightWave3D nennt sich diese Funktion SubPatch.
Im zweiten Abschnitt der Modellierung wird der Kopf realisiert. Hierzu legt
man am besten zwei Bilder in den Hintergrund der Modellierungssoftware.
Eines in der Front-Perspektive und ein weiteres Bild, auf dem der Kopf von
der Seite zu sehen ist. An dieser Stelle ist es wichtig darauf zu achten, dass
beide Bilder den Kopf in der gleichen Größe darstellen. Des Weiteren ist ein
möglichst hoher Detailgrad beziehungsweise eine möglichst hohe
Auflösung der Bilder zu empfehlen. Nach diesem Schritt fängt man an und 4-33 Bild vom Auge
zeichnet ein Polygon. Das Auge bietet sich hierbei als Startpunkt an, wobei des Charakters.
man das erste Polygon immer wieder durch Extrude erweitert und im Anschluss daran so verschiebt,
dass ein Auge entsteht.
Wenn das Auge fertiggestellt ist, ist es empfehlenswert im
nächsten Schritt die Nase zu modellieren. Hierzu geht man wie
bereits beschrieben vor. Die Linien, die in den Abbildungen
eingezeichnet sind skizzieren die eigentlichen Polygone. Das
heißt, diese Polygone sind von Hand modelliert. Wohingegen
der weiche, abgerundete Eindruck durch die zu anfang
beschriebene Funktion (SubPatch) erreicht wird.
Nun, da sowohl die Augen als auch die Nase fertiggestellt sind,
folgt die Verknüpfung der beiden Objekte. Hierzu werden
passende Polygone durch Extrude von der Nase erweitert.
Durch die Funktion Weld lassen sich die Vertices der Nase und
der Augen an den sich überlagernden Stellen vereinen. Auf
4-34 Bild vom Gesicht des
diese Weise gelangt man letztendlich zu einem Objekt – das
Charakters.
keine unerwünschten Lücken besitzt.
S e i t e | XXXI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
In Folge dessen lassen sich schließlich die Wangen und der Rest des
Kopfes modellieren. Hierzu erweitert man wie bisher auch die
Polygone und achtet insbesondere darauf, dass das Ergebnis mit den
Ansichten, in denen die Bilder hinterlegt sind, übereinstimmen.
Außerdem empfiehlt es sich die perspektivische Ansicht als
Kontrolle zu verwenden.
Die oberhalb beschriebene Technik lässt sich für fast alle
Körperteile anwenden. So zum Beispiel auch um die Füße oder
Finger detaillierter zu modellieren. An dieser Stelle wird auf eine
Beschreibung allerdings verzichtet. Eine einfachere Variante für
Hände und Finger bilden jedoch Manipulationen der Polygone.
Beispielsweise wenn man im Subpatch-Modus arbeitet und die
Polygone durch Extrude und Bevel so erweitert, dass man zuerst das
Nagelbett mittels negativem Bevel erstellt und anschließend den
Fingernagel durch ein weiteres positives Bevel erzeugt.
Der letzte Schritt in der Modellierung betrifft die Haare. In einer 4-35 Bild vom Gesicht des
Animation, wie sie für animierte Kinofilme erstellt wird, lassen sich Charakters.
Haare mit einem extrem hohen Detailgrad und einer ausgiebigen physikalischen Berechnung
realisieren. In einer Animation, wie sie in einem Computerspiel vorkommt, ist dies aus zeittechnischen
Gründen nicht möglich. Eine solche Berechnung wäre schlicht zu aufwendig. Daher werden die Haare
normalerweise als simple Polygone modelliert. Um eine Bewegung der Haare innerhalb der
Animation zu realisieren ist beispielsweise ein Bone für einen Zopf denkbar. Hierdurch ist ein
angemessener Rechenaufwand in Verbindung mit einem gelungenen optischen Eindruck vereinbar.
Außerdem können weitere Details effektiver durch Texturen und im speziellen Bumpmaps
hinzugefügt werden.
4.1.2. Animation
Nach der Modellierung folgt die Animation. Hierbei ist zu beachten, dass der Charakter einmal für das
Tweening und ein weiteres mal für das Skinning animiert werden muss. An dieser Stelle sei jedoch
gesagt, dass es bei beiden Verfahren möglich ist mit Bones innerhalb der Modellierungssoftware zu
arbeiten. Beim Exportieren in das gewünschte Format muss man aber darauf achten, dass die Bones
und Animationsdaten mit erfasst werden oder eben nicht. Von daher ist die Auswertung der Datei und
die Verwendung der darin enthalten Informationen entsprechend der Animationstechnik anzupassen.
Außerdem sollte man gut überlegen bei welchen Animationen Skinning beziehungsweise Tweening
vorteilhafter ist.
Aus diesem Grund basiert die Animation auf Key-Frames in Verbindung mit Bones. Hierzu werden
die Bones so manipuliert, dass eine flüssige Animation entsteht. Auf eine Beschreibung dieser
Animierung des Charakters wird verzichtet, letztendlich müssen hierbei „nur“ die Bones entsprechend
rotiert und die Parameter der Bones angepasst werden, so dass jeweils nur die gewünschten Vertices
beeinflusst werden.
4.2. Implementierung der DirectX-Anwendungen
Dieser Teilbereich der Ausarbeitung beschränkt sich auf die Implementierung der Animationen mit
DirectX. Der erste Schritt ist dabei die Erstellung einer Windows-Anwendung und im zweiten Schritt
die Initialisierung von DirectX, beziehungsweise aller benötigten DirectX-Komponenten. Aus
Gründen der Komplexität und da es nicht weiter notwendig ist, beschränken sich die Anwendungen
dabei auf die Komponenten DirectX Graphics und DirectInput. Nachdem diese Schritte durchgeführt
wurden, folgt das Laden der 3D-Objekte und ihrer Animationsdaten. Der letzte – und bedeutendste –
S e i t e | XXXII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Teil dieses Kapitels liegt jedoch in der Implementierung der eigentlichen Animationen mit Hilfe von
DirectX Graphics und HLSL.
4.2.1. Aufbau einer Windows-Applikation
Dieses Kapitel beschreibt, wie man den Rahmen einer Windows-Anwendung erstellt und welche
Bedeutung die einzelnen Bestandteile haben. Dies ist von grundlegender Bedeutung, da jede
Anwendung, also auch ein Spiel auf Basis von DirectX, sich in Windows integrieren muss. Hierbei
stehen der Anwendung nicht alle System-Ressourcen zur alleinigen Verfügung, wie es bei MS DOS
war, daher wird erläutert, wie diese Integration funktioniert.
Eine wichtige Überlegung vorweg. Da sich die Anwendung das System mit vielen anderen
Anwendungen teilt und Windows dies managen muss, benötigt man hierfür einen speziellen
Mechanismus. Auch muss jede Anwendung Nachrichten von Windows empfangen und verarbeiten
können. Beispielsweise wenn der Benutzer auf den Schließen-Button drückt oder der Benutzer ein
Fenster verschiebt, dass zuvor einen Teil des Anwendungsfensters dieser Anwendung verdeckt hat.
Doch bevor es so weit ist, gilt es den Einstiegspunkt in die Anwendung zu implementieren.
int APIENTRY _tWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow
);
4-36 API der WinMain-Funktion.
Der Code-Ausschnitt 4.4 zeigt die Schnittstelle der WinMain-Methode. Diese Methode ist
vergleichbar mit einer main-Funktion in einer normalen C-Anwendung. Von Bedeutung sind dabei in
erster Linie die Parameter hInstance und nCmdShow. hInstance ist die aktuelle Instanz der
Anwendung und nCmdShow beschreibt den Darstellungsmodus. Im Darstellungsmodus wird zum
Beispiel unter minimiert und maximiert unterschieden. Durch den Zugriff auf den Parameter
hInstance lassen sich zum Beispiel auch neue Dialoge anzeigen.
Die WinMain-Methode hat die Aufgabe einen Call-Back-Handler einzurichten. Dieser ist dafür
verantwortlich, dass das System der Anwendung Nachrichten senden kann. Die Schnittstelle des CallBack-Handlers ist im Ausschnitt 4.5 aufgeführt.
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
);
4-37 API für einen Callback-Handler.
Der Name der Methode ist dabei frei wählbar. Der Parameter hWnd ist das Fenster, auf das sich die
Botschaft bezieht. Der zweite Parameter message definiert die Nachricht anhand einer ID und die
beiden letzten Parameter enthalten spezifische Informationen der Nachrichten.
Nach diesen Grundlagen folgt die Erstellung eines Fensters. Hierzu muss eine Datenstruktur vom Typ
WNDCLASSEX erstellt und mit Daten gefüllt werden. Daten bedeuten in diesem Fall Informationen
über den Titel, den Style, das Icon, den Cursor, das Menü und weitere Eigenschaften. Diese
Datenstruktur muss Registriert werden. Scheitert dieser Vorgang, so wird in der Regel die Anwendung
beendet. Des Weiteren sollten die Akzeleratoren geladen werden. Akzeleratoren sind definierte
Tastenkürzel, denen symbolische Konstanten zugewiesen sind. Im nächsten Schritt lässt sich das
Fenster der Anwendung erzeugen.
S e i t e | XXXIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x, int y,
int nWidth, int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
4-38 API der Funktion zum Erstellen eines Windows-Fensters.
Wobei die Parameter nach den vorangegangenen Erläuterungen selbsterklärend sind. Wenn der Aufruf
von CreateWindow keinen Fehler verursacht, so ist man fast am Ziel. Der letzte Schritt ist die
Implementierung der Hauptnachrichtenschleife.
while(msg.message != WM_QUIT){
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
RenderAnimation();
MoveAnimation();
}
4-39 Beispiel für eine Hauptprogrammschleife.
In dieser Schleife wird in jedem Durchlauf überprüft, ob Windows Nachrichten an die Anwendung
gesendet hat. Ist dies der Fall, so werden diese verarbeitet. Andernfalls, oder nach der Abarbeitung der
Nachrichten, werden weitere Methoden ausgeführt. In diesem Beispiel sind es zwei Methoden zum
Rendern und Bewegen der 3D-Animation.
Beendet wird diese Endlosschleife dann, wenn der Benutzer die Anwendung schließt. Daher muss
nach der Schleife gegebenenfalls Speicher wieder freigegeben werden oder andere Aufgaben
ausgeführt werden.
4.2.2. Initialisierung der benötigten DirectX-Komponenten
Nach der Einführung in die Windows-Programmierung steht als nächster Schritt die Initialisierung der
benötigten DirectX-Komponenten an. Hierzu wird aufgezeigt wie und an welcher Stelle dies
implementiert ist.
4.2.2.1. DirectX-Graphics
Bei der Implementierung einer DirectX-Anwendung ist die Initialisierung der benötigten
Komponenten der erste Schritt. Hierbei gilt es zu aller erst eine IDirect3D9-Schnittstelle zu
erstellen. Durch diese Schnittstelle lassen sich alle auf dem Computer zur Verfügung stehenden
Adapter auslesen. Das Makro D3D_SDK_VERSION ist in der Direct3D-Headerdatei deklariert und
stellt die auf dem Computer installierte Version dar.
LPDIRECT3D9 d3d = Direct3DCreate9(D3D_SDK_VERSION);
if(!d3d) return 0;
4-40 Quell-Code zum initialisieren einer IDirect3D9-Schnittstelle.
Sollte die Initialisierung fehlgeschlagen sein, kann dies durch eine Message Box angezeigt werden.
Der Benutzer müsste dann seine DirectX-Version erneuern. In diesem Fall wird dann jedoch nur 0
S e i t e | XXXIV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
zurückgegeben, wodurch die Anwendung beendet wird. Ist die Initialisierung erfolgreich verlaufen, so
erlangt man Zugriff auf die im System vorhanden Adapter. Ein Adapter ist in diesem Fall eine
Grafikkarte oder ein On-Board-Grafikchip. Benötigt man die Schnittstelle nicht mehr, so gibt man sie
durch die Methode Release frei. Diese Methode wurde von der Schnittstelle IUnknown geerbt, die
im zweiten Kapitel beschrieben ist.
Durch diese Schnittstelle lassen sich im nächsten Schritt alle auf dem System zur Verfügung
stehenden Adapter auflisten. Doch um einen detaillierteren Konfigurationsdialog zu erstellen müssen
auch die Caps (Capabilities – Fähigkeiten) der Grafikkarte ausgelesen werden. Auf eine Beschreibung
und Implementierung eines solchen Dialoges wird hier jedoch verzichtet. Stattdessen werden wenn
möglich die Standardeinstellungen beibehalten. Prinzipiell geht man aber wie folgt vor.
if( d3d->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps) <
0){
MessageBox( hWnd, "Kein HAL-Device verfügbar!", "Achtung!", MB_OK |
MB_ICONWARNING | MB_SETFOREGROUND);
devtype = D3DDEVTYPE_REF; // REF – Software Referenzimplementierung
}else
devtype = D3DDEVTYPE_HAL; // HAL – Hardware Abstraction Layer
4-41 Abfrage des HAL-Devices eines Systems.
Bei tiefergehendem Interesse an dieser Thematik ist die Quelle (6) sehr zu empfehlen. Wichtig ist es
aber in jedem Fall zu überprüfen, ob ein HAL-Device zur Verfügung steht. Das sollte heute zwar der
Fall sein, aber es empfiehlt sich immer Fähigkeiten zuerst zu überprüfen und Rückgabewerte zu
kontrollieren. Der Vorteil für den Programmierer von DirectX 10 liegt an dieser Stelle klar auf der
Hand. Während bei DirectX 9 viele Fähigkeiten optional sind, müssen DirectX 10 fähige Grafikkarten
eine große Menge an Funktionen zwingend Unterstützen.
if(d3d->CreateDevice(D3DADAPTER_DEFAULT, devtype, hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &device) < 0)
return 0;
4-42 Quell-Code zum Erstellen eines Direct3D9-Devices.
Angenommen es sind alle Einstellungen wie zum Beispiel Auflösung und Antialiasing gewählt, so
lässt sich im nächsten Schritt ein Device erstellen. Hierfür benötigt man eine IDirect3DDevice9Schnittstelle.
Durch diese Schnittstelle lassen sich bei erfolgreicher Initialisierung die Objekte laden und auf den
Bildschirm zeichnen. Zuvor beschreibt das nächste Kapitel jedoch die Initialisierung von DirectInput.
4.2.2.2. DirectInput
Wie bereits erwähnt ist DirectInput für alle Arten von Eingabegeräten zuständig. In diesem Fall reicht
es allerdings aus auf die Standard-Maus und Standard-Tastatur des Systems zuzugreifen. Wie bei
DirectX-Graphics auch, ist der erste Schritt die Initialisierung der benötigten Schnittstelle. In
diesem Fall vom Typ LPDIRECTINPUT8. Zur Initialisierung wird die folgende Methode
aufgerufen.
HRESULT DirectInput8Create(
HINSTANCE hinst,
DWORD dwVersion,
REFIID riidltf,
LPVOID *ppvOut,
LPUNKNOWN punkOuter
);
4-43 Quell-Code zum Erstellen einer DirectInput-Schnittstelle.
S e i t e | XXXV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Durch den Zugriff auf diese Schnittstelle lassen sich die Maus- und Tastatur-Devices erstellen und
initialisieren. Diese Geräte sind vom Typ LPDIRECTINPUTDEVICE8. An dieser Stelle sei
daraufhin gewiesen, dass die 8 am Ende der Bezeichnungen auch bei
DirectX 9.0c aktuell ist. Es wird also kein DirectX 8 verwendet.
HRESULT CreateDevice(
REFGUID rguid,
LPDIRECTINPUTDEVICE * lplpDirectInputDevice,
LPUNKNOWN pUnkOuter
);
4-44 Quell-Code zum Erstellen eines DirectInput-Devices.
Um die Verbindung zur Systemmaus herzustellen, muss der Parameter rguid auf die Konstante
GUID_SysMouse gesetzt werden. Bei der Systemtastatur ist es GUID_SysKeyboard. Ist hierbei
kein Fehler aufgetreten, so kann man im nächsten Schritt das Datenformat festlegen.
HRESULT SetDataFormat(
LPCDIDATAFORMAT lpdf
);
4-45 Quell-Code zum Setzen des Datenformats eines DirectInput-Devices.
Bei der Maus wird zwischen c_dfDIMouse und c_dfDIMouse2 unterschieden. Der
Unterschied liegt hierbei darin, dass c_dfDIMouse nur eine normale
Maus mit 4 Tasten abbildet und c_dfDIMouse2 bis zu 8 Tasten. Bei der
Tastatur wird als Parameter für das Datenformat c_dfDIKeyboard
verwendet. Es ist aber auch möglich, seine eigenen Datenformate zu
erstellen.
Der letzte Schritt liegt in der Festlegung des Kooperationslevels.
Hiermit gibt man an, ob die Anwendung die Maus oder Tastatur
exklusiv oder nicht exklusiv verwendet oder ob die Daten auch dann
an
das
Programm
gesendet
werden
sollen,
wenn
sich
das
Anwendungsfenster nicht im Vordergrund befindet. Hier wird jedoch
eine Kombination aus nicht exklusiv und Vordergrund verwendet, da
die Anwendung in einem Fenster und nicht im Vollbildmodus läuft.
HRESULT SetCooperativeLevel(
HWND hwnd,
DWORD dwLevel
);
keyboardDevice->SetCooperativeLevel(hWindow, DISCL_NONEXCLUSIVE |
DISCL_FOREGROUND);
4-46 Quell-Code zum Setzen des Kooperationslevels.
Durch den Aufruf der Methode Acquire des Devices lässt sich die Verbindung nun noch aktivieren.
Diese Methode muss auch immer dann aufgerufen werden, wenn der Zugriff darauf verloren gegangen
ist. Dies passiert beispielsweise dann, wenn der Benutzer die Windows-Taste drückt oder wenn Er mit
der Maus eine andere Anwendung aktiviert.
4.2.3. Laden von 3D-Objekten aus einer X-Datei
S e i t e | XXXVI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Nachdem alle benötigten Komponenten initialisiert wurden, folgt im das Laden der 3D-Objekte. In
diesem Fall beschränkt sich die Beschreibung auf einen normalen Mesh. Eventuell weitere Objekte in
der Szene werden nicht beschrieben. Bei Interesse sehen Sie sich bitte den passenden Quell-Code an.
LPD3DXMESH meshSource;
D3DMATERIAL9 *materialien;
DWORD anz_mat;
LPDIRECT3DTEXTURE9 *texturen;
LPD3DXBUFFER materialbuffer = 0;
4-47 Variablen zur verwaltung eines Meshs sowie dessen Materialien und Texturen.
Oberhalb sind eine Reihe Variablen aufgeführt, die zum Laden und anzeigen eines Meshs benötigt
werden. Wie bereits beschrieben ist in der Datenstruktur LPD3DXMESH ein Mesh enthalten. Des
Weiteren existieren Variablen in denen nachfolgend dessen Materialien und Texturen gespeichert
werden. Wie Sie im Ausschnitt 4.16 sehen, werden einige dieser Variablen übergeben, in denen nach
erfolgreicher Ausführung der Ladefunktion wichtige Informationen gespeichert sind.
HRESULT D3DXLoadMeshFromX(
LPCTSTR PFILENAME,
DWORD OPTIONS,
LPDIRECT3DDEVICE9 PD3DDEVICE,
LPD3DXBUFFER * PPADJACENCY,
LPD3DXBUFFER * PPMATERIALS,
LPD3DXBUFFER * PPEFFECTINSTANCES,
DWORD * PNUMMATERIALS,
LPD3DXMESH * PPMESH
);
4-48 Funktion zum Laden einer X-Datei.
Sollte das Laden fehlgeschlagen sein, so wird 0 zurückgegeben. Dadurch springt die Anwendung
zurück in die WINMAIN-Funktion. An dieser Stelle gilt es daraufhin die Anwendung zu beenden und
gegebenenfalls Aufräumarbeiten durchzuführen. Neben dieser Ladefunktion existieren auch erweiterte
Varianten, um etwa auf Animationen zuzugreifen. Wenn die X-Datei erfolgreich geladen wurde,
stehen das Verarbeiten der Materialien und das Laden der Texturen an. Die Texturen müssen dabei
separat geladen werden, da in den X-Dateien nur die entsprechenden Pfade gespeichert sind.
mat = (D3DXMATERIAL*)materialbuffer->GetBufferPointer();
materialien = new D3DMATERIAL9[anz_mat];
texturen = new LPDIRECT3DTEXTURE9[anz_mat];
for( i = 0; i < anz_mat; i++){
materialien[i] = mat[i].MatD3D;
materialien[i].Ambient = D3DXCOLOR( 1, 1, 1, 0);
if( D3DXCreateTextureFromFile( device,
mat[i].pTextureFilename,
&texturen[i]) < 0)
texturen[i] = NULL;
}
4-49 Quell-Code zum Auslesen der Materialien und Texturen eines Meshs.
Um sicher zu stellen, dass der Mesh Normalen-Vektoren enthält, wird dessen FVF angepasst. Dies
kann man beispielsweise dadurch erreichen, dass man den alten Mesh klont und dabei das gewünschte
FVF angibt. In dem temporären Mesh ist an dieser Stelle Speicherplatz für die Normalen der Vertices
enthalten. Um die Normalen zu berechnen muss aber die Funktion D3DXComputeNormals(…)
aufgerufen werden.
meshSource->CloneMeshFVF( meshSource->GetOptions(), meshSource->GetFVF()|
D3DFVF_NORMAL, device, &tmp_mesh );
meshSource->Release();
meshSource = tmp_mesh;
S e i t e | XXXVII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
D3DXComputeNormals( meshSource, 0);
4-50 Quell-Code zum Klonen eines Meshs um dessen FVF anzupassen.
Nachdem diese Schritte durchgeführt wurden, sind alle benötigten Daten vorhanden. Daher wird in
den nachfolgenden Kapiteln beschrieben, wie man diese Daten verarbeitet und in Abhängigkeit der
Zeit eine Animation berechnet.
4.2.4. Zeitmessung
Im Kapitel 3.1. wurde beschrieben warum es von Bedeutung ist die vergangene Zeit für die Animation
zu Grunde zu legen. Dieses Kapitel erläutert wie man praktisch in einer Anwendung diese
Berechnungen durchführen kann. In C gibt es die bekannte Funktion timeGetTime, um sich die
aktuelle Systemzeit zurückgeben zu lassen. Die hier verwendete Methode ist jedoch noch genauer.
__int64 cntsPerSec = 0;
QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec);
float secsPerCnt = 1.0f / (float)cntsPerSec;
4-51 Quell-Code zur Zeitmessung (1 von 3). Auf Basis von (19).
Um die in jedem Frame vergangene Zeit beziehungsweise die Zeit seit Anwendungsstart zu berechnen
wird ein Anfangswert benötigt. Des Weiteren beruht diese Zeitmessung auf dem PerformaceTimer,
wozu windows.h eingebunden werden muss. Die Berechnung bezieht sich dabei auf Einheiten
namens Counts. Die in diesem Kontext in Sekunden umgerechnet werden müssen.
__int64 prevTimeStamp = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&prevTimeStamp);
__int64 currTimeStamp = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&currTimeStamp);
float dt = (currTimeStamp - prevTimeStamp)*secsPerCnt;
4-52 Quell-Code zur Zeitmessung (2 von 3). Auf Basis von (19).
Der im Ausschnitt 4.20 dargestellt Code berechnet die vergangene Zeit zwischen zwei Messpunkten.
Diese Berechnung wird in jedem Frame einmal ausgeführt und die Zeit für einen Durchlauf in der
Variable dt gespeichert. Im nachfolgenden Ausschnitt 4.21 wird auf Basis einer Animation zwischen
zwei Meshs und einer Animationsdauer von 3 Sekunden die aktuelle Zeit der Animation berechnet. Da
die Animation, wenn sie am Ende angekommen ist wieder in ihre Ursprungsposition zurückkehren
soll, wird die Zeit erst erhöht, dann erniedrigt und so weiter.
if(animDirection == 0) curMorphTime += dt;
else curMorphTime -= dt;
if(curMorphTime >= 3.0f) animDirection = -1;
else if(curMorphTime <= 0) animDirection = 0;
4-53 Quell-Code zur Zeitmessung (3 von 3).
Es entsteht also ein Kreislauf zwischen zwei Meshs. Der letzte Ausschnitt in diesem Kapitel zeigt den
Aufruf der Methode zum Tweening zweiter Meshs mit der CPU basierten Methode. Dabei wird die
aktuelle Zeit der Animation durch 3 dividiert, was den Formeln im Kapitel 3.1 entspricht. Somit ist die
Berechnung der Zeit abgeschlossen und kann nachfolgend verwendet werden.
morph(curMorphTime / 3.0f, meshSource, meshTarget, meshToDraw);
morph(curMorphTime / 3.0f, meshWater, meshWaterTarget, meshWaterToDraw);
4-54 Quell-Code zum AUfruf der Morphing-Funktion in Abhängigkeit der Zeit.
4.2.5. CPU-basierte Charakter-Animation durch Tweening
S e i t e | XXXVIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Dieses Kapitel beschreibt, wie der Algorithmus zum Tweening mit der CPU aufgebaut ist und wie er
funktioniert. Im vorherigen Kapitel wird die Berechnung der Zeit und der Aufruf der
Tweening/Morphing Funktion beschrieben. Die in diesem Kapitel beschriebene Funktion ist genau
die, dessen Aufruf im Ausschnitt 4.22 dargestellt wird.
In dieser Funktion wird zwischen zwei Meshs linear interpoliert. Key-Frames sind hier noch nicht von
Bedeutung. Allerdings ändert die Verwendung von Key-Frames im Vergleich zu mehreren Objekten
nichts am Algorithmus. Da man in diesem Fall sowohl den Anfangszustand, den Endzustand und auch
den jeweils zu zeichnende Mesh benötigt, werden insgesamt drei Variablem vom Typ Mesh
verwendet. Zur Berechnung der aktuellen Position benötigt man den Zugriff auf die Vertices der
Meshs. Dazu wird die Funktion LockVertexBuffer aufgerufen. Im nächsten Schritt werden die
Größen der Vertexdaten berechnet. Die Funktion D3DXGetFVFVertexSize erwartet dazu ein FVF,
anhand der darin enthaltenen Daten wird dessen Größe berechnet und zurückgegeben.
char *pSourcePtr, *ptargetPtr, *pResultPtr;
meshSource->LockVertexBuffer(D3DLOCK_READONLY, (void **)&pSourcePtr);
meshTarget->LockVertexBuffer(D3DLOCK_READONLY, (void **)&ptargetPtr);
meshToDraw->LockVertexBuffer(0, (void **)&pResultPtr);
DWORD pSourceSize
DWORD pTargetSize
DWORD pResultSize
4-55 Quell-Code zum
= D3DXGetFVFVertexSize(meshSource->GetFVF());
= D3DXGetFVFVertexSize(meshTarget->GetFVF());
= D3DXGetFVFVertexSize(meshToDraw->GetFVF());
Tweening mit der CPU (1 von 3).
Danach werden in einer Schleife alle Vertices durchlaufen. Am Anfang wird ein Pointer für jeden
Vertex der drei Meshs erstellt und in eine Struktur gecastet, die die Vertexposition, dessen Normale
und dessen Farbe enthält. Nach diesem Schritt folgt die Interpolation, wobei aus Effizienzgründen die
Berechnung von 1-currentAnimationTime besser aus der Schleife heruasgezogen werden sollte. Am
Ende der Schleife wird der Pointer so verschoben, dass er auf das nächste Element zeigt.
DWORD numVertices = meshToDraw->GetNumVertices();
for(DWORD i=0; i<numVertices; i++){
meinvertex *pSourceVertex = (meinvertex*) pSourcePtr;
meinvertex *pTargetVertex = (meinvertex*) ptargetPtr;
meinvertex *pResultVertex = (meinvertex*) pResultPtr;
D3DXVECTOR3 vecSource = pSourceVertex->pos;
vecSource *= (1.0f - currentAnimationTime);
D3DXVECTOR3 vecTarget = pTargetVertex->pos;
vecTarget *= currentAnimationTime;
pResultVertex->pos = vecSource + vecTarget;
pSourcePtr += pSourceSize;
ptargetPtr += pTargetSize;
pResultPtr += pResultSize;
}
meshSource->UnlockVertexBuffer();
meshTarget->UnlockVertexBuffer();
meshToDraw->UnlockVertexBuffer();
4-56 Quell-Code zum Tweening mit der CPU (2 von 3).
Nachdem die Vertices interpoliert wurden, werden die VertexBuffer wieder freigegeben und der Mesh
kann gerendert werden. Aus diesem Grund ist in 4.25 der Ausschnitt der Renderfunktion dargestellt,
der die Materielien und Texturen des Meshs setzt und den Mesh zeichnet.
DWORD i;
for( i = 0; i < anz_mat; i++ ){
S e i t e | XXXIX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
device->SetMaterial( &materialien[i]);
device->SetTexture( 0, texturen[i]);
meshToDraw->DrawSubset( i);
}
4-57 Quell-Code zum Tweening mit der CPU (3 von 3).
Dieser Vorgang ist in einer Schleife implementiert, da alle Untermengen des Meshs so durchlaufen
werden, auch wenn in diesem Beispiel der Mesh nur aus einer einzigen Menge besteht. Eine andere
Variante der Interpolation liegt in der von DirectX bereitgestellten Funktion zum Verfahren LERP.
D3DXVECTOR3 * D3DXVec3Lerp(
D3DXVECTOR3 * pOut,
//
CONST D3DXVECTOR3 * pV1, //
CONST D3DXVECTOR3 * pV2, //
FLOAT s
);
4-58 Definition der Funktion D3DXVec3Lerp.
Diese Funktion erwartet als pOut einen Vektor, in dem das Ergebnis gespeichert wird. Dies entspricht
demnach dem Positionsvektor des Ergebnis-Meshs (pResultVertex->pos). Die Parameter pV1
und pV2 stellen die beiden Eingangsvektoren des Source- und Target-Meshs dar und der Parameter s
die aktuelle Zeit der Animation.
4.2.6. GPU-basierte Charakter-Animation durch Tweening mit einem HLSL-Vertexshader
(Effekt-Datei)
Dieses Kapitel beschreibt die Animation mit einem Vertexshader basierend auf dem Verfahren
Tweening. Im vorherigen Kapitel wurde die CPU basierte Methode vorgestellt, daher gilt es nun die
Unterschiede herauszuarbeiten. Ein grundlegender Unterschied liegt darin, dass kein separater Mesh
benötigt wird, der den aktuell zu zeichnenden Zustand repräsentiert. Es werden im Gegensatz dazu
beide Meshs, eher gesagt dessen Vertexdaten, dem Shader übergeben. Der Vertexshader wird
dementsprechend einmal je Vertex aufgerufen und berechnet dessen aktuelle Position. Durch dieses
Verfahren wird sowohl Zeit als auch Arbeitsspeicher eingespart.
Da der Vertexshader in diesem Fall sowohl die Vertexdaten des Anfangsmeshs als auch die des
Zielmeshs benötigt, müssen ihm zwei Streams zugeordnet werden. Bevor es aber soweit ist, müssen
die Vertexdaten entsprechend ausgelesen und in das benötigte Format gewandelt werden. Die Struktur
meinvertex repräsentiert dabei einen Vertex und die aufgelisteten Variablen enthalten die Meshs, die
Effektdatei, die Vertexbuffer und weitere Elemente.
struct meinvertex{
D3DXVECTOR3 Position;
D3DXVECTOR3 Normal;
D3DXVECTOR2 Texture;
};
// Position des Vertex
// Normalenvektor des Vertex
// Texturkoordinate des Vertex
LPD3DXMESH meshSource;
// Anfangs-Mesh
LPD3DXMESH meshTarget;
// Ziel-Mesh
LPD3DXEFFECT effect;
// Effekt (fx-Datei)
IDirect3DVertexBuffer9* vertexBuffer[2]; // Vertexbuffer
IDirect3DIndexBuffer9* indexBuffer;
// Indexbuffer
IDirect3DVertexDeclaration9* declaration; // Vertex-Deklaration
DWORD numIndices;
// Anzahl Indizes
DWORD numVertices;
// Anzahl Vertices
4-59 Quell-Code zur Definition einer Vertexstruktur und Variablen.
Im nachfolgenden Ausschnitt ist die Vertexdeklaration aufgeführt, die zur Spezifikation der Streams
dient und die der Vertexshader als Input erwatet. Dabei ist zu erkennen, dass jeweils zwei Vertices
S e i t e | XL
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
übergeben werden. Dabei ist dessen Position, Normale und Texturkoordinate der ersten Texturebene
enthalten. Es könnten an dieser Stelle auch mehrere Ebenen vorhanden sein, wenn man Multitexturing
verwendet.
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_POSITION, 0 }, //
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_NORMAL,
0 }, //
{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_TEXCOORD, 0 }, //
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_POSITION, 1 }, //
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_NORMAL,
1 }, //
{ 1, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_TEXCOORD, 1 },
D3DDECL_END()
// Definiert das Ende der Deklaration.
};
if(device->CreateVertexDeclaration(decl, &declaration) != D3D_OK) return 0;
4-60 Quell-Code zur Definition einer Vertexdeklaration.
Nach dieser Definition wird der Mesh geladen, wobei dies grundlegend dem entspricht, was im
Kapitel 4.2.3 ausgeführt wird. Um sicher zu gehen, dass das FVF des Meshs dem benötigten Format
entspricht, wird auch hier ein temporärer Mesh erstellt, der exakt diesem Format entspricht und dabei
die Daten des originalen Meshs enthält. Nach diesem Schritt wird der originale Mesh verworfen und
der Zeiger umgebogen, wodurch er das originale Objekt enthält und dem gewünschten Format
entspricht. Da im Rest der Anwendung der Mesh an sich nicht benötigt wird, sondern nur dessen
Vertex und Indexbuffer, werden diese ausgelsen und in entsprachende Variablen kopiert.
meshTarget->GetIndexBuffer(&indexBuffer);
device->CreateVertexBuffer(ddsdDescSrcVB.Size, D3DUSAGE_WRITEONLY, 0,
D3DPOOL_MANAGED, &vertexBuffer[1], 0);
vertexBuffer[1]->Lock(0, ddsdDescDolphinVB.Size, (void **)&pDest,
D3DLOCK_NOSYSLOCK);
pSrcVB->Lock(0, ddsdDescSrcVB.Size, (void **)&pSrc, D3DLOCK_NOSYSLOCK);
memcpy(pDest, pSrc, ddsdDescSrcVB.Size);
pSrcVB->Unlock();
vertexBuffer[1]->Unlock();
4-61 Quell-Code zum Extrahieren der Vertexdaten der Meshs.
Nach diesen Schritten ist alles zum Rendern vorbereitet. In der Renderfunktion muss an der
entsprechenden Stelle der Effekt noch aktiviert und zuvor dessen Konstanten gesetzt sowie die
Streams zugewiesen werden. Die Funktionen hierzu dürften an dieser Stelle nach der Einführung in
HLSL selbsterklärend sein.
device->SetStreamSource(0, vertexBuffer[0], 0, sizeof(meinvertex));
device->SetStreamSource(1, vertexBuffer[1], 0, sizeof(meinvertex));
device->SetVertexDeclaration(declaration);
device->SetIndices(indexBuffer);
effect->SetTechnique("morph");
effect->SetTexture("ps_textur", texturen[0]);
effect->SetFloat("vs_weightTarget", curMorphTime/3.0f);
effect->SetFloat("vs_weightSource", (1.0f - curMorphTime/3.0f));
effect->SetMatrix("vs_worldtrans", &world);
effect->SetVector("vs_LightDir", &lightEye2);
effect->SetVector("vs_LightAmbient", &ambient);
4-62 Quell-Code zum Setzen der Vertexdaten-Streams und Indices.
S e i t e | XLI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Zum Rendern des Objekts müssen anschließend die Passes des gewählten Effekts durchlaufen werden.
Dieses vorgehen im Ausschnitt dargestellt. Dabei wurden zuvor die Vertex- und Indexdaten des
Objekts dem Device zugewiesen. Anhand dieser Daten lässt sich durch die Methode
DrawIndexedPrimitive das Objekt als Dreiecksliste zeichnen.
effect->SetTechnique("morph");
effect->SetTexture("ps_textur", texturen[0]);
effect->SetFloat("vs_weightTarget", curMorphTime/3.0f);
effect->SetFloat("vs_weightSource", (1.0f - curMorphTime/3.0f));
effect->SetMatrix("vs_worldtrans", &world);
effect->SetVector("vs_LightDir", &lightEye2);
effect->SetVector("vs_LightAmbient", &ambient);
4-63 Quell-Code zum Setzen der Eigenschaften eines Effekts.
UINT uPasses;
if (D3D_OK == effect->Begin(&uPasses, 0)) {
for (UINT uPass = 0; uPass < uPasses; uPass++) {
effect->BeginPass(uPass);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, numVertices, 0,
numIndices/3);
effect->EndPass();
}
effect->End();
}
4-64 Quell-Code zum Rendern mit einer Effekt-Datei.
Nachdem der Teil der Anwendung nun ausführlich erläutert wurde, folgt die Erklärung des Shaders an
sich. Die Input-Struktur entspricht dabei der Vertexdeclaration aus 4.26. Den Shader-Code sollten Sie
nach der Einführung ohne Erläuterung verstehen, die Berechnungen werden jedoch erläutert. In der
Main-Methode des Vertex-Shaders wird zuerst eine Instanz der Output-Datenstruktur erstellt, welche
als Input-Datenstruktur für den anschließenden Pixelshader dient, der hier nicht aufgeführt ist. Der
erste Schritt der Berechnungen ist die lineare Interpolation zwischen der Vertexposition des ersten und
zweiten Meshs. Im zweiten Schritt wird die selbe Berechnung auf die Normalen angewendet, dies ist
notwenig, da die anschließende Licht und Schattenberechnung darauf basiert. Bei Interesse an Licht
und Schattenberechnungen ist (20) zu empfehlen. Der letzte Schritt, der hier dargestellt wird, ist die
Transformation der Vertexposition. Der Vertex wird demnach an die korrekte Position im
Koordinatensystem verschoben. Wie Sie sehen, ist es die selbe Berechnung, die sonst auf CPU-Basis
durchgeführt wurde. Hier hat man jedoch den Vorteil, dass man kein separates Objekt benötigt, in dem
die jeweils aktuelle Position gespeichert werden muss. Das Verfahren funktioniert demnach „on the
fly“.
struct VS_INPUT{
float4 pos
: POSITION;
float3 norm
: NORMAL;
float2 tex
: TEXCOORD0;
float4 pos2
: POSITION1;
float3 norm2
: NORMAL1;
float2 tex2
: TEXCOORD1;
};
struct VS_OUTPUT{
float4 transpos : POSITION;
float2 tex
: TEXCOORD0;
float2 tex1
: TEXCOORD1;
float4 color
: COLOR0;
}
4-65 Vertexshader Datenstrukturen
//
//
//
//
//
//
Position des ersten Meshs
Normalenvektor des ersten Meshs
Texturkoordinate des ersten Meshs
Position des zweiten Meshs
Normalenvektor des zweiten Meshs
Texturkoordinate des zweiten Meshs
//
//
//
//
Endposition des Vertex
Texturkoordinate Ebene 0
Texturkoordinate Ebene 1
Farbwert des Vertex
für Tweening.
VS_OUTPUT vs_main( VS_INPUT inp ){
VS_OUTPUT outp;
S e i t e | XLII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
// Interpolation
float4 interpolatedPos = inp.pos * vs_weightSource + inp.pos2 *
vs_weightTarget;
inp.norm = inp.norm * vs_weightSource + inp.norm2 * vs_weightTarget;
// Transformation
outp.transpos = mul(interpolatedPos, vs_worldtrans);
// Licht und Schattenberechnungen…
return outp;
}
technique morph{
pass P0{
VertexShader = compile vs_2_0 vs_main(); //
PixelShader = compile ps_2_0 ps_main(); //
}
}
4-66 Ausschnitt des Quell-Codes der Effektdatei zum Tweening.
Nach diesen Vorüberlegungen und Implementierungen sind Sie in der Lage eine Animation durch
Tweening zwischen zwei Meshs auf Basis der CPU und Basis eines HLSL Shaders durchzuführen. Da
eine Animation aber selten zwischen nur zwei Formen ausgeführt werden soll, gilt es im nächsten
Schritt die Key-Frames der Animationen einer X-Datei auszulesen.
4.2.7. Repräsentation der Animationen in einer X-Datei
In diesem Kapitel wird beschrieben, wie und warum Key-Frames verwendet werden, worin der Vorteil
im Vergleich zur Verwendung separater Meshs liegt und wie diese in einer X-Datei repräsentiert
werden. Die nachfolgende Abbildung zeigt ein Objekt, dass im Laufe der Zeit verformt wird. Dabei
entspricht jede Form einem Key-Frame. Bisher wurden immer zwei Meshs in die Anwendung
geladen. Einer wurde Source und der andere Target genannt. Es war also ohne weiteres nicht möglich
zwischen mehr als zwei Formen zu interpolieren.
Aus diesem Grund wird in diesem Kapitel sowohl die interne Darstellung als auch der Zugriff auf
Animationen und Key-Frames beschrieben. Durch den Zugriff auf diese Informationen lässt sich
jeweils zwischen zwei Key-Frames interpolieren und somit eine beliebig komplexe Animation
rendern. Um den Hintergrund aber verstehen zu können, wird an dieser Stelle noch etwas tiefgehender
auf das X-Format eingegangen. Von besonderer Bedeutung sind hierbei die Templates Animation,
AnimationSet und AnimationKey.
template Animation {
4-67 Skizze zur Veranschaulichung von Key-Frames.
<3d82ab4f-62da-11cf-ab39-0020af71e433>
[...]
}
4-68 Definition des X-File Templates Animation. Quelle: (21)
Target
Source
Target
Source
S e i t e | XLIII
T
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Das Animation-Template enthält Animationen in Bezug auf einen vorherigen Frame. Dabei kann es
neben der Referenz zum vorherigen Frame auch ein Set von AnimationKey-Objekten oder auch
AnimationOptions-Objekte enthalten.
template AnimationKey {
<10dd46a8-775b-11cf-8f52-0040333594a3>
DWORD keyType;
DWORD nKeys;
array TimedFloatKeys keys[nKeys];
}
template TimedFloatKeys
{
< F406B180-7B3B-11cf-8F52-0040333594A3 >
DWORD time;
FloatKeys tfkeys;
}
4-69 Definition der X-File Templates AnimationKey und TimedFloatKeys. Quelle: (21)
Das AnimationKey-Template definiert ein Set von TimedFloatKeys, wobei dieses Template ein
Set von float-Variablen definiert, die die Transformation beschreiben und einen Zeitstempel, um die
Animation zu steuern. Des Weiteren definiert der keyType des Templates AnimationKey ob es sich
bei den Transformationsdaten um Matrizen oder um separate Werte für Rotation, Skalierung und
Position handelt. An dieser Stelle sei auf frühere Betrachtungen über Quaternionen und Interpolation
verwiesen.
template AnimationSet
{
< 3D82AB50-62DA-11cf-AB39-0020AF71E433 >
[ Animation < 3D82AB4F-62DA-11cf-AB39-0020AF71E433 > ]
}
4-70 Definition der X-File Templates AnimationSet. Quelle: (21)
Das AnimationSet-Template fügt letzen Endes eine oder mehr Animation-Objekte zusammen,
wobei alle Animationen die gleiche Zeitdauer besitzen. Im Anschluss an diese Betrachtung der
Repräsentation der Animationen innerhalb der X-Datei folgt eine Beschreibung der Repräsentation
innerhalb einer DirectX-Anwendung. Dabei sind zwei Datenstrukturen von großer Bedeutung und
zwar die Strukturen D3DXFRAME und D3DXMESHCONTAINER.
typedef struct D3DXFRAME {
LPSTR Name;
D3DXMATRIX TransformationMatrix;
LPD3DXMESHCONTAINER pMeshContainer;
D3DXFRAME * pFrameSibling;
D3DXFRAME * pFrameFirstChild;
} D3DXFRAME, *LPD3DXFRAME;
typedef struct D3DXMESHCONTAINER {
LPSTR Name;
D3DXMESHDATA MeshData;
LPD3DXMATERIAL pMaterials;
LPD3DXEFFECTINSTANCE pEffects;
DWORD NumMaterials;
DWORD * pAdjacency;
LPD3DXSKININFO pSkinInfo;
D3DXMESHCONTAINER * pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINE;
4-71 Definition der Structs D3DXFRAME und D3DXMESHCONTAINER.
S e i t e | XLIV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Des Weiteren gibt es in DirectX Graphics den AnimationController, mit dem man beispielsweise eine
einfache Animation ausführen oder auch Überblendungen zwischen zwei Animationen auf einfache
Weise realisieren kann. Dadurch erleichtert sich die Berechnung ungemein. Außerdem kann man
durch eine Instanz des Animation Controllers, die beim Laden eines Meshs mit der Funktion
D3DXLoadMeshHierarchyFromX(…) erstellt wird, auf die in der X-Datei enthaltenen Animationen
zugreifen.
HRESULT D3DXLoadMeshHierarchyFromX(
LPCSTR Filename,
DWORD MeshOptions,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXALLOCATEHIERARCHY pAlloc,
LPD3DXLOADUSERDATA pUserDataLoader,
LPD3DXFRAME* ppFrameHierarchy,
LPD3DXANIMATIONCONTROLLER* ppAnimController
);
4-72 Definition der Funktion D3DXLoadMeshHierarchyFromX(…). Quelle: (21)
Durch die oberhalb beschriebene Technik lässt sich jedoch nur Skinning realisieren. Das X-Format
unterstützt standardmäßig kein Tweening. Allerdings wurde im Kapitel 2.3.1. - Das X-Format beschrieben, dass es erweiterbar ist. Dazu werden zwei neue Templates erstellt.
template MorphAnimationKey
{
<4E50C940-3978-4428-A19E-7FE8CDAA451A>
DWORD Time;
// Zeit des Key-Frames.
CSTRING MeshName;
// Name des dazugehörigen Meshs.
}
template MorphAnimationSet
{
<8E3C96D4-91DD-4d64-AEBC-9E83950CDC67>
DWORD NumKeys;
// Anzahl der Key-Frames.
array MorphAnimationKey Keys[NumKeys]; // Array der einzelnen Key-Frames.
}
4-73 Erweiterung des X-Dateiformats durch Morphing. Auf Basis von Quelle (3).
Wobei das Template MorphAnimationKey eine Art Key-Frame darstellt und somit eine Variable für
die Zeit und eine Variable für den zugehörigen Mesh enthält. Die X-Datei enthält demnach mehrere
Meshs, die durch Klonen entstanden sein können. Das Template MorphAnimationSet fasst die Keys
zusammen, wodurch eine Liste entsteht, in der die aufeinanderfolgenden Tweening-Schritte (Zeit und
Mesh) repräsentiert werden. Man kann natürlich auf solch eine Erweiterung verzichten. Möchte man
aber ohne solch eine Erweiterung arbeiten, so wird man die einzelnen Meshs in der Anwendung
auslesen müssen und kann diese Informationen nicht vernünftig kapseln.
Soviel zur benötigten Theorie und dem praktischen Hintergrund, der für die weiteren Schritte benötigt
wird. Im nächsten Kapitel wird die angesprochene Erweiterung implementiert, wobei die hier
beschriebenen Grundlagen verwendet werden.
4.2.8. Erweiterung des X-Dateiformats für Morphing und Key-Frames
Im vorherigen Kapitel wurden zwei neue Templates definiert. Um die dazugehörigen GUIDs zu
erstellen, verwendet man das Tool GUID-Generator. Der Algorithmus, der an dieser Stelle für das
Morphing verwendet wird, basiert auf der CPU-basierten Methode. Es wurden auf Grund teilweiser
unterschiedlicher Datenstrukturen jedoch kleine Änderungen vorgenommen. Des Weiteren ist an
dieser Stelle eine Berechnung notwendig, um die aktuellen Key-Frames und die dazugehörigen Meshs
zu ermitteln. Wobei auch hier die Animation in einer Endlosschleife abläuft.
S e i t e | XLV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Wie schon angemerkt, wird zum Laden einer X-Datei mit benutzerdefinierten Templates die Funktion
D3DXLoadMeshHierarchyFromX verwendet. Hierbei gilt es ähnlich wie bei der Klasse
AllocateHierarchy eine Klasse zu implementieren, die die vorhandenen Methoden überschreibt.
In diesem Fall handelt es sich um die Klasse LoadUserData. Wobei nur die Methode
LoadTopLevelData von Bedeutung ist. Bevor man allerdings auf die
gewünschten
Daten
zugreifen
kann,
sind
ein
paar
Schritte
abzuarbeiten. Man muss mit dem Makro DEFINE_GUID die GUIDs in der
Anwendung registrieren, womit man ihnen auch einen eindeutigen Namen
zuteilt.
// {4E50C940-3978-4428-A19E-7FE8CDAA451A}
DEFINE_GUID(GUID_MORPHANIMATIONKEY, 0x4e50c940, 0x3978, 0x4428, 0xa1, 0x9e,
0x7f, 0xe8, 0xcd, 0xaa, 0x45, 0x1a);
// {8E3C96D4-91DD-4d64-AEBC-9E83950CDC67}
DEFINE_GUID(GUID_MORPHANIMATIONSET, 0x8e3c96d4, 0x91dd, 0x4d64, 0xae, 0xbc,
0x9e, 0x83, 0x95, 0xc, 0xdc, 0x67);
4-74 Definition der GUIDs mit dem DEFINE_GUID Makro.
Außerdem müssen passende structs zu den Templatedefinitionen
erstellt werden. Wobei gleichermaßen auf die Datentypen und auf die
korrekte Reihenfolge der Variablen zu achten ist.
struct MorphAnimationKey{
DWORD Time;
// Zeitpunkt, ab dem dieser Key-Frame aktuell ist.
char* MeshName;
// Name des Meshs, der zu diesem Key-Frame gehört.
};
struct MorphAnimationSet{
DWORD NumKeys;
// Anzahl der Key-Frames.
MorphAnimationKey Keys[255]; // Array der Key-Frames (Maximal 255).
char* name;
// Name der Animation.
};
4-75 Definition der structs zu dem Templates der X-Datei.
Übergibt man der Funktion D3DXLoadMeshHierarchyFromX nun zusätzlich zu
einem Objekt der Klasse AllocateHierarchy ein Objekt der Klasse
LoadUserData, so wird die Funktion LoadTopLevelData aufgerufen, so bald
der Parser auf das Data-Object des Templates MorphedAnimationSet
trifft.
HRESULT LoadUserData::LoadTopLevelData(LPD3DXFILEDATA pXofChildData){}
4-76 Funktionsdefinition LoadTopLevelData der Klasse LoadUserData.
Um die structs mit den Daten der X-Datei zu füllen, muss
sicherheitshalber überprüft werden um welches Template (GUID) es
sich handlet. Dazu bietet das Interface ID3DXFILEDATA die Funktion
GetType an. Vergleicht man die dadurch erhaltene GUID mit der zuvor
definierten GUID MORPHANIMATIONSET, so lässt sich feststellen ob es
sich an dieser Stelle um das gesuchte Data-Object handelt. Im
nächsten Schritt werden die Daten der X-Datei geparst und in die
zuvor definierten structs gespeichert. Um dies zu realisieren hat
das Interface ID3DXFILEDATA die Funktion Lock. Übergibt man dieser als
Parameter das zuvor erstellte struct, so werden bei erfolgreicher
Ausführung die Daten darin gespeichert. Danach gilt es die structs
auszulesen
und
neue
Datenstrukturen
zu
erzeugen
(TweeningAnimationSet, TweeningAnimatioonKey), die in der Anwendung
dauerhaft gespeichert und zur Animation verwendet werden. Nach dem
S e i t e | XLVI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
die Daten an die entsprechenden Positionen kopiert wurden, gilt es
durch die Funktion Unlock das Interface wieder freizugeben.
Nachdem in der Klasse LoadUserData die benutzerdefinierten Templates
geladen und in der Klasse AllocataHierarchy alle Meshs ausgelesen
wurden,
gilt
es
diese
Daten
zusammenzuführen.
Die
Klasse
TweeningAnimator ist dabei an das in der D3DX Bibliothek integrierte
Interface ID3DXAnimationController angenährt – befindet sich jedoch
noch in einem frühstadium und bietet daher nur grundlegende Methoden
zum hinzufügen einer TweeningAnimationSets sowie den Zugriff auf eine
public Variable mit den Meshs sowie eine Methode zum Tweening mit
der CPU an.
4.2.9. Charakter-Animation durch Skinning
In diesem Kapitel wird sowohl beschrieben wie man eine X-Datei inklusiver ihrer Hierarchien, Bones
und Animationen lädt als auch der Algorithmus zum Bewegen und Rendern des Charakters. Dabei
wird, um den Umfang dieser Arbeit nicht zu sehr zu sprengen, weniger Code dargestellt. Bitte sehen
Sie sich den passenden Code der Funktionen in der C++ Datei an.
4.2.9.1. Laden des Meshs und Aufbau der benötigten Datenstrukturen
Im vorherigen Kapitel wurde die Funktion D3DXLoadMeshHierarchyFromX in der Abbildung 4.36
aufgeführt. Diese unterscheidet sich von der zuvor verwendeten Funktion zum Laden einfacher 3DObjekte und nimmt unter anderem Parameter für einen AnimationController und ein Zeiger
auf ein Objekt der Klasse AllocateHierarchy an. Bei diesem Parameter
handelt es sich eigentlich um eine Instanz einer eigenen Klasse, die
von AllocateHierarchy abgeleitet ist. Diese Klasse, mit dem Namen
CAllocateHierarchy, überschreibt vier Funktionen. Die zum Laden und
Zerstören der Objekte dienen, wobei sie automatisch von DirectX
aufgerufen werden. Ein weiterer Parameter ist ppFrameHierarchy. Das
ist ein Zeiger auf das Root-Element der Mesh-Hierarchie, wodurch die
Möglichkeit besteht die gesamte Baumstruktur zu traversieren.
Das Ziel ist dabei Datenstrukturen aufzubauen, die ein Navigieren
zwischen den Hierarchien erlauben, was durch die schon beschriebenen
Strukturen D3DXFRAME und D3DXMESHCONTAINER gegeben ist. Auf der
anderen Seite werden aus performancegründen diese Strukturen
erweitert und Arrays erstellt, wodurch ein schneller zeigerbasierter
Zugriff
auf
einzelne
Elemente
wie
beispielsweise
die
Transformationsmatrix eines Bones ermöglicht wird. Dieses Vorgehen
wird in den restlichen Abschnitten dieses Kapitels näher erklärt.
Die Methode CreateFrame der Struktur CAllocateHierarchy wird während des Parsens von
DirectX aufgerufen und dient dazu die Member-Variablen des Frames (D3DXFRAME_DERIVED) zu
initialisieren. Im Anschluss daran wird die Methode CreateMeshContainer (Abbildung 4.39)
aufgerufen. Wie anhand der Parameter zu sehen ist, sind hier alle Informationen über den Mesh
enthalten. Es gilt demnach an dieser Stelle sowohl den normalen Mesh inklusive dessen Vertexdaten
auszulesen als auch die Texturen und Skinning-Informationen.
HRESULT CreateMeshContainer(
LPCSTR Name,
CONST D3DXMESHDATA * pMeshData,
CONST D3DXMATERIAL * pMaterials,
CONST D3DXEFFECTINSTANCE * pEffectInstances,
DWORD NumMaterials,
S e i t e | XLVII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
CONST DWORD * pAdjacency,
LPD3DXSKININFO pSkinInfo,
LPD3DXMESHCONTAINER * ppNewMeshContainer
);
4-77 Beschreibung der Funktion CreateMeshContainer des Interfaces
ID3DXAllocateHierarchy Quelle: (21)
Wie auch zuvor, wird hier zuerst das FVF überprüft und wenn nötig angepasst. Beispielsweise indem
die Normalen berechnet und hinzugefügt werden. Im Anschluss daran werden die Variablen der
Struktur D3DXFRAME_DERIVED initialisiert und mit passenden Werten belegt. Beispielsweise werden
an dieser Stelle die Materialien ausgelesen, geladen und in der Struktur gespeichert. Wenn der
Parameter pSkinInfo nicht Null ist, dann sind Skinning-Informationen enthalten, welche es
auszuwerten gilt. Dazu werden die Anzahl der Bones ermittelt und in einer Schleife die OffsetTransformationsmatrizen der Struktur D3DXFRAME_DERIVED berechnet. Im Anschluss daran wird die
Funktion GenerateSkinnedMesh aufgerufen, die im nächsten Abschnitt beschrieben wird.
Diese Funktion dient hauptsächlich dazu, den Mesh beziehungsweise dessen FVF entsprechend
anzupassen. Die Funktion ConvertToIndexedBlendedMesh vom Interface ID3DXSkinInfo
nimmt einen Mesh und weitere Daten, die in der Struktur D3DXFRAME_DERIVED gespeichert sind
entgegen und gibt einen Mesh zurück, der blend weights, Indizes und eine Bone KombinationsTabelle enthält. Diese Tabelle beschreibt welche Bones welche Subsets des Meshs beschreiben. Da
der Vertexshader aber eine Vertex-Deklaration benötigt und kein FVF, muss diese anschließend
erstellt werden. Dabei ist darauf zu achten, dass sie der Input-Datenstruktur des Vertexshaders
entspricht. Im letzten Schritt dieser Funktion werden die Bone-Matrizen noch in einer globalen
Variable gespeichert, die später dem Shader übergeben wird.
4.2.9.2. Animation des Charakters
Zur Animation wird der AnimationController verwendet, der beim erfolgreichen Laden des
Meshs erstellt wird (siehe 4.37). Da in diesem Projekt der Charakter nur eine Animation besitzt, kann
auf eine detailliertere Beschreibung verzichtet werden. Zur Überblendung mehrerer Animation sehen
Sie bitte in der Dokumentation (21) nach. Wie auch zuvor gilt es in dieser Animation die Zeit seit
Anwendungsstart zu messen. Übergibt man diesen Wert der Funktion AdvancedTime der
AnimationController-Instanz, so berechnet diese die aktuelle Animation. Gibt man dabei keinen
Callback-Handler an, so wird die Animation in einer Endlosschleife wiederholt.
Im nächsten Schritt müssen die kombinierten Transformationsmatrizen neu berechnet werden, die wie
im vorherigen Kapitel beschrieben aus Performancegründen angelegt werden. Da sowohl ein Zeiger
auf das Root-Element vorhanden ist, als auch der AnimationController den aktuellen Stand berechnet
hat, muss die Baumstruktur durchlaufen werden. Dabei wird in der rekursiven Funktion
UpdateFrameMatrices die Struktur zuerst in der Tiefe durchlaufen und die jeweilige kombinierte
Transformationsmatrix entsprechend den Formeln aus dem Kapitel 3.2.2.1. berechnet.
4.2.9.3. Rendern des Charakters mit einem HLSL Vertexshader
Beim Rendern wird an dieser Stelle gleich die Shader-basierte Methode vorgestellt, auf eine CPUbasierte Methode wird aus Performancegründen verzichtet, da sie in der Praxis kaum eine Rolle spielt.
Wobei der später dargestellte Ausschnitt aus einer Effektdatei sich auf den relevanten Teil, der das
Skinning realisiert, beschränkt.
Bevor der Render-Vorgang beginnt, müssen die benötigten Konstanten gesetzt werden. Wichtig sind
dabei vor allem die Transformationsmatrizen. Diese werden zuvor in einer Schleife jedoch noch so
manipuliert, dass dessen Offset mit einbezogen wird. Im Anschluss daran, wenn auch alle anderen
Konstanten, wie Texturen oder Lichter, gesetzt wurden, wird der Mesh gerendert. Dazu wird wie im
Ausschnitt 4.29 vorgegangen.
S e i t e | XLVIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Die Input-Datenstruktur für den Vertexshader ist hier grundverschieden im Vergleich zu derer des
Shaders zum Tweening. Wie zuvor beschrieben, enthält der Mesh neben seinem Positionsvektor,
Normalenvektor und seinen Texturkoordinaten diesmal auch Informationen über die Gewichtungen
der Bones in Bezug zum jeweiligen Vertex. Diese Informationen sind anhand der Schlüsselwörter den
Variablen zugewiesen, wobei die Schlüsselwörter selbsterklärend sind. Um bei Interesse nachzulesen,
können Sie sich auf (21) über die Semantics informieren. Außerdem wird nur ein Stream zugewiesen,
da keine zwei Objekte gleichzeitig benötigt werden. Die zugehörigen Matrizen zur Berechnung der
Transformationen werden als Konstante übergeben, in diesem Fall ist es die Konstante
mWorldMatrixArray, in der die passenden Matrizen entsprechend der Bones enthalten sind.
static const int MAX_MATRICES = 26;
float4x3 mWorldMatrixArray[MAX_MATRICES] : WORLDMATRIXARRAY;
float4x4 mViewProj : VIEWPROJECTION;
struct VS_INPUT{
float4 Pos : POSITION;
float4 BlendWeights : BLENDWEIGHT;
float4 BlendIndices : BLENDINDICES;
float3 Normal : NORMAL;
float3 Tex0 : TEXCOORD0;
};
//
//
//
//
//
Position des Vertex
Gewichte
Indices
Normalenvektor
Texturkoordinaten
struct VS_OUTPUT{
float4 Pos : POSITION;
// Transformierte Position
float4 Diffuse : COLOR;
// Farbwert
float2 Tex0 : TEXCOORD0;
// Texturkoordinaten
};
4-78 Konstanten und Datenstrukturen des Skinning-Vertexshaders. Quelle: (21)
Nachdem die eingehenden Datenstrukturen erläutert wurden, folgt die Berechnung. Dabei liegen die
im theoretischen Kapitel ausgearbeiteten Formeln zugrunde, um die Vertices entsprechend zu
manipulieren.
Am Anfang der Berechnung wird eine Ausgangsdatenstruktur erstellt, in der alle Transformierten
Daten gespeichert werden. Danach werden mehrere Variablen erstellt, die als zwischenspeicher für die
Berechnungen dienen. Des Weiteren werden die BlendIndices sicherheitshalber in UBYTE4
gewandelt, da sonst ein Fehler in Verbindung mit der Verwendung einer Geforce 3 Grafikkarte
auftreten kann. Im Anschluss daran werden noch die Arrays, die die Gewichte und dazugehörigen
Indices enthalten so umgewandelt, dass später leicht darauf zugegriffen werden kann. Dieser Cast wird
demnach nur an einer Stelle durchgeführt, was Performancevorteile besitzt. In der darauffolgenden
Schleife wird die Position und Normale des Vertex transformiert. Das ganze wurde deshalb in einer
Schleife implementiert, um leichter erweiterbar zu sein, wenn beispielsweise jeder Vertex von vier
Bones beeinflusst wird. Außerdem wird in der Schleife das Gewicht, den letzten Bones berechnet. Das
Ergebnis hieraus berechnet sich folgendermaßen.
Wobei n gleich der Anzahl der beeinflussenden Bones ist, w für weight (Gewicht) steht und somit die
Formel die Akkumulation innerhalb der Schleife repräsentiert. Nach der Berechnung dieses Gewichtes
wird der entsprechende Bone hierdurch in Verbindung mit der Vertex-Position und –Normale
transformiert. Der letzte Schritt ist dann noch die Transformation der Position vom WorldKoordinatensystem in das Projektions-Koordinatensystem.
VS_OUTPUT VShade(VS_INPUT i, uniform int NumBones)
{
VS_OUTPUT o;
// Output-Datenstruktur
S e i t e | XLIX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
float3 Pos = 0.0f;
// Output-Position
float3 Normal = 0.0f;
// Output-Normalenvektor
float LastWeight = 0.0f;
// letztes Gewicht
// Kompensiert den Fehler der Geforce3 bei UBYTE4 Variablen.
int4 IndexVector = D3DCOLORtoUBYTE4(i.BlendIndices);
// Arrays in Vektoren casten, um sie nachfolgend zur Berechnung zu
// verwenden.
float BlendWeightsArray[4] = (float[4])i.BlendWeights;
int IndexArray[4] = (int[4])IndexVector;
// Berechnung der Position/Normale mittels der Gewichte und
// Akkumulation der Gewichte.
for (int iBone = 0; iBone < NumBones-1; iBone++){
LastWeight = LastWeight + BlendWeightsArray[iBone];
Pos += mul(i.Pos, mWorldMatrixArray[IndexArray[iBone]]) *
BlendWeightsArray[iBone];
Normal += mul(i.Normal, mWorldMatrixArray[IndexArray[iBone]]) *
BlendWeightsArray[iBone];
}
// Berechnung des resultierenden Gewichtes für den letzen Bone.
LastWeight = 1.0f - LastWeight;
// Berechne die letzte Transformation für Position und Normale.
Pos += (mul(i.Pos, mWorldMatrixArray[IndexArray[NumBones-1]]) *
LastWeight);
Normal += (mul(i.Normal, mWorldMatrixArray[IndexArray[NumBones-1]]) *
LastWeight);
// Transformiere den vertex vom World-Koordinatensystem in das
// Projektions-Koordinatensystem.
o.Pos = mul(float4(Pos.xyz, 1.0f), mViewProj);
// Weitere Berechnungen hier nicht aufgeführt.
return o; // Output-Datenstruktur zurückgeben
}
4-79 Quell-Code der Skinning-Vertexsahder. Quelle: (21)
Um das Skinning abzuschließen ist jedoch bei zwei Bones je Vertex ein zweiter Durchlauf dieser
Vertexshader-Methode erforderlich. Dabei besitzt die Variable NumBones im ersten Durchlauf den
Wert eins und im zweiten Durchlauf den Wert zwei. Bei näherer Betrachtung dieses Vorgehens im
Vergleich zur zugrunde liegenden Theorie macht dies Sinn. So wird zuerst der erste Bone auf den
Vertex angewendet und dessen Koordinate entsprechend der korrekten neuen Position transformiert
und im Anschluss daran folgt die gleiche Berechnung anhand des zweiten Bones.
Hiermit ist das Skinning und somit der DirectX-Teil zur Implementierung abgeschlossen. In den
nachfolgenden Kapiteln wird auf Basis von OpenGL ein Charakter animiert.
Seite |L
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
4.3. Implementierung der OpenGL-Anwendungen
4.3.1. Laden der 3D Objekte
Unabhängig davon, ob die Animation mit der normalen OpenGL Renderingpipeline oder auf Basis
von GLSL Shadern durchgeführt wird, müssen zuerst die Daten der Objekte in die Anwendung
geladen
werden.
Dazu
wird
das
Programm
von
http://www.spacesimulator.net/tut5_vectors_and_lighting.html
verwendet. Es ist gut dokumentiert und einfach zu handhaben, da der Quellcode kompakt ist und sich
darauf konzentriert, die Grundelemente zur Darstellung eines 3DS Objekts (Vertices, Polygone) zu
laden und zu speichern. Zudem werden die Normalen eines Objekts berechnet, was später für die
Implementierung von Licht- und Materialeigenschaften notwendig ist. Natürlich eignet sich der
Loader nicht für komplexere Objekte mit Bones, Keyframes etc. Zur Erläuterung von Morphing
reichen einfache Objekte jedoch aus. In diesem Kapitel soll deshalb die Funktionsweise des
verwendeten 3DS Loaders erläutert werden.
Zunächst ein Überblick über die Datenstruktur für die Objektdaten. Hierzu wird im Loader ein struct
definiert, dass alle Vertices, Polygone, Normalen und die jeweilige Anzahl an Vertices und Polygonen
speichert. Der folgende Codeausschnitt illustriert dies:
Fehler: Referenz nicht gefunden
4-80 Datenstruktur eines Objekts in OpenGL
Die Funktion Load3DS zum Laden der Objektdaten wird in der Datei load_3ds.c realisiert. Dabei
wird ein Zeiger auf die obige Objektstruktur übergeben, sowie der Name der zu öffnenden Datei. Die
Datei wird anschließend von Anfang bis Ende durchlaufen und in einer switch- case Anweisung
jeweils die ID des aktuellen Chunks untersucht. Dazu muss sich der Dateizeiger immer an der
richtigen Position befinden, also am Anfang eines Chunks. Zur Darstellung eines Objekts ist es
ausreichend, alle Vertices und Polygone auszulesen. Anhand der 3DS Chunk Hierarchie in Abschnitt
2.3.2 lässt sich erkennen, welche Chunks relevant sind, nämlich „VERTICES LIST“ (Chunk ID
0x4110) für alle Vertexdaten und „FACES DESCRIPTION“ (Chunk ID 0x4120) für die Polygone.
Alle nicht benötigten Chunks werden übersprungen, indem der Dateizeiger anhand der Chunk Länge
weiterbewegt wird. Aufgrund der hierarchischen Struktur der 3DS Datei müssen die übergeordneten
Elemente (Main Chunk“, „3D Editor Chunk“, „Object Block“, sowie „Triangular Mesh”)mitgelesen
werden, selbst wenn diese keine relevanten Daten enthalten. Wie bereits erwähnt, ist die Länge aller
Kind Elemente in einem Vater Element mit enthalten Würde man etwa den Main Chunk einfach
überspringen, indem der Dateizeiger anhand der Länge des Main Chunks weiterbewegt wird, wäre
bereits das Dateiende erreicht.
Folglich liest der Loader zuerst den Main Chunk ein, der Dateizeiger wird weiterbewegt und befindet
sich nun bei der ID des ersten Kind Elements, „3D Editor Chunk“. Das Verfahren wird fortgeführt, bis
der Dateizeiger schließlich bei der ID 0x4110 ist. Als erstes wird nun die Gesamtzahl der Vertices des
Objekts ermittelt, um damit die Größe des Feldes mit den Vertices zu erhalten. Anschließend werden
in einer for Schleife alle Vertexpunkte in das struct Element vertex geschrieben:
Fehler: Referenz nicht gefunden
4-81 Vertices der Datenstruktur hinzufügen
Die Struktur zum Einlesen der Polygondaten ist identisch. Auch hier werden die Anzahl der Polygone
des Objekts, sowie die Polygonkoordinaten eingelesen und gespeichert. Im default Zweig werden, wie
S e i t e | LI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
bereits erwähnt, alle nicht benötigten Chunks übersprungen. Damit hat man die benötigten
Grundelemente der Objekte. Auf erweiterte Funktionen, wie etwa Texturen, wird hier verzichtet.
Da der verwendete Loader nur die Grundelemente eines Objekts lädt gab es jedoch Probleme bei der
Darstellung des Modells aus Kapitel 4.1, da dieses unter anderem biped Elemente verwendet. Deshalb
wird für den OpenGL Teil auf ein anderes Modell zurückgegriffen. Ein menschlicher Kopf im 3DS
Format, welcher unter
http://www.3dvalley.com/models/models_bodyparts.shtml
kostenlos heruntergeladen werden kann. Um nun eine zweite Objektposition zu erhalten, wurde das
Objekt in Lightwave importiert, entsprechend neu positioniert (Rotation, Translation) und
anschließend neu abgespeichert (als 3DS Export). Der Loader bietet standardmäßig nur die Funktion
zum Laden eines Objekts an, deshalb ist noch eine weitere Funktion zum Laden des zweiten Objekts
integriert worden. Zudem benötigt man nun natürlich 2 Elemente des structs obj_type.
4.3.2. Normalenberechnung des 3DS Loaders
Damit das zu animierende Objekt nachher nicht flach, sondern dreidimensional erscheint, benötigt
man Licht in der Szene. Zur korrekten Lichtberechnung werden alle Normalenvektoren eines Objekts
benötigt. Das folgende Kapitel erläutert, wie der Loader die Normalen der Vertices und Polygone
berechnet. Als Grundlage dient die Berechnung des Vektorprodukts von zwei Vektoren, die koplanar
(Vektoren liegen in einer Ebene) zum jeweiligen Polygon des Objekts sind. Hierfür werden zwei
Seiten von jedem Polygon herangezogen. Das Ergebnis wird anschließend noch normalisiert, das heißt
auf die Einheitslänge 1 abgebildet. Zum Durchführen dieser Operation sind mehrere Schritte nötig:
Zunächst wird in einer Schleife durch alle ermittelten Polygone gegangen. Jedes Polygon ist in diesem
Fall ein Dreieck, so dass die drei Eckpunkte zunächst zwischengespeichert werden. Anschließend
werden anhand von jeweils zwei Eckpunkten zwei koplanare Vektoren erstellt. Die Vektoren werden
gebildet, indem die Koordinaten des zweiten Eckpunktes minus den Koordinaten des ersten
Eckpunktes gerechnet werden. Anschließend wird das Vektorprodukt der zwei erzeugten Vektoren
berechnet.
Fehler: Referenz nicht gefunden
4-82 Berechnung der Normalen des Polygons
Der daraus resultierende Vektor stellt die jeweilige Polygon Normale dar und wird nun normalisiert.
Dazu berechnet man zuerst die Länge des Vektors und dividiert anschließend alle drei Komponenten
(x,y,z) des Vektors durch dessen Länge.
Fehler: Referenz nicht gefunden
4-83 Normalisieren eines Vektors
Um nun die Normalen der Vertices zu berechnen, wird zuerst die Anzahl der Vertices bestimmt, die zu
jedem Polygon gehören. Anschließend werden die Normalenwerte durch diese Anzahl dividiert:
Fehler: Referenz nicht gefunden
4-84 Normalen Berechnung der Vertices
(22)
4.3.3. Initialisierung von OpenGL und Glut
Nachdem bisher auf die Funktionen des Loaders zugegriffen wurde, erläutern die folgenden Kapitel
den selbst implementierten Teil der Arbeit. Zur Fensterverwaltung wird die GLUT Bibliothek
eingesetzt. Deshalb müssen zunächst einige GLUT Funktionen als Callback angemeldet werden. Zum
einen die display Funktion, die immer dann aufgerufen wird, wenn das Ausgabefenster neu gezeichnet
S e i t e | LII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
werden muss. Ändert der Benutzer die Größe des Ausgabefensters, so müssen das Viewing Volumen
und der Viewport entsprechend angepasst werden. Hierfür wird die reshape Funktion registriert. Die
dritte und letzte benötigte Callback Funktion ist die glutIdleFunc Funktion. Dieser wird als
Parameter eine andere Funktion übergeben, die immer dann neu ausgeführt wird, wenn kein anderes
Ereignis des Window Systems empfangen wird. Dadurch eignet sich die idle Funktion ideal zum
Durchführen der Animation. Anstatt die display Funktion explizit in einer Endlosschleife auszuführen.
reicht es aus, der idle Funktion die display Funktion zu übergeben. Hiermit wird die display Funktion
zur Zeichnung der aktuellen Objektposition automatisch immer wieder neu aufgerufen. Außerdem
wird unter anderem noch festgelegt, dass der Double Buffer und Z Buffer genutzt werden soll. Der
Double Buffer sorgt dafür, dass die Animation nicht flackert. Während der eine Teil des Buffers
(Front Buffer) das aktuelle Bild im Fenster darstellt, wird im zweiten Buffer(back Buffer) bereits das
nächste Bild berechnet. Durch den Z Buffer kann ermittelt werden, welche Teile eines Objekts
sichtbar sind und welche verdeckt werden. Die verdeckten Elemente werden dadurch nicht gezeichnet.
Fehler: Referenz nicht gefunden
4-85 Main Funktion des Programms
Für die OpenGL Initialisierung sind nur wenige Dinge nötig. Als erstes wird die Hintergrundfarbe des
Ausgabefensters festgelegt. Anschließend werden die Licht-und Materialparameter spezifiziert. Diese
wurden so festgelegt, dass ein gesichtsfarbener Lichteindruck entsteht, da innerhalb des Loaders keine
Farbinformationen ausgelesen werden, was jedoch auch nicht notwendig ist. Als Position für die
Kamera werden die Default Einstellungen verwendet, da sich die Objekte in der Nähe des Ursprungs
befinden. In der display Funktion findet lediglich eine Translation der Objekte in negative Z Richtung
statt. Zudem wird noch der Tiefentest für den Z Buffer aktiviert. Für detaillierte Informationen sei hier
auf den entsprechenden Quellcode verwiesen.
4.3.4. Morphing mit OpenGL
Die notwendigen Voraussetzungen zur Durchführung des Morphings sind nun erfüllt. Es werden 2
Objekte geladen und deren Vertices, Polygone, sowie die Normalen errechnet und gespeichert. Zudem
wird Licht in der Szene aktiviert. Der erste Schritt für das Morphing besteht nun darin, die Zeit zu
berechnen, die für einen Frame benötigt wurde(siehe Kapitel 3.1). Es gibt unter C mehrere
Möglichleiten, Zeit zu messen. Nicht jede Methode ist jedoch effektiv. Zunächst wurde der Versuch
unternommen, die Zeitmessung mittels der clock() Funktion aus time.h zu realisieren. Diese ermittelt
die verbrauchte CPU Zeit eines Programms. Allerdings hat sich herausgestellt, dass die Funktion keine
brauchbaren Werte liefert. Selbst nach Angabe von 8 Stellen hinter dem Komma, kommt bei der
Zeitdifferenzmessung oft 0 heraus. Woran dies genau liegt, wurde nicht ersichtlich. Allerdings kann
man vermuten, dass bei heutigen Rechnern mit 2 oder mehr Kernen für eine einfache Animation kaum
CPU Zeit verbraucht wird. Deshalb wird zur Zeitmessung, analog zum DirectX Teil, ebenfalls auf den
PerformanceTimer von windows.h zurückgegriffen. Die Implementierung unterscheidet sich kaum
von der aus Kapitel 4.2.4 und soll deshalb nicht noch einmal erläutert werden. Wichtig zu wissen ist
nur, dass die Zeitberechnung und Aktualisierung in der Display Funktion realisiert werden muss, da
diese Funktion wiederholt aufgerufen wird und somit jeweils den aktuellen Animationsfaktor benötigt.
Durch die Ausgabe der Zeitwerte konnte festgestellt werden, dass die Zeitmessung mittels
PerformanceTimer zuverlässiger und genauer arbeitet als clock(). Die Animation wird nicht anhand
von Keyframes, sondern durch Interpolation der Normalen und Vertices eines Ausgangsobjekts und
eines Zielobjekts durchgeführt. Der zweite Schritt besteht deshalb darin, alle Vertices und Normalen
der beiden Objekte auszulesen und pro Vertex/Normale den interpolierten Wert anhand der Formel
aus Kapitel 3.2.2.2 zu errechnen. Da die 3DS Objekte als Menge von Polygonen mit jeweils 3
Eckpunkten in der Datenstruktur abgelegt sind, wird nun in einer for Schleife durch alle Polygone des
ersten Objekts durchgegangen. Für das Morphing zweier Objekte, sollten diese die gleiche Anzahl an
Vertices/Polygonen haben, deshalb bekommt man beim Durchlauf aller Polygone für ein Objekt auch
alle Werte des zweiten Objekts. Dabei werden sowohl für die Normalen, als auch für die Vertices, die
S e i t e | LIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
interpolierten Werte berechnet und jeweils in einem Feld zwischengespeichert. Der folgende
Codeausschnitt stellt dies anhand des ersten Vertex eines Polygons für die interpolierten Normalen
beispielhaft dar:
Fehler: Referenz nicht gefunden
4-86 Berechnung der interpolierten Normalen
Die Werte der Vertices und Normalen werden deshalb in je einem Feld ( normals[], vertices[])
abgespeichert, da zum Zeichnen der aktuellen Objektposition und Ausrichten der Normalen Vertex
Arrays verwendet werden. 3D Objekte bestehen aus etlichen Vertices und Polygonen, würde deshalb
zum Zeichnen jedes einzelnen Vertexpunktes die Funktion glVertex() und für die Normalen
glNormal()innerhalb einer glBegin/glEnd Anweisung aufgerufen werden, resultiert dies in einer
verringerten Performance der Anwendung. Pro Vertex/Normale ist dann ein separater Funktionsaufruf
nötig. Für die einfache Animation innerhalb dieses Projektes mag der Unterschied noch
vernachlässigbar sein, bei komplexeren Anwendungen sollte man in der Regel aber Vertex Arrays
oder noch besser Displaylisten verwenden. Bei Vertex Arrays werden alle Primitivdaten durch einen
einzigen Funktionsaufruf an OpenGL übergeben.
Sind also alle interpolierten Vertices und Normalen berechnet, wird als nächstes die
Zeichnungsfunktion aufgerufen. Darin werden zunächst mittels glVertexPointer() und
glNormalPointer() die entsprechenden Objektdaten spezifiziert. Es werden jeweils 3 Float
Komponenten verwendet. Als Daten werden die beiden arrays (normals[], vertices[])
übergeben. Das jeweilige Vertex Array muss anschließend aktiviert werden. Durch den Aufruf von
glDrawArrays() erfolgt dann das Zeichnen des Objekts. Die Realisierung eines Primitivs erfolgt
als Folge von Dreiecken, da jedes Polygon durch 3 Eckpunkte bestimmt ist. Die Normalen werden
hierbei automatisch mitberücksichtigt.
Fehler: Referenz nicht gefunden
4-87 Zeichnen des Objekts mittels Vertex arrays
Damit eine flüssige Bewegung entsteht muss der oben beschrieben Vorgang natürlich wiederholt
ausgeführt werden. Wie im vorherigen Kapitel beschrieben, geschieht dies durch das Nutzen der
glutIdleFunc() Funktion. Dadurch wird die Display Funktion kontinuierlich neu aufgerufen. Der
Skalierungsfaktor skalar wird jeweils anhand der Zeit neu berechnet und läuft dadurch von 0.0
beginnend langsam nach 1.0. Zu Beginn wird somit nur Objekt 1 gezeichnet, am Ende nur Objekt 2
und dazwischen die Übergänge von Objekt 1 nach Objekt 2. Anschließend wird die
Animationsrichtung umgekehrt. Da der erneute Aufruf der display Funktion und das Zeichnen der
aktuellen Objektposition sehr schnell geschieht, entsteht eine flüssige Bewegung (Animation).
4.3.5. Morphing mit GLSL
In diesem Kapitel wird die Realisierung des Morphing Verfahrens mittels Vertex- und
Fragmentshadern unter Nutzung der GLSL Sprache erläutert und Unterschiede, sowie
Gemeinsamkeiten zur Implementierung im vorherigen Kapitel dargestellt. Einen großen
Geschwindigkeitszuwachs bietet diese Lösung nicht, da die Animation relativ simpel ist und die
Zeitdauer zur Berechnung eines Frames im Milisekundenbereich liegt. Die höhere Performance der
GPU kommt erst bei komplexeren Szenen zum Tragen. Grundsätzlich gibt es zwei Schritte, die
durchgeführt werden müssen. Zum einen muss die bestehende Implementierung modifiziert werden,
damit die Shaderprogramme in die OpenGL Anwendung integriert werden und wichtige Parameter an
das Shader Programm übergeben werden können. Zum anderen muss die eigentliche Programmlogik
des Vertex- und Fragmentshaders entwickelt werden.
S e i t e | LIV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
4.3.5.1. Erweiterung der bestehenden Implementierung
In Kapitel 3.3.3.2 wurde die generelle Vorgehensweise zur Integration von Shadern in ein OpenGL
Programm bereits dargestellt. Durch die dort gezeigten Schritte können zuvor erzeugte Vertex- und
Fragment Shader genutzt werden, für das Durchführen des Morphing Algorithmus sind aber noch
einige Erweiterungen nötig. Dazu gilt es, sich zu überlegen, welche Teile der OpenGL
Renderingpipeline durch die Shader ersetzt werden und welche Programmabschnitte dadurch nicht
mehr mit herkömmlicher OpenGL Syntax durchgeführt, sondern durch GLSL ersetzt werden. Durch
das Nutzen des Vertex Shaders muss zunächst die Interpolation der Vertices anhand des
Skalierungsfaktors und das Zeichnen der resultierenden Vertexpunkte im Shader erfolgen. Der Vertex
Shader benötigt also für jeden zu zeichnenden Frame den Wert des Skalierungsfaktors. Da dieser für
ein ganzes Primitiv gilt, bietet sich eine uniform Variabel zur Speicherung an. Damit man den
aktuellen Skalierungsfaktor an den Vertex Shader übergeben kann, benötigt man zwei Funktionen.
Zunächst wird mit dem Aufruf
Fehler: Referenz nicht gefunden
die Position der im Vertex Shader spezifizierten uniform Variable ermittelt. Dabei bezeichnet
ShaderProgramHandle den zuvor erzeugten Shader Programm Container und „skalar“ ist der
Name der uniform Variable im Shader. Um Fehler zu vermeiden, sollte sichergestellt werden, dass
die entsprechende Variabel auch exakt so heißt, wie man es im Funktionsaufruf angibt. Am Anfang
der display Funktion kann nun der aktuelle Wert des Skalierungsfaktors durch den Aufruf von
Fehler: Referenz nicht gefunden
an die uniform Variabel im Vertex Shader übergeben werden. skalar enthält hier den float Wert des
Skalierungsfaktors. Im Vertex Shader werden zudem die Werte der Normalen von den beiden
Objekten, sowie deren Vertexdaten benötigt. Da Vertex- und Normalendaten innerhalb eines Primitivs
unterschiedlich sind, werden hierfür attribute Variablen verwendet. Wie bereits in Kapitel 2.5.3.11
erläutert, können normale OpenGL Zeichnungsaufrufe nach wie vor verwendet werden, die Daten
gelangen dadurch aber direkt an den Vertex Shader und die built-in-attribute Variablen gl_Vertex
und gl_Normal. Nun benötigt man aber noch Variablen zur Speicherung der Daten des zweiten
Objekts und einen geeigneten Übergabemechanismus. Ein solcher Mechanismus wird in GLSL durch
generic vertex attributes realisiert. Diese werden unter OpenGL durch Angabe des Index Wertes des
generic vertex attributes (von 0 beginnend) angesprochen. Im Shader kann eine attribute Variable zur
Speicherung des jeweils aktuellen Vertexwertes verwendet werden. Innerhalb der OpenGL
Anwendung wird nun zuerst mittels
Fehler: Referenz nicht gefunden
die Zuordnung des Indexes des generic vertex attributes (hier 1) zur angegebenen Variable
(vertObj2) vorgenommen. Dieser Aufruf muss vor dem Binden des Shader Programms erfolgen.
Anschließend kann mit der glVertexAttribPointer() Funktion, ein Feld von Vertexdaten an die
oben beschriebene attribute Variable als kontinuierlicher Datenstrom übergeben werden. Für die
konkrete Realisierung sorgt etwa der Aufruf
Fehler: Referenz nicht gefunden
dafür, dass alle Vertexdaten von Objekt 2, bestehend aus jeweils 3 float Komponenten, über den Index
1 an die oben zugeordnete attribute Variable (vertObj2) übergeben werden. Der vierte Parameter
(GL_FALSE ) gibt an, ob die Vertexdaten zu normalisieren sind oder nicht. Anschließend muss noch
der normale OpenGL Aufruf zum Zeichnen des Vertex Arrays erfolgen.
S e i t e | LV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Der erste Implementierungsansatz verfolgte deshalb das Ziel, nur die Normalen und Vertices des
zweiten Objekts über eine attribute Variable zu übergeben, die des ersten Objekts sollten über ein
Vertex- und Normalenarray in die built-in-Variablen gl_Vertex und gl_Normal geschrieben
werden. Für die Vertexdaten funktionierte dieser Ansatz auch, allerdings hatte das Aktivieren des
Normalenarrays keinen Effekt. Es wurden nur die Normalen des Objekts zur Lichtberechnung
berücksichtigt, die über ein generic vertex attribute übergeben wurden. Aufgrund dessen werden in der
finalen Implementierung drei attribute Variablen für alle Normalen, sowie die Vertexdaten des
zweiten Objekts verwendet. Die Vertexdaten des ersten Objekts erhält der Shader über den
glDrawArrays Aufruf. Konkret sieht dies folgendermaßen aus:
Fehler: Referenz nicht gefunden
4-88 Aufruf des Vertex Shaders
Die Beleuchtungsberechnung muss nun ebenfalls im Shader realisiert werden. Näheres dazu finden
Sie im nächsten Kapitel. Für die bestehende OpenGL Anwendung sind keine Änderungen nötig. Das
Setzen der Licht und Materialeigenschaften bleibt so, wie bisher. Auf die dort gesetzten Werte kann
im Shader mittels spezieller built-in Variablen zugegriffen werden. Anzumerken ist hier nur, dass das
Aktivieren einer Lichtquelle und die Aktivierung der OpenGL Beleuchtungsberechnung über
glEnable(GL_LIGHT1)und glEnable (GL_LIGHTING)keinen Effekt mehr haben. Das muss im
Shader implementiert werden.
4.3.5.2. Implementierung des Vertex Shaders
Im Vertex Shader wird das Morphing, sowie die Berechnung der Licht- und Materialparameter
durchgeführt. Als Ausgabedaten erhält man die interpolierten Vertexdaten. Um die Shader Programme
später leichter debuggen zu können, wird das kostenlose Werkzeug „Render Monkey“ von
http://ati.amd.com/developer/rendermonkey/ eingesetzt. Auf eine Erläuterung des Programms wird
hier verzichtet, da lediglich die Funktion zum direkten Kompilieren der Shader und Anzeigen
eventueller Fehler Verwendung findet und der Code an sich von Hand programmiert wird.
Der Vertex Shader wird pro Vertex des zu zeichnenden Objekts jeweils einmal aufgerufen. Zur
Durchführung des Morphings deklariert man zunächst die globalen uniform und attribute Variablen
für den Skalierungsfaktor und die Vertex- sowie Normalendaten. Der aktuelle Vertexpunkt des ersten
Objekts befindet sich, wie bereits erwähnt, in der built-in-attribute Variable gl_Vertex.
Anschließend berechnet man die interpolierten Normalen und Vertices entweder analog zur normalen
OpenGL Implementierung anhand der Formel oder nutzt dafür gleich die in GLSL integrierte
Funktion mix(x,y,a). Diese Funktion berechnet x*(1-a) + (y*a) und ist damit identisch zur vorher
benutzen Formel. Somit ergibt sich folgender Code zur Berechnung der interpolierten Normalen und
Vertices:
Fehler: Referenz nicht gefunden
4-89 Berechnung der interpolierten Normalen und Vertices im Shader
In jedem Vertexshader muss am Programmende die built-in Variable gl_Position geschrieben
werden. Diese enthält die homogenen Koordinaten des zu setzenden Vertexpunktes. Dazu wird der
interpolierte Vertexpunkt mit der GLSL uniform Variable gl_ModelViewProjectionMatrix
multipliziert und man erhält die Clipping Koordinaten. Da es eine 4x4 Matrix ist, der Vertexpunkt
aber nur drei Koordinaten enthält, wird dem Vertexpunkt zuvor noch der Wert 1.0 als vierte
Koordinate hinzugefügt.
Fehler: Referenz nicht gefunden
4-90 Schreiben der aktuellen Vertexposition
Damit fehlt nun noch die Beleuchtungsberechnung. Hierfür wird das Lighthouse Tutorial „Directional
Light per Pixel” verwendet. Die zu Beginn durchgeführte Normalisierung des Normalenvektors ist
S e i t e | LVI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
hier nicht mehr nötig, da der Shader bereits normalisierte Daten erhält. Zunächst wird die Position
bestimmt, aus der das Licht kommt. Hierfür bietet GLSL die Datenstruktur gl_LightSource an.
Mit gl_LightSource[0].position erhält man den im OpenGL Programm definierten
Positionsvektor für die erste Lichtquelle. Da nachher im Fragment Shader mit dem Kosinus des
Winkels zwischen Normalen- und Lichtvektor gerechnet wird, muss der Positionsvektor des Lichts
zunächst normalisiert werden. Als nächstes wird der „halfVector“ benötigt. Dieser Vektor befindet
sich genau in der Mitte zwischen Licht- und Kameravektor und ist später zur Berechnung des
spekularen Lichtanteils nötig. Der halfVector wird ebenfalls normalisiert.
Fehler: Referenz nicht gefunden
4-91 Normalisierungen im Vertex Shader
Anschließend wird der diffuse und ambiente Anteil des Lichts anhand der im OpenGL Programm
gesetzten Licht- und Materialparameter berechnet. Die Implementierung des Vertex Shaders ist damit
abgeschlossen.
Fehler: Referenz nicht gefunden
4-92 Lichtberechnung im Vertex Shader
4.3.5.3. Implementierung des Fragment Shaders
Im Fragment Shader wird anhand der berechneten Lichtwerte im Vertex Shader das Licht pro Pixel
gesetzt. Dabei bildet man die Standard OpenGL Lichtberechnung bei Nutzung der statischen
Renderingpipeline (also ohne Shader) ab. Das Lighthouse Tutorial verwendet für den diffusen
Lichtanteil die „Lambertian Reflection“ Formel:
Intensität = Licht(diffus) * Material(diffus) *cos(Winkel zwischen Licht und Normalenvektor)
Der ambiente Anteil wird unverändert vom Vertex Shader übernommen. Zur Berechnung des
spekularen Lichtanteils wird das „Blinn Phong Modell“ genutzt, eine Vereinfachung des bekannten
Phong Shading Algorithmus. Dabei bestimmt man die Intensität des spekularen Lichtanteils anhand
des Winkels zwischen dem halfVector und dem Normalenvektor. Je größer der Winkel ist, umso
intensiver wird das spekulare Licht. Die Formel hierzu ist:
Spekular = (NormalV x halfV) shininess * Licht(spekular) * Material(spekular)
Zur Durchführung der Berechnung werden zunächst einige Werte aus dem Vertexshader benötigt,
unter anderem der Normalenvektor. Zum Zugriff auf diese Werte dienen varying Variablen, wobei die
Variablennamen identisch zu denen aus dem Vertex Shader sein müssen. Für den diffusen und
spekularen Lichtanteil wird nun das Skalarprodukt zwischen Licht- und Normalenvektor berechnet,
man erhält den Kosinus des eingeschlossenen Winkels. Sofern dieser größer als 0 ist, werden die
Lichtanteile anhand der oben beschriebenen Formeln berechnet:
Fehler: Referenz nicht gefunden
4-93 Lichtberechnung im Fragment Shader
Wie der Vertex Shader mit gl_Position, so besitzt auch der Fragment Shader eine spezielle Output
Variabel, die immer geschrieben werden muss. In diesem Fall ist das gl_FragColor für die zu
setzende Farbe des Fragments/Pixels. Die letzte Operation im Fragment Shader ist deshalb:
Fehler: Referenz nicht gefunden
4-94 Schreiben der Farbe des aktuellen Pixels
4.4. Zusammenfassung
S e i t e | LVII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Während die grundlegenden Berechnungen im Kapitel 2 und 3 erörtert wurden, galt es sie an dieser
Stelle umzusetzen. Dazu wurde zu erst ein Charakter modelliert und Animiert, sowie im Anschluss
daran in das passende Format für 3D-Objekte exportiert. Des Weiteren wurden die Verfahren
Tweening und Skinning im Teilbereich DirectX implementiert, wobei zum Anfang Tweening
zwischen zwei separat geladenen 3D-Objekten durchgeführt wurde. Da die Anwendungszwecke
hierfür aber nur sehr gering sind und praktisch nicht von großer Bedeutung, wurde das X-Format
erweitert um Tweening-Animationen erweitert. Außerdem wurde ein Animation-Controller für
Tweening implementiert, der mehrere Animationen zwischen beliebig vielen Key-Frames kapselt. Im
Anschluss daran wurde das Skinning-Verfahren auf Basis der DirectX-Samples implementiert. Hierbei
ist die Analogie zwischen dem in DirectX vorhandenem Animation-Controller für Skinning und dem
zuvor implementierten Animation-Controller für Tweening zu beachten. Des Weiteren wurde in
diesem Kapitel die Integration und Implementierung der Shader erörtert und an praktischen Beispielen
erklärt. Im OpenGL Teil wurde die Animation mittels Morphing durch Interpolation der Vertices und
Normalen zweier Objekte betrachtet. Dies beinhaltet zunächst die Erläuterung des verwendeten 3DS
Loaders. Anschließend folgten die nötigen Schritte zur Initialisierung von OpenGL und GLUT. Im
Kernteil wurde nun die Realisierung des Morphing Verfahrens mittels CPU und durch Nutzung von
Vertex- und Fragment Shader mit der GLSL Sprache beschrieben und Gemeinsamkeiten, sowie
Unterschiede und nötige Erweiterungen aufgezeigt.
S e i t e | LVIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
5. Fazit
Zusammenfassend lässt sich sagen, dass die Charakteranimation ein sehr interessantes und vor allem
umfangreiches Thema ist, was in dieser Ausarbeitung bei weitem nicht erschöpfend erläutert wurde.
Zwar wurden die grundlegenden Techniken betrachtet und entsprechende Lösungen dafür
implementiert, aber auf andere interessante Gebiete wie Gesichtsanimationen – sprich Mimik – wurde
nicht besprochen.
Die Implementierung der DirectX-Anwendungen lässt sich durchweg als erfolgreich zusammenfassen.
Das einzige Problem, was dabei entstand, war dass das X-Dateiformat keine Tweening-Animationen
mit Key-Frames unterstützt. Zwar wird an einigen Stellen beschrieben wie man das Format erweitert
und wie die Templates dafür aussehen könnten. Jedoch gab es keine brauchbaren Informationen, außer
den sehr knapp gehaltenen Dokumentationen von Microsoft, zur API. Aus diesem Grund wurde in den
Teilbereich, der in dieser Ausarbeitung einen eher geringen Umfang besitzt verhältnismäßig viel Zeit
investiert. Nichts desto trotz sind dadurch am Ende sowohl Animationen zwischen beliebig vielen
Key-Frames auf Basis von Skinning und Tweening möglich. Leider fehlte die Zeit aufwendige
Animationen und Charaktere zu erstellen, was allerdings in einem darauf aufbauendem Projekt sehr
gut machbar ist. Daher möchte ich an dieser Stelle noch einen Ausblick in die Zukunft geben. Die
Fortführung dieses Projektes könnte zu erst in der Modellierung und Animierung aufwendigerer,
realistischer Charaktere liegen und im Anschluss daran die Implementierung der Überblendungen
zwischen diesen Animationen beinhalten. Sind diese Schritte abgearbeitet, so kann man die Charaktere
durch eine 3D-Szene bewegen und beispielsweise einen Übergang von Gehen über Laufen bis Rennen
und einem anschließendem Sprung durchführen. Doch um dies zu realisieren, wäre wiederum eine
Kollisionserkennung notwendig – was ein weiteres interessantes Gebiet darstellt.
Bei der Implementierung des OpenGL Teils bestand zunächst das Problem, ein geeignetes Dateiformat
und einen entsprechenden Loader zu finden. Da dies viel Zeit benötigte, wird als Kompromiss ein
einfacher 3DS Loader eingesetzt. Mit diesem lässt sich das Morphing Verfahren mit CPU und GPU
Berechnung gut und verständlich durchführen. Bones werden allerdings nicht unterstützt. Da die
Implementierung der übrigen Verfahren jedoch bereits sehr lange dauerte, konnte das Skinning mit
OpenGL ohnehin zeitlich nicht mehr durchgeführt werden. Der übrige Teil der OpenGL
Implementierung lässt sich aber als erfolgreich bezeichnen. Insbesondere die Shader Programmierung
war sehr interessant, da man zwar schon einige Begriffe aus diesem Bereich gehört oder gelesen hat,
bisher aber nie eine konkrete Anwendung dazu realisiert hat.
S e i t e | LIX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
6. Anwendungsdokumentation
6.1. Erläuterung der Ordnerstruktur
In diesem Kapitel wird die Struktur des beiliegenden Datenträgers beschrieben. In Anbetracht dessen
erleichtert Ihnen das folgende Diagramm den Überblick.
6-95 Skizze der Ordnerstruktur des Datenträgers
• Im Ordner „Ausarbeitung“ befindet sich dieses Textdokument.
• Im Ordner „Plug-Ins“ sind beispielsweise Lightwave-Plug-Ins für den Export nach .X.
• Im Ordner „Source-Code“ befindet sich der Quell-Code der entwickelten Anwendungen.
• Im Ordner „Ausführbare Dateien“ befinden sich kompilierte Versionen der entwickelten
Anwendungen.
• Der Ordnet Quellen beinhaltet Kopien von Internetquellen.
6.2. Verwendete Werkzeuge
Werkzeug
Beschreibung
MS Windows XP
Adobe Acrobat Professional
Lightwave 3D
Export DirectX
MS Visual Studio 2008
Rendermonkey
FX Composer
MS DirectX SDK (March 2008)
Betriebssystem
Dokumentationswerkzeug
3D-Modellierungs- und Animationssoftware.
Lightwave Plug-In zur Konvertierung nach .X.
Entwicklungsumgebung
Shader IDE
Shader IDE
DirectX Software Development Kit
6-96 Verwendete Werkzeuge
6.3. Aufwandsabschätzungen
S e i t e | LX
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Aufwand (in Stunden)
Aufgabe
35
2
20
6
15
30
108
Erstellung der Dokumentation
Erstellung der Präsentation
Implementierung – Tweening mit der GPU
Implementierung – Tweening mit einem Vertex-Shader
Implementierung – Tweening mit Key-Frames
Implementierung – Skinning
Gesamt
6-97 Aufwandsabschätzung – Michael Genau
Aufwand (in Stunden)
Aufgabe
30
2
20
5
20
77
Erstellung der Dokumentation
Erstellung der Präsentation
Suchen und Einrichten des Loaders
Implementierung – Morphing mit OpenGL
Implementierung – Morphing mit Shadern
Gesamt
6-98 Aufwandsabschätzung Andreas Gärtner
S e i t e | LXI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
VI.
WS 08/09
Literaturverzeichnis
1. Bartsch, Hans-Jochen. Taschenbuch Mathematischer Formeln. s.l. : Fachbuchverlag Leipzig im
Carl Hanser Verlag, 2004. ISBN: 3-446-22891-8.
2. Pipho, Evan. Focus On 3D Models. s.l. : Premier Press, 2003. ISBN: 1-59200-033-9.
3. Adams, Jim. Advanced Animation with DirectX. s.l. : Premier Press, 2003. ISBN-10: 1592000371.
4. [Online] http://www.spacesimulator.net/tut4_3dsloader.html.
5. [Online] http://www.jalix.org/ressources/graphics/3DS/_unofficials/3ds-info.txt.
6. Scherfgen, David. 3D-Spiele-Programmierung mit DirectX 9 und C++. s.l. : Hanser
Fachbuchverlag, 2006. ISBN-13: 978-3-446-40596-7.
7. Rousselle, Christian. Jetzt lerne ich DirectX 9 und Visual C++. s.l. : Markt und Technik, 2003.
ISBN-10: 3827264545.
8. Heinzel, Werner. Skript zur Vorlesung "Computer Graphics". 2008.
9. —. Skript zur Lehrveranstaltung "Graphische Datenverarbeitung". 2005.
10. Rost, Randi J. OpenGL Shading Language Second Edition. ISBN 0-321-33489-2.
11. Kaiser, Ulrich and Lensing, Philipp. Spieleprogrammierung mit DirectX und C++. Bonn :
Galileo Computing, 2007. ISBN: 978-3-89842-827-9.
12. Parent, Rick. Computer Animation: Algorithms and Techniques. s.l. : Morgan Kaufmann, 2001.
1-55860-579-7 .
13. Möller, Thomas and Eric, Haines. Real-Time Rendering. s.l. : B&T, 2002. 1568811829.
14. Calver, Dean, et al. Shader X³ Advanced Rendering with DirectX and OpenGL. s.l. : Charles
River Media, 2004. ISBN-10: 1584503572.
15. Engel, Wolfgang F. Direct3D Shaderx: Vertex and Pixel Shader Tips and Tricks. s.l. : Wordware
Publishing Inc.,U.S., 2002. ISBN: 1-55622-041-3.
16. Rössler, Ronny. Einführung in die Shader Programmierung unter OpenGL 2.0.
17. [Online] http://wiki.delphigl.com/index.php/Tutorial_glsl.
18. [Online] http://www.lighthouse3d.com/opengl/glsl/index.php.
19. www.gamedev.net. [Online] [Cited: November 03, 2008.]
20. Heinzel, Werner. Skript zur Vorlesung Grafik-Programmierung. 2007.
21. MSDN Microsoft. [Online] [Cited: November 26, 2008.] http://msdn.microsoft.com.
22. [Online] http://www.spacesimulator.net/tut5_vectors_and_lighting.html.
23. Lever, Nik. Real-time 3D Character Animation with Visual C++. s.l. : Focal Press, 2002. ISBN:
0-240-51664-8.
S e i t e | LXII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
VII. Abbildungsverzeichnis
2-1 Gimbal Lock. Quelle: (2).........................................................................................................................................3
2-2 Visualisierung von LERP und SLERP. Quelle: (2).........................................................................................4
2-3 Hierarchie einiger 3ds chunk Elemente, Quelle (4)....................................................................................5
2-4 Felder eines Chunks, Quelle: (4).........................................................................................................................6
2-5 Beschreibung der Schnittstelle IUnknown. Quelle: (6).............................................................................9
2-6 Skizze der DirectX Renderpipeline. Quelle: (11)..........................................................................................9
2-7 Funktionsweise eines Vertexshader. Quelle: (11).......................................................................................9
2-8 OpenGL Historie Quelle (8)................................................................................................................................10
2-9 OpenGL Renderingpipeline, Quelle: (9)........................................................................................................11
dinput8.lib, dxguid.lib, dxerr9.lib, dsound.lib, d3dx9.lib, winmm.lib, d3d9.lib, kernel32.lib,
user32.lib, comdlg32.lib, gdi32.lib, shell32.lib
3-10 Ein Beispiel für zusätzliche Linkeroptionen für DirectX-Anwendungen......................................14
3-11 Beispiel für eine Bone-Hierarchie.................................................................................................................15
3-12 Definition der Input- und Output-Strauktur eines Vertex-Shaders................................................19
3-13 Definition einer Main-Methode in einem Vertex-Shader....................................................................19
3-14 Definition bedeutender DirectX-Variablen zur Verwendung von Vertex-Shadern..................19
3-15 DirectX-Funktion zum Laden eines Shaders aus einer Datei.............................................................20
3-16 DirectX-Funktion zum Erstellen eines vertex-Shaders........................................................................20
3-17 DirectX-Funktion für den Zugriff auf die Konstanten eines Shaders..............................................20
3-18 DirectX-Funktion zum Setzen einer Konstanten (einer Matrix).......................................................20
3-19 DirectX-Funktion zum Setzen eines Vertex-Shaders im Rendervorgang......................................20
3-20 DirectX-Funktion zum Zeichnen eines Meshs..........................................................................................20
3-21 DirectX-Funktion zum Laden einer Effektdatei.......................................................................................22
3-22 DirectX-Funktion für den Zugriff auf die Konstanten einer Effektdatei........................................22
3-23 Code-Ausschnitt zum Rendern eines Effekts............................................................................................22
3-24 Besonderheiten bei der Initialisierung von GLSL Variablen, Quelle (16).....................................25
3-25 Nutzung des vec Datentyps, Quelle (17)....................................................................................................25
3-26 Erzeugung eines Shader Containers, Quelle: (18)..................................................................................26
3-27 Laden des Shader Codes in den Shader Container , Quelle: (18)......................................................26
3-28 Kompilieren des Shader Codes, Quelle: (18)............................................................................................26
3-29 Erzeugung eines Programm Containers, Quelle: (18)..........................................................................26
3-30 Shader-Code zum Shader-Programm hinzufuegen, Quelle: (18).....................................................26
3-31 Shader Programm binden , Quelle: (18).....................................................................................................27
3-32 Shader Code auf Grafikkarte installieren , Quelle: (18).......................................................................27
4-33 Bild vom Auge des Charakters.......................................................................................................................28
4-34 Bild vom Gesicht des Charakters...................................................................................................................28
4-35 Bild vom Gesicht des Charakters...................................................................................................................29
S e i t e | LXIII
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
4-36 API der WinMain-Funktion..............................................................................................................................30
4-37 API für einen Callback-Handler.....................................................................................................................30
4-38 API der Funktion zum Erstellen eines Windows-Fensters.................................................................31
4-39 Beispiel für eine Hauptprogrammschleife.................................................................................................31
4-40 Quell-Code zum initialisieren einer IDirect3D9-Schnittstelle...........................................................31
4-41 Abfrage des HAL-Devices eines Systems....................................................................................................32
4-42 Quell-Code zum Erstellen eines Direct3D9-Devices..............................................................................32
4-43 Quell-Code zum Erstellen einer DirectInput-Schnittstelle..................................................................32
4-44 Quell-Code zum Erstellen eines DirectInput-Devices...........................................................................33
4-45 Quell-Code zum Setzen des Datenformats eines DirectInput-Devices...........................................33
4-46 Quell-Code zum Setzen des Kooperationslevels.....................................................................................33
4-47 Variablen zur verwaltung eines Meshs sowie dessen Materialien und Texturen.....................34
4-48 Funktion zum Laden einer X-Datei...............................................................................................................34
4-49 Quell-Code zum Auslesen der Materialien und Texturen eines Meshs..........................................34
4-50 Quell-Code zum Klonen eines Meshs um dessen FVF anzupassen..................................................35
4-51 Quell-Code zur Zeitmessung (1 von 3). Auf Basis von (19)................................................................35
4-52 Quell-Code zur Zeitmessung (2 von 3). Auf Basis von (19)................................................................35
4-53 Quell-Code zur Zeitmessung (3 von 3). .....................................................................................................35
4-54 Quell-Code zum AUfruf der Morphing-Funktion in Abhängigkeit der Zeit..................................35
4-55 Quell-Code zum Tweening mit der CPU (1 von 3)..................................................................................36
4-56 Quell-Code zum Tweening mit der CPU (2 von 3)..................................................................................36
4-57 Quell-Code zum Tweening mit der CPU (3 von 3)..................................................................................37
4-58 Definition der Funktion D3DXVec3Lerp....................................................................................................37
4-59 Quell-Code zur Definition einer Vertexstruktur und Variablen........................................................37
4-60 Quell-Code zur Definition einer Vertexdeklaration...............................................................................38
4-61 Quell-Code zum Extrahieren der Vertexdaten der Meshs...................................................................38
4-62 Quell-Code zum Setzen der Vertexdaten-Streams und Indices.........................................................38
4-63 Quell-Code zum Setzen der Eigenschaften eines Effekts.....................................................................39
4-64 Quell-Code zum Rendern mit einer Effekt-Datei....................................................................................39
4-65 Vertexshader Datenstrukturen für Tweening..........................................................................................39
4-66 Ausschnitt des Quell-Codes der Effektdatei zum Tweening...............................................................40
4-67 Skizze zur Veranschaulichung von Key-Frames.....................................................................................40
4-68 Definition des X-File Templates Animation. Quelle: (21)....................................................................40
4-69 Definition der X-File Templates AnimationKey und TimedFloatKeys. Quelle: (21).................41
4-70 Definition der X-File Templates AnimationSet. Quelle: (21)..............................................................41
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINE;
4-71 Definition der Structs D3DXFRAME und D3DXMESHCONTAINER.................................................41
4-72 Definition der Funktion D3DXLoadMeshHierarchyFromX(…). Quelle: (21)...............................42
S e i t e | LXIV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
4-73 Erweiterung des X-Dateiformats durch Morphing. Auf Basis von Quelle (3)..............................42
4-74 Definition der GUIDs mit dem DEFINE_GUID Makro............................................................................43
4-75 Definition der structs zu dem Templates der X-Datei..........................................................................43
4-76 Funktionsdefinition LoadTopLevelData der Klasse LoadUserData................................................43
4-77 Beschreibung der Funktion CreateMeshContainer des Interfaces ID3DXAllocateHierarchy
Quelle: (21).......................................................................................................................................................................45
4-78 Konstanten und Datenstrukturen des Skinning-Vertexshaders. Quelle: (21)............................46
4-79 Quell-Code der Skinning-Vertexsahder. Quelle: (21)............................................................................47
4-80 Datenstruktur eines Objekts in OpenGL.....................................................................................................48
4-81 Vertices der Datenstruktur hinzufügen......................................................................................................48
4-82 Berechnung der Normalen des Polygons...................................................................................................49
4-83 Normalisieren eines Vektors..........................................................................................................................49
4-84 Normalen Berechnung der Vertices.............................................................................................................49
4-85 Main Funktion des Programms......................................................................................................................50
4-86 Berechnung der interpolierten Normalen ................................................................................................51
4-87 Zeichnen des Objekts mittels Vertex arrays.............................................................................................51
4-88 Aufruf des Vertex Shaders...............................................................................................................................53
4-89 Berechnung der interpolierten Normalen und Vertices im Shader................................................53
4-90 Schreiben der aktuellen Vertexposition.....................................................................................................53
4-91 Normalisierungen im Vertex Shader...........................................................................................................54
4-92 Lichtberechnung im Vertex Shader..............................................................................................................54
4-93 Lichtberechnung im Fragment Shader.......................................................................................................54
4-94 Schreiben der Farbe des aktuellen Pixels .................................................................................................54
6-95 Skizze der Ordnerstruktur des Datenträgers...........................................................................................57
6-96 Verwendete Werkzeuge....................................................................................................................................57
6-97 Aufwandsabschätzung – Michael Genau....................................................................................................58
6-98 Aufwandsabschätzung Andreas Gärtner....................................................................................................58
VIII-99 Text-Version einer einfachen X-Datei. Quelle: (3)........................................................................LXIII
VIII-100 Liste einiger Templates von X-Dateien. Quelle: (21).................................................................LXIV
S e i t e | LXV
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
VIII. Weitere Anlagen
xof 0302txt 0032
template Header {
<3D82AB43−62DA−11cf−AB39−0020AF71E433>
DWORD major;
DWORD minor;
DWORD flags;
}
template Frame {
<3D82AB46−62DA−11cf−AB39−0020AF71E433>
[FrameTransformMatrix]
[Mesh]
}
Header {
1;
0;
1;
}
Frame Scene_Root {
FrameTransformMatrix {
1.000000, 0.000000, 0.000000, 0.000000,
0.000000, 1.000000, 0.000000, 0.000000,
0.000000, 0.000000, 1.000000, 0.000000,
0.000000, 0.000000, 0.000000, 1.000000;;
}
Frame Pyramid_Frame {
FrameTransformMatrix {
1.000000, 0.000000, 0.000000, 0.000000,
0.000000, 1.000000, 0.000000, 0.000000,
0.000000, 0.000000, 1.000000, 0.000000,
0.000000, 0.000000, 0.000000, 1.000000;;
}
Mesh PyramidMesh {
5;
0.00000;10.00000;0.00000;,
58
−10.00000;0.00000;10.00000;,
10.00000;0.00000;10.00000;,
−10.00000;0.00000;−10.00000;,
10.00000;0.00000;−10.00000;;
6;
3;0,1,2;,
3;0,2,3;,
3;0,3,4;,
3;0,4,1;,
3;2,1,4;,
3;2,4,3;;
MeshMaterialList {
1;
6;
0,0,0,0,0,0;;
Material Material0 {
1.000000; 1.000000; 1.000000; 1.000000;;
0.000000;
0.050000; 0.050000; 0.050000;;
0.000000; 0.000000; 0.000000;;
}
}
}
}
}
VIII-99 Text-Version einer einfachen X-Datei. Quelle: (3)
S e i t e | LXVI
Charakter-Animation
Eine Gegenüberstellung unter DirectX und OpenGL
WS 08/09
Name des Templates
Beschreibung
Animation
AnimationKey
Definiert Animationsdaten für einen Frame.
Definiert einen Key Frame für das übergeordnete AnimationTemplate.
Enthält Informationen zur Wiedergabe der Animation.
Enthält eine Sammlung von Animation-Templates.
Enthält einen boolschen Wert.
Enthält zwei boolsche Werte.
Enthält rot, grün und blau Werte.
Enthält rot, grün, blau und einen alpha Wert.
Definiert zwei Koordinatenwerte.
Enthält ein Array von float-Werten.
Enthält eine Transformationsmatrix für den übergeordneten
Frame.
Ein Template, das eine Hierarchie beschreibt.
Der X-Datei Header, inkl. Versionsnummern.
Enthält einen indexierten Farbwert.
Enthält Materialfarbwerte.
Enthält einen homogenen 4x4 Matrixcontainer.
Enthält die Daten eines Mesh-Objektes.
AnimationOptions
AnimationSet
Boolean
Boolean2d
ColorRGB
ColorRGBA
Coords2d
FloatKeys
FrameTransformMatrix
Frame
Header
IndexedColor
Material
Matrix4x4
Mesh
VIII-100 Liste einiger Templates von X-Dateien. Quelle: (21)
S e i t e | LXVII

Similar documents