Dokumentation Gegenüberstellung vom Direct3D und Java3D

Werbung
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Seminararbeit Java 3D
“Gegenüberstellung von Direct3D und
Java3D am praktischen Beispiel“
Florian Heidinger
[email protected]
1
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Seminararbeit Java 3D
“Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Inhalt
Einführung
1
Ist ein Vergleich von Java3D und Direct3D möglich?
2
Architektur von Java3D und Direct3D
4
Warum gibt es Direct3D und was will es?
5
Eine Anwendung in Java3D
7
Ein neues geometrisches Objekt
7
Ein Klassenentwurf für Java3D
9
Klasse Pyramide (Java3D)
10
Klasse Pyramidenboden (Java3D)
11
Klasse Pyramidenflaeche (Java3D)
14
Eine einfache Beispielszene
16
Ausführung der internen Form
20
Die Anwendung in Direct3D
21
Initialisierung der Zeichenfläche
25
Initialisierung von Direct3D
26
Die Direct3D Rendering Pipeline
32
Die Szene in Direct3D
34
Geometriedefinition in Direct3D
37
Das 3D Koordinatensystem von Java3D und Direct3D
38
Beleuchtung in Direct3D
39
Texturen und Material in Direct3D
40
Zeichnen der Szene
42
Literaturhinweise
45
Anlage: Quellcodes
2
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Einführung
Die Seminararbeit mit dem Thema „Gegenüberstellung von Direct3D und Java3D am
praktischen Beispiel“ veranschaulicht grundlegende Gemeinsamkeiten und Unterschiede
der beiden 3D-Graphik-APIs anhand eines konkreten Anwendungsbeispiels. Dabei soll auf
die wesentlichen Aspekte, die bei der Konzeption und Umsetzung dieses Beispiels in
beiden Technologien eine Rolle spielten aufgezeigt, erläutert und soweit dies möglich ist
vergleichend gegenübergestellt werden.
Ziel dieser Auseinandersetzung kann deshalb keine vollständige Betrachtung der
Anwendungsschnittstellen, sondern lediglich eine Fokussierung auf wesentliche Merkmale
und Unterschiede aus Sicht des Anwendungsentwicklers sein.
Ist ein Vergleich von Java3D und Direct3D möglich?
Ziel der
Seminararbeit
Im Gegensatz zu Vergleichen zwischen OpenGL und Direct3D, die im Grunde genommen
immer der scheinbar entscheidenden Frage nachgehen: „Welche der beiden Technologien
liefert schönere, schnellere Ergebnisse, ist vollständiger und einfacher zu handhaben ?“ ,
findet man kaum Literaturstellen oder Quellen im WWW, die sich mit einer
Gegenüberstellung von Java3D und Direct3D befassen. Die Thematik dieser Seminararbeit
erschließt also ein völlig neues, bisher fast unbearbeitetes Gebiet der
Graphikprogrammierung.
Man kann nun zwei Gründe für das Fehlen einer vergleichenden Beurteilung dieser, in der
modernen Computergraphik zentralen Technologien annehmen. Entweder sind Java3D und
Direct3D zwei völlig verschiedene, nicht vergleichbare „Welten“ oder sie stehen nicht in
einem Konkurrenz-, sondern in einer Art Kooperationsverhältnis – einer Symbiose zweier
graphischer Systeme, um einen Begriff aus der Ökologie zu bemühen.
Wie die nachfolgenden Ausführungen deutlich machen werden ist die Antwort auf diese,
In welchem
Verhältnis
stehen
Java3D und
Direct3D ?
sich zwangsläufig bei der Auseinandersetzung mit diesen beiden GraphikprogrammierungsSchnittstellen aufkommenden Frage in der Realität nicht eindeutig zu liefern.
Unbestritten ist die Tatsache, dass in Java3D und Direct3D tatsächlich zwei, zumindest auf
den ersten Blick völlig verschiedene Konzepte und Philosophien aufeinandertreffen.
Auf der einen Seite eine Allianz von Unternehmen wie Sun Microsystems, Intel, Apple oder
IBM, um nur die wichtigsten zu nennen, welche 1997 die Java3D API ins Leben rief - auf der
anderen Seite Microsoft mit seinem seit Anfang der 90er Jahre entwickelten DirectX-Paket,
dessen wesentlicher Bestandteil Direct3D darstellt. Unternehmen mit völlig
unterschiedlichen, ja teilweise gegensätzlichen Philosophien.
Während Sun Microsystems seit jeher großen Wert auf Plattformunabhängigkeit,
Kompatibilität und Portabilität („Write once, run anywhere“) legt und der Open-Source
3
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Gedanke vorherrscht, entwickelt Microsoft ausschließlich Lösungen für eigene Plattformen
und Betriebssysteme, die zumeist lizensierungspflichtig sind, wobei das DirectX Self
Development Kit (SDK) hier eine der wenigen Ausnahmen darstellt.
Es ist samt Dokumentation frei verfügbar und kann unter http://www.mircrosoft.com/directx
Direct3D
SDK
Download
bezogen werden.
Ferner treffen aufeinander der objektorientierte Ansatz, der in Java3D konsequent
umgesetzt wurde und der eher prozedurale Ansatz, den man in Direct3D oder auch
OpenGL verfolgt.
Damit zusammenhängend finden auch andere Programmiersprachen Verwendung.
Während in Java3D die einzig verwendbare Sprache natürlich Java darstellt, kann man in
Direct3D scheinbar frei wählen, ob man seine Anwendung lieber in C/C++, Visual Basic
oder Microsoft Java entwickeln möchte.
Diese Liste der Gegensätzlichkeiten ließe sich noch beliebig fortsetzen. Allein eine
Diskussion der Thematik welche Programmiermodelle und Sprachen für die
Graphikprogrammierung besser geeignet sind, könnte leicht einige Dutzend Seiten füllen
und würde hier nicht zum erwünschten Ergebnis führen.
Trotzdem kann man festhalten, dass die Entwicklung verschiedener Graphik-APIs
maßgeblich von den Entwicklung der Programmiermodelle, der Programmiersprachen und
Einordnung
Java3D und
Direct3D in
3D Graphik
APIs
natürlich auch dem technischen Fortschritt in der Entwicklung leistungsfähiger GraphikHardware geprägt wurde. Diese Entwicklungen öffneten die Graphikprogrammierung für
eine Vielzahl unterschiedlicher Anwender und Anwendungen mit verschiedenen
Ansprüchen und Erwartungen an eine 3D-Graphik-API.
APIBezeichnung
1.
Siggraph
Generation
CORE
2.
Generation
PHIX/PEX
3.
OpenGL® /
Generation
Direct3D®
Hardware
Programmier-
Anwender /
sprache
Anwendungsbereiche
Raster
Terminals /
FORTRAN
Minicomputer
Workstations
C
3D
C/C++
beschleunigte
VisualBasic
Workstations
MS Java
MCAD
Unternehmen
MCAE
Workstation-Benutzer
Animation
Simulation
professionelle Spiele
Entwickler
Netzwerk4.
Generation
Java3D®
Rechner,
Java Terminals,
Java
Spiele Entwickler
Web-Designer
Spiele-Konsolen
4
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Wie man aus der vorangehenden Tabelle ersehen kann, sind OpenGL und Direct3D
Graphik-APIs der älteren dritten Generation, während Java3D eine API der neuen vierten
Aus „Java
Markets
Whitepaper”,
S. 2
Generation darstellt.
Da die APIs einer bestimmten Generation in der Regel keine einfachen
Weiterentwicklungen der APIs älterer Generationen darstellen, sondern jeweils völlig
andere Technologien und Konzepte aufgreifen, sowie andere Benutzerkreise ansprechen,
scheint ein Vergleich von Java3D und Direct3D in der Tat nicht besonders sinnvoll zu sein.
Architektur von Java3D und Direct3D
Warum also kann die Aussage der völlig verschiedenen „Welten“ nun trotzdem nicht
eindeutig beantwortet werden ?
Trotz aller Unterschiede muss folglich in irgend einer Form doch ein mehr oder weniger
enger Zusammenhang zwischen Java3D und Direct3D bestehen.
Betrachten wir die Architektur der beiden APIs etwas näher, um diesen Sachverhalt zu
klären.
Die Java3D API stellt eine „plattformunabhängige High-Level 3D API mit einem hohen Grad
an Interaktivität“ (aus „The Java 3D API – Technical Whitepaper“), also eine abstrakte, völlig
Was sind
High- und
Low-Level
GraphikAPIs ?
plattform- und hardwareunabhängige Programmier-Schnittstelle dar. Eine solche API
benutzt in der Regel andere, hardwarenähere APIs, um ihre Aufgaben durchzuführen,
indem sie entsprechende Aufrufe aus den Benutzerprogrammen in native Befehle
umwandelt und der Low-Level API zur Verarbeitung übergibt. Als Low-Level APIs finden
neben OpenGL und QuickDraw3D, wie vielleicht bereits vermutet unter WindowsBetriebssystemen vor allem die Direct3D API Verwendung.
Man kennt hier verschiedene Versionen der Java3D zur Umwandlung in native Aufrufe
gegen Direct3D oder OpenGL, die von der Sun Microsystems-Webseite
(http://java.sun.com/) heruntergeladen und installiert werden können. Im Rahmen der
Java3D API
1.2 für
Direct3D
Download
Ausarbeitungen des Seminars „Java3D“ wurde jedoch hauptsächlich die Java3D API in der
Version 1.2 gegen die Direct3D API verwendet.
Java-Applet oder Anwendung
Java3D
Aus „Java
3D API“,
S. 1
Java2
C/C++-Direct3D Anwendung
Direct3D API
Native Graphik-API Aufrufe
OpenGL
QuickDraw3D
Graphik-Treiber und -Hardware
5
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Wie Java3D oder auch OpenGL, stellt Direct3D eine Sammlung von APIs, also eine Menge
von Funktionen und Methoden dar, die von Anwendungsprogrammen oder High-Level APIs
zur Graphikausgabe verwendet werden können.
Zusammenspiel von
Java3D und
Direct3D
Der vereinfachte Architekturüberblick lässt an dieser Stelle bereits vermuten, dass unter
Windows-Betriebssystemen für graphische Szenen gleichen Inhaltes (in Java3D- und
Direct3D-Anwendungen) das Ergebnis der von der Java Virtual Machine mit Hilfe der
Java3D-Implementierung durchgeführten Umwandlung in native Aufrufe mehr oder weniger
exakt der (kompilierten) Form der Direct3D-Anwendung entspricht.
Warum gibt es Direct3D und was will es?
Setzen wir uns zunächst mit der entscheidenden, existenzberechtigenden Frage nach der
Notwendigkeit einer Low-Level Graphik- oder 3D-API wie Direct3D auseinander.
Wie bereits anhand der tabellarischen Übersicht im vorangegangenen Abschnitt verdeutlicht
wurde, sind die Entwicklungen im Graphikbereich vor allem auf Fortschritte in der
Entwicklung von Hardware und Programmiersprachen zurückzuführen. Zu nennen sind hier
vor allem das Aufkommen von Graphikkarten mit hardwareunterstützter 3D
Beschleunigung, die Steigerung der Rechenleistung bei handelsüblichen Rechnersystemen
und objektorientierter Programmiersprachen. Die daraus resultierende Vielfalt an
Hardwarevarianten und Funktionen stellte die Entwickler von multimedialen oder graphisch
aufwändigen Anwendungen vor nicht unerhebliche Probleme.
Zweifelsohne mussten die Möglichkeiten der modernen Hardwarebeschleuniger genutzt
werden, um Fortschritte in Geschwindigkeit und Darstellungs-Qualität der Anwendungen zu
erreichen. Aufgrund der Vielzahl von Hardwarevarianten und Treibern, die im Umlauf waren
und zumindest was die besonderen Funktionen anbelangte jeweils spezielle eigene
Ansteuerungsroutinen verlangten, war dies jedoch nur eingeschränkt möglich. Dies hätte
eine Vervielfachung des Entwicklungsaufwandes bedeutet und vom Anwendungsentwickler
weitreichende Kenntnisse der Treiber- und Hardwarearchitekturen erfordert.
Wie wurden
graphische
Anwendungen vor
Direct3D
oder Java3D
programmiert ?
Es fehlte ein einheitlicher Standard zur Hardwareansteuerung, der eine
hardwaretransparente Anwendungsentwicklung ermöglichen konnte.
Man beschränkte sich deshalb auf einen Kompromiss, indem man Kompatibilitäts-Gruppen
bildete und jeweils nur die Hardware-Eigenschaften in den Anwendungen unterstützte, die
bei allen Gruppenmitgliedern vorhanden und auf die gleiche Art und Weise ansteuerbar
waren. Innovationen und spezielle Entwicklungen der Hardwarehersteller, die
Verbesserungen in Graphikqualität und Performance hätten bringen können wurden also
nicht genutzt. Diese Tatsache verhinderte jahrelang entscheidende Fortschritte im
Graphikbereich, wie sie in anderen Hardwarebereichen an der Tagesordnung waren.
Hatte die geschilderte Methode zwar graphisch wenig für den Endbenutzer zu bieten, besaß
sie jedoch zumindest den Vorteil der Performanz, da auf DOS-Ebene direkt gegen die
Hardwaretreiber programmiert wurde. Mit der schrittweisen Ablösung von MS-DOS entstand
6
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
jedoch ein dringender Handlungsbedarf. Zwar kennt auch Windows selbst bereits eine
Graphik-API, das sogenannte Graphics Device Interface (GDI), diese war jedoch keinesfalls
in der Lage, den gestellten Anforderungen gerecht zu werden.
Diese Tatsache führte schließlich zur Entwicklung von DirectX.
DirectX sollte einerseits das Problem der unvollkommenen Hardwareausnutzung
Was ist
DirectX ?
beseitigen, indem es sich als, die transparente Ansteuerung der zugrundeliegenden
Hardware durch die Anwendung ermöglichende Software-Schnittstelle zwischen
Anwendung und Hardware bzw. Hardwaretreiber legt, andererseits aber trotzdem eine
möglichst performante und direkte Hardwareansteuerung (daher auch der Name Direct3D)
unter Umgehung der Standard Windows-Graphikroutinen (GDI) ermöglicht.
Hierfür stellt die DirectX API verschiedene, jeweils eine bestimmte Hardwarekategorie
ansteuernde Bestandteile bereit, von denen für die Graphikausgabe Direct3D verantwortlich
zeichnet.
Wie bereits im Architekturüberblick erwähnt, handelt es sich bei der Direct3D API um eine
sogenannte Low-Level API, die jedoch im Gegensatz zu vielen anderen maschinen- oder
Was
versteht
man unter
dem
Hardware
Abstraction
Layer ?
hardwarenahen Schnittstellen einen geräteunabhängigen Zugriff auf die Graphikhardware
aus einem Anwendungsprogramm heraus erlaubt. Dies schließt die Verwendung von, durch
die Hardware beschleunigten Funktionen ein. Möglich wird dies über das sogenannte
Hardware Abstraction Layer (HAL), welches der Direct3D API signalisiert, welche
Funktionen durch die Hardware unterstützt werden und welche nicht. Die API lässt dann so
viele Berechnungen wie möglich durch die spezialisierten Graphikprozessoren erledigen,
um eine Performance-Steigerung zu erzielen. Das HAL, welches vom Hardwarehersteller
geliefert wird ist meist Bestandteil des Hardwaretreibers und sorgt dafür, dass die
geräteunabhängigen Direct3D-Nachrichten in die entsprechenden treiberverständlichen
Befehle umgesetzt werden. Von der Hardware nicht unterstützte Funktionen werden von der
API softwareseitig emuliert. Dies stellt sicher, dass auch Rechnersysteme mit älteren
Graphikkarten die Direct3D-Anwendungen ausführen können.
Java3D-Anwendungen, welche die Java3D Implementierung für Direct3D verwenden,
können also implizit die hardwarebeschleunigten Funktionen nutzen.
Trotz der soeben angesprochenen Vereinfachungen für den Anwendungsentwickler fordern
Low-Level-APIs im Allgemeinen und Graphik APIs der dritten Generation im Besonderen
Wo liegen
Nachteile
von LowLevel APIs
der 3.
Generation
?
sehr viel Programmiererfahrung, um anwendungsseitig ein optimales Laufzeitverhalten zu
erzielen.
Diese Tatsache führte zur Entwicklung der Java3D API, die vor allem für, in der
Graphikprogrammierung weitgehend unerfahrene Programmierer oder GelegenheitsComputergraphiker eine interessante Alternative darstellt. Der Entwickler kann sich hier voll
und ganz auf Konzeption und Umsetzung der eigentlichen Szene konzentrieren, ohne
ständig die Auswirkungen (der Reihenfolge) bestimmter Anweisungen auf die Performance
7
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
seiner Anwendung im Hinterkopf behalten zu müssen. Die Java3D Implementierung nimmt
dem Anwendungsentwickler hier einen Großteil der Arbeit ab, für die ein Direct3DProgrammierer selbst Verantwortung trägt.
Trotzdem sollte der Java3D-Entwickler einige grundlegende Regeln beachten, die der
Java3D Implementierung die Aufgabe erleichtern und akzeptable Verbesserungen und
Optimierungen erlauben.
Eine Anwendung in Java3D
Anhand eines konkreten Beispiels soll nun in einem ersten Schritt auf den Aufbau einer
exemplarischen, in Java3D programmierten Szene eingegangen werden, die neben der
Definition eines neuen geometrischen Objekts, seiner Materialeigenschaften und seiner
Texturierung auch die Verwendung von Transformationen, Beleuchtung und Interaktionen in
Java3D aufzeigt.
In einem nächsten Schritt wird die, bei der Kompilierung und Ausführung dieser Szene
entstehende interne Form diskutiert und versucht, die von der Java Virtual Maschine
erzeugten nativen Aufrufe gegen die Direct3D-API mit Hilfe der Struktur einer geeigneten, in
C++ gegen die Direct3D API programmierten Anwendung mit identischem Szeneninhalt zu
erläutern.
Ein neues geometrisches Objekt
Im Rahmen der Beispielszene soll eine Pyramide mit quadratischer Grundfläche als neue,
in den Java3D-Bibliotheken noch nicht vorhandene Geometrie-Klasse mit Hilfe der zur
Verfügung stehenden graphischen Primitive modelliert werden.
Auch nach intensiver Suche in verschiedenen Quellen im weltweiten Netz und Konsultation
der Java3D API-Spezifikation, sowie des Tutorials konnte ich neben den beinahe
obligatorischen Sphere, Cone- und Cube-Objekten keine weiteren, aus Primitiven
gebildeten vordefinierten geometrischen Objekte oder Objektbibliotheken finden.
Hier ist es also Aufgabe des Programmierers sich selbst einen Vorrat an immer wieder
benötigten einfach zu handhabenden aus Bryce, 3DStudioMax, Lightwave oder Maya
bekannten weiteren geometrischen Objekten wie Röhren (offene Zylinder), Torus („Donut“)
oder eben Pyramiden zu programmieren, die in zukünftigen Projekten mit wenigen
Anweisungen in den Szenengraphen eingebunden werden können.
Die wachsende Java3D-Gemeinde wäre sicherlich über eine solche, frei zur Verfügung
gestellte Erweiterung alles andere als unglücklich.
8
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Im folgenden sollen die Überlegungen zur Geometrieberechnung kurz aufzeigt werden.
Eine einfache Pyramide mit quadratischer Bodenfläche besteht aus einem quadratischen
Boden-Polygon und vier, jeweils auf dem Boden-Polygon aufsetzenden gleichschenkligen
kongruenten Dreiecken, die an ihren Spitzen gegeneinandergeneigt sind. Die Berechnung
der Geometrie gestaltet sich in diesem Fall sehr einfach und kann ohne weiteres ohne
Zuhilfenahme von visuellen Modellierungswerkzeugen berechnet und erstellt werden.
Sinnvollerweise konzipiert man ein neues geometrisches Objekt in Java3D aber auch in
anderen graphischen Systemen so, dass zumindest seine wesentlichen geometrischen und
Worauf
sollte man
bei der
Definition
eines neuen
geometrisch
en Objekts
achten ?
visuellen Eigenschaften durch Angabe von Parametern entsprechend den Wünschen der
Benutzer eingestellt werden können. Man erreicht hierdurch eine optimale
Wiederverwendbarkeit des Objektes, was es somit für die Aufnahme in eine Java3D
Graphik- und Objektbibliothek geeignet macht.
Um die Wiederverwendbarkeit der Pyramide für einen möglichst großen Benutzerkreis zu
gewährleisten, sollte der Anwendungsentwickler in der Lage sein, die Seitenlänge der
quadratischen Grund- oder Bodenfläche, sowie die Höhe der Pyramide über Parameter
einzustellen. Aus diesen Angaben können dann die konkreten Modellkoordinaten leicht
erzeugt werden.
Seitenlänge der Pyramide (fBreite)
Höhe der Pyramide
(fHoehe)
Den Mittelpunkt sollte bei neu zu schaffenden geometrischen Objekten immer der Ursprung
des lokalen Koordinatensystems (Modellkoordinatensystem) bilden. Eine abweichende
Modellierung der Modellkoordinaten kann bei Transformationen (Rotationen) zu
unerwarteten oder unerwünschten Ergebnissen führen.
9
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Ein Klassenentwurf für Java3D
Ein Klassenentwurf für die Pyramide, die zur direkten Verwendung im Szenengraphen von
einer Gruppenklasse (Group oder GroupNode) abgeleitet werden sollte und vier
Pyramidenflächen, sowie eine Bodenfläche, die jeweils von Shape3D abgeleitet werden als
Referenz aggregiert, stellt sich wie folgt dar:
Javax.media.j3d.*
Group
Shape3D
setGeometry()
setAppearance
()
addChild()
Pyramide
Pyramide
fHoehe : float
fBreite : float
shp3dFlaechen : Shape3d[]
app : Appearance
Pyramide(float fHoehe, float fBreite)
erstelleAppearance() : Appearance
GetFlaechen() : Shape3D[]
1
1
1
Pyramidenboden
fHoehe : float
fBreite : float
pt3fBoden : Point3f[]
farbe : Color3f
Pyramidenboden(float fHoehe, float fBreite, Appearance app)
erstelleAppearance() : Appearance
erstelleGeometrie() : Geometry
getCoordinates() : Point3f[]
4
Pyramidenflaeche
fHoehe : float
pt3fBodenkoord : Point3f[]
punkt1 : Point3f
punkt2 : Point3f
farbe : Color3f
Pyramidenflaeche(float fHoehe, float fBreite, Appearance app)
erstelleAppearance() : Appearance
erstelleGeometrie() : Geometry
10
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Klasse Pyramide (Java3D)
Im Konstruktor der von „Group“ abgeleiteten Klasse „Pyramide“ wird ein Objekt der Klasse
„Pyramidenboden“ instanziiert, die von dieser Instanz errechnete Geometrieinformation (der
Bodenfläche) über „getCoordinates()“ abgerufen und anschließend vier Objekte der Klasse
„Pyramidenfläche“ erzeugt, die im Array „shp3dFlaechen“ zur späteren Verwendung
abgelegt werden. Bei der Instanziierung der Pyramidenflächen werden den Konstruktoren
die beiden jeweils richtigen über „getCoordinates()“ abgerufenen Eckpunkte der
Bodenfläche übergeben, die dann in Kombination mit dem Raumpunkt der Pyramidenspitze
ein Dreieck der Seitenfläche bilden.
Die so aggregierten Referenzen werden dann in einer Schleife durchlaufen und über die
von „Group“ geerbte „addChild()“-Methode dem Pyramiden-Knoten mittels einer ParentChild-Beziehung zugeordnet.
Vor der Instanziierung der Teilflächen wird im Konstruktor die Methode
„erstelleAppearance()“ aufgerufen.
Diese hat die Aufgabe, ein geeignetes „Appearance“-Objekt zu instanziieren und mit
Referenzen auf zu erstellende „ColoringAttributes“, „Material“ und „Texture2D“ Objekte zu
füllen.
Jedes dieser Objekte wird mit geeigneten Werten belegt, die entweder direkt dem
Konstruktor übergeben oder über set-Methoden einstellt werden können.
Dies beinhaltet das Setzen der Polygonfüllfarbe als RGB(A)-Wert und des Shading-Modus
in der „Coloring-Attributes“ Instanz, der ambienten, diffusen, spekulären, und emittierten
Objekt-Farbe, sowie der Größe der spekulären Glanzpunkte im „Material“-Objekt und das
Laden und Zuweisen der gewünschten Oberflächentextur bzw. das Einstellen des
Texturierungsmodus.
Neben Gouraud- (SHADE_GOURAUD) und Flat-Shading (SHADE_FLAT) kennt Java3D
Welche
ShadingModi kennt
man in
Java3D
(Direct3D) ?
hier noch die zusätzlichen Optionen NICEST und FASTEST, die die am besten
erscheindende bzw. die schnellste von der installierten Hardware bzw. der von Java3D
genutzten Low-Level API unterstützte Shading-Variante auswählen. In Direct3D entspricht
NICEST dem GOURAUD_SHADING, FASTEST entsprechend dem ebenfalls von Direct3D
unterstützten FLAT_SHADING-Modus.
Wie Direct3D, verwendet auch Java3D standardmäßig den GOURAUD-SHADING Modus,
um dem Benutzer die bestmögliche Darstellungsqualität zu bieten.
Anschließend muss ein Objekt der Klasse „Material“ erzeugt und dem „Appearance“-Objekt
zugewiesen werden. Die Klasse „Material“ verwendet vier verschiedene RGB-Farbwerte für
ambientes, diffuses, spekuläres und vom Objekt emittiertes Licht, sowie einen Wert, der die
Reflexionseigenschafen des mit dem Material versehenen Objektes definiert.
11
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Zusätzlich zu den Materialeigenschaften für ein geometrisches Objekt ist häufig der Einsatz
von Texturen sinnvoll, um feine komplexer Strukturen auf der Oberfläche durch eine
Graphik nachzubilden.
Die Verwendung einer Textur gestaltet sich in Java3D und Direct3D von der
Vorgehensweise sehr ähnlich.
Zunächst ist in einem geeigneten Bildbearbeitungsprogramm eine Texturgraphik zu
erstellen. In beiden Technologien sind hier Bildabmessungen gefordert, die in Höhe bzw.
Breite einer Potenz von 2 entsprechen. Unterschiede kann man jedoch bei den von den die
Texturen ladenden Klassen bzw. Objekten feststellen. Während Java3D vor allem die
gebräuchlichen JPG und GIF-Formate unterstützt, sind in Direct3D zusätzliche Formate wie
zum Beispiel Bitmap-Dateien (BMP) verwendbar. Zum Laden einer Texturgraphik bieten
beide APIs in ihren Klassen- / Objektbibliotheken geeignete Komponenten an, die eine
Textur aus einer Datei in ein programmtechnisch verwendbares Objekt übertragen können.
Eine dieser Komponenten stellt die Klasse „TextureLoader“ des Java3D Hilfspaketes
„com.sun.j3d.utils“ dar, mit deren Hilfe sehr einfach eine Bilddatei in geeignetem Format in
einer programmtechnischen Form als „ImageComponent2D“-Objekt abgelegt werden kann.
Klasse Pyramidenboden (Java3D)
Im Konstruktor der Klasse „Pyramidenboden“ werden die übergebenen Parameter in den
Klassenvariablen abgelegt, die Klassen-Methode „erstelleGeometrie()“ aufgerufen, deren
Rückgabereferenz, sowie die Rückgabereferenz der Klassen-Methode
„erstelleAppearance()“ werden dann den von Shape3D geerbten Methoden „setGeometry()“
und „setAppearance()“ übergeben, um Geometrie und visuelle Eigenschaften des
Pyramidenbodens entsprechend festzulegen.
Die im Konstruktor der Klasse „Pyramidenboden“ aufgerufene Methode
„erstelleGeometrie()“ errechnet die Eckpunkte der quadratische Bodenfläche bei einer, über
die Konstruktorparameter eingestellten Pyramidenhöhe „fHoehe“ und einer Seitenlänge
„fBreite“ und legt sie im Array „pt3fBoden“ ab.
Java3D verwendet ein rechtshändiges
V0
V3
Koordinatensystem. Bei der Angabe der
In welcher
Reihefolge
sind die
Raumpunkte
in Java3D
anzugeben
?
Raumpunkte für das Bodenquadrat ist jedoch
entgegen der sonstigen Gewohnheit darauf zu
X
achten, dass diese im Uhrzeigersinn und nicht
entgegengesetzt spezifiziert werden, da die
Bodenfläche aus einer unteren Ansicht der
Pyramide sichtbar sein soll.
V1
V2
Z
Raumpunkte eines, aus der Aufsicht
sichtbaren Polygons in einem
rechtshändigen Koordinatensystem
12
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Zusätzlich zu den Geometriedaten ist es sinnvoll für Shading- oder Texturierungseffekte
Normalenvektoren und Texturkoordinaten anzugeben.
Java3D bietet zur Unterstützung des Entwicklers hier eine Helfer-Klasse zur automatischen
Generierung der Normalenvektoren. Diese ist besonders bei komplexeren Objekten sehr
hilfreich, erweist sich aber bei Primitiven, in denen Raumpunkte mehrfach verwendet
werden als wenig handlich.
Der Normalengenerator wandelt zum Beispiel ein TRIANGLEFANARRAY implizit zunächst
ein indiziertes Primitiv um. Für jeden Eintrag der Indexliste wird dann ein Normalenvektor
generiert, so dass für mehrfach verwendete Raumpunkte auch mehrere Normalenvektoren
je nach Verwendungshäufigkeit berechnet werden. Die Anzahl der Vektoren, die der
Generator liefert stimmt letztlich also nicht mit der Anzahl der Raumpunkte im ursprünglich
erzeugten Primitiv überein und eine Zuweisung über „setNormals()“ verursacht einen
Ausnahmefehler.
Da die Geometrie der Pyramide von benutzerdefinierten Parametern abhängt, gestaltet sich
die manuelle Berechnung der Normalenvektoren, wie sie in der Beispielanwendung
Wie können
Normalenvektoren von
Hand
errechnet
werden ?
durchgeführt wurde, nicht so einfach wie bei einem Würfel. Man benötigt schon ein klein
wenig mathematisches Grundwissen, um eine einfache Möglichkeit der
Normalenberechnung zu realisieren.
Graphische Systeme verwenden als Grundelemente zur Nachbildung komplexerer Objekte
Dreiecke. Diese besitzen den Vorteil, dass alle Eckpunkte auf einer Ebene liegen, also
koplanar sind. Anders herum betrachtet kann man feststellen, dass diese Ebene durch die
drei Raumpunkte eines Dreiecks eindeutig definiert ist. Sieht man die Raumpunkte als
Vektoren an, so kann man die beiden, die Ebene aufspannenden Vektoren gewinnen,
indem man jeweils den Vektor der Dreiecksspitze von den Vektoren der Basispunkte
subtrahiert. Einen auf dieser Ebene senkrecht stehenden Vektor (den Normalenvektor)
erhält man dann durch Bildung des Kreuzproduktes zwischen den beiden Vektoren. Bei der
Durchführung dieser Vektorfunktion kann man die in der Klasse „Vector3f“ definierte
Funktion „cross“ verwenden. Normalisiert man anschließend den Ergebnisvektor, so erhält
man im Prinzip das gleiche Ergebnis, das auch der Normalengenerator liefert.
Zur Verwendung von Texturen muss die Geometrieinformation um eine weitere zusätzliche
Komponente, die Texturkoordinaten ergänzt werden, die in Java3D als Array von Objekte
der Klasse „TexCoord2f“ oder „TexCoord3f“ erzeugt werden.
Java3D bietet auch hier, wie bei den Normalen eine Klasse zur Generierung der
Texturkoordinaten an, die vor allem beim Versehen komplexerer Objekte mit
Texturkoordinaten eine große Hilfe darstellt. Das einfache Pyramiden-Beispiel erlaubt
jedoch ohne weiteres eine Angabe der Texturkoordinaten von Hand, die sich für das, die
Bodenfläche bildende Quadrat, sowie die vier Seitenflächen eigentlich von selbst ergibt.
13
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Gegen Ende der Methode wird eine, von der Klasse „Geometry“ abgeleitete Primitv-Klasse
verwendet, um ein Objekt zu instanziieren, welches die Geometriedaten, Normalenvektoren
und Texturkoordinaten aufnimmt. Die Referenz auf dieses Objekt wird dem Konstruktor der
Klasse „Pyramidenboden“ dann über den „return“-Befehl zurückgegeben und kann mit Hilfe
der von Shape3D geerbten Methode „setGeometry()“ der Bodenfläche zugeordnet werden
(siehe Konstruktor).
Zur Beschreibung der Bodenfläche bietet sich als geometrisches Primitv hier die
Verwendung eines QUADARRAY an, bei dem jeweils vier Raumpunkte der zugeordneten
Geometrie zu einem rechteckigen Polygon zusammengefasst werden. Bei der
Instanziierung eines QUADARRAY-Objektes sind hier die Anzahl an Raumpunkten des
Primitivs, sowie die Art der Daten (Geometrie, Normalenvektoren, Texturkoordinaten)
anzugeben, die für das Primitiv verwendet werden sollen.
Die errechneten Geometrie, -Normalen und Texturdaten können dann entweder einzeln
oder als Array über set–Methoden der Primitiv-Klasse eingestellt werden.
Zur effizienteren internen Verarbeitung werden jedoch auch die durch QUADARRAY
definierten Polygone immer auf Dreiecke zurückgeführt, da dann in jedem Fall sichergestellt
ist, dass alle das Polygon (Dreieck) spezifizierenden Raumpunkte in einer Ebene liegen
(koplanar sind).
Ähnlich wie im Konstruktor der Pyramidenklasse stellt sich das Erzeugen und Zuweisen der
visuellen Eigenschaften mittels eines Objektes der Klasse „Appearance“ dar. Die vom
Konstruktor der Klasse „Pyramidenboden“ aufgerufene Methode „erstelleAppearance()“
instanziiert ein entsprechendes Objekt und gibt dieses dem Konstruktor zurück, der
daraufhin die Zuweisung über die von Shape3D geerbte Methode „setAppearance()“
durchführen kann.
Die Methode „erstelleAppearance()“ führt dabei ähnliche Operationen aus, wie die
entsprechende, in der Klasse Pyramide definierte Methode, setzt in den „Texture2D“,
„Material“ und „ColoringAttributes“-Objekten jedoch andere Werte.
Bei der Instanziierung eines Pyramidenboden-Objekts werden also automatisch die beiden
Referenz-Beziehungen zur Festlegung der geometrischen und visuellen Eigenschaften
Wie sieht
der
Teilgraph für
einen
Pyramidenboden aus ?
hergestellt.
Der Szenenteilgraph für einen Pyramidenboden stellt sich somit folgendermaßen dar:
PyramidenbodenTeilgraph
14
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Klasse Pyramidenflaeche (Java3D)
Der Pyramidenflächen-Konstruktor speichert wie die bereits beschriebenen Konstruktoren
die übergebenen Parameter zunächst in privaten Klassenvariablen ab. Im Gegensatz zur
„Pyramidenboden“-Klasse verwendet er mit „erstelleGeometrie()“ jedoch lediglich eine
einzige eigene Methode
Über die von Shape3D geerbte Methode „setAppearance()“ wird von jeder
Pyramidenseitenfläche eine Referenz auf das, durch die Klasse „Pyramide“ instanziierte
und dem Konstruktor einer Pyramidenfläche als Referenz übergebene „Appearance“-Objekt
(app) verwendet.
Die seitlichen, die Pyramidenform bildenden Dreiecke verwenden außer denen, für die
Bodenfläche definierten Punkten lediglich einen weiteren Raumpunkt - den der
Pyramidenspitze. Die beiden anderen, erneut zu verwendenden Punkte können, da sie für
die Grundfläche in der Methode „erstelleGeometrie()“ bereits errechnet und als Objekte der
Klasse „Point3f“ instanziiert wurden, dem Konstruktor über die Referenzen punkt1 bzw.
punkt2 übergeben werden.
Eine solche Vorgehensweise, d. h. die Übergabe von Geometrie- oder Texturinformation
Geometrieund
Texturdaten
sind falls
möglich per
Referenz zu
übergeben
per Referenz wird auch im „Java 3D API Collateral — 1.2.1 Performance Guide“ als
speichersparende Maßnahme bei der Erstellung von geometrischen Objekten in Java3D
empfohlen, auch wenn sie für ein derart einfache geometrische Objekte wie im vorliegenden
Fall kaum ins Gewicht fallen dürfte.
So wird das erste Dreieck aus der immer einbezogenen Spitze der Pyramide mit den
Raumkoordinaten (0.0, fHoehe/2, 0.0) und den Raumpunkten v1 und v0 in dieser
Reihenfolge gebildet, um eine sichtbare Fläche (Front-Face) zu erhalten.
V0
S
V1
S
V3
V2
V1
V0
Alle weiteren Flächen können auf ähnliche Weise durch Übergabe der Raumpunkt-Paare
(v1, v2), (v2, v3), sowie v3 und v0 durch den Konstruktor der Klasse „Pyramide“ bei der
Instanziierung der Pyramidenflächen erhalten werden. In der Methode „erstelleGeometrie()“
müssen auch hier wieder, wie im Abschnitt zur Klasse „Pyramidenboden“ beschrieben, die
Normalenvektoren und Texturkoordinaten ermittelt und über die set-Methoden des
gewählten Geometrie-Primitivs gesetzt werden.
15
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Bei der Instanziierung eines geeigneten Primitv-Objektes kann man hierbei zum Beispiel auf
die Klasse TRIANGLESTRIPARRAY zurückgreifen. Die ersten drei spezifizierten
Raumpunkte bilden hier ein Dreieck, also genau das was in diesem Fall gewünscht ist.
Alle weiteren bilden mit den beiden unmittelbaren vorangehenden Raumpunkten jeweils ein
weiteres Dreieck, so dass ein Streifen aus Dreiecken gebildet werden kann.
Die vier Pyramidenseitenflächen bilden folgenden Teilgraphen:
Wie sieht
der
Teilgraph
einer
Pyramidenflaeche aus
?
Eine vollständige Pyramide stellt sich also als Teil-Szenengraph wie folgt dar:
Wie sieht
der
Teilgraph
einer
Pyramide
aus ?
Wie am oben abgebildeten Teilszenengraph für die Pyramide zu erkennen ist, kommen für
Sparsamer
Umgang mit
Shape3DObjekten
die Definition der Pyramide fünf Objekte der Klasse Shape3D zum Einsatz. Die Verwendung
von Shape3D Objekten oder von Objekten, deren Klassen von Shape3D abgeleitet wurden,
ist immer mit einem gewissen Mehraufwand beim Zeichnen der Szene verbunden.
Der „Java 3D API Collateral — 1.2.1 Performance Guide“ empfiehlt deshalb, lediglich „so
viele Shape3D Objekte wie nötig, so wenige wie möglich zu verwenden“.
16
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Der Anwendungsentwickler kann hier zum Beispiel die Zusammenfassung der Dreiecke, die
die Seitenflächen bilden zu einem einzigen Shape3D Objekt, welches etwa ein
TRIANGLEFANARRAY als Primitiv nutzt realisieren. Mehrere Objekte sind dagegen immer
dann erforderlich, wenn denen durch Shape3D-Objekte definierten geometrischen Objekten
jeweils verschiedene visuelle Eigenschaften, d.h. Materialien oder Texturen zugewiesen
werden sollen. Dies ist im vorliegenden Beispiel jedoch nicht der Fall, zumindest was die
Pyramidenseitenflächen anbelangt. Eine Zusammenfassung der Geometrie wäre hier also
ohne weiteres möglich. Der Optimierer der Java3D Umgebung kann den
Anwendungsentwickler bei der Zusammenfassung von Shape3D Objekten unterstützen,
indem er sie selbständig durchführt, sobald mehrere Shape3D Knoten, die dem gleichen
Gruppenknoten über eine Parent-Child-Beziehung zugeordnet wurden und dasselbe
Appearance-Objekt referenzieren zusammenfasst.
Eine einfache Beispielszene
Das soeben beschriebene, in Form von drei Klassen gebildete Pyramiden-Paket kann nun
sehr einfach in einer Java3D-Anwendung verwendet werden. Im Rahmen des Beispiel soll
das SimpleUniverse mit Standard-Einstellungen für die Betrachterposition und –ausrichtung
genutzt werden.
Die Definition des Szenengraphen unterteilt sich deshalb in die bekannten fünf Schritte:
Welche
Schritte sind
bei der
Erstellung
einer Szene
in Java3D
durchzuführ
en ?
1. Erzeugen eines Fensters und eines „Canvas3D“-Objektes
2. Erzeugen des Ansichtsgraphen (Verwendung des „SimpleUniverse“-Objektes)
3. Erzeugen des Inhaltsgraphen
4. Kompilieren des / der Graphen
5. Hinzufügen der Graphen in das „Locale“-Objekt des „SimpleUniverse“
Nachdem in einer Java3D-Anwendung ein geeignetes Fenster mit eingebettetem
„Canvas3D“-Objekt zur graphischen Ausgabe erzeugt wurde, kann die eigentliche Szene
beschrieben werden. Dies geschieht durch die Definition eines sogenannten
Szenengraphen, der durch Verwendung von Java3D-Objekten entsteht bzw. aus diesen
zusammengefügt wird. Man kennt hier Objekte zur Durchführung von Transformationen und
Animationen, zur Erstellung von geometrischen Objekten, Lichtquellen oder ähnlichem.
Tatsächlich kann eine Java3D Szene als Graph aus Knoten, den Objekten des
„javax.j3d.media“-Pakets bzw. von diesen abgeleiteten Objekten und Kanten, den
Beziehungen zwischen diesen Objekten dargestellt werden.
Die Anordnung oder Definition der Kanten-Beziehungen unterliegt jedoch einigen
Restriktionen. Während Gruppen-Objekte, also solche für Transformations- oder
Geometriegruppen lediglich einen Vorgänger, jedoch beliebig viele Nachfolgeknoten im
Szenengraphen besitzen dürfen, (parent-Child-Beziehung) sind alle von „Leaf“ abgeleiteten
17
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Klassen, zu denen vorwiegend die geometrischen Primitive, aber auch Beleuchtungsobjekte
gehören, nur einem direkten Vorgängerknoten zuzuordnen. Sie befinden sich also in den
Blättern des Szenengraphen, der somit das Aussehen einer hierarchischen Baumstruktur
annimmt.
Mit Hilfe einer zweiten Beziehungsart, den Relationen können von den Blatt-Objekten
mehrere andere Objekte referenziert oder diesen zugeordnet werden. Es handelt sich dabei
um Objekte von „NodeComponent“-Unterklassen wie Materialien, Texturen oder
Geometriedaten, die das Aussehen eines geometrischen Primitivs bestimmen bzw. seine
Geometrie definieren. Sie unterliegen nicht der Parent-Child-Restriktion, können also von
beliebigen Blatt-Objekten („Vorgängern“) auch mehrfach referenziert werden.
Wichtig ist bei der Definition der Beziehungen, dass der definierte Graph keine Zyklen
bildet, um eine eindeutige Umwandlung der Beziehungspfade in eine lineare, von einer
geeigneten Low-Level 3D-API verarbeitbaren Folge von nativen Aufrufen erlaubt.
Da entlang des Pfades von der Wurzel des Baumes zu einem der Blatt-Objekte, dem
sogenannten „Szenengraphen-Pfad“ vorwiegend Transformationen bzw.
Transformationsgruppen angeordnet sind, die Skalierungen, Rotationen oder Translationen
für die untergeordneten jeweils durch Parent-Child-Beziehungen verbundenen Blatt-Objekte
definieren, ist durch den Szenengraphen-Pfad eines Blatt-Objektes seine Größe,
Ausrichtung und Position in der Szene eindeutig definiert.
Dies sollte den aufmerksamen Leser an eine aus anderen graphischen Systemen wie zum
Beispiel OpenGL bekannte Transformationsart erinnern, die sich mit eben der
Positionierung von Objekten in der virtuellen Welt beschäftigt: der Welttransformation, die
ein Objekt mit seinen Modellkoordinaten in das für alle Objekte der Szene gemeinsame
Weltkoordinatensystem transformiert.
Die Welttransformationsmatrix zur Positionierung und Ausrichtung eines Objektes in der
virtuellen Welt ist wie der Szenen-Graph Pfad in der Regel für keine zwei Objekte gleich
(diese würden sonst an der gleichen Stelle gezeichnet). Sie ergibt sich durch Kombination
(Multiplikation) aller Transformationen auf dem Pfad von der Wurzel zum jeweiligen BlattObjekt.
Die Reihenfolge, in der die Objekte des Szenen-Graphen gezeichnet werden, ist also für
das Ergebnis nicht entscheidend und kann durch den Java3D-Programmierer auch nicht
beeinflusst werden.
Es ergeben sich im Zusammenhang mit der Betrachtung des Aufbaus und der Arbeitsweise
einer typischen Direct3D-Anwendung interessante Erkenntnisse über die Arbeitsweise der
Java3D Implementierung bei Kompilierung und Konvertierung des Szenengraphen in eine
für Low-Level APIs geeignete Form. Letztlich muss das Ergebnis der Verarbeitung dieser
optimierten Form durch die Java Virtual Machine dem einer kompilierten C++-Anwendung,
welche direkt gegen die Direct3D API programmiert wurde und die gleiche Szene darstellt,
mehr oder weniger exakt entsprechen.
18
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Jeder Szenengraph besitzt eine Wurzel, das sogenannte „VirtualUniverse“, dem ein oder
mehrere „Locale“-Objekte zugeordnet werden können. Man kann sich ein solches „Locale“Objekt als Weltkoordinatensystem der diesem Objekt zugeordneten Szene vorstellen. Eine
Verwendung mehrerer Weltkoordinatensysteme dürfte für die meisten Anwendungen nicht
sinnvoll sein und wird von Direct3D im übrigen auch nicht direkt unterstützt. Es handelt sich
hierbei folglich um eine, durch die Java3D API nachgebildete Funktion. Eine API-interne
Realisierung wäre zum Beispiel durch eine Erweiterung der Welttransformationen aller
Blatt-Objekte eines bestimmten „Locale“-Objektes um den relativen Ursprungsvektor
möglich.
Ein Szenengraph besteht aus zwei Teilgraphen: dem Inhaltsgraphen (Content Branch
Graph) und den Ansichtsgraphen (View Branch Graph), die wie die Bezeichnungen bereits
Woraus
besteht der
Szenengraph ?
erahnen lassen den Inhalt einer Szene, d. h.
Ausrichtung, Position und Größe von geometrischen
Objekten, ihr Aussehen und ihre Beleuchtung bzw.
Position und Ausrichtung des Betrachters bzw. der
Kamera beschreiben.
Im Rahmen der einfachen Beispielszene sollen
innerhalb des Szenengraphen zwei Pyramiden
erzeugt und über geeignete Welttransformationen
platziert und ausgerichtet werden. Zudem wird die
Szene durch zwei Lcihtquellen, ein
Umgebungslicht, und ein Spot-Light beleuchtet. Mit
Hilfe des MouseRotate-Verhaltens (Behavior) kann
ein Rotieren der gesamten Szene durch
Verschieben der Maus bei gedrückter linker
Maustaste realisiert werden.
Es ergibt sich der rechts abgebildete Szenen-Graph:
Zum Kompilierungszeitpunkt findet die Umwandlung
des Szenengraphen in eine optimierte interne
Was
geschieht in
Schritt 4 der
Szenendefinition ?
Darstellungsform statt, indem Transformationsgruppen
ohne gesetztes „Capability-Bit“ zu einer
Gesamttransformation zusammengefasst werden. Das
standardmäßig nicht gesetzte „Capability-Bit“
garantiert in diesem Fall, dass sich die durch die
Gruppen-Objekte repräsentierten Matrizen zur Laufzeit
nicht ändern. Sie können also durch Multiplikation zu
einer konstanten Matrix zusammengefasst und einer
einzelnen Transformationsgruppe zugeordnet werden.
Die Multiplikation muss auch hier wie aus anderen
19
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
graphischen Systemen gewohnt nach dem LIFO-Prinzip erfolgen und kann auch durch den
Entwickler mit Hilfe der „mul“-Methode der Klasse „Transform3D“ durchgeführt werden.
Es empfiehlt sich deshalb „Capability-Bits“ nur zu setzen, falls dies beispielsweise zur
Realisierung von Animationen oder Benutzerinteraktionen unbedingt erforderlich ist, da die
Möglichkeiten des Java3D Compilers in diesem ersten Optimierungsschritt sonst sehr stark
eingeschränkt werden.
In einem sich unmittelbar anschließenden zweiten Optimierungsschritt können nun mehrere
Shape3D-Objekte (oder solche von Shape3D-Unterklassen), die im Szenengraphen
Warum
sollten
AppearanceInstanzen
mehrfach
referenziert
werden ?
Nachfolger einer gemeinsamen Transformationsgruppe sind zu einer einzigen Figur
zusammengefasst werden, sofern sie das gleiche Appearance-Objekt referenzieren.
Die Geometrieinformation verschiedener Shape3D-Knoten, die zu einer
Transformationsgruppe gehören wird in der Regel nicht gleich sein, es ist jedoch
empfehlenswert ein „Appearance“-Objekt einmalig zu instanziieren, um es dann von
mehreren der Shape3D-Knoten aus über eine Referenz-Beziehung zu verwenden.
Dadurch ist Java3D-Compiler in der Lage, die Zusammengehörigkeit der Shape3D Knoten
zu erkennen und sie zusammenführen. Dies geschieht so, dass die Geometrieinformation
der Blattknoten in einem geeigneten größer dimensionierten Primitiv kombiniert und mit der
„Appearance-Referenz“ einem einzigen resultierenden Shape3D-Objekt zugewiesen wird.
Da in der Beispielanwendung
alle Pyramidenseitenflächen
das gleiche, im Konstruktor der
Klasse „Pyramide“ instanziierte
„Appearance“-Objekt
referenzieren, können alle vier
Seitenflächen zu einem
einzigen Primitiv
zusammengelegt werden, was eine Reduzierung von fünf auf zwei Shape3D-Objekte pro
Pyramide in der optimierten internen Form des Szenengraphen bedeutet.
Diese Vorgehensweise besitzt mehrere Vorteile. Neben dem wiederum geringeren
Speicherverbrauch, der aus der Wiederverwendung des „Appearance“-Objekts mittels
Referenzen entsteht, kann auch die Anzahl und damit der Zeichenaufwand für die
geometrischen Primitive reduziert werden.
Das resultierende größere Primitiv mit höherer Polygonzahl wird zudem effizienter
bearbeitet, da der Zustand der Rendering-Pipeline weniger häufig geändert werden muss.
In diesem speziellen Fall muss also das Umladen der Geometrie- und Texturdatendaten
weniger häufig pro Einzelbild (Frame) geschehen.
Zum Laden und Entladen von Geometrie- und / oder visueller Information vieler kleiner
geometrischer Primitive wird die Hardware mit einem, im Verhältnis zum tatsächlichen
Zeichenaufwand sehr hohen Verwaltungsaufwand belastet.
20
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Java3D bietet zur manuellen Kombination der Geometrie mehrerer Objekte im Utility-Paket
(com.sun.j3d.util.*) ein sogenanntes „Stripifier“-Werkzeug an, welches ein entsprechendes
TRIANGLESTRIPARRAY liefert.
Sollen verschiedene Flächen eines zusammengesetzten geometrischen Objektes jeweils
zumindest teilweise andere visuelle Eigenschaften aufweisen, d. h. kann eine
„Appearance“-Instanz aus konzeptionellen Gründen nicht von allen, dieses Objekt
beschreibenden Shape3D-Objekten referenziert werden, so empfiehlt sich aus den oben
genannten Gründen zumindest die mehrfache Verwendung / Referenzierung von Texturen
und Materialien.
Ausführung der internen Form
Der erzeugte minimale Szenengraph kann nun in einem nächsten Schritt durch die Java
Virtual Machine in native Graphik-API Aufrufe umgewandelt werden, indem er in geeigneter
In welcher
Reihenfolge
werden die
Objekte
gezeichnet ?
Reihenfolge durchlaufen wird, um die verschiedenen Blatt-Objekte zu zeichnen.
Da die visuellen und geometrischen Eigenschaften wie Position, Ausrichtung, Größe, Farbe,
Material und Textur eines Objekts vollständig und überdies eindeutig durch den Pfad von
der Wurzel des gerichteten zyklenfreien Szenengraphen zu den Blättern bestimmt sind,
können die einzelnen geometrischen Objekte bei Verwendung eines Z-Puffers in beliebiger
Reihenfolge durch Übergabe der entsprechenden Daten und Aufrufe an die Direct3D API
gezeichnet werden.
Man spricht in diesem Zusammenhang auch vom ungeordneten Zeichnen („Unordered
Rendering“).
Aufgabe der Java3D-Implementierung ist es, eine möglichst optimale Reihenfolge zu
ermitteln, in der die Blattobjekte gezeichnet werden sollen. Die Umordnung der Blattobjekte
Nach
welchen
Kriterien
findet die
Ermittlung
der
Zeichenfolge
statt ?
hat dabei idealerweise so geschehen, dass die Rendering-Pipeline der zugrundeliegenden
Low-Level API, also die von Direct3D zur Darstellung eines Einzelbildes (Frame) mit
möglichst wenigen Statusänderungen auskommt. Die entstehende Zeichenfolge von
geometrischen Primitiven und Objekten bezeichnet man deshalb auch als „State Ordered
Rendering“.
Dabei werden Blattknoten so umgeordnet, dass solche mit gleicher Textur, Geometrie,
Material oder Welttransformation unmittelbar aufeinanderfolgend gezeichnet werden.
21
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Nachdem die „State Ordered Rendering“Liste erstellt wurde, kann der Java3DWie arbeitet
der Java3DRenderer ?
Renderer gestartet werden, um die Objekte
durch die Low-Level API zeichnen zu
lassen. Dieser führt bei der Verarbeitung
einer Szene eine Endlosschleife aus, in der
Benutzereingaben, sowie Animationen in
(Teil)-Bereichen des Szenengraphen
verarbeitet, und der Szenengraph in der
geeigneten Reihenfolge durchlaufen wird.
Die sichtbaren Objekte werden dann der
While (true)
{
Eingaben verarbeiten
If (Programmende)
Break
Verarbeite Behavior-Objekte
Traversiere den optimierten
Szenengraphen und übergebe
die geometrischen und visuellen
Daten an die D3D API zum
zeichnen
}
Aufräumen und Beenden
Direct3D API zum Zeichnen übergeben.
Die Endlosschleife kann nach der Verarbeitung der Benutzereingaben verlassen werden,
falls eine entsprechende Benutzeraktion zum Abbruch der Anwendung (Schließen des
Ausgabefensters) vorausging.
Die Anwendung in Direct3D
Bei der Ausführung der Szene übersetzt die Java Virtual Maschine die Anwendung mit der
internen Form des Szenengraphen mit Hilfe der Laufzeitbibliotheken in native Aufrufe gegen
die Direct3D und die Windows32 API.
Zu Beginn einer jeden C++-Anwendung zur graphischen Ausgabe unter Windows muss,
analog zu den fünf Schritten zur Java3D-Programmierung ein Fenster erzeugt werden.
Obwohl man in beiden Technologien zum gleichen Ergebnis, einem Standard-WindowsFenster ohne Menü oder Inhalt gelangt, stellt sich der Weg aus Sicht des
Anwendungsentwicklers bis hin zu diesem Resultat in beiden Anwendungen völlig anders
dar.
Das eigentliche Zeichnen und die Verwaltung des Fensters wird in beiden Fällen nicht durch
die Java3D- bzw. Direct3D-API, sondern durch die Windows32-API durchgeführt.
Während die Direct3D-Anwendung hier direkt mit der Windows-API durch den Aufruf
entsprechender Methoden zur Anmeldung, Initialisierung und Erzeugung des Fensters
kommuniziert, bietet eine Java- oder Java3D-Anwendung hier nur den Umweg über die
Java Virtual Machine bzw. deren systemabhängige Laufzeitbliotheken, die die Anweisungen
des plattformunabhängigen Bytecodes in entsprechende Aufrufe gegen die Windows-API
umsetzen. Diesen Sachverhalt beschreibt auch nachfolgende Graphik.
22
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Direct3D- / WindowsAnwendung
Java / Java3DAnwendung
Laufzeitbibliothek
Java Virtual Machine
Windows32 API
Hardware- und Hardwaretreiber
Direct3D- / WindowsAnwendung
Java / Java3DAnwendung
Java3D bedient sich dabei der Klassen des AWT
(Abstract Windowing Toolkit), die stets importiert
Laufzeit-
Wie wird ein
Fenster in
Java3D
erzeugt ?
werden müssen, um ein Fenster zur
Ausgabe
bibliothek
import java.awt.event.*;
import java.awt.BorderLayout;
Java
Virtual Machine
import java.awt.Frame;
anlegen zu können.
Windows32
API
Das
eigentliche Erzeugen eines Ausgabepublic static void main(String args[])
Fensters für eine einfache Java3D-Anwendung
{
Frame fenster = new
geschieht gewöhnlich in der Main-Methode und
Hardware- und Hardwaretreiber
MainFrame(new Szene(), 800, 600);
gestaltet sich sehr einfach, schnell und
fenster.setTitle("Java3D Seminar");
unkompliziert, da lediglich eine Objektinstanz der
}
Klasse „Frame“ des AWT-Paketes erzeugt
werden muss. Dem Konstruktor wird dabei in der
Regel gleich eine Applet-Objektinstanz, sowie einige Parameter übergeben, die das
Fensterformat, d. h. die Höhe und Breite des Ausgabe-Fensters beschreiben.
Über die set-Methoden der Klasse „Frame“ kann das erstellte Fenster dann weiter an die
individuellen Bedürfnisse angepasst werden. So sind z. B. der Fenstertitel, das Fenstericon,
die Koordinaten der linken oberen Fensterecke, das Cursoraussehen uvm. veränderbar.
Diese wenigen benutzerdefinierten Anweisungen, hinter denen jedoch komplexe, in den
AWT-Bibliotheken implementierte Klassen stehen, werden bei der Programmausführung in
native Aufrufe gegen die Windows32 API umgesetzt. Diese entsprechen weitgehend der
kompilierten Form, der in C++-Anwendungen formulierten Anweisungen zum Anlegen eines
Ausgabefensters, die im nächsten Abschnitt kurz vorgestellt werden sollen.
In einer Direct3D oder Standard-Windows Anwendung muss sich der Programmierer mit
Hilfe von Aufrufen gegen die Windows-API selbst ein geeignetes Fenster erzeugen. Dies
Wie wird ein
Fenster in
C++-Anwendungen
erzeugt ?
stellt sich wesentlich aufwendiger und auch fehlerträchtiger als in Java dar. Zahlreiche
Tutorials und Dokumentationen, aber auch moderne Entwicklungsumgebungen für C++, wie
zum Beispiel Microsoft Visual C++ können den unerfahrenen Anwendungsentwickler hierbei
unterstützen. Im folgenden soll das eigenhändige Erzeugen eines Fensters mit Hilfe der
23
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Windows API in seinen wesentlichen Schritten kurz vorgestellt werden. Wenn es aus Sicht
der Java-Entwickler zunächst auch kompliziert und umständlich erscheint, so kann man
doch die einmal erzeugten Klassen und Headerdateien bei überlegt durchgeführter
Modularisierung in zukünftigen Projekten als „Windowing Toolkit“ (WT) erneut verwenden,
wodurch sich das Erzeugen eines Fensters unter C++ dann ebenso einfach und komfortabel
wie unter Java mit Hilfe der AWT-Klassen darstellt.
Auch hier wird das Erzeugen eines Fensters, vergleichbar der Vorgehensweise in JavaAnwendungen in der WinMain-Funktion, dem Startpunkt der Ausführung einer WindowsAnwendung angestoßen. Neben der auch hier erforderlichen Einbindung von Hilfsstrukturen
in Form der „windows.h“-Headerdatei sind zur Programmierung eines Fensters unter
Windows drei Schritte notwendig.
1) Erzeugen eines Fensterobjektes (Fensterschablone)
Zunächst muss durch den Anwendungsentwickler ein geeignetes Fensterobjekt
definiert werden. Dies geschieht mit Hilfe der in der „windows.h“-Headerdatei
enthaltenen Datenstruktur WNDCLASSEX, über deren Strukturelemente
verschiedene Eigenschaften des zu schaffenden Fenster festlegt werden können.
Bemerkenswerterweise bietet diese Datenstruktur Strukturelemente für
Fenstereigenschaften wie Fenstericon, Cursoraussehen, Hintergrundfarbe oder
Menü, die für die „Frame“-Klasse des AWT-Objekts über entsprechende setMethoden eingestellt werden können.
Man kann also davon ausgehen, dass Java unter Windows zur Erstellung eines
Fensters (Frame) ebenfalls eine WNDCLASSEX Datenstruktur mit vordefinierten
Standard-Werten verwendet (Teetassen-Icon, Standard-Cursor, weißer
Hintergrund, ...).
Diese Standardeinstellungen kann der Benutzer dann über die entsprechenden setMethoden manipulieren, um ein, seinen individuellen Anforderungen eher
entsprechendes Ergebnis zu erhalten.
Hat man sich in C++ einmal ein geeignetes Fensterobjekt definiert, so spricht nichts
dagegen, dieses immer wieder zu verwenden. Da man sich dann die jeweiligen
Aufrufe zum expliziten Setzen von bestimmten Eigenschaften sparen kann, ist diese
Vorgehensweise auf Dauer sogar etwas komfortabler in Java – immer
vorausgesetzt, Sie geben sich mit den Java-Standardeinstellungen nicht zufrieden.
2) Registrieren des Fensterobjektes bei Windows
In einem zweiten Schritt muss das soeben erzeugte Fensterobjekt bei Windows
registriert werden. Erst nachdem Windows die erstellte Schablone bekannt gemacht
wurde, kann ein entsprechendes Fenster erzeugt werden. Die Registrierung
geschieht über die API-Funktion „RegisterClassEx“, der lediglich die Adresse der
erzeugten WNDCLASSEX Strukturvariablen übergeben werden muss.
24
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
3) Erstellen des Fensters
Im letzten Schritt, der dem eigentlichen Erzeugen und Anzeigen des Fensters dient,
werden der „CreateWindowEx“-API-Methode neben dem Schablonennamen des
Fensterobjekts, welches bereits einige der Eigenschaften des Fensteraussehens
definiert (s. o.) weitere Merkmale wie Fenstertitel, Koordinaten der linken oberen
Fensterecke und Höhe bzw. Breite des Fensters übergeben.
Hier wird auch deutlich, warum in Java Fenstertitel, Höhe und Breite des Fensters,
sowie die Koordinaten der linken oberen Fensterecke direkt dem Konstruktor der
Klasse Frame übergeben werden und nicht wie Cursor, Hintergrundfarbe oder Icon
nur durch SET-Methoden manipuliert werden können. Die Java Virtual Machine
setzt die, dem Konstruktor der Klasse Frame übergebenen Werte einfach in die
CreateWindowEx-Methode ein, während die sekundären Fenstereigenschaften
üblicherweise durch die, der Java Virtual Machine ebenfalls zur Verfügung
stehenden Standardschablone ausreichend festgelegt sind.
Wichtig ist in diesem Zusammenhang der Wert der dem Strukturelement
„lpszClassName“ zugewiesen wurde. Dieser wird beim eigentlichen Erzeugen des
Fensters benötigt, damit das gewünschte, bereits bei Windows registrierte
Fensterobjekt (Schablone) identifiziert werden kann.
Die „CreateWindowEx“-Methode liefert als Rückgabenwert ein HWND-Objekt,
welches ein Handle für ein Fenster darstellt. Ein Handle ist in diesem
Zusammenhang nicht anderes als ein eindeutiger Identifizierer eines aktiven
Fensters, der eine Unterscheidung von den anderen aktiven Fenstern und damit zur
Laufzeit eine Zugriffsmöglichkeit auf das Fenster bietet.
Darüber hinaus können im Rahmen der „CreateWindowEx-Methode“ verschiedene
Fensterstile, z. B. randlose Fenster, Fenster ohne Buttons zum Minimieren /
Maximieren, usw. ausgewählt werden, was von Java nicht unterstützt wird. Das
AWT-Paket bietet hier wirklich nur die Fenster-Funktionen und Eigenschaften, die
von allen Betriebssystemen, für die eine Java Virtual Machine existiert, unterstützt
werden.
Die eigentliche Darstellung, Ausgabe und Verwaltung eines Fensters obliegt in jedem Fall
der Windows-Implementierung, die letztlich darüber entscheidet welche Formen, Farben
und Funktionen das Fenster (Titelbalken und Umrandung) bzw. die Fensterelemente
besitzen. So sieht ein, in Direct3D oder Java3D-Anwendungen erzeugtes Fenster unter
WindowsXP in der Regel völlig anders aus als zum Beispiel unter Windows98.
25
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Initialisierung der Zeichenfläche
Nachdem ein leeres Fenster zur Ausgabe zur Verfügung steht, findet in Java oder Java3DAnwendungen die Aufteilung der sichtbaren Fensterfläche in bestimmte Regionen mit Hilfe
Welche
Schritte sind
in Java3D
zur
Initialisierung der
Zeichenfläche
erforderlich
?
geeigneter Layout-Manager statt.
Mindestens eine der Layout-
public Szene()
{
setLayout(new BorderLayout());
Manager-Regionen bekommt dann
ein Canvas3D-Objekt als
Canvas3D zeichenflaeche = new Canvas3D(null);
add("Center", zeichenflaeche);
...
Zeichenfläche für die Java3DAnwendung zugewiesen. Andere
Bereiche des definierten Layouts
können zum Beispiel mit Standard-
NORTH
AWT oder Swing-Komponenten
belegt werden, um eine Steuerung
der 3D-Applikation zu gestatten.
EAST
CENTER
WEST
Diese Komponenten werden wie in
gewöhnlichen Java-Oberflächen-
Canvas3D-Objekt
SOUTH
Anwendungen durch das AWT bzw.
Swing-Paket verwaltet.
Im Rahmen der Beispielanwendung
wird nur der CENTER-Bereich des BorderLayout-Managers mit einer Canvas3DKomponente belegt. Alle anderen Bereiche (NORTH, SOUTH, EAST und WEST) bleiben
ungenutzt, weshalb sich die Zeichenfläche über den gesamten sichtbaren Fensterbereich
erstreckt.
Nun kann der Fensterbereich, in den das Canvas3D-Objekt integriert wurde zur Ausgabe
von 3D-Graphiken verwendet werden. Beim Anlegen einer entsprechenden Canvas3D
Objekt-Instanz hat der Anwendungsentwickler jedoch nur wenig Einfluss darauf, wie die
vorhandene Hardware zur Ausgabe der zu erstellenden Szene genutzt werden soll.
Lediglich zwei Konstruktoren, denen jeweils ein „GraphicsConfiguration“-Objekt übergeben
werden kann, bieten hier Möglichkeiten an, auf virtuellen Bildschirmen zu zeichnen.
Anders und wie vielleicht bereits vermutet wesentlich komplizierter als in Java3DAnwendungen gestaltet sich die Vorbereitung eines soeben erzeugten Fensters zur
Welche
Schritte sind
in Direct3D
zur
Initialisierung der
Zeichenfläche
erforderlich
?
Direct3D-Graphikausgabe.
Bis zu diesem Zeitpunkt handelt es sich bei dem, durch Aufrufe gegen die Windows API
geschaffenen Fenster lediglich um ein gewöhnliches Windows-Fenster, auf dessen
Darstellungsfläche die aus der täglichen Arbeit mit Windows bekannten 2D-Dialogelemente
angeordnet werden können.
Dies kann hier jedoch nicht wie in Java mit Hilfe von Layoutmanagern realisiert werden, die
26
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Position und Größe der Dialogelemente bei einer Änderung der Fenstergröße entsprechend
der neuen sichtbaren Darstellungsfläche so anpassen, dass sie die zur Verfügung stehende
Fläche bestmöglich genutzt wird. Windows-Formulare und Dialoge werden
dementsprechend immer mit festen Positionen für die linke obere Ecke der Dialogelemente
entworfen. Diese Einschränkung fällt hier weniger ins Gewicht, da die relativ zur linken
oberen Ecke der Darstellungsfläche angegebenen Positionen der Dialogelemente unter
Windows-Systemen, denn nur dort läuft eine entsprechende Anwendung, bei gleicher
Auflösung auch in identische Bildschirmkoordinaten übersetzt werden.
Für eine Direct3D Anwendung, die den gesamten sichtbaren Darstellungsbereich eines
Fensters durch entsprechende Definition des Viewports nutzen soll oder gar im VollbildModus ausgeführt wird, spielt diese Tatsache jedoch keine Rolle.
Um nun auch graphische Ausgaben durch Direct3D in dem erzeugten Fenster durchführen
lassen zu können, muss die API zunächst initialisiert werden. Im Rahmen dieser
Initialisierung besteht auch die Möglichkeit eine Direct3D-Anwendung als VollbildApplikation auszuführen, indem Direct3D die Kontrolle über das erstelle Standard-Windows
Fenster übergeben wird.
Java3D-Anwendungen verfügen nicht über dieser erweiterten Möglichkeiten der
Fenstermanipulation, da sie sich zur Bereitstellung von Fenstern allein des Abstract
Windowing Toolkit und nicht der entsprechenden Funktionalitäten der DirectX-API bedienen
können. Dies hätte aus Kompatibilitätsgründen auch wenig Sinn, da alle anderen 3D APIs
wie OpenGL oder QuickDraw diese Möglichkeiten ebenfalls unterstützen müssten um eine
einheitliche Schnittstelle in Java3D anbieten zu können.
Die Ausführung einer Anwendung als Vollbild-Anwendung unter Windows besitzt neben der
Vergrößerung der Darstellungsfläche noch einen weiteren, mindestens ebenso wichtigen
Nebeneffekt. Die Rechenkapazität muss hier nicht mit anderen Windows-Anwendungen
(Fenstern) geteilt werden, die ansonsten immer einen bestimmten Anteil der zur Verfügung
stehenden Rechenkapazität als Menge von Zeitscheiben zur Durchführung ihrer
(Hintergrund-)Aktionen erhalten.
Initialisierung von Direct3D
Die Direct3D API, die dem Anwendungsentwickler in Form der Objektbibliothek „d3d8.lib“
zur Verfügung gestellt wird muss, bevor mit den ersten Aufrufen gegen die Schnittstelle
begonnen werden kann in das Projekt eingebunden (link) werden. Zusätzlich empfiehlt sich
die Verwendung der Werkzeugbibliotheken (d3dx8.lib) und im Rahmen dieser
Beispielanwendung auch die der DirectInput API (dinput.lib) zur Verarbeitung von
Benutzerinteraktionen.
Jeder dieser Objektbibliotheken ist eine entsprechende Headerdatei zugeordnet, welche die
im Umgang mit den Bibliotheksfunktionen benötigten Datenstrukturen und
27
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Methodendefinitionen enthält. Diese sind durch „include“-Anweisungen in den Quelltext zur
Initialisierung von Direct3D bzw. DirectInput einzubeziehen.
Direct3D API
Direct3DX
Werkzeugbibliothek
DirectInput API
Objektbibliothek
d3d8.lib
d3dx8.lib
dinput.lib
Headerdatei
d3d8.h
d3dx8.h
dinput.h
Die eigentliche Initialisierung von Direct3D kann entweder auf einem leichten oder aber
einem schweren Weg durchgeführt werden.
Entscheidet man sich für die einfachere Variante, so mag dies in vielen Fällen ausreichen.
Für anspruchsvollere Anwendungen auf leistungsstarken Hardwareplattformen empfiehlt es
sich jedoch den steinigeren Weg einzuschlagen. Die Initialisierung kann im einfachen Fall
durch einige wenige Funktionsaufrufe gegen die API, welche die Verwendung von
Standard-Hardware und Standard Hardwareeinstellungen zur Folge haben durchgeführt
werden.
Für die graphischen Ausgaben wird dann einfach die erstbeste Graphikkarte, welche auch
von Windows verwendet wird, eingesetzt. Nachteilig ist diese Verfahrensweise immer dann,
wenn leistungsfähigere 3D-Beschleuniger bei dieser „Auswahl“ von Geräten einfach
ignoriert werden, obwohl sie für die Anwendung die wesentlich geeignetere Alternative
darstellen würden.
Der schwerere Weg ist nun dadurch gekennzeichnet, dass mittels einer geeigneten Abfrage
zunächst alle installierten Graphikkarten mit ihren jeweiligen Funktionen und Features
aufgezählt werden. Man spricht in diesem Zusammenhang auch von HardwareEnumerierung bzw. Hardware-Aufzählung.
Aus dem Aufzählungsergebnis wird dann die leistungsfähigste Komponente mit den besten
/ meisten hardwareseitig realisierten Funktionalitäten initialisiert und damit zur
Graphikausgabe ausgewählt.
Ein Beispiel für eine solche Funktion stellt zum Beispiel ein Hardware-Rasterisierer dar, der
als verdrahteter Algorithmus im Graphikkarten-Prozessor wesentlich effizienter als eine
entsprechende, den Haupt-Prozessor nutzende Software-Implementierung arbeitet. Die
CPU ist durch diese spürbare Entlastung in der Lage andere Aufgaben durchzuführen.
In Direct3D wird eine Graphikkarte auch als Adapter bezeichnet. Dieser unterstützt mehrere
verschiedene Kombinationen von Auflösungen und Farbtiefen, die sogenannten GraphikWas ist ein
Adapter
bzw. 3DDevice ?
Modi.
Zudem sind jeder Graphikkarte in der Regel verschiedene 3D-Devices zugeordnet.
Bei der Enumerierung in einer Direct3D-Anwendung wird nun nichts anderes gemacht, als
für jeden im Rechnersystem installierten Adapter die unterstützen Gaphik-Modi, sowie die
28
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
angeschlossenen 3D-Devices abzufragen und eine geeignete Adapter / 3D Device / ModusKombination auszuwählen.
Zu diesem Zweck sind durch den Anwendungsentwickler geeignete Datenstrukturen zu
definieren, die die abgefragten Eigenschaften aufnehmen können. Sinnvollerweise wählt
man hier drei verschiedene Strukturen für Adapter, 3D-Device und Graphikmodi, die man
hierarchisch strukturiert konzipiert, d.h. Die Devices-Struktur besitzt ein StrukturelementArray zum Speichern von 3D Device-Strukturen, dieses enthält wiederum ein
Strukturelement-Array zum Speichern der Strukturen der vom jeweiligen Device
unterstützten Bildmodi (vgl. „Strukturen.h“).
Bei der Abfrage der verschiedenen 3D-Devices, die von einem Graphikadapter bzw. einer
einzelnen Graphikkarte unterstützt werden unterschiedet man in Direct3D drei Klassen.
Welche 3DDeviceTypen kennt
man in
Direct3D ?
D3DDEVTYPE_HAL
Unter die Hardware Abstraction Layer-Klasse (HAL) fallen
alle Adapter, die die hardwareseitige Berechnung von
Dreiecken, sowie deren Rasterisierung und Zeichnung
(Rendering) unterstützen. Moderne Graphikkarten dieser
Kategorie verfügen in der Regel zusätzlich über eine
sogenannte T(ransform)&L(ighting)-Engine, die die
Ausführung von Beleuchtungs- und
Transformationsoperationen ebenfalls in der Hardware
ausführen kann.
D3DDEVTYPE_SW
In die zweite Klasse der Devicetypen fallen alle Adapter
ohne 3D-Fähigkeit, d. h. reine 2D-Karten. Die Berechnung
von Dreiecken, sowie alle weiteren 3D-Funktionen muss hier
durch geeignete Softwarealgorithmen realisiert werden. Es
handelt sich also um ein reines Software-Device, das
wesentlich geringere Bildraten als die HAL-Typen erreicht.
In der Regel unterstützt das Software-Device auch nicht alle
Funktionen der jeweils aktuellen Direct3D Version
D3DDEVTYPE_REF
Um diesem Umstand abzuhelfen wurde eine dritte Klasse,
die sogenannte Referenzklasse (Reference-Rasterizer)
entworfen, die zwar alle aktuellen Funktionen unterstützt,
jedoch ebenfalls rein softwaremäßig implementiert ist.
Aufgrund der dadurch entstehenden Performanceprobleme
empfiehlt sich der Einsatz des Reference Rasterizer Device
lediglich zu Testzwecken
29
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Zur Aufzählung der Graphikadapter, 3D-Devices und der vom jeweiligen Device
unterstützten Graphik-Modi, wird in der Methode „geraeteAufzaehlen()“ der Klasse
„D3Dinit.cpp“ ein erstes Direct3D Objekt, das bezeichnenderweise den Namen DIRECT3D8
trägt und mit Hilfe der Methode „Direct3DCreate8()“ bereitgestellt wird genutzt. Es handelt
sich um ein typisches COM-Objekt, welches ausschließlich über sein Schnittstellenobjekt
vom Typ LPDIRECT3D8 zugegriffen werden kann. Die Direct3DCreate8-Methode gibt
deshalb eben dieses Schnittstellenobjekt zurück, das in jeder Direct3D-Anwendung den
Ausgangspunkt zur Erzeugung weiterer COM-Objekte bildet.
Das „Component Objekt Model“ (COM) stellt ein objektorientiertes Programmiermodell dar,
welches eine strenge Kapselung der Objekte fordert.
Was ist
unter dem
Component
ObjectModel
(COM) zu
verstehen ?
Diese dürfen also keine „public“-Methoden anbieten. Stattdessen sind jedem COM-Objekt
eine oder mehrere Schnittstellen zugeordnet, die den Zugriff auf die Objekt-Methoden
gestatten.
COM-Objekte kann man sich deshalb als „Black-Boxes“ vorstellen, die den Anwendungen
Dienste und Methoden über ihre Schnittstellen zur Verfügung stellen.
Sowohl die DirectX API, als auch die Laufzeitumgebung selbst liegen vornehmlich als COMkonforme Objekte vor.
Das DIRECT3D8-Objekt kann nun in der Methode „devicelisteErstellen()“ zunächst dazu
verwendet werden, die Anzahl der installierten Adapter (GetAdapterCount()) zu ermitteln.
Anschließend iteriert man in einer Schleife über alle Adapter, von denen jeder durch einen
eindeutigen numerischen Wert, der dem Schleifenzähler entspricht, identifiziert wird, um die
vom jeweiligen Adapter unterstützten Bild- und Farbtiefenformate (EnumAdapterModes())
zu erhalten.
In einer weiteren geschachtelten Schleife, die über die drei bekannten Devicetypen läuft
prüft man nun ab, welche der ermittelten Adapter-Formate von welchem 3D-Device
unterstützt werden. Dies kann sehr einfach über die Methode „CheckDeviceType()“
geschehen. Dieser wird neben der numerischen Adapter-ID und dem zu testenden 3D
Device-Bezeichner (HAL, SW, REF) das zu testende Bildformat übergeben. Zusätzlich kann
untersucht werden, welche hard- oder softwareseitige Vertex-, bzw. Transformations- und
Beleuchtungs-Operationen durch das 3D-Device durchgeführt werden können. Unmittelbar
an diese Tests, muss in der „findeStencilFormat()“-Methode zusätzlich ermittelt werden, ob
für die Device / Format-Kombination ein Stencil- und ein Back-Buffer erzeugt werden
können (CheckDeviceFormat()).
Entspricht mindestens einer der vom Device angezeigten Graphik-Modi der gewünschten,
der Methode über die Parameter „dwBreite“ und „dwHoehe“ übergebenen Auflösung bei 16Bit Farbtiefe und wird dieser Graphik-Modus vom 3D-Device voll unterstützt, so das 3DDevice bzw. die 3D-Deviceklasse zur Ausgabe geeignet und kann zur Initialisierung von
Direct3D verwendet werden.
30
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
In einem letzten Schritt
Ermittle Anzahl installierter Adapter
iteriert man über die
Über alle installierten Adapter
Ermittle die vom Adapter unterstützten Graphik-Modi
Über alle drei 3D Devicetypen (HAL, REF, SW)
Über die gefundenen Graphik-Modi d. Adapters
Prüfe, ob der Graphik-Modus vom 3D Device
unterstützt wird
gefundenen Adapter / 3D
Device-Kombinationen, die
den gewünschten GraphikModus unterstützen und
wählt das erste in der Liste
aus. Dabei wird den Adapter
Führe weitere Tests für 3D DeviceFunktionen wie zum Beispiel Hardware
Transformations und
Beleuchtungsoperationen durch
/ HAL-DeviceKombinationen Priorität
eingeräumt, da in der
Prüfe, ob für das 3D Device ein Back- bzw.
Stencil-Buffer erzeugt werden kann
Schleife über die
Trage die Adapter / Device-Kombination bei
erfolgreichen Tests in eine Liste ein
Devices untersucht wurden.
Devicetypen zuerst HALDie entsprechenden
Kombinationen stehen also
an den führenden
Listenpositionen.
JA
Liste geeigneter Adapter/DeviceKombinationen leer ?
NEIN
Über alle Listeneinträge
Fehler: Direct3D kann
nicht initialisiert werden
Wähle erste
gefundene
Kombination
Wurde nun endlich ein geeigneter Adapter mit 3D Device für den geforderten GraphikModus gefunden, kann Direct3D in der Methode „d3dObjekteInitialisieren()“ initialisiert
werden. Wichtigster Schritt hierbei stellt das Erzeugen eines D3DDEVICE8-Objektes dar,
welches der Anwendung den Zugriff auf die 3D-Graphik-Hardware gestattet. Wie beim
DIRECT3D8 COM-Objekt wird auch hier über die „CreateDevice()“-Methode der
LPDIRECT3D8-Schnittstelle ein COM Objekt instanziiert dessen Schnittstelle als
LPDIRECT3D8DEVICE-Objekt zurückgegeben wird.
Der „CreateDevice“-Methode können hier eine Vielzahl von Parametern, teilweise in Form
einer vorher zu füllenden Strukturvariablen übergeben werden. Zunächst sind die Nummer
(ID) des mühsam ermittelten Graphikadapters nebst dem gefundenen 3DDeviceIdentifizierer (HAL, SW, REF) zu übergeben. Zudem können in der Struktur zum Beispiel
Anzahl und Format der Back-Buffer, Windowed-Parameter, uvm. eingestellt werden.
Das Back-Buffer-Format, d. h. Höhe und Breite des Hintergrund-Bildspeichers entspricht
dabei natürlich implizit der gewünschten, als Parameter übergebenen Auflösung der
Darstellungsfläche (Graphik-Modus).
Gleichzeitig erwartet die Funktion den Handle auf das bereits erzeugte Windows Ausgabe-
31
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Fenster. Hierdurch ist DirectX in der Lage die Kontrolle über das Fenster zu übernehmen,
um etwa in den Vollbildmodus zu wechseln. Da dies im Rahmen der Beispielanwendung
geschehen soll, wird das „Windowed“-Element der D3DPRESENT_PARAMETERS-Struktur
auf dem Wert FALSE gesetzt.
Das soeben erzeugte DIRECT3DDEVICE8-Objekt kann nun sofort verwendet werden, um
einige Parameter der Direct3D Rendering-Pipeline zu setzen. Hier sind vor allen Dingen die
beabsichtigte Verwendung von Beleuchtung, die Farbe des ambienten Lichtes, der CullModus oder die Verwendung von Z-Puffern anzuzeigen. Alle Einstellungen können durch
Aufrufe der „SetRenderState()“-Methode des DIRECT3DDEVICE8-Objektes über die
zugehörige LPDIRECT3DDEVICE8-Schnittstelle gesetzt werden. Dabei ist als erster
Methodenparameter die zu ändernde Eigenschaft der Rendering-Pipeline wie zum Beispiel
D3DRS_LIGHTING, D3DRS_AMBIENT, usw. und als zweiter, vom Wert dieses ersten
Parameters abhängiger Parameter der zu setzende Wert zu übergeben. Meist können hier
in der „d3d8.h“-Headerdatei vordefinierte „sprechende“ Bezeichner (D3DCULL_NONE,
D3DZB_TRUE) oder einfach ein boolescher Wert verwendet werden. Jeder dieser einfach
zu handhabenden Bezeichner ist headerintern mit einem ganzzahligen Wert codiert, auf den
er zum Zeitpunkt der Programmübersetzung zurückgeführt wird.
Es ist nun mit Hilfe zahlreicher Anweisungen und Parameter eine Fläche geschaffen
worden, in die die Anwendung mit Hilfe der Direct3D-API Ausgaben machen kann. Was sich
in Java3D quasi mit zwei Zeilen Code realisieren lässt kann den Entwickler für Direct3DAnwendungen einige Stunden bis Tage an Entwicklungsarbeit kosten. Dank des
Wiederverwendbarkeitskonzepts objektorientierter Programmiersprachen, zu denen auch
C++ gehört, können die erzeugten Methoden jedoch in nahezu jeder zukünftig zu
entwickelnden Direct3D-Anwendung wiederverwendet werden.
Der kritische Leser wird nun anmerken, dass Routinen, die derart häufig benötigt werden,
doch eigentlich in Form von optimierten Bibliotheksfunktionen angeboten werden sollten. In
Warum
existieren
keine
Bibliotheksfu
nktionen zur
„schweren“
Direct3DInitialisierung ?
der Tat wäre eine solche Lösung für den unerfahreneren Anwender zu begrüßen. Man sollte
jedoch nicht vergessen, dass der „einfache“ Weg der Initialisierung von Direct3D durch
Auswahl des Standard-Graphikadapters, des Standard-3D-Device, sowie der Ausführung im
Fenster dieser Zielgruppe doch wesentlich praktikabler erscheinen dürfte.
Zudem werden parametrisierbare Bibliotheksfunktionen in der Regel niemals alle Methoden
anbieten können, um sämtliche Funktionen des Direct3D Paktes durch wenige globale
Aufrufe nutzbar zu machen. Eine vollständige Bibliothek wäre so komplex, umfangreich und
unhandlich, dass eine lange Einarbeitungszeit unumgänglich ist, von den Nachteilen im
Laufzeitverhalten durch spezialisierte Möglichkeiten ganz zu Schweigen. DirectX im
Allgemeinen und Direct3D im Besonderen folgen in diesem Zusammenhang dem Prinzip
dem Anwendungsentwickler „nur so viel Funktionalität wie unbedingt nötig anzubieten“.
32
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Die Direct3D Rendering Pipeline
Im vorangegangenen Abschnitt wurde bereits kurz die Direct3D Rendering-Pipeline
erwähnt, auf deren Eigenschaften durch die „setRenderState()“-Methode des
Welche
Bedeutung /
Funktion hat
das
DIRECT3DD
EVICE8Objekt ?
DIRECT3DDEVICE8-Objektes im Rahmen der Initialisierung von Direct3D zugegriffen
wurde. Das DIRECT3DDEVICE8-Objekt bietet zahlreiche weitere Funktionen an, um den
Zustand der zugrundeliegenden Rendering-Pipeline zur Laufzeit zu setzen bzw. zu
manipulieren, was zur graphischen Ausgabe einer jeden Szene unerlässlich ist.
Es bietet also einen abstrakten und hardwareunabhängigen Zugriff auf die RenderingPipeline und stellt damit das wesentliche Bindeglied zwischen Anwendung und Hardware
dar.
Für die nachfolgende Betrachtung der C++-Anwendung zur Ausgabe der Beispielszene ist
es durchaus sinnvoll, kurz auf Aufbau und Struktur der Direct3D Rendering-Pipeline
einzugehen, obwohl sich im Vergleich zu der aus OpenGL bekannten nur wenige
Überraschungen ergeben dürften.
VertexBuffer
Welttransformation
Vertex-Shader
Ansichtstransformation
Projektionstransformation
Farbe
„Vertex-Assembly“-Prozess
Texturkoordinaten
Primitv-Operationen
Textur
PixelShader
Material
Alpha, Stencil, Tiefentest
FrameBuffer
33
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Die Rendering-Pipeline von Direct3D stellt eine typische Pipeline zur Graphikverarbeitung
mit mehreren sequentiell zu durchlaufenden Verarbeitungsschritten dar. Aufgabe der
Geometrie- / Transformations-, Beleuchtungs- und Textur-Verarbeitungseinheiten ist
letztlich immer die Umwandlung der in der Anwendung definierten räumlichen
Szenenbeschreibung in Bildpunkte auf einem zweidimensionalen Bildschirm.
Die Direct3D Rendering-Pipeline besitzt dazu im wesentlichen zwei programmier- bzw.
steuerbare Einheiten: Vertex-Shader und Pixel Shader.
Die Vertex Shader bearbeiten dabei die, durch die Anwendung initialisierte und in VertexPuffern abgelegte reine Geometrieinformation, die in Form von Vektoren (Raumpunkten)
vorliegt. Hier finden Geometrie-, Projektions- und Ansichtstransformationsoperationen auf
den Daten statt, die durch den Direct3DX Vertex Shader Assembler gesteuert werden. Der
erste Schritt in der Verarbeitungsfolge stellt also eine Art Geometrie- oder
Transformationspipeline dar, die mit Hilfe der durch die Anwendung spezifizierten Matrizen
die Welt-, Ansichts- und Projektionstransformation durchführt.
Die resultierenden Daten werden dann im sogenannten Vertex-Assembly-Prozess, bei dem
weitere Eigenschaften eines Raumpunktes wie Farbinformation, Texturkoordinaten oder
ähnliches in die Verarbeitung mit einbezogen werden zu Einheiten aus geometrischer und
visueller Information kombiniert.
Diese werden durch Operationen zum Zeichnen von Primitiven (Draw-Primitive) in
vorläufige Bildpunktinformation umgewandelt und einem Pixel-Shader übergeben, der unter
anderem die Farb- und Transparenzwerte von Texturen bei der Berechnung der
Farbinformation eines Bildpunktes mit berücksichtigt. Die Art und Weise wie der Pixel
Shader die zur Verfügung gestellten Daten verarbeitet, wird auch hier wieder durch eine
programmierbare Einheit, den Direct3DX Pixel Shader Assembler gesteuert. Das Ergebnis
der Berechnungen, also der resultierende Bildpunkt durchläuft schließlich Alpha-, Stencilund Tiefentests, bevor er durch den Rasterisierer in den FrameBuffer ausgegeben werden
kann.
Im Herzstück eines jeden graphischen Systems, der Transformations- oder GeometriePipeline finden nacheinander eine Welttransformation, also Anordnung, Ausrichtung und
Skalierung von Objekten in der 3D-Welt eine Ansichtstransformation, die Positionierung des
Betrachters oder der Kamera in der Szene realisiert und eine Projektionstransformation, die
die Szene auf eine imaginäre zweidimensionale-„Bildplatte“ projiziert statt.
In Direct3D müssen deshalb stets entsprechende Matrizen für die Modell-, Ansichts- und
Projektionstransformation spezifiziert und zur Laufzeit geladen bzw. umgeladen werden.
Soll beispielsweise ein bestimmtes geometrisches Objekt gezeichnet werden, so ist die
Pipeline stets vor dem Aufruf der DrawPrimitve-Methode über geeignete Methodenaufrufe
auf dem DIRECT3DDEVICE8-Objekt mit den Geometriedaten, der Objekttextur, dem
34
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Material bzw. der Polygon-Füllfarbe, der Welttransformationsmatrix zur Anordnung des
Objekts usw. zu laden.
Befinden sich zumindest Teile dieser Daten aus dem unmittelbar vorausgehenden
Zeichenvorgang noch in der Pipeline, so müssen sie nicht erneut geladen werden, sondern
können für alle weiteren Zeichenvorgänge genutzt werden. Besonders bei sehr großen
Geometriedatenmengen, vor allem aber bei hochauflösenden Texturen kann dies eine nicht
unerhebliche Zeitersparnis mit sich bringen, die sich in einer höheren möglichen Bildrate
äußert.
Diese Tatsache muss eine auf Laufzeiteffizienz abgestimmte Direct3D Anwendung durch
Realisierung einer geeigneten Zeichenfolge unbedingt berücksichtigen.
Die Szene in Direct3D
In einem nächsten, sich unmittelbar an die Erzeugung des Fensters und des Canvas3DObjektes anschließenden Schritt wird in Java3D-Anwendungen ein Ansichtsgraph erstellt,
welcher die Position relativ zum Ursprung des „Locale“-Objektes, sowie die Blickrichtung
und Ausrichtung des Benutzers bzw. der Kamera in der Szene definiert. Es besteht hier
jedoch die Möglichkeit eine Klasse mit Standard-Einstellungen (SimpleUniverse) zu
verwenden, die in den meisten Fällen absolut ausreicht und auch im Rahmen der Java3DBeispielanwendung zum Einsatz kommt.
Der Entwickler von Java3D-Anwendungen bleibt also bei Verwendung des Simple-Universe
von der Definition und Initialisierung spezieller Matrizen für Projektions- und
Ansichtstransformation verschont. Es sind lediglich Objekte der Klasse „3Dtransform“
erforderlich, die einer geeigneten Transformationsgruppe (TransformGroup) zugeordnet
werden, um Welttransformationen oder Animationen von geometrischen Objekten zu
ermöglichen.
Man kann deshalb davon ausgehen, dass für die Projektionstransformation in Java3D stets
eine Standardmatrix Verwendung findet. Die Ansichtstransformation wird im
„SimpleUniverse“ ebenfalls über eine vordefinierte Matrix durchgeführt. Hierzu wird der
Betrachter in negative z-Richtung schauend im Punkt (0, 0, 2.4) positioniert.
Zur Durchführung von Animationen, bei denen die Betrachterposition verändert wird,
verwendet Java3D eine erweiterte Welttransformation, d. h. ein „Entfernen“ des Betrachters
Was ist eine
erweiterte
Welttransfor
mation ?
um 2 Einheiten in positive Z-Richtung (0, 0, 4.4) führt statt zu einer Änderung der
Ansichtsmatrix zu einer Verschiebung der Objekte in der Szene um 2 Einheiten vom fixen
Betrachterpunkt weg in negative Z-Richtung.
Grundgedanke hierbei ist, dass für Animationen des virtuellen Betrachterstandortes mit
dieser Technik die Matrix für die Ansichtstransformation lediglich einmalig zu Beginn der
Szeneninitialisierung und nicht für jeden Frame erneut berechnet und in die
Transformationspipeline geladen werden muss, während die jeweiligen Matrizen für die
35
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Positionierung der Objekte in der Szene über Welttransformationen ohnehin für jeden zu
zeichnenden Frame für jedes zu zeichnende Objekt in die Pipeline geladen werden
müssen.
Diese Maßnahme bietet neben dem, zumindest für einfache Szenen sicherlich
entstehenden Gewinn in der Bildrate auch eine Vereinfachung für den
Anwendungsentwickler mit sich, der sich lediglich um eine einzige Transformationsart
kümmern muss.
In Direct3D sind dagegen keine Standardmatrizen für Projektions- und
Ansichtstransformation definiert, so dass der Programmierer dies selbst übernehmen muss.
Wie müssen
Ansichtsund
Projektionstransformation in
Direct3D
gewählt
werden ?
Es spricht jedoch auch hier nichts dagegen eine ähnliche Technik wie in Java3D zu
realisieren, die statt einer permanenten Anpassung der Ansichtstransformation eine
erweiterte Welttransformation realisiert. Man bezeichnet die entstehende Matrix auch als
„World-View“-Matrix. Diese Vorgehensweise wird unter anderem auch in der
„Dokumentation zu DirectX 8 für C++“ im Kapitel „The View Transformation – Setting Up a
View Matrix“ empfohlen.
Analog zur Erstellung eines Ansichtsgraphen in Java3D, findet auch in Direct3DAnwendungen eine (einmalige) Spezifizierung der Betrachter- oder Kameraposition und –
ausrichtung in der Szene durch die Definition von Projektions- und Ansichtsmatrix statt.
Die entsprechenden Routinen können einmalig nach der Erzeugung des
DIRECT3DDEVICE8-Objekts erfolgen (s. Methode „szeneInitialisieren()“der Klasse
„D3Dinit.cpp“), wenn für Animationen der Betrachterposition eine World-View-Matrix
verwendet wird, was im Rahmen des C++Beispielprogramms geschehen soll.
Wie Java3D arbeitet auch Direct3D mit einer 4x4-Matrix, die als Struktur (D3DXMATRIX) in
der „d3dx8.h“-Headerdatei für die Werkzeugbibliothek definiert ist. Generell ist festzustellen,
dass in der Direct3D-Programmierung sehr häufig Strukturen zum Einsatz kommen,
während die in Java3D implementierten Entsprechungen als Objekte vorliegen. Die Java3D
Implementierung bildet die Werte der Klassenvariablen in geeigneter Weise auf die
entsprechenden Strukturelemente ab, wobei die von Java3D nicht unterstützten StrukturEigenschaften durch die API mit Standardwerten gefüllt und an Direct3D übergeben
werden.
Die Reihenfolge, in der die jeweiligen Matrizen für Projektions- und Ansichtstransformation
deklariert, mit Werten gefüllt und in die Pipeline geladen werden ist dabei nicht von
Bedeutung. Beginnt man zum Beispiel mit der Angabe Projektionsmatrix, so kann man sich
der Helfermethode „D3DXMatrixPerspectiveLH()“ der „DXUtility“-Bibliothek bedienen, um
die Matrix mit, entsprechend den übergebenen Parametern von der Methode berechneten
Werten füllen zu lassen. Die Endung LH deutet hier darauf hin, dass innerhalb der Szene
ein linkshändiges Koordinatensystem zum Einsatz kommen soll, wie es in Direct3DAnwendungen standardmäßig der Fall ist. Man kennt hier jedoch auch eine entsprechende
36
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Methode für rechtshändige Koordinatensysteme (D3DXMatrixPerspectiveRH()).Die
Parameter spezifizieren ähnlich wie in OpenGL ein Viewing Volume, welches durch den
Öffnungswinkel, sowie die Z-Koordinaten der vorderen bzw. hinteren Begrenzungsebenen
definiert werden kann. Zusätzlich ist der Aspect-Ratio Korrekturfaktor aus den
Abmessungen des automatisch bei der Instanziierung des DIRECT3DDEVICE8-Objekts
erstellten Viewport zu berechnen und ebenfalls als Parameter zu übergeben.
Die den Viewport beschreibende Struktur (D3DVIEWPORT8) mit den Strukturelementen
Höhe, Breite und den Koordinaten des linken oberen Eckpunktes kann über die
„getViewport()“-Methode des DIRECT3DDEVICE8-Objektes zugegriffen werden. Die hier
eingetragenen Werte für Höhe und Breite des Viewports können dann dazu verwendet
werden, um das Pixelseitenverhältnis als Quotient zu berechnen.
Mit Hilfe der „setTransform()“-Methode des DIRECT3DDEVICE8-Objektes kann die so
erzeugte Projektionsmatrix in die Transformationspipeline geladen werden, wo sie in der
Regel bis zum Ende des Programms in unveränderter Form abgelegt wird.
Der Typ der übergebenen Matrix wird beim Aufruf von „setTransform()“-Methode durch die
Konstante D3DTS_PROJECTION als Projektionsmatrix angezeigt.
Neben den perspektivischen Projektionen für links- und rechtshändige Koordinatensysteme
kennt man in Direct3D auch verschiedene orthogonale Projektionsarten.
Als nächstes muss eine konstante Ansichtsmatrix so erstellt werden, dass die Szene
unmittelbar nach dem Programmstart aus einer ähnlichen Position wie in Java3D betrachtet
werden kann. Die DirectX Werkzeug-Bibliothek bietet hierfür ebenfalls geeignete
Hilfsfunktionen für links- und rechtshändige Koordinatensysteme an, mit denen eine
entsprechende Matrix nach Angabe des Betrachterstandpunktes (0.0, -2.41, 0.0), des
Zielpunktes, auf den der Blick gerichtet wird und des Kameralagevektors (aufrecht stehend
mit 0.0, 1.0, 0.0) automatisch mit den richtigen Werten gefüllt wird. Die Helferfunktion
„D3DXMatrixLookAtLH()“, die im Rahmen des Beispielprogramms zum Einsatz kommt,
erwartet als Parameter die Adressen einer D3DXMATRIX-Struktur, welche innerhalb der
Methode mit den errechneten Werten gefüllt wird, sowie von drei D3DXVECTOR3Strukturen mit Informationen zu Betrachterstandpunkt (Augpunkt), Zielpunkt und
Kameralagevektor.
Durch Angabe von D3DTS_VIEW als erstem Parameter der bereits bekannten
„setTransform()“-Methode des DIRECT3DDEVICE8-Objektes wird die Matrix in die
Transformationspipeline eingestellt.
Diese einmaligen Initialisierungen der Projektions- und Ansichtsmatrizen, sowie der
wichtigsten Direct3D-Objekte bildet den Ansichts-Szenengraphen, wie er in einer Java3DAnwendung bei Verwendung des SimpleUniverse entsteht in Direct3D, exakt nach.
Zusätzlich ist es sinnvoll, die Objektgeometrien und die, für die visuellen Eigenschaften von
geometrischen Objekten benötigten Daten wie Texturen und Materialien, usw. einmalig vor
dem eigentlichen Zeichnen der Szene im Rahmen einer Szeneninitialisierungsroutine zu
berechnen bzw. zu laden. Rein theoretisch könnten diese Schritte auch für jeden zu
37
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
zeichnenden Frame ausgeführt werden, die Dateizugriffe zum permanenten Laden von
Texturen oder komplexe Geometrieberechnungen würden jedoch die Bildrate deutlich
absenken.
Geometriedefinition in Direct3D
Die konkrete Berechnung der Geometrie, sowie die Klassenaufteilung wurden von mir für
diese Seminararbeit ähnlich realisiert wie in der Java3D Beispielanwendung.
Die Pyramide liegt also auch hier wiederum in Form der drei Klassen „Pyramide.cpp“,
„Pyramidenflaeche.cpp“ und „Pyramidenboden.cpp“ vor, die aus jeweils zwei Dateien
bestehen. Jedes dieser Dateipaare enthält erstens die Klassendefinition in einer
entsprechend bezeichneten Headerdatei und zweitens die Methodendefinition in einer C++Datei. Die Klassenschnittstelle weist dabei Methoden mit nahezu identischen Signaturen
wie die Java-Anwendung auf. Zusätzliche private Variablen oder Methodenparameter sind
implementierungstechnisch zu begründen. In diesem Zusammenhang ist die für mich
wichtigste Eigenschaft gewesen, dass die Pyramidenklassen in der Lage sein sollten, dem
DIRECT3DDEVICE8-Objekt ihre Geometriedaten direkt zu übergeben, um anschließend
über den Aufruf der „DrawPrimitive()“-Methode „ihren“ Zeichenvorgang einzuleiten.
Unter Direct3D werden in der Struktur eines Raumpunkts neben den 3D-Koordinaten immer
auch die zusätzlich benötigten Informationen wie Normalen- oder Texturkoordinaten mit
Worin
bestehen
Unterschiede in der
Geometriedefinition
zwischen
Java3D und
Direct3D ?
abgespeichert, während in Java3D vordefinierte Objekte für Raumpunkte, Textur- und
Normalkoordinaten jeweils als Objektarry erzeugt und mit Daten gefüllt werden müssen.
Da Art und Umfang der für einen Raumpunkt in Direct3D benötigten Information von
Anwendung zu Anwendung, ja selbst von Objekt zu Objekt variieren kann, die Struktur für
die Raumpunkte also unterschiedliche Strukturelemente besitzt (von den X, Y und ZKoordinaten einmal abgesehen) muss dem DIRECT3DDEVICE8 bzw. dem Vertex-Shader
der Aufbau der Struktur für die Raumpunkte explizit mitgeteilt werden. Dieser Aufbau wird
im sogenannten Direct3D Flexible Vertex Format, kurz D3DFVF spezifiziert. Zur Definition
müssen hier nach dem Baukastenprinzip vorgegebene Bezeichner wie zum Beispiel
D3DFVF_XYZ, D3DFVF_NORMAL oder D3DFVF_TEX1 für die möglichen
Strukturelemente bzw. Strukturelementgruppen zusammengestellt werden.
Der Vorteil einer solchen Vorgehensweise ist sehr einfach erklärbar. Für jeden Raumpunkt
eines Objektes in einer Szene muss ein Speicherbereich entsprechend der Größe der, den
Raumpunkt beschreibenden Datenstruktur reserviert werden. Je nach Anwendung wäre es
denkbar, dass zumindest einige der Strukturelemente eines Standard-Vertexformats nicht
benötigt werden, jedoch trotzdem Speicher belegen. Diesen verschwenderischen Umgang
mit System-Ressourcen kann man sich bei komplexen animierten graphischen Szenen nicht
erlauben.
Diese Tatsache besitzt auch in Java3D-Anwendungen eine bemerkenswerte Parallele. So
muss bei der Instanziierung eines Primitvs angegeben werden, welche Informationen bzw.
38
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Datenarrays dem Primitv zugeordnet verwendet werden können. Java3D besitzt also
implizit für jedes Primitv-Objekt ein eigenes Vertex-Format, das der Direct3D API beim
Zeichnen angezeigt wird.
Bei der Berechnung der Pyramidengeometrien ist in Direct3D-Anwendungen darauf zu
achten, dass hier ein linkshändiges Koordinatensystem zum Einsatz kommt.
Das 3D Koordinatensystem von Java3D und Direct3D
Man kennt in graphischen Systemen zwei Arten von kartesischen Koordinatensystemen:
Wodurch
sind linksbzw. rechtshändige
Koordinaten
systeme
gekennzeichnet ?
linkshändiges und rechtshändiges. Die Achsenrichtungen dieser kartesischen Systeme
folgen dabei jeweils der Rechte- bzw. Linke-Hand-Regel, die positive x-Achse ist also nach
rechts, die positive y-Achse nach oben gerichtet. Unterschiede bestehen in der Richtung der
positiven Z-Achse.
Während Java3D ein rechtshändiges Koordinatensystem definiert, bei dem die positive ZAchse dem Betrachter entgegengerichtet ist (er schaut in negative Z-Richtung), verwendet
Direct3D ein linkshändiges Koordinatensystem bei dem die positive Z-Achse quasi in den
Betrachtungsraum hineingerichtet ist.
Da die Java3D-API auf der Direct3D API aufsetzt und diese zur Ausgabe der Szene nutzt,
müssen vor der Übergabe der Daten einige Änderungen durchgeführt werden, um die in
einem rechtshändigen Koordinatensystem definierte Szene auch tatsächlich wie erwartet
ausgeben zu können.
Diese Änderungen beziehen sich einerseits auf die Reihenfolge der Raumpunkte, die die
Polygone definieren, andererseits auch auf die durchzuführende Ansichtstransformation.
Rechtshändige Koordinatensysteme besitzen die Eigenschaft, dass die Raumpunkte der
Polygone (Dreiecke), die ein beliebig komplexes Objekt bilden entgegen dem Uhrzeigersinn
angegeben werden müssen, damit die Polygonfläche gefüllt dargestellt wird. Polygone,
deren Raumpunkte im Uhrzeigersinn angegeben wurden werden nicht angezeigt (BackFace Culling). Hierdurch kann ein graphisches System Rechenzeit beim Zeichnen von
Objekten sparen, indem es bestimmte Flächen, nämlich die Rückseiten von Objekten von
der Verarbeitung ausschließt.
Damit die in Java3D definierten Geometrie-Objekte durch Direct3D wie gewünscht darstellt
werden können, muss die Reihenfolge der Raumpunkte, die jeweils ein Primitiv bilden also
invertiert werden. Aus der Definition eines Dreiecks mit den benutzerspezifizierten
Raumpunkten v0, v1 und v2 in Java3D würde dann die Reihenfolge v0, v2, v1 durch die
Java3D API erzeugt und an die Direct3D API übergeben. Da der Betrachter einer in einem
linkshändigem Koordinatensystem definierten Szene den in Java3D erstellen Objekten den
„Rücken zukehrt“, indem er in positive Z-Richtung blickt muss eine weitere Anpassung
durchgeführt werden. Möglich wäre hier etwas eine inverse Welttransformation in ZRichtung für alle Szenenobjekte. Wesentlich einfacher gestaltet sich hier jedoch eine
39
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Skalierungs-Ansichtstransformation um den Faktor –1 in Z-Richtung, was einer Spiegelung
der Raumpunkte an der X-Y-Ebene entspricht.
Bei der Pyramidenbodenfläche war zudem die Verwendung eines anderen Primitivs
erforderlich, da in Direct3D das in Java3D und OpenGL vorhandene QUADARRAY nicht zur
Verfügung stellt. Die Java3D API Implementierung für Direct3D wandelt ein QUADARRAY
also implizit in andere, auf dreieckigen Polygonen basierende Primitive um. Hier kann zum
Beispiel das von mir verwendete TRIANGLEFANARRAY oder ein beliebiges anderes,
gegebenenfalls auch Indiziertes Primitiv zum Einsatz kommen.
Ein weiterer Unterschied zur Java3D Anwendung stellt die Bereitstellung der Vertexdaten in
VertexBuffern dar, die bei der Erzeugung auf das spezielle Vertexformat eingestellt werden.
Was sind
VertexBuffer
?
Das Erstellen der Vertex-Buffer kann einmalig bei der Initialisierung der Geometrie
stattfinden, falls es sich um Objekte mit statischen Geometriedaten handelt. Eine Ausnahme
stellen hier die im Rahmen dieser Seminararbeit nicht behandelten Morph-Objekte dar.
Ein VertexBuffer kann dabei nur beschrieben werden, wenn er durch die „Lock()“-Methode
für andere Zugriffe gesperrt wurde. Dies ist deshalb erforderlich, da die Vertex-Daten häufig
im stark frequentierten Graphikspeicher gehalten werden, für den es in der Regel keine
besonderen Speicherschutzmechanismen gibt. Die Sperre muss anschließend durch das
Gegenstück zu Lock, der „Unlock()“-Methode aufgehoben werden.
Die Berechnung der Normalenvektoren über das Kreuzprodukt von zwei Vektoren, die zu
jeweils einer das Dreieckspolygon begrenzenden Kanten parallel sind und die
anschließende Normalisierung kann ähnlich wie in Java3D durch Methoden der
Hilfsbibliothek geschehen. Anders als in Java3D kennt man in Direct3D jedoch auch im
Rahmen der Werkzeugbibliotheken keine Routinen, um Texturkoordinaten oder
Normalenvektoren automatisch zu generieren. Eine Spezifizierung muss hier also stets
manuell auf die an anderer Stelle geschilderte Art und Weise für Normalenvektoren
geschehen. Bei komplexeren Objekten gestaltet sich die Spezifizierung der
Texturkoordinaten per Hand jedoch ungleich schwieriger.
Die Definition von Textur- oder Texelkoordinaten geschieht in Direct3D genau wie in
Java3D durch Angabe vor Werten zwischen 0 und 1. Zudem verlangt auch Direct3D die
Beschränkung des Bildformates auf ein Potenz von 2.
Beleuchtung in Direct3D
Neben den Geometrien können auch die im Java3D-Szenengraphen definierten
Lichtquellen in der Initialisierungsmethode der Direct3D-Anwendung als D3DLIGHT8Strukturen deklariert, mit geeigneten, die Lichteigenschaften beschreibenden Wertern
gefüllt und mittels der „setLight()“-Methode des DIRECT3DDEVICE8-Objektes der
Beleuchtungspipeline zugeordnet werden.
40
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Die auf diese Art und Weise in die Beleuchtungs-Pipeline eingestellten Lichter sind in
Direct3D standardmäßig ausgeschaltet und müssen explizit über einen Aufruf der Methode
„LightEnable()“ des DIRECT3DDEVICE8 einzeln eingeschaltet werden.
Eine Sonderstellung kommt den Umgebungslichtern (ambiente Lichtquellen) zu. Eigentlich
kennt Direct3D lediglich Direktional-, Punkt- und Spot-Lichtquellen, während das
Umgebungs-Licht nicht als Lichtquelle zählt. Die Farbe des ambienten Lichtes kann nach
der erfolgreichen Initialisierung von Direct3D über „setRenderStates()“ des
DIRECT3DDEVICE8-Objekts eingestellt werden.
Die in Java3D spezifizierten Parameter für die Lichtquellen wie Farbe, Position, Richtung
und Öffnungswinkel besitzen in ihren Direct3D Gegenstücken geeignete Entsprechungen,
so dass hier eine einfache Umsetzung stattfinden kann. Von den Java3D API-Lichtobjekten
nicht unterstützte Eigenschaften wie der innere Öffnungswinkel bei SpotLights, Unterteilung
der Lichtfarbe in ambiente, diffuse und spekuläre Farben, usw. werden durch die Java3DImplementierung mit Standardwerten gefüllt.
Bei der Verwendung von Lichtquellen in Direct3D ist stets auf Vollständigkeit der
Parameter-Werte zu achten. Sind eine oder mehrere Eigenschaften nicht mit (gültigen)
Werten versehen, so setzt die Direct3D API keine Standardeinstellungen wie Java3D,
sondern die Lichtquelle verhält sich auf unvorhersehbare Art und Weise.
Texturen und Material in Direct3D
Zusätzlich werden im Initialisierungsschritt einer Direct3D-Anwendung alle (in Java3D von
den Shape3D-Objekten referenzierten) Texturen und Material-Objekte geladen bzw. in
geeigneten Datenstrukturen zur späteren Verwendung während des Zeichenvorgangs
abgelegt. Für die Polygonfüllfarbe aus Java3D, den sogenannten „ColoringAttributes“
existiert hier kein entsprechendes Pendant. Die Farbinformation, mit der eine Fläche gefüllt
werden soll, kann in den jeweiligen Strukturen für die Raumpunkte mit abgelegt werden,
falls dies im flexiblen Vertex-Format (FVF) entsprechend angemeldet wurde.
Der Ladevorgang einer Textur kann auch hier wie in Java3D mit Hilfe einer in der
Werkzeug-Bibliothek angebotenen Methode (D3DXCreateTextureFromFileA()) durchgeführt
werden. Neben dem Pfad der Texturdatei, die verschiedene Formate besitzen kann ist hier
vor allem das DIRECT3DDEVICE8 und ein Surface-Objekt zu spezifizieren.
In Direct3D wird eine Textur zu programmtechnischen Verwendung immer in einem
sogenannten Surface abgelegt. Ein Surface repräsentiert im Prinzip einen rechteckigen
oder quadratischen Speicherbereich im System- oder Graphikspeicher, der mit Hilfe des
Surface-COM-Objekts verwaltet werden kann. Ein Surface erlaubt neben dem Speichern
und Holen der Bild- / Texturinformation auch die Abfrage der Surface- und damit der
Bildgröße. Man erkennt auch hier wieder deutlich die starke Anlehnung der in Java3D
konzipierten Klassen an die Möglichkeiten von Direct3D.
41
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Um den zu zeichnenden Objekten ein bestimmtes Erscheinungsbild /
Reflexionseigenschaften zu geben werden anschließend die im Rahmen der Szene
verwendeten Materialien als Strukturvariablen vom Typ D3DMATERIAL8 definiert, dessen
Strukturelemente ebenfalls weitgehend mit den Klassenvariablen der in Java3D
verwendeten „Material“-Klasse identisch sind.
Der Anwendungsentwickler kann hier also die Farbinformation als RGBA-Wert für
beleuchtete bzw. selbst leuchtende Bereiche (ambientes, diffus, spekulär, emittiert)
definieren, sowie die Reflexionseigenschaften des Objektes über einen „Rauhigkeitsfaktor“
spezifizieren.
Zeichnen der Szene
Nach den erfolgreich durchgeführten Initialisierungen kann endlich mit dem Zeichenvorgang
begonnen werden. Jede fensterbasierte Windows-Anwendung realisiert dazu in ihrer
WinMain-Methode (Klasse „Hauptprogramm.cpp“) eine Endlosschleife (vgl. Arbeitsweise
des Java3D Renderers !!), in der die vom Betriebssystem an diese Anwendung adressierten
Nachrichten verarbeitet werden. Diese Nachrichten werden vom System nicht direkt an die
Anwendung übergeben (Push), sondern in einer Nachrichtenwarteschlange abgelegt, die in
der Endlosschleife bei jedem Durchlauf auf wartende Nachrichten untersucht wird (Pull
durch „PeekMessage()“-Methode). Warten Nachrichten im Puffer, so werden diese über
„TranslateMessage()“ aufbereitet und per „DispatchMessage()“ über das Betriebssystem an
eine sogenannte Callback-Prozedur, deren Adresse (Funktionsname) beim Anlegen des
Fensterobjektes in der Schablone spezifiziert werden muss geschickt.
Die Callback-Prozedur ist dabei als mehr oder weniger umgangreiche Switch-CaseAnweisung (s. „EventListener.h“) ausgeführt, die die Art der Nachricht auswertet und in
Abhängigkeit davon entsprechende Aktionen durchführt. In der Beispielanwendung wird
eine Nachricht der Art WM_KEYDOWN, also ein Tastaturanschlag abgefangen und mit
Hilfe des Ereignisparameters WPARAM, der in konkreten Fall einen Identifizierer der
gedrückten Taste enthält näher untersucht. Handelt es sich bei der gedrückten Taste um die
Escape-Taste VK_ESCAPE, so wird eine weitere Nachricht (PostMessage(WM_CLOSE,
...)) in die Nachrichtenwarteschlange geschrieben, die zum Beispiel auch beim Schließen
des Fensters durch einen Mausklick auf das x-Symbol eingestellt würde.
Die „WM_Close“-Nachricht wird nicht explizit in einem Zweig der Switch-Case Anweisung
sondern durch die von Windows standardmäßig definierten Routinen verarbeitet. Dies wird
durch die DefWindowProc()-Anweisung implizit veranlasst.
Wartet keine Nachricht, was bei der Mehrzahl der Durchläufe der Fall sein sollte, so kann
der eigentliche Zeichenprozess durchgeführt werden.
In der Beispielanwendung ist neben der Verarbeitung der Tastaturereignisse zusätzlich eine
Behandlung von Mausereignissen erforderlich, um beispielsweise eine Bewegung des
42
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Benutzers in der Szene analog zu den „MouseRotate“-Verhalten in Java3D zu ermöglichen.
Die Java3D Implementierung verwendet hierfür ebenfalls die AWT-Routinen, die auf dem
Windows-Nachrichtensystem aufsetzen. Es wäre also durchaus möglich, die Verarbeitung
der Mausereignisse ebenfalls in der Callback-Prozedur durchzuführen. DirectX bietet hierfür
jedoch mit DirectInput eine spezialisierte API-Sammlung an, mit der neben der
hochperformanten Ansteuerung von Standard-Eingabegeräten wie Maus und Tastatur auch
spezielle Eingabegeräte wie Joysticks, mit einer Vielzahl von Knöpfen und Funktionstasten
und mehreren Achsen angesteuert werden können. Da die DirectInput-API nicht
Gegenstand der Seminararbeit darstellt, soll an dieser Stelle nur kurz erwähnt werden, dass
wie im Windows Nachrichtensystem eine Warteschlange definiert wird, in die bei
Mausereignissen Nachrichten für Bewegungen in X- und Y-Richtung, sowie das Drücken
oder Loslassen von Buttons eingestellt werden. Diese Nachrichten, die neben dem
Interaktionstyp zusätzliche Ereignisparameter enthalten, über die zum Beispiel festgestellt
werden kann, welche Taste gedrückt wurde oder wie weit die Maus in eine bestimmte
Richtung bewegt wurde, kann wiederum durch eine Switch-Case-Anweisung
(„MausPufferVerarbeiten()“-Methode) verarbeitet werden.
In den Switch-Case-Zweigen findet hier die Realisierung der erweiterten WeltAnsichtstransformation statt.
Für jedes in der Szene dargestellte Objekt muss für eine adäquate Nachbildung des in
Java3D angebotenen „MouseRotate“-Verhaltens eine Rotation entgegen der
Bewegungsrichtung der Maus erfolgen. Soll die Szene also beispielsweise im Uhrzeigersinn
um eine bestimmte Achse rotiert werden, so sind die Objekte um den entsprechenden
Winkel entgegen dem Uhrzeigersinn um diese Achse zu rotieren. Wichtig ist hierbei die
Behandlung dieser Transformation nach dem LIFO-Prinzip, d.h. die entsprechende
Rotationsmatrix muss in der resultierenden World-View Matrix immer als erster Faktor des
Produktes, die aktuelle Weltmatrix des Objektes als zweiter Faktor eingehen.
Neben den Verhalten zur Behandlung von Benutzerinteraktionen kennt man in Java3D auch
solche zu Realisierung von Animationen, die sich eines geeigneten Alpha-Objektes
bedienen, um zum Beispiel Rotationswinkel pro Frame, Rotationsanzahl und
Rotationsgeschwindigkeit zu definieren.
Diese Funktionalität muss in Drect3D durch eine, in der Endlosschleife für jeden Frame vor
dem Zeichnen des zu animierende Objekts stattfindende Anpassung der
Welttransformationsmatrix erfolgen.
Nach der Verarbeitung der Mausereignisse und der „Animationen“ kann das Zeichnen der
Objekte mit Hilfe der bei der Initialisierung vorbereiteten Datenstrukturen für Geometrien
(VertexBuffer), Texturen (Surfaces) und Materialien begonnen werden.
Beginn und das Ende der Zeichenvorgänge müssen dem DIRECT3DDEVICE8-Objekt
durch Aufrufe von „BeginScene()“ und „EndScene()“ angezeigt werden. Nur zwischen
43
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
diesen beiden Aufrufen ist es möglich, Primitive durch Aufruf von „DrawPrimitive()“ zu
zeichnen.
Dazu muss die Rendering-Pipeline in möglichst optimaler Reihenfolge mit den Vertexdaten,
Materialien, Texturen und Transformationsmatrizen versorgt (vgl. State Ordered Rendering
Was
geschieht
vor dem
Zeichnen
eines
Objekts ?
in Java3D) werden. Alle diese Operationen können mit Hilfe der Schnittstellenmethoden des
DIRECT3DDEVICE8-Objektes durchgeführt werden, das auch in diesem Zusammenhang
wiederum eine zentrale Rolle einnimmt.
Die Pipeline nimmt dabei zum Zeichnen eines konkreten Objektes einen eindeutig
definierten, in Java3D Anwendungen durch den Pfad von der Wurzel des Inhaltsgraphen
zum jeweiligen Blatt-Objekt vollständig definierten Zustand an, der zur Ausgabe des in der
Zeichenfolge unmittelbar nachfolgenden Objektes möglichst nur geringfügig geändert
werden sollte.
Wichtig ist dabei Tatsache, dass einmal in der Initialisierungsphase der Anwendung
vorbereitete, mit Daten gefüllte und über das Device-Objekt in der Rendering-Pipeline
eingestellte Material-Struktur, Textur-Surface oder Matrizen für Projektions-, Ansichts- und
Welttransformationen für alle, ab diesem Zeitpunkt gezeichneten Objekte bis zum
Austausch einer oder mehrerer dieser Komponenten Verwendung finden.
Die Änderung des Pipeline-Status gestaltet sich dabei je nach auszutauschender
Komponente unterschiedlich aufwendig. So erfordert das Einstellen einer anderen
Oberflächentextur den größten Aufwand. Dies gilt ganz besonders dann, wenn die Texturen
im Systemspeicher und nicht im schnelleren Graphikkartenspeicher abgelegt werden
können und bei einem Wechsel folglich über das Bussystem vom Hauptspeicher in den
Graphikspeicher transportiert werden müssen. Etwas einfacher gestaltet sich ein Wechsel
der Geometriedaten, d. h. die Umstellung auf einen anderen VertexBuffer. Trotzdem sollte
man den Aufwand für diese Operation nicht unterschätzen. Was für Java3D-Anwendungen
gilt, ist also auch für Direct3D Anwendungen von Bedeutung: Die Verwendung vieler kleiner
Primitive und damit VertexBuffer kann sich nachhaltig negativ auf das Laufzeitverhalten
auswirken. Es liegt hier also in der Verantwortung des Anwendungsentwicklers, Geometrien
soweit als möglich zu größeren Einheiten zusammenzufassen, die in einem einzigen
VertexBuffer abgelegt werden können. Ein Stripifier-Werkzeug wie in Java3D steht hierfür
jedoch nicht zur Verfügung.
Der Wechsel von Material-Struktur oder Welttransformationsmatrix sind in diesem
Zusammenhang weniger kritische Operationen auf der Rendering Pipeline, die jedoch
ebenfalls möglichst selten durchgeführt werden sollten. In der Regel ist zumindest ein
Austausch der Welttransformationsmatrix für jedes zu zeichnende Objekt erforderlich, da
sich sonst mehrere Objekte mit gleicher Position, Ausrichtung und Größe ganz oder
zumindest teilweise in der Szene überdecken.
Die angestellten Überlegungen zur Vorgehensweise der Java3D Implementierung bei der
Ausgabe einer Szene führen auch den Direct3D-Entwickler auf Regeln zur optimalen
Gestaltung des Programms.
Auf die Beispielanwendung bezogen sollte also durch die Java3D Implementierung bzw.
den Direct3D Entwickler folgende Zeichenreihenfolge eingehalten bzw. erstellt werden.
44
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
1. Zeichnen der Objekte mit gleicher Textur
2. Zeichnen der Objekte mit verschiedenen Texturen aber gleicher Geometrie
3. Zeichnen der Objekte mit verschiedenen Texturen und Geometrien aber gleichen
Materialeigenschaften und / oder Welttransformationen
Für die Beispielanwendung bedeutet dies, dass zum Beispiel zunächst die (durch die
Java3D Implementierung zusammengefassten) Pyramidenseitenflächen gezeichnet werden
sollten. Diese verwenden gleiche Texturen und Materialien, es müssen also lediglich die
Geometriedaten und die Welttransformationsmatrix ausgetauscht werden
Nach einem Austauschen der Textur, des Materials, der Geometrie und der
Welttransformationsmatrix kann dann der erste Pyramidenboden gezeichnet werden. Zur
Ausgabe der zweiten Bodenfläche müssen dann wiederum lediglich die Geometriedaten
und die Welttransformationsmatrix ausgetauscht werden.
Natürlich könnte in diesem einfachen Beispiel auch zunächst die Ausgabe der
Pyramidenböden erfolgen und erst anschließend die Pyramidenseitenflächen gezeichnet
werden.
Die im Rahmen der Objekt-Definition der Pyramide spezifizierte Zusammengehörigkeit der
geometrischen Objekte Pyramidenfläche und Pyramidenboden wird also beim
Wieso sind
Direct3DAnwendungen
prozedural
aufgebaut ?
Zeichenvorgang in geeigneter Weise aufgespalten, um eine optimale, den Status der
Rendering-Pipeline möglichst weitgehend erhaltende Reihenfolge zu erhalten.
Diese Vorgehensweise widerspricht dem Prinzip der Objektorientierung, erlaubt jedoch die
effiziente Nutzung der zugrundeliegenden Hardware. Hierin ist sicherlich einer der Gründe
zu suchen, weshalb Direct3D-Anwendungen trotz der in C++ möglichen Klassenbildung
vorwiegend rein prozedural konzipiert und implementiert sind. Vor vier Tutorials zur
Thematik Direct3D verfolgt lediglich eines einen objektorientierten Ansatz, alle anderen,
auch die offiziell von Microsoft in der SDK Dokumentation veröffentlichten
Beispielanwendungen bilden da keine Ausnahme. Trotzdem reichen diese Erkenntnisse in
überschaubaren Anwendungen wie der Beispielanwendung aus, um auch als unerfahrener
Entwickler eine geeignete Zeichenfolge zu erstellen. Was hier jedoch nur im kleinen
Rahmen bzw. mit umfangreichen Erfahrungen möglich ist wird dem Anwendungsentwickler
in Java3D vollständig durch die Implementierung abgenommen.
Für die Beispielanwendung habe ich trotzdem eine Bereitstellung der Pyramide in
Klassenform gewählt da bei einem derart einfachen geometrischen Objekt durchaus der
Aspekt der Wiederverwendbarkeit in anderen Projekten den Ausschlag gegenüber der nur
unwesentlich besseren Zeichenreihenfolge geben kann.
45
Florian Heidinger
„Gegenüberstellung von Direct3D und Java3D am praktischen Beispiel“
Literaturhinweise
Java3D
Bouvier, Dennis
“The Java3D Tutorial” – Kapitel 1-7 (1999)
Subra, Mohan
“Java Markets Whitepaper” (1998)
Sun Microsystems,
“The Java3D API - Technical Whitepaper” (1997)
Sun Microsystems,
“Java 3D API Collateral – 1.2.1 Performance Guide” (2002)
Sun Microsystems,
“Java 3D Programming: A Technical Overview” (1998)
www.j3d.org
“Java 3D API”
Direct3D
Dunlop, Robert
“Teach Yourself DirectX 7 in 24 Hours” (1998)
Microsoft,
“DirectX 8.0 Documentation (Visual C++)“ (2000)
Zerbst, Stefan
“3D Graphik-Programmierung und Spiele-Programmierung mit
DirectX“ (2002)
46
Herunterladen