Masterarbeit Entwicklung und Einbindung von 3D

Transcription

Masterarbeit Entwicklung und Einbindung von 3D
Masterarbeit
Entwicklung und Einbindung
von 3D-Grafikanimationen in ein
Car-Multimedia-Framework
Tsz Kwong YUNG
Matrikel-Nr.: 702251
Datum: 08.10.2005
Fachhochschule Darmstadt
Fachbereich Informatik
Referent:
Prof. Dr. Joachim Wietzke
Korreferent:
Prof. Dr. Thomas Horsch
Inhaltsverzeichnis
1
2
Einleitung .................................................................................................. 7
1.1
Motivation .......................................................................................................... 7
1.2
Aufgabenstellung ............................................................................................... 7
HMI mit 3D-GUI ...................................................................................... 9
2.1
2.1.1
Automotive Framework Integration............................................................... 9
2.1.2
OpenGL........................................................................................................ 14
2.1.2.1
OpenGL Konventionen ............................................................................ 16
2.1.2.2
Programmstruktur..................................................................................... 17
2.1.2.3
Grafik-Primitive: Lines, Polygon… ......................................................... 18
2.1.2.4
Vertex Array............................................................................................. 18
2.1.2.5
Koordinatensystem und Transformation .................................................. 19
2.1.2.6
Modell- und View-Transformation .......................................................... 21
2.1.2.7
Projektion Transformation ....................................................................... 22
2.1.2.8
Texture Mapping ...................................................................................... 24
2.2
Implementierung .............................................................................................. 25
2.2.1
Mesa ............................................................................................................. 26
2.2.2
OpenGL Photon Anbindung......................................................................... 27
2.2.3
3D GUI......................................................................................................... 30
2.3
3
Grundlagen ......................................................................................................... 9
Diskussion ........................................................................................................ 31
Geländedarstellung.................................................................................. 33
3.1
Grundlagen ....................................................................................................... 33
3.1.1
3.2
Implementierung .............................................................................................. 38
3.2.1
Funktionsbeschreibung................................................................................. 39
3.2.2
Probleme bei der Implementierung .............................................................. 42
3.3
4
Der SOAR (Stateless One-pass Adaptive Refinement) Algorithmus .......... 35
Evaluierung und Diskussion............................................................................. 43
OpenGL Java-Binding ............................................................................ 47
4.1
Grundlage ......................................................................................................... 48
4.1.1
4.1.1.1
4.1.2
JOGL ............................................................................................................ 48
Native-Anbindung.................................................................................... 51
JAVA3D....................................................................................................... 55
2
4.1.2.1
4.1.3
SWTOGL ..................................................................................................... 63
4.1.3.1
4.1.4
5
Native-Anbindung.................................................................................... 64
JNI und NIO ................................................................................................. 67
4.2
Implementierung .............................................................................................. 73
4.3
Evaluierung und Diskussion............................................................................. 78
3D-Modelle und Fisheye View ............................................................... 80
5.1
Grundlagen ....................................................................................................... 80
5.1.1
3D-Formate .................................................................................................. 80
5.1.1.1
Das OBJ-Format....................................................................................... 82
5.1.1.2
Das 3DS-Format....................................................................................... 82
5.1.2
6
Native-Anbindung.................................................................................... 61
Fisheye View................................................................................................ 83
5.2
Implementierung .............................................................................................. 87
5.3
Diskussion ........................................................................................................ 88
Zusammenfassung und Ausblick ............................................................ 91
Literatur ............................................................................................................... 93
Anhang A: Technische Daten der Target-Hardware........................................... 95
Selbständigkeitserklärung ................................................................................... 97
3
Abbildungsverzeichnis
Abbildung 1 Automotive Framework Architektur. Quelle [Wie05]........................................ 10
Abbildung 2 Aufbau der HMI-Komponente ............................................................................ 11
Abbildung 3 Raw-Widget mit Photon GUI-Builder ................................................................ 12
Abbildung 4 Integration von 3D-GUI mit Grafik-Widget ....................................................... 12
Abbildung 5 (a) Die einzelne Layer. (b) Die Überlagerung der Layer. (c) Das Ergebnis. ...... 13
Abbildung 6 Framework Integration Multi-Layer-Technik..................................................... 14
Abbildung 7 OpenGL Namenskonvention............................................................................... 16
Abbildung 8 Geometrische Objekte......................................................................................... 18
Abbildung 9 Würfel mit acht Eckpunkte ................................................................................. 19
Abbildung 10 Das OpenGL Koordinatensystem ..................................................................... 20
Abbildung 11 Transformationsstufen....................................................................................... 20
Abbildung 12 (a) Translation, (b) Rotation und (c) Skalierung............................................... 21
Abbildung 13 View-Transformation........................................................................................ 22
Abbildung 14 Orthografische Projektion ................................................................................. 23
Abbildung 15 Perspektivische Projektion. (a) glFrustum(), (a) gluPerspective() 24
Abbildung 16 Texture-mapping ............................................................................................... 24
Abbildung 17 (a) GUI in der normalen Stellung. (b) GUI in der aufgeklappten Stellung....... 31
Abbildung 18 Darstellung eines Geländes mit einem quadratischen Dreieck-Gitter .............. 33
Abbildung 19 (a) Darstellung als vollständiges Gitter. (b) Darstellung mit LOD.................. 34
Abbildung 20 Quadtree Hierarchie .......................................................................................... 34
Abbildung 21 Höhenunterschied zwischen Original-Punkt und der approximierten Stelle
(weißer Punkt). ......................................................................................................................... 35
Abbildung 22 Longest edge bisection ...................................................................................... 35
Abbildung 23 Rekursive Verfeinerung .................................................................................... 36
Abbildung 24 Schematischer Ablauf des Verfeinerungsalgorithmus ...................................... 37
Abbildung 25 Für die Geländedarstellung: (a) Textur. (b) Heightmap.................................... 38
Abbildung 26 Ablaufdiagramm des Gelände-Darstellungsprogramms ................................... 39
Abbildung 27 Blick auf das Gelände mit einem Symbol für die Navigation und ................... 42
Abbildung 28 Screenshot aus unterschiedlichen Winkeln....................................................... 42
Abbildung 29 Fehlerhaft dargestelltes Gelände ....................................................................... 43
Abbildung 30 Vektorisierte Strasse auf einem Gelände-Gitter. Die neu entstandenen Punkte
sind in blau gekennzeichnet. .................................................................................................... 45
Abbildung 31 Klassendiagramm der wichtigsten JOGL Klasse.............................................. 50
4
Abbildung 32 Sequenz-Diagramm beim Aufruf der Methode GLCanvas.reshape()............... 53
Abbildung 33 Klassendiagramm GLContext Hierarchie ......................................................... 53
Abbildung 34 Aufbau eines Szenen-Graphen in Java3D aus [Sun01]..................................... 57
Abbildung 35 Hierarchie der wichtigsten Szenen-Graphen-Klassen....................................... 58
Abbildung 36 Beispiel HelloUniverse Szenen-Graph ............................................................. 59
Abbildung 37 Screenshot Java3D-Beispielanwendung ........................................................... 61
Abbildung 38 Sequenz-Diagramm beim Erzeugen des GLContext Objektes ......................... 65
Abbildung 39 Namenskonvention der Native-Funktion bei JNI ............................................. 68
Abbildung 40 Vordefinierte C-Typen auf Java-Objekte.......................................................... 69
Abbildung 41 Klassen-Hierarchie von Java-NIO-Buffer. Quelle [Hit02]. .............................. 71
Abbildung 42 Schematische Aufbau eines Buffer-Objektes. Quelle [Hit02]. ......................... 71
Abbildung 43 Klassendiagramm SWT Widget........................................................................ 73
Abbildung 44 Sequenz-Diagramm beim Instantiieren des GLContext Objektes .................... 74
Abbildung 45 Sequenz-Diagramm CreateContext................................................................... 75
Abbildung 46 Gears Demo in Java mit SWTOGL .................................................................. 78
Abbildung 47 Schematische Darstellung des 3DS-Formats .................................................... 83
Abbildung 48 (a) Eine klassische Fisheye-Projektion. (b) Eine normale 2D-Karte Darstellung.
.................................................................................................................................................. 84
Abbildung 49 Hervorhebung durch 3D-Fisheye Zoom bei der Visualisierung ....................... 84
Abbildung 50 (a) Objekte/Häuser auf der Ebene. .................................................................... 85
Abbildung 51 Winkel θ und φ in Beziehung zu Punkt p ......................................................... 86
Abbildung 52 Herleitung der Projektion von p nach p’ bei φ = 90° oder 270° ....................... 86
Abbildung 53 Bestimmung von θ und φ .................................................................................. 87
Abbildung 54 Ein gerendertes POI Objekt. ............................................................................. 88
Abbildung 55 Stadt-Modell als normale Perspektivendarstellung........................................... 88
Abbildung 56 Stadt-Modell in Fisheye-View aus verschiedenen Blick-Richtung .................. 89
Abbildung 57 Darstellung einer künstlichen 3D-Straßen-Szene ............................................. 90
Abbildung 58 Fotografische Abbildung der Target-Hardware ................................................ 95
Abbildung 59 Schematische Darstellung der Hardware. Quelle [ICM04]. ............................. 96
5
Tabellenverzeichnis
Tabelle 1 Gegenüberstellung der Applikationsgröße mit und ohne 3D-Grafik ....................... 32
Tabelle 2 Messergebnisse bei einem 256x256 Gitter............................................................... 44
Tabelle 3 Messergebnisse bei einem 512x512 Gitter............................................................... 44
Tabelle 4 Zuordnung der Java- und OpenGL-Datentypen....................................................... 49
Tabelle 5 Zuordnung der Java- und C-Datentypen .................................................................. 69
Tabelle 6 Messergebnisse Gears-Demo mit Java/SWTOGL und C auf dem Target............... 79
Tabelle 7 Technische Daten der Target-Hardware. Quelle [ICM04]....................................... 95
Listingverzeichnis
Listing 1 Ein OpenGL Beispielprogramm ............................................................................... 17
Listing 2 Implementierung der glICM Funktionen .................................................................. 30
Listing 3 OpenGL Code in (a) Java/JOGL, (b) C/OpenGL ..................................................... 49
Listing 4 JOGL „HelloWorld“-Programm ............................................................................... 51
Listing 5 Code-Beispiele (a) Java/SWTOGL, (b) C/GLX...................................................... 63
6
1 Einleitung
1.1 Motivation
Echtzeit-3D-Computergrafik ist längst kein Privileg mehr der Highend-Workstation oder der
Spielkonsolen. Sei es auf dem PC oder im Bereich der Unterhaltungselektronik, überall sind
3D-Grafik garnierte Applikationen zu sehen. Deshalb ist es nicht verwunderlich, dass immer
mehr Hersteller im Automobilbereich1 ihre Produkte mit 3D-Applikationen ausstatten. 3Dbasierte Infotainment- und Navigationssysteme sind entweder in der Planung oder bereits
etabliert. Derartige Systeme gehören nicht nur zum aktuellen Trend, sondern sie werden oft
vom Kunden als Innovation angesehen.
Vor der Realisierung eines solchen Systems sind allerdings viele Fragen zu klären, die die
technische Umsetzung betreffen, wie z.B.:
•
•
•
•
•
•
Welche 3D-Bibliothek2 kann eingesetzt werden?
Wie ist der Verbrauch von Speicher- und CPU-Kapazitäten?
Können bestehende 3D-Programme portiert werden?
Wie kann eine 3D-Bedienoberfläche/-Anwendung in ein bestehendes AutomotiveFramework integriert werden?
Welche Programmiersprache kann eingesetzt werden?
Und wie ist das „Look-and-Feel“ einer solchen Anwendung?
1.2 Aufgabenstellung
Anhand von ausgewählten Themen sollen Einsatzmöglichkeit, Durchführbarkeit und
Portierbarkeit von Echtzeit 3D-Applikationen auf einer Automotive-Embedded-Hardware
untersucht werden. Die Aufgaben sind im Einzelnen folgende:
•
HMI (Human Machine Interface) mit 3D GUI (Graphical User Interface)
Eine mit 3D-Grafik animierte Benutzeroberfläche3 soll prototypisch implementiert
werden. Neben dem Demonstrieren des „Look-and-Feel“ soll hier insbesondere der
einwandfreie Einsatz von OpenGL sowie die Einbindung in ein AutomotiveFramework auf dem Target4 untersucht werden.
•
Geländedarstellung
Für echte 3D-Navigation ist eine Kartendarstellung mit Geländeinformationen
unabdingbar. Daher soll hier ein geeignetes Verfahren zur Geländevisualisierung auf
dem Target portiert werden. Ziel ist es, die Durchführbarkeit und die Portierbarkeit
eines solchen Algorithmus auf dem Target zu untersuchen und die Problemgrösse zu
erfassen.
•
OpenGL Java-Binding
Im Rahmen dieser Untersuchung soll eine geeignete freiverfügbare5 OpenGL JavaBinding-Implementierung auf dem Target unter QNX portiert werden. Es soll der
1
engl. automotive
engl. library
3
engl. GUI (Graphical User Interface)
4
siehe Anhang A
5
engl. Open Source
2
7
Frage nachgegangen werden, inwieweit die Entwicklung von 3D-Applikation auf
Java-Basis auf dem Target möglich und sinnvoll ist.
•
3D-Modelle und Fisheye-View
Eine weitere wichtige Anwendung im 3D-Bereich ist die Visualisierung von 3DModellen. Ein prototypischer 3D-Viewer ist bei dieser Untersuchung zu entwickeln.
Der Viewer soll ein 3D-Modell aus einem gängigen 3D-Dateiformat einlesen und mit
OpenGL auf dem Target darstellen. Diese Modelle können sowohl einzelne Gebäude
als auch eine ganze Stadt beinhalten. Als zusätzliche Funktion soll das Programm ein
eingelesenes 3D-Stadt-Modell mit einer „Fisheye-Projektion“ visualisieren. Ziel
dieser Untersuchung ist die Realisierbarkeit eines 3D-Viewers sowie die
Durchführbarkeit von Echtzeit-Berechnung von visuellen Effekten mit
umfangreicheren 3D-Modellen zu evaluieren.
Als 3D-Library soll OpenGL eingesetzt werden. Die Anwendungen sollen auf der im
Multimedia-Labor der FH-Darmstadt installierten Target-Hardware prototypisch
implementiert werden. Technische Daten des Targetsystems sind im Anhang A angeführt.
8
2 HMI mit 3D-GUI
Will man heutzutage eine Anwendung mit 3D-Grafik Unterstützung im Desktop-Bereich
entwickeln, so ist die Entscheidung für die geeignete 3D-Library sowie auf das GUIFramework relativ einfach zu treffen. Je nach Hardware, Betriebssystem,
Programmiersprache und finanziellen Rahmenbedingungen findet man hier schnell das
passende Produkt. Für Embedded-Anwendungen trifft diese Situation nicht zu.
Selbstverständlich existieren auch hier eine Fülle von guten Produkten, aber die
Anforderungen und Eigenschaften der eingesetzten Hard- und Software sind sehr speziell und
sehr verschieden, sodass man nicht nach einem „Einheitsrezept“ vorgehen kann. Außerdem
ist ein ausgefeiltes GUI-Framework für einfache Anwendungen, wie z.B. zur Steuerung von
Waschmaschinen, nicht unbedingt erforderlich sind, auch wenn es bereits solche Produkte mit
voll grafikfähigem Display, vergleichbar mit einem Navigationssystem, auf dem Markt gibt.
Im Rahmen einer prototypischen Implementierung einer mit 3D-Grafik animierten GUI soll
in dieser Ausarbeitung untersucht werden, wie sich die Integration von 3D-Grafik in ein GUIund Automotive-Framework realisieren lässt.
In Kapitel 2.1.1 wird zunächst auf mögliche Vorgehensweisen und Techniken für die
Realisierung einer 3D-GUI sowie die Integration der GUI bzw. HMI in ein AutomotiveFramework eingegangen. Dabei werden grundlegende Konzepte der Software-Architektur
erörtert. Einen Überblick zu den Grundlagen von OpenGL wird schließlich in Kapitel 2.1.2
gegeben.
Im Implementierungsteil (Kapitel 2.2) wird die prototypische Realisierung vorgestellt. Auf
die konkrete Target-Portierung der OpenGL-Library und die Anbindung von OpenGL zum
QNX-Window-System Photon wird in diesem Abschnitt ebenfalls eingegangen.
2.1 Grundlagen
2.1.1 Automotive Framework Integration
In einem modernen Auto werden Geräte im Infotainmentbereich über den MOST-Bus (Media
Oriented System Transport6) angeschlossen. Die Steuerung des gesamten Systems erfolgt
über ein Softwaresystem, welches in der Regel auf der Headunit betrieben wird. Das
klassische Software-Design im Embedded-Bereich beschränkt sich auf das Strukturieren der
Funktionalitäten in Unterprogrammen und auf das Aufteilen der Aufgaben in statische
Schichten und Module. Ein Multi-Tasking/-Processing fähiges Betriebsystem ist je nach
Einsatzgebiet nicht immer vorhanden. Zur Laufzeit befindet sich die Steuerungssoftware
allein im System und hat jederzeit die volle Kontrolle.
Diese einfache Vorgehensweise ist für ein modernes Infotainmentsystem zwar auch möglich,
aber längst nicht optimal. Um die Geräte und den Kommunikationsbus besser abstrahieren zu
können, ist eine modernere Architektur erforderlich. Abbildung 1 zeigt die schematische
Darstellung einer Software-Architektur für Automotive-Systeme7.
6
7
vgl. [MOST01]
vgl. [Wie05]
9
Abbildung 1 Automotive Framework Architektur. Quelle [Wie05]
Die Architektur wird durch ein Framework8 geregelt. Logische und physikalische Geräte
werden in Software-Komponenten gekapselt und zur Laufzeit als eigenständiger Thread oder
Prozess in das Gesamtsystem integriert. Die Kommunikation zwischen Komponenten erfolgt
über Austausch von Nachrichten9. Events werden in der Regel asynchron verarbeitet und in
Shared-Memory-Queues abgelegt. Jede Komponente verfügt über eigene Queues (Intern-,
Extern- und System-Queue) und einen Dispatcher für den Empfang und die Verarbeitung von
Events.
Neben den Geräte-Komponenten (Tuner, CD usw.), die jeweils ein logisches oder
physikalisches Gerät repräsentieren, gibt es weitere Komponenten wie z.B. die AdminKomponente, die spezifische Aufgaben im System erledigen.
Eine zentrale Rolle spielt dabei die Main-Dispatcher-Komponente. Hier erfolgt nämlich die
Steuerung des MOST-Treibers. Wie bereits oben erwähnt, werden die Geräte physikalisch
über den MOST-Bus angeschlossen. Zum Ansteuern eines Gerätes werden MOST-Events
gemäß dem MOST-Protokoll über den MOST-Treiber zum Gerät gesendet. Auch
ankommende Daten von den Geräten sind über den MOST-Treiber zu empfangen. Außerdem
ist diese Komponente auch für die interne Kommunikation zuständig. Alle internen Events
werden zunächst zum Main-Dispatcher gesendet und von dort aus zum Empfänger weiter
geleitet.
8
9
Die genaue Definition und Aufgabe von einem Framework ist in [Wie05] ausführlich beschrieben.
engl. Events
10
Für die grafische Darstellung der Bedienoberfläche sowie Bearbeitung der BenutzerInteraktion ist die HMI-Komponente zuständig. Diese Komponente besteht aus zwei
logischen Schichten10. Die GUI-Schicht und die „Main Desktop Manager“-Schicht (siehe
Abbildung 2). Aufgabe der GUI-Schicht ist die Darstellung der grafischen Bedien-Elemente
sowie das Anzeigen von Meldungen und Informationen. Der Main-Desktop-Manager
kümmert sich um das Event-Handling, also die Kommunikation mit anderen Komponenten,
und steuert je nach Kontext und Zustand die Ausgabe der GUI. Benutzer-Eingaben werden in
der Regel als Event gekapselt, vom Main-Dispatcher an die HMI weitergeleitet und vom
Main-Desktop-Manager ausgewertet.
Abbildung 2 Aufbau der HMI-Komponente
Die Anbindung von 3D-Animationen erfolgt logischerweise in der GUI-Schicht. Die GUI
kann mit verschiedenen Techniken realisiert werden. Einige Möglichkeiten werden
nachfolgend vorgestellt:
•
Window-System und Widget-Toolkit
Die klassische Art und Weise eine GUI zu erstellen ist die Verwendung eines
Window-Systems und der dazu passenden Widget-Toolkit-Library. Bei den meisten
Toolkits sind in der Regel komfortable GUI-Builder verfügbar. Damit kann man in
kürzester Zeit eine GUI per visuellen Editor zusammenstellen und den
entsprechenden Programmcode generieren.
Soll die Anwendung vollständig in das GUI-Framework integriert werden, so kann
man je nach Ablauflogik Callback-Funktionen den einzelnen Bedienelementen
(Button, Slider) zuweisen. Diese Callback-Funktionen werden bei einer Aktivierung
des GUI-Elements („anklicken“ eines Buttons) entsprechend aufgerufen. Eine
derartige Integration setzt aber voraus, dass die Eingabegeräte (TouchscreenDisplay, Commander) auch in das Betriebssystem integriert sein müssen, damit das
Event-Handling des GUI-Framework funktioniert. Im Embedded-Bereich ist das
jedoch aus diversen Gründen nicht immer möglich. Bei einem Touchscreen-Display
ist die Integration noch relativ einfach, das Drücken auf dem Display kann direkt als
eine Interaktion mit x- und y-Koordinaten an das GUI-Event-Framework
weitergeleitet werden. Bei einem komplexen Eingabegerät, etwa dem Commander
eines modernen InCar-Multimedia-Systems, ist eine einfache Zuordnung nicht mehr
möglich.
Für die Darstellung von 3D-Grafik werden von den Toolkit-Libraries spezielle
Grafik- oder Raw-Widget zur Verfügung gestellt. Die von der 3D-Engine gerenderte
Grafik kann durch entsprechende Funktionen auf diesen Widgets angezeigt werden.
Abbildung 3 zeigt ein Beispiel mit QNX-Photon.
10
Die „Virtuelle GUI“ nach [Wie05], die die tatsächliche GUI softwaretechnisch abstrahiert, wird hier nicht
behandelt.
11
Abbildung 3 Raw-Widget mit Photon GUI-Builder
Für die Animation kann beispielsweise die Timer-Funktion des GUI-Frameworks
verwendet werden. D.h. es wird jeweils nach einem festgelegten Zeit-Intervall eine
Callback-Funktion aufgerufen, die ein neues Bild rendert und anzeigt. Alternativ
kann die 3D-Engine auch in einer Schleife explizit aufgerufen werden. In diesem Fall
ist die Ausführung der GUI getrennt als ein separater Thread zu empfehlen, damit die
HMI nicht unnötig durch das Rendern der Grafik geblockt wird. Eine mögliche
Integration in ein Automotive Framework ist schematisch in Abbildung 4 dargestellt.
GUI
Loop
GUI Thread
HMI Prozess
Events
Disp.
Main Desktop Manager
Main Thread
Abbildung 4 Integration von 3D-GUI mit Grafik-Widget
Die GUI-Schicht wird als getrennter Thread ausgeführt und ist für die grafische
Darstellung und Animation der Bedienoberfläche zuständig und übernimmt auch die
Initialisierung des Window-Systems. So kann sich der Main-Desktop-Manager auf
das Framework-Event-Handling und die Steuerung der Ablauflogik konzentrieren.
Der Vorteil dieser Methode ist die einfache Integration in die bestehende GUI. 3DGrafik-Animationen können dadurch auch mit High-Level-Widgets wie z. B.
Browser oder Text-Editor kombiniert werden. Der Nachteil ist die im Vergleich zu
anderen Methoden schlechtere Performance.
12
•
Multi-Layer
Unterstützt die Grafik-Hardware Multi-Layer Technik, so kann die GUI auch
entsprechend ausgelegt werden. Beispielsweise kann ein Layer im Hintergrund eine
3D-Navigationskarte darstellen, während im Vordergrund die 2D-Bedienknöpfe
angezeigt werden. Abbildung 5 zeigt das Konzept schematisch. Die einzelnen Layer
der Beispiel-GUI sind in Abbildung 5(a) abgebildet. Diese können wie in Abbildung
5(b) hardwaremäßig aufeinander gelegt werden. Abbildung 5(c) zeigt das für den
Anwender sichtbare Ergebnis.
(a)
(b)
(c)
Abbildung 5 (a) Die einzelne Layer. (b) Die Überlagerung der Layer. (c) Das Ergebnis.
Die 2D Elemente können dabei mit einem klassischen GUI-Toolkit erstellt und
angezeigt werden. Der bei der Überlagerung sichtbare Bereich des VordergrundLayers wird als transparent markiert. Die 3D-Karte im Hintergrund-Layer kann mit
der OpenGL-Engine unabhängig von den anderen Layern gerendert werden.
Eine mögliche Integration in das Framework zeigt Abbildung 6. Bei diesem Modell
bietet sich an für jede Layer einen separaten Thread einzusetzen. Die Steuerung der
gesamten GUI über alle Layer hinweg wird wie bereits erwähnt vom Main-DesktopManager übernommen.
13
GUI-2D
GUI-Info
GUI-3D
Loop
GUI Thread
HMI Prozess
Events
Disp.
Main Desktop Manager
Main Thread
Abbildung 6 Framework Integration Multi-Layer-Technik
Das Verfahren bietet einige Vorteile gegenüber anderen Verfahren. Zum einen wird
eine modulare GUI-Entwicklung besser unterstützt. Zum anderen ist eine bessere
Performance zu erzielen; bei der Aktualisierung der 3D-Karte beispielsweise müssen
die anderen Layer nicht erneut gerendert werden. Der Nachteil bei dieser Methode
ist, dass die Steuerung im Desktop-Manager komplexer wird.
•
3D im Direct-Mode
Obwohl OpenGL als 3D-Engine eingesetzt wird, kann man diese Library auch
hervorragend für das 2D-Rendering benutzen. Die Idee bei diesem Modell ist, die
2D-Bedienelemente mit der 3D-Szene zusammen in OpenGL zu erzeugen und zu
rendern. Dadurch ist es möglich, auf das Window-System zu verzichten; die
Grafikausgabe kann direkt zum Grafiktreiber weitergeleitet werden. Diese
Vorgehensweise wird überwiegend bei der Spieleprogrammierung verwendet. Die
Integration in das Framework erfolgt wie beim oben genannten ersten Modell. Das
Rendern im Direct-Mode bringt in erster Linie Performance-Vorteile. Nachteile sind
dagegen die aufwendigen Programmierungen, da OpenGL standardmäßig keine
(2D/3D) Widget-Library bietet.
2.1.2 OpenGL
Im Bereich der 3D-Computergrafik hat sich OpenGL in den letzten Jahren als IndustrieStandard durchgesetzt. OpenGL (Open Graphics Library) wurde von der Firma SGI (Silicon
Graphics) entwickelt und 1992 der Öffentlichkeit vorgestellt. Die aktuelle Version vom Stand
September 2004 ist 2.0. Das Entwicklungsziel war eine effiziente, plattformunabhängige und
einfach benutzbare API für 2D- und 3D-Grafik bereit zu stellen. Wesentliche Eigenschaften
von OpenGL zusätzlich zu den gängigen 2D-Grafik-APIs sind:
•
Transparenz und Alpha-Blending
Objekte und Farben können als transparent dargestellt werden. Realisiert wird es
durch eine zusätzliche Farbinformation im Alpha-Kanal, d.h. eine Farbe wird durch
vier Werte (rot, grün, blau und alpha) beschrieben. Der Alpha-Wert gibt den Grad
der Transparenz an.
14
•
Anti-Aliasing
Bei der Darstellung von schräg verlaufender Linien und Kurven entstehen unter
normalen Umständen sog. Treppen-Effekte. Bei Anti-Aliasing wird die Farbe der
umliegenden Punkte so angepasst, dass diese Linien insgesamt weicher erscheinen.
•
3D-Projektion
3D-Szenen können sowohl in Perspektiven als auch in orthogonalen Ansichten
dargestellt werden. Bei der Perspektivenansicht erscheinen entfernte Objekte mit
zunehmender Entfernung kleiner. Bei der orthogonalen Projektion werden Objekte
wie bei einer CAD-Zeichnung ohne räumliche Verzerrung dargestellt.
•
Texturen
2D-Bilder können als Oberfläche auf Polygone projiziert werden. Verändert sich die
Größe der Polygone, so wird die erforderliche Anpassung der Textur-Größe
automatisch durchgeführt.
•
Licht- und Schatten-Effekte
Vielfältige Licht- und Schatten-Effekte sind mit OpenGL realisierbar. Eigenschaften
wie Lichtquellen, Materialien und Beleuchtungsmodelle können beliebig eingestellt
werden.
•
Nebel und atmosphärische Effekte
Durch Nebel und atmosphärische Effekte können sehr realitätsnahe Grafiken erstellt
werden.
OpenGL setzt sich aus drei Hauptkomponenten zusammen:
•
OpenGL Library
Die OpenGL Library (z.B. GL.a unter Linux) enthält alle Implementierungen der
Basis-Funktionen. Alle Basis-Funktionen beginnen mit dem Präfix „gl“. OpenGLBefehle sind leistungsfähige, aber primitive, Rendering-Funktionen. Komplexe 3DModelle müssen durch eine Menge von einfachen geometrischen Objekten, wie
Punkte, Linien und Polygone, aufgebaut werden.
•
OpenGL Utility Library
Die GLU Library (z.B. GLU.a unter Linux) enthält eine Menge von Hilfsfunktionen,
um komplexere 3D-Objekte wie Ellipsoide oder Splines zu erstellen. Nach der
OpenGL-Konvention erhalten alle GLU-Funktionen den Präfix „glu“.
•
Plattform/Window-System abhängige Library
Da OpenGL plattformunabhängig ist, wird für die Darstellung der gerenderten Grafik
eine Plattform- und Window-System abhängige Library verwendet. Die wichtigsten
sind:
o GLX: Für Unix / X-Window-Systeme
o WGL: Für Microsoft Windows
o GlPh: Für QNX-Photon
15
Außerdem existieren noch weitere Plattform- und Window-System unabhängigen
Toolkits, die mit OpenGL zusammenarbeiten. Die bekanntesten sind:
o GLUT: Das OpenGL Utility Toolkit [GLUT] ist eine Open-Source Library
und wird oft in der Literatur als Beispiel verwendet.
o SDL: Die Simple DirectMedia Layer Library [SDL] ist ebenfalls Open-Source,
wird überwiegend bei Computerspiele eingesetzt.
Im Folgenden werden einige Grundbegriffe von OpenGL erläutert, die für das Verständnis
dieser Ausarbeitung wichtig sind. Eine umfassende Erläuterung dieses Kontexts ist in der
weiterführenden Literatur [Shr04] verfasst.
2.1.2.1 OpenGL Konventionen
Die Benennung der Funktionen und Konstanten bei OpenGL erfolgt über bestimmten
Konventionen. Diese sind in Abbildung 7 zusammengefasst dargestellt.
glVertex3fx(...)
Wenn v, Vektor oder Array Version
Suffix
OpenGL-Typ
Daten-Typ
f
d
b
s
i
ub
us
uI
GLfloat
4-byte float
GLdouble
8-byte float
GLbyte
signed 1-byte integer
GLshort signed 2-byte integer
GLint
signed 4-byte integer
GLubyte unsigned 1-byte integer
GLushort unsigned 2-byte integer
GLuint
unsigned 4-byte integer
GLenum
GLbitfield
Anzahl der Komponenten (2,3 oder 4)
Funktionsname
Prefix für OpenGL-Befehle
Abbildung 7 OpenGL Namenskonvention
Zum Beispiel setzt sich die Funktion glVertex3f()aus den folgenden Argumenten
zusammen:
- Präfix:
gl
- Name:
Vertex
- Anzahl der Komponenten:
3
- OpenGL Typ der Operanden:
f
Konstanten beginnen bei OpenGL immer mit dem Präfix „GL_“ und werden immer in großen
Buchstaben geschrieben, z.B. GL_POINTS.
16
2.1.2.2 Programmstruktur
Anhand eines einfachen Beispiels soll die Struktur eines OpenGL Programm gezeigt werden.
Display *display = NULL;
Window
window;
XVisualInfo *visualInfo = NULL;
GLXContext glxContext;
void main() {
// X11-Code
display = XOpenDisplay( NULL );
visualInfo = glXChooseVisual( display, DefaultScreen(display), ...);
window = XCreateWindow( pDisplay, ...);
XMapWindow( display, window );
// GLX-Code
glxContext = glXCreateContext( display, visualInfo, NULL, GL_TRUE );
glXMakeCurrent( display, window, glxContext );
// OpenGL-Init-Code
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0f, 1.0f, 1.0f, 5.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 3D-Objekt-Code
glColor3f(1.0f, 0.3f, 0.3f);
glBegin(GL_QUADS);
glVertex3f(0.25f, 0.25f, 0.0f);
glVertex3f(0.75f, 0.25f, 0.0f);
glVertex3f(0.75f, 0.75f, 0.0f);
glVertex3f(0.25f, 0.75f, 0.0f);
glEnd();
// GLX-SwapBuffer
glXSwapBuffers( display, window );
...
}
Listing 1 Ein OpenGL Beispielprogramm
Für die X11 Anbindung verwendet das Beispielprogramm GLX. Die wesentlichen Schritte
sind:
•
Window-System initialisieren (X11-Code)
Mit den Funktionen XOpenDisplay() und XCreateWindow() wird ein Fenster unter
X11 initialisiert.
•
OpenGL-Context aus dem Window-System-Context erstellen (GLX-Code)
Der Handler bzw. Context des erstellten Fensters wird mit den Funktionen
glXCreateContext() und glXMakeCurrent() an OpenGL übergeben. Damit wird
die gerenderte Grafik auf diesem Fenster ausgegeben.
•
OpenGL Grundeinstellung durchführen (OpenGL-Init-Code)
17
Grundeinstellungen, wie z.B. Projektionsmodus glOrtho () oder Hintergrundfarbe
glClearColor() werden getätigt.
•
3D-Objekt erstellen (3D-Objekt-Code)
Beliebige Grafik-Objekte können an dieser Stelle mit den entsprechenden OpenGLFunktionen erstellt werden. Ein geometrisches Objekt, z.B. GL_QUADS, wird durch
eine Menge von Punkten (Vertices) beschrieben. Ein Punkt wird im OpenGL 3DRaum wiederum durch die Funktion glVertex()definiert.
•
Das gerenderte Ergebnis darstellen (GLX-SwapBuffer)
Das Ergebnis wird mit der Funktion glXSwapBuffers() auf dem Fenster gezeigt. In
der Regel verwendet man bei OpenGL für das Rendering den Double-Buffer-Modus,
d.h. das Rendering erfolgt zunächst auf dem Back-Buffer und erst nach
Fertigstellung wird der Buffer umgeschaltet und damit sichtbar gemacht. Damit ist
ein flüssiger Framewechsel möglich. Wird dagegen der Single-Buffer-Modus
verwendet, so genügt ein glFlush() Aufruf um das Ergebnis anzuzeigen.
Es ist zu beachten, dass OpenGL eine State-Machine ist. D.h. die Zustände bzw. getätigten
Einstellungen, z.B. die Hintergrundfarbe, bleiben so lange gültig bis zur nächsten Änderung.
2.1.2.3 Grafik-Primitive: Lines, Polygon…
Wie bereits in Listing 1 gezeigt, erfolgt die Spezifikation eines geometrischen Objektes mit
Hilfe des Funktionspaares glBegin()und glEnd(). D.h. alle Vertices innerhalb der
glBeginn() und glEnd() Klammer gehören zu diesem Objekt. In der Funktion glBeginn()
ist der Typ des geometrischen Objektes einzutragen. Es gibt zehn primitive Grafik-Typen in
OpenGL. Diese sind in Abbildung 8 angeführt.
V1
V0
V1
V3
V0
V2
V4
V4
V5
V1
V3
GL_TRIANGLE_STRIP
V2
V0
V5
V4
GL_LINE_STRIP
GL_LINES
V1
V5
V1
V2
V2
V4
V1
V0
V3
GL_TRIANGLE_FAN
V2 V5
V3 V4
GL_QUADS
V2 V4 V6
V1
V0
V7
V3
V5
GL_QUADS_STRIPS
V4
V3
V5
GL_TRIANGLES
GL_LINE_LOOP
V6
V1
V0
V2
V3
V5
V2
V4
V4
V3
V0
V3
V0
V6
V7
GL_POINTS
V0
V1
V2
V7
V0
V4
V1
V2
V3
GL_POLYGON
Abbildung 8 Geometrische Objekte
2.1.2.4 Vertex Array
Ein Vertex Array wird benutzt, um die Anzahl der Aufrufe von OpenGL-Befehlen zum
Erstellen eines 3D-Objektes zu minimieren. Zum Beispiel benötigt das Programm in Listing 1
zum Zeichnen eines Viereckes sechs Aufrufe. Je einen Aufruf für glBegin() und glEnd()
bzw. vier für die Vertex-Definitionen. Bei komplexeren Objekten steigt die Anzahl der
OpenGL-Aufrufe entsprechend an. Ein weiteres Problem ist die redundante Angabe von
gemeinsam genutzten Vertices. Das Problem wird im folgenden Beispiel erläutert:
Der Würfel in Abbildung 9 soll z.B. gerendert werden.
18
2
6
3
7
0
4
1
5
Abbildung 9 Würfel mit acht Eckpunkte
Ein Würfel kann durch acht Eckpunkte und sechs Flächen beschrieben werden. Da OpenGL
keinen Würfel als 3D-Objekt kennt, muss dieser durch einfache geometrische Objekte
zusammengesetzt werden. Verwendet man dafür als Grafik-Primitive ein Viereck
(GL_QUADS), so werden sechs GL_QUADS Objekte mit je vier Vertices benötigt. Das sind
insgesamt 24 glVertex()-Aufrufe. Die Summe der Aufrufe ist nicht effizient.
Die Lösung dieses Problem ist ein Vertex-Array. Dabei werden die Koordinaten der Vertices
eines 3D-Objektes in einem Array gespeichert und mit einem einzigen Befehl an OpenGL
weitergeleitet. Darüber hinaus können auf gemeinsam genutzte Vertices mit Hilfe von IndexListen zugegriffen werden. Das folgende Beispiel zeigt die Verwendung eines Vertex-Arrays.
static GLfloat myVertices[] = {
0.25f, 0.25f, 0.0f,
0.75f, 0.25f, 0.0f,
0.75f, 0.75f, 0.0f,
0.25f, 0.75f, 0.0f };
static Glubyte triangleIndices[] = { 0, 1, 2 };
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, myVertices);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, triangleIndices);
Beispiel mit Vertex Array, ein Dreieck wird gezeichnet
Als erstes muss das Array mit der Funktion glEnableClientState() aktiviert werden. Dann
wird das Array mit glVertexPointer() an OpenGL übergeben. Schließlich wird das Objekt
durch ein Vertex-Index-Array dereferenziert und mit der Funktion glDrawElements()
gerendert.
In der Praxis ist die Verwendung von Vertex-Array bei komplexeren 3D-Objekte
unabdingbar.
2.1.2.5 Koordinatensystem und Transformation
OpenGL verwendet ein 3-dimensionales Euklidisches Koordinatensystem. Die Ausrichtungen
der x, y und z-Achsen ist in Abbildung 10 abgebildet.
19
+y
-z
-x
+x
+z
-y
Abbildung 10 Das OpenGL Koordinatensystem
Ein 3D-Objekt wird durch seine Vertices spezifiziert. Für die Visualisierung muss das 3DObjekt zunächst im Koordinatensystem positioniert werden. Anschließend sind eine Reihe
von weiteren Transformationen bis zum fertigen Ergebnis notwendig. Abbildung 11 zeigt die
erforderlichen Transformationsstufen.
Abbildung 11 Transformationsstufen
•
Modell-View Transformation
In dieser Stufe wird das 3D-Objekt im 3D-Raum auf die richtige Position
(Translation, Rotation) gebracht. Außerdem wird die Kamera-Position eingestellt.
•
Projektions-Transformation
Je nach Projektionsmodus (perspektivisch oder orthogonal) und je nach Parameter
werden die Koordinaten entsprechend transformiert.
•
Normierung und Viewport Transformation
Zum Schluss werden die Koordinaten in die Ausgabegeräte spezifische Koordination
transformiert.
3D-Transformationen werden in der Computergrafik durch Matrix-Multiplikation realisiert.
Eine Transformationsmatrix ist eine 4 x 4 Matrix. Bei der Transformation werden die
Koordinaten eines jeden Punktes in der 3D-Welt mit dieser Matrix multipliziert.
Matrix-Multiplikation in OpenGL wird immer auf den Matrix-Stack angewendet, d.h. die als
Operand übergebene Transformationsmatrix wird mit der „aktuellen“ (Top of Stack) Matrix
20
multipliziert. Das Ergebnis davon bleibt als aktuelle Matrix auf dem Stack. Wenn also C die
aktuelle Matrix und M die Transformationsmatrix ist, dann ist das Ergebnis nach der
Multiplikation immer C·M. OpenGL bietet folgende Funktionen für die Matrix-Multiplikation
an.
•
glLoadMatrixf(const GLfloat *M)
Eine 4 x 4 Matrix wird als die aktuelle Matrix geladen.
•
glLoadIdentity(GLvoid)
Die Einheitsmatrix wird geladen, die Einheitsmatrix wird zum Initialisieren benötigt.
•
glMultMatrix(const GLfloat *M)
Die eigentliche Multiplikationsfunktion. Die als Parameter übergegebene Matrix
wird mit der Aktuellen Matrix multipliziert.
•
glPushMatrix(GLvoid), glPopMatrix(GLvoid)
Matrizen auf dem Matrix-Stack werden mit glPushMatrix()eine Stufe nach
verschoben und mit glPopMatrix() aus dem Stack gelöscht.
unten
Diese Funktionen beziehen sich immer auf den aktuell ausgewählten Matrix-Modus. Der
Matrix-Modus kann mit der Funktion glMatrixMode() eingestellt werden. Mögliche Werte
sind: GL_MODELVIEW, GL_PROJECTION und GL_TEXTURE.
2.1.2.6 Modell- und View-Transformation
•
Modell Transformation
Translation, Rotation und Skalierung von 3D-Objekten sind grundlegende
Operationen bei der Modell-Transformation (siehe Abbildung 12).
+y
+y
+y
+x
+z
+z
(a)
+x
+x
+z
(b)
(c)
Abbildung 12 (a) Translation, (b) Rotation und (c) Skalierung
Bei OpenGL sind folgende Funktionen vorgesehen:
o
o
o
•
glTranslate()
glRotate()
glScale()
View Transformation
Der Ansichtspunkt (View Point) oder die Kamera-Position werden mit der Funktion
21
gluLookAt( eyeX, eyeY, eyeZ,
centerX, centerY, centerZ,
upX, upY, upZ)
eingestellt. Dabei spezifizieren die eye* Parameter die Position der Kamera und die
center* Parameter den Mittelpunkt auf den die Kamera gerichtet ist. Die up*
Parameter geben den sog. Up-Vektor an, also die „Orientierung“ des
Ansichtspunktes (siehe Abbildung 13)
Abbildung 13 View-Transformation
2.1.2.7 Projektion Transformation
Nach der Modell-View-Transformation sind alle Punkte der 3D-Objekte an der richtigen
Position im 3D-Raum. Für die Darstellung auf einem 2D-Display müssen die Punkte jedoch
entsprechend der Projektionsregeln erneut transformiert werden. In OpenGL sind zwei
Projektionstransformationen möglich: die orthogonale (parallel-) und die perspektivische
Transformation.
•
Orthogonale Projektion
In der orthogonalen Projektion werden alle Punkte durch parallele Strahlen auf die
Projektionsfläche abgebildet. Die Größen und Winkel der Objekte bleiben konstant,
unabhängig von der Entfernung der Kamera. Diese Projektionsart wird vor allem bei
CAD-Systemen verwendet. Eine orthogonale Projektion kann in OpenGL mit der
folgenden Funktion eingestellt werden:
glOrtho( left, right, bottom, top, near, far )
Die Bedeutung der Parameter ist in Abbildung 14 dargestellt. Für eine 2DDarstellung mit OpenGL kann ebenso die Funktion glOrth2D() verwendet werden.
22
top
far
left
right
near
bottom
Abbildung 14 Orthografische Projektion
•
Perspektivische Projektion
Objekte erscheinen bei der perspektivischen Projektion zunehmend kleiner, je weiter
sie von der Kamera entfernt sind. Die perspektivische Projektion kann in OpenGL
auf zwei Arten definiert werden.
o
glFrustum( left, right, bottom, top, near, far)
o
gluPerspective( fovy, aspect, near, far)
Die Parameter der beiden Funktionen sind in Abbildung 15 dargestellt. Der
Parameter fovy ist der vertikale (y- / z-Achse) Blickwinkel mit einem Wertebereich
von 0° bis 180°. Der Parameter aspect ist das Verhältnis zwischen dem horizontalen
und vertikalen Blickwinkel.
23
top
Aspect=w/h
right
left
near
w
far
fovy
h
θ
near
far
bottom
(a)
(b)
Abbildung 15 Perspektivische Projektion. (a) glFrustum(), (a) gluPerspective()
2.1.2.8 Texture Mapping
Mit Texture-Mapping kann man eine Textur als Oberfläche für ein grafisches Objekt
zuweisen. Eine Textur ist in der Regel ein zwei dimensionales Bild11. Ein Datenelement in der
Textur wird Texel genannt. Das Prinzip des Texture-Mappings ist in Abbildung 16 gezeigt.
t1
t2
p1
p2
p4
t4
t3
p3
3D Objekt
Texture-Image
Abbildung 16 Texture-mapping
Jeder Punkt des 3D-Objektes bekommt als zusätzliche Information die Koordinaten des
Texels zugeordnet. Dabei beziehen sich die Texel-Koordinaten immer auf das aktuelle
Textur-Objekt. Die benötigte Funktion glTextCoord2f() wird wie folgt in Verbindung mit
glVertex3f() verwendet.
11
engl. image
24
glBegin(GL_QUADS);
glTextCoord2f(0.0f,
glTextCoord2f(1.0f,
glTextCoord2f(1.0f,
glTextCoord2f(0.0f,
glEnd();
0.0f);
0.0f);
1.0f);
1.0f);
glVertex3f(0.25f,
glVertex3f(0.75f,
glVertex3f(0.75f,
glVertex3f(0.25f,
0.25f,
0.25f,
0.75f,
0.75f,
0.0f);
0.0f);
0.0f);
0.0f);
Beispiel mit glTextCoord
Eine moderne 3D-Grafikkarte verwendet eine spezielle Texture-Engine und einen TextureSpeicher, um das Rendering zu beschleunigen. Es ist nicht sinnvoll Texturen bei jedem Frame
an die 3D-Engine zu schicken. Texturen werden daher in OpenGL als Textur-Objekte
verwaltet und je nach Implementierung im Video-Speicher abgelegt. Ein 3D-Objekt kann ein
Textur-Objekt über ein von OpenGL generiertes Index (Name) referenzieren. Das folgende
Beispiel zeigt diesen Mechanismus.
glGenTextures(1, &texName);
glBindTexture(GL_TEXTURE_2D, texName);
glTexParameteri(GL_TEXTURE_2D, ...);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RAGBA, width, high,
0, GL_RGBA, GL_UNSIGNED_BYTE, myImage);
...
glBindTexture(GL_TEXTURE_2D, texName);
glBegin(GL_QUADS);
glTextCoord2f(0.0f, 0.0f); glVertex3f(0.25f, 0.25f, 0.0f);
...
glEnd();
Beispiel mit glGenTextures
Mit der Funktion glGenTextures() wird ein freier Index für ein neues Textur-Objekt
ermittelt und mit glBindTexture() initialisiert. Weitere Eigenschaften können mit der
Funktion glTexParameteri() eingestellt werden. Die Funktion glTexImage2D() lädt
schließlich das Image zu dem Textur-Objekt. Ein mit glBindTexture() aktiviertes TexturObjekt hat bis zum erneuten Aufruf Gültigkeit für alle folgenden Objekt-Definitionen.
2.2 Implementierung
Für die Entwicklung von 3D-Appliaktionen mit OpenGL bietet die Firma QNX ein 3DGraphic Technology Development Kit (TDK)12 an. Das Paket enthält neben den Basis
OpenGL-Libraries auch ein Framework für die QNX-Photon-Anbindung13. Ausserdem
werden für einige unterstützte Grafik-Chipsätze OpenGL-Treiber mit
Hardwarebeschleunigung zur Verfügung gestellt.
Für die Implementierung der Aufgabe stand das TDK nicht zur Verfügung, was jedoch keine
negativen Auswirkungen auf die angestrebten Untersuchungen hatte. Eine
hardwarebeschleunigte OpenGL-Implementierung für den im Target verwendeten GrafikChip Lynx3DM von Silicon Motion14 war ohnehin nicht verfügbar, obwohl der Chip
12
vgl. [QNX3D]
Details zum GLPh-Framework sind in [Photon04] beschrieben.
14
vgl. [SMLynx]
13
25
hardwareseitige 3D-Beschleunigungsfunktionen kennt. Des Weiteren existiert eine OpenSource OpenGL-Implementierung namens Mesa15, die 3D-Rendering auf Softwarebasis
anbietet. Die Mesa-Libraries sind feste Bestandteile der gängigen Linux-Distribution und
somit bei fast allen Linux-Rechnern ohne spezielle 3D-Grafikkarte im Einsatz.
Aus diesen Gründen wurde entschieden für die Durchführung der vorliegenden Arbeit die
Mesa-Libraries auf QNX-X86 und QNX-SH4 zu portieren. Details über die Portierung sind in
Kapitel 2.2.1 beschrieben.
Ein weiteres Problem ist das fehlende GLPh-Framework. Schließlich muss die gerenderte
Grafik auch angezeigt werden. Die fehlende Verbindung zum Window-System Photon musste
daher selbst entwickelt werden. Diese wurde prototypisch implementiert und ist in Kapitel
2.2.2 aufgeführt.
Die für Demonstrationszwecke zu entwickelnde 3D-GUI wird in Kapitel 2.2.3 beschrieben.
2.2.1 Mesa
Für die Portierung auf QNX wurde die Mesa-Library in der Version 6.0 benutzt. Folgende
Änderungen sind dafür nötig gewesen:
•
Das Buildscript ./Make-config
# QNX V4 & Watcom Compiler
#qnx:
# $(MAKE) $(MFLAGS) -f Makefile.X11 targets \
# "GL_LIB = libGL.a" \
# "GLU_LIB = libGLU.a" \
# "GLUT_LIB = libglut.a" \
# "GLW_LIB = libGLw.a" \
# "OSMESA_LIB = libOSMesa.a" \
# "CC = qcc" \
# "CFLAGS = -O" \
# "APP_LIB_DEPS = -L/usr/X11/lib -lX11 -lm"
qnx:
$(MAKE) $(MFLAGS) -f Makefile.X11 targets \
"GL_LIB = libGL.a" \
"GLU_LIB = libGLU.a" \
"GLUT_LIB = libglut.a" \
"GLW_LIB = libGLw.a" \
"OSMESA_LIB = libOSMesa.a" \
"CC = qcc" \
"CFLAGS = -O -Y_ecpp-ne -I/usr/X11R6/include - \
DDEFAULT_SOFTWARE_DEPTH_BITS=31" \
"APP_LIB_DEPS = -L/usr/X11R6/lib -lglut -lGLU -lGL -lX11 -lm"
Änderungen in der Datei Make-config
•
Das Shellscript ./bin/mklib
'QNX')
15
vgl. [Mesa]
26
LIBNAME="lib${LIBNAME}.a"
echo "mklib: Making QNX library: " ${LIBNAME}
ar -ruv ${LIBNAME} ${OBJECTS}
FINAL_LIBS=${LIBNAME}
;;
Änderungen in der Datei ./bin/mklib
Da es keine offizielle QNX-Photon Unterstützung von Mesa gibt, wird die Library als eine
Unix-Variante erstellt. Hierbei ist das Problem, dass Mesa bei einem Unix-Build die CHeader-Dateien von X11 benötigt. Diese Header-Dateien können, falls nicht bereits
vorhanden, aus dem Open-Source XFree86-Distribution für QNX nachträglich installiert
werden. Alternativ kann die Mesa-Library auch als Offscreen-Version „gebaut“ werden. In
diesem Fall sind die X11-Header nicht mehr erforderlich. Für die SH4-Offscreen-Version
sind folgende Änderungen nötig.
qnx:
$(MAKE) $(MFLAGS) -f Makefile.X11 targets \
"GL_LIB = libGL.so" \
"GLU_LIB = libGLU.so" \
"GLUT_LIB = libglut.so" \
"GLW_LIB = libGLw.so" \
"OSMESA_LIB = libOSMesa.so" \
"CC = qcc" \
"CFLAGS = -O -Y_ecpp-ne -DDEFAULT_SOFTWARE_DEPTH_BITS=31 \
-Vgcc_ntoshle" \
"APP_LIB_DEPS = -lGLU -lGL -lm"
Änderungen in der Datei Make-config für OS-Mesa und SH4
2.2.2 OpenGL Photon Anbindung
Die Library für die OpenGL-X-Window Anbindung, GLX, verwendet für die Darstellung
einer von OpenGL gerenderten Grafik drei Funktionen (vgl. Listing 1):
•
•
•
CreateContext
MakeCurrent
SwapBuffer
Für die Problemlösung ist es nahe liegend, aus dem Sourcecode von GLX die entsprechenden
Funktionen zu extrahieren und zu portieren. Ein Blick in den Sourcecode von Mesa sowie in
die GLX Library zeigt jedoch, dass die Implementierung recht komplex und umfangreich ist.
Eine Portierung in einem vernünftigen Zeitrahmen war daher nicht möglich.
Alternativ kann man die Offscreen-Version von Mesa verwenden. Die Idee dabei ist anstatt
die Grafik direkt auf einem Ausgabefenster anzuzeigen, das Ergebnis im Offscreen-Modus in
einen Image-Buffer zu rendern. Dieser Buffer wird anschließend über normale PhotonFunktionen auf dem Bildschirm angezeigt. Diese Methode ist sicherlich nicht besonders
effizient, aber portabler und einfacher zu realisieren. Für die Implementierung des Prototyps
wurde daher diese Methode verwendet.
Liegt eine Grafik in einem Image-Buffer vor, so gibt es mehrere Möglichkeiten die Grafik auf
einem QNX-Photon Fenster anzuzeigen.
27
•
Label-Widget
Die einfachste Möglichkeit, ein Image auf einem Photon-Fenster anzuzeigen, ist die
Verwendung einees Label-Widget mit einem Image als Inhalt. Solch ein Widget
kann mit dem Photon-GUI-Builder erstellt werden. Im Programm wird eine neue
Image-Struktur mit dem Inhalt der gerenderten Grafik dem Label-Widget
zugewiesen. Der folgende Code zeigt ein Beispiel.
PhImage_t *img;
img = PhCreateImage(NULL,width,height,Pg_IMAGE_DIRECT_8888,NULL,0,0);
...
img->image = (char*) myOpenGLBuffer;
PtSetResource( ABW_3Dlabel, Pt_ARG_LABEL_IMAGE, img, 0);
PtFlush();
...
Label-Widget Beispiel
•
Memory und Shared Memory Blit16
Bei dieser Methode wird die Funktion PgDrawImage verwendet. Im Gegensatz zu der
ersten Methode wird das Image nicht über die Widget-Library, sondern direkt zum
Photon-Window-Manager geschickt. Ein Image kann beispielsweise wie folgt
angezeigt werden:
unsigned short *image; // 16 bit RGB565 Data
...
image = (unsigned short*) malloc(width * height * 2);
...
PhPoint_t pos = {0, 0};
PhDim_t size = {width, height};
PgDrawImage(image, 0, &pos, &size, pitch, 0);
pgFlush();
Memory Blit Beispiel
Unter QNX läuft das Window-System und die Applikation in unterschiedlichen
Prozessen. Für die Anzeige muss das Image daher komplett von der Applikation zum
Window-System/Grafik-Treiber in Form von Messages übertragen werden. Um eine
bessere Performance zu erzielen, bietet Photon u.a. die Funktion PgDrawImagemx()
an, die Shared-Memory als Image-Buffer verwendet, Dadurch entfällt die
zeitaufwendige Übertragung der Image-Daten. Ein Shared-Memory-Buffer kann mit
der Funktion PgShmemCreate() erstellt werden, Details dazu siehe [Photon].
unsigned short *image; // 16 bit RGB565 Data
...
image = (unsigned short*) PgShmemCreate(width * height * 2, NULL);
...
PhPoint_t pos = {0, 0};
PhDim_t size = {width, height};
PgDrawImagemx(image, 0, &pos, &size, pitch, 0);
pgFlush();
16
vgl . [Feh04]
28
Shared Memory Blit Beispiel mit PdDrawImagemx
Prinzipiell wird das Image immer noch zum Window-Manager kopiert. Der Vorteil
ist allerdings, dass das Kopieren des Images aus dem Shared-Memory schneller
vonstatten geht. Zu beachten ist dabei, dass der Shared-Memory nach der Benutzung
wieder freigegeben wird, um „Shared-Memory-Leak“ zu vermeiden. Die aktuell im
System belegten Shared-Memories sind im Device-Verzeichnis /dev/shmem zu
sehen.
•
Video Memory Blit
Eine weitere Methode um die Performace zu steigen, ist die Verwendung von VideoSpeicher als Image-Buffer. Dadurch entfällt der externe Kopier-Vorgang des ImageBuffers. Der folgende Code zeigt die benötigten Funktionen.
unsigned short *image; // 16 bit RGB565 Data
PdOffscreenContext_t *ctx;
...
ctx = PdCreateOffscreenContext(0, width, height, Pg_OSC_MEM_PAGE_ALIGN);
image = PdGetOffscreenContextPtr(ctx);
...
PhRect_t rect = { {0, 0}, {width, height} };
PgContextBlit(ctx, &rect, NULL, &rect);
pgFlush();
Video Memory Blit Beispiel
Die Verwendung von Video-Speicher hat jedoch einige Einschränkungen. Zum
Beispiel ist der Video-Speicher in der Regel auf einer relativ kleine Größe
beschränkt. Eine Art „virtueller Video-Speicher“ um den Speicher zu vergrößern
existiert nicht. Außerdem ist das Anlegen bzw. Initialisieren von Video-Speicher
sehr zeitaufwendig.
•
Video Memory Blit im Direct-Mode
Eine weitere, wenn auch nur minimale Performance-Steigerung kann durch die
Grafik-Ausgabe im Direct-Mode erzielt werden.
unsigned short *image; // 16 bit RGB565 Data
PdOffscreenContext_t *ctx;
...
PdDirectContext_t *direct_ctx;
direct_ctx = PdCreateDirectContext();
PdDirectStart(direct_ctx);
...
ctx = PdCreateOffscreenContext(0, width, height, Pg_OSC_MEM_PAGE_ALIGN);
image = PdGetOffscreenContextPtr(ctx);
...
PhRect_t rect = { {0, 0}, {width, height} };
PgContextBlit(ctx, &rect, NULL, &rect);
pgFlush();
Direct-Mode Beispiel
Der Window-Manager wird in diesem Fall deaktiviert und das
Anwendungsprogramm hat die alleinige Kontrolle über den Grafik-Device. Die
29
Konsequenz davon ist, dass die Maus sowie alle vom Window-Manager verwalteten
Eingabe-Geräte ebenfalls deaktiviert werden.
Für die Implementierung der glICM-Funktionen wurden die letzten beiden Methoden
gewählt. Die Auswahl von Direct-Mode erfolgt über ein Parameter beim Aufruf der
createContext() Funktion. Listing 2 zeigt ein Auszug davon.
...
int glICMCreateContext(int width, int height, int directMode) {
...
// Start Direct Mode
if (directMode) {
// Create the direct context
direct_context = PdCreateDirectContext();
PdDirectStart(direct_context);
} else {
glICMInitPhotonWindow();
}
...
// Create the offscreen context
buff=PdCreateOffscreenContext(0, screenWidth, screenHeight+1,
Pg_OSC_MEM_PAGE_ALIGN | Pg_OSC_MEM_2D_WRITABLE);
...
OSMesaBuffer = (unsigned char *) PdGetOffscreenContextPtr(buff);
glICMOSMesaInit();
return;
}
int glICMOSMesaInit(void) {
OSMesaCtx = OSMesaCreateContextExt( OSMESA_RGB_565, 8, 8, 8, NULL );
...
}
int glICMMakeCurrent() {
if (!OSMesaMakeCurrent( OSMesaCtx, OSMesaBuffer,
GL_UNSIGNED_SHORT_5_6_5, screenWidth, screenHeight )) {
printf("OSMesaMakeCurrent failed!\n");
return 0;
}
return 1;
}
void glICMSwapBuffer(void) {
glICMSwapImage(OSMesaBuffer, screenHeight, screenWidth, 2);
PgContextBlit(buff,&rect, NULL, &rect);
PgFlush();
}
Listing 2 Implementierung der glICM Funktionen
2.2.3 3D GUI
Als Animation soll eine aufklappbare Bedientafel realisiert werden. Nach dem Aufklappen
soll eine Instrumententafel erscheinen. Abbildung 17 zeigt den schematischen Aufbau.
30
+y
+y
+x
-x
+x
+z
+z
(a)
(b)
Abbildung 17 (a) GUI in der normalen Stellung. (b) GUI in der aufgeklappten Stellung
Die Bedien- und Instrumententafel werden in OpenGL als GL_QUAD mit jeweils vier Punkten
definiert und mit den dazugehörigen Texturen überzogen.
2.3 Diskussion
Die implementierte GUI ist eine einfache OpenGL-Anwendung und zeigt praktische Beispiele
der OpenGL-Programmierung. Das definierte Ziel, eine 3D-GUI mit OpenGL zum
Demonstrationszweck zu entwickeln und in ein Automotive-Framework zu integrieren, wurde
erreicht.
Die erfolgreiche Portierung von Mesa war ausschließlich auf Anpassungen des Buildscripts
beschränkt. Das zeigt, dass der C-Code von Mesa plattformunabhängig geschrieben wurde.
Auch die ausgereifte Cross-Compile-Technologie von QNX / GNU wird hier eindrucksvoll
bestätigt. Zwischen der x86 und SH4 Version wurden selbst bei einer derart komplexen
Library keine Unterschiede festgestellt.
Die selbstentwickelte glICM-Funktion hat nur prototypischen Charakter und dient hier
lediglich als Ersatz für das fehlende GlPh-Framework. Durch diese Realisierung wurden gute
Einblicke in das Window-System Photon gewonnen.
Die im Target eingesetzte Grafik-Chip, ein Silicon Motion Lynx3DM, ist hardwareseitig
bereits mit multimedia- und 3D-Beschleunigungsfunktionen ausgestattet. Leider ist eine
hardwarebeschleunigte OpenGL-Implementierung nur als kommerzielle Individuallösung
erhältlich. Ein Blick in deren Datenblätter lassen interessante Möglichkeiten vermuten.
Eine Gegenüberstellung der Applikationsgröße mit und ohne 3D-Grafik zeigt
Tabelle 1. Auf dem Target beträgt der Mehrverbauch an Speicher etwa 1,8 MB.
31
SH4
SH4 mit Debug-Option
X86
X86 mit Debug-Option
Mit statisch gelinken
Mesa-Libraries
3,7 MB
6,8 MB
3,5 MB
7,1 MB
Ohne 3D-Grafik
1,9 MB
5,0 MB
1,9 MB
5,7 MB
Tabelle 1 Gegenüberstellung der Applikationsgröße mit und ohne 3D-Grafik
Die in diesem Kaptitel portierten und implementierten OpenGL-Funktionen dienen als Basis
für die Untersuchungen in den Kapiteln 3, 4 und 5. Insbesondere in Kapitel 3 wird eine
komplexerer OpenGL-Anwendung, die die 3D-Engine wesentlich mehr belastet, auf das
Target portiert und untersucht.
32
3 Geländedarstellung
Haupteinsatzgebiet von Echtzeit 3D-Grafik in einem InCar-Multimedia-System ist die
Darstellung von Navigationskarten. Bisherige Systemen haben meistens einen 3D-Modus, der
jedoch nur 2D-Karten in einer Perspektivendarstellung anzeigt. Das liegt einerseits daran,
dass das kommerziell verfügbare Kartenmaterial nur als 2D-Information vorliegt, andererseits
hat die Grafikfähigkeit der Hardware Grenzen. Eine Perspektiven-2D-Darstellung trägt zwar
zu einer besserer Orientierung des Benutzers bei, jedoch erhöht sich der Informationsgewinn
für ihn, wenn die Straßenkarte in einem 3D-Gelände integriert visualisiert wird.
Im Rahmen dieser Arbeit soll daher ein geeigneter Gelände-Visualisierungsalgorithmus
prototypisch auf dem Target implementiert werden. Ziel der Untersuchung ist zu evaluieren,
inwieweit ein solches Visualisierungsverfahren, das in der Regel für High-End-Workstation
konzipiert ist, auf dem Target angewendet werden kann.
In Kapitel 3.1 wird zunächst auf die Probleme und Grundlagen der Gelände-Visualisierung
eingegangen. Die Implementierung des Verfahrens wird in Kapitel 3.2 erläutert. Die
Ergebnisse der Evaluierung werden schließlich in Kapitel 3.3 zusammengefasst und
diskutiert.
3.1 Grundlagen
Als Beispiel wird ein Gelände mit der Größe von 100.000m x 100.000m, das in 1m
Auflösung dargestellt werden soll, betrachtet. Die Höhendaten liegen, wie im Bereich der
Gelände-Visualisierung üblich, in einer sog. Heightmap vor. Eine Heightmap ist ein
zweidimensionales Array, in dem die Höhendaten als Wert gespeichert sind. Dabei
entsprechen die Indizes den x und y Koordinaten des Geländes. Die einfachste Möglichkeit,
das Gelände darzustellen, ist ein quadratisches Dreieck-Gitter aufzubauen (Abbildung 18).
Abbildung 18 Darstellung eines Geländes mit einem quadratischen Dreieck-Gitter
Zwei Probleme treten bei diesem Beispiel auf. Erstens, das Gitter ist mit einer Größe von ca.
120GB17 ziemlich groß. Zweitens, für die Darstellung des Geländes werden ca. 20
Milliarden18 Dreiecke benötigt, die dann von der 3D-Engine gerendert werden.
17
18
100.000 · 100.000 · 3 Punkte · 4 Byte für Float-Variable pro Punkt
Formel: (n -2)2 · 2 mit n = Anzahl der Punkte
33
Für das erste Problem hilft nur, eine niedrigere Auflösung zu nehmen. Ein 1k x 1k Gitter ist
für die heutige Hardware eine gängige Größe. Das zweite Problem kann durch den Einsatz
von einem LOD (Level of Detail) Algorithmus effektiv gelöst werden.
Das Grundprinzip eines LOD Algorithmus ist, das Originalgelände (repräsentiert durch eine
Heightmap oder eine vergleichbare Struktur) durch ein entsprechend vereinfachtes Gitter zu
approximieren. Das generierte Gitter soll dabei so wenig Punkte/Dreiecke wie nötig enthalten
und gleichzeitig so viele Details wie möglich darstellen. Abbildung 19 zeigt den Idealfall;
eine ebene Fläche kann durch einige wenige Dreiecke dargestellt werden.
(a)
(b)
Abbildung 19 (a) Darstellung als vollständiges Gitter. (b) Darstellung mit LOD
LOD Verfahren werden in zwei Gruppen eingeteilt: diskrete und kontinuierliche LOD
Verfahren. Diskrete Verfahren arbeiten mit einer festen Anzahl von statischen
Modellen/Gittern mit unterschiedlichen Auflösungen. Werden zur Laufzeit mehr Details
gefordert, so wird das Gitter mit der nächst höheren Auflösung eingeblendet. Solche
Verfahren kommen meistens zum Einsatz, wenn LOD auf 3D-Objekte angewendet werden.
Für die Darstellung von Flächen und Geländen sind die CLOD (continuous LOD) Verfahren
besser geeignet. Bei CLOD gibt es keine festen Detailstufen, die Detaillierung des Gitters
wird dynamisch generiert.
Eine typische Vorgehensweise ist die Darstellung des Gitters als Quadtree, um darauf eine
rekursive Verfeinerung auf die einzelnen Teil-Quadranten des Gitters vorzunehmen (siehe
Abbildung 20).
Abbildung 20 Quadtree Hierarchie
Um zu entscheiden, ob ein betroffener Quadrant weiter verfeinert wird, wird ein sog. „Vertex
Interpolation Error“19 berechnet. Überschreitet der Vertex-Interpolation-Error einen
bestimmten Schwellenwert, so wird dieser Quadrant rekursiv verfeinert. Je nach Algorithmus
werden für den Vertex-Interpolation-Error unterschiedliche Aspekte berücksichtigt. Im
einfachsten Fall ist es der Höhenunterschied des Punktes zwischen den einzelnen DetailLevels (siehe Abbildung 21). Auch die Entfernung des Punktes zum Betrachter wird häufig in
die Berechnung einbezogen; je weiter die Punkte entfernt liegen, desto weniger Details sind
erforderlich.
19
vgl [Ulr00]
34
Abbildung 21 Höhenunterschied zwischen Original-Punkt und der approximierten Stelle (weißer Punkt).
Für eine flüssige Animation, wie etwa bei einem Flugsimulator, ist eine Frame-Rate von
mindesten 25 Frames pro Sekunde erforderlich. Um solche Anforderung zu erfüllen, arbeiten
die meisten LOD-Verfahren mit einem dynamischen Schwellenwert für den VertexInterpolation-Error. Diese Fehlerschranke steuert direkt den Detaillierungsgrad der
Darstellung. Dauert die Berechnung von einem Frame zu lange, so kann man für den nächsten
Frame die Detaillierung verringern. Damit ist eine ruckfreie Animation gewährleistet.
3.1.1 Der SOAR (Stateless One-pass Adaptive Refinement) Algorithmus
Für die Evaluierung wurde eine auf dem SOAR-Algorithmus basierte Implementierung auf
dem Target adaptiert. Das SOAR-Verfahren wurde von Peter Lindstrom und Valerio Pascucci
an der Lawrence Livermore National Laboratory20 entwickelt und zählt zu den am häufigsten
eingesetzten LOD-Verfahren. Im Gegensatz zu anderen vergleichbaren Verfahren wird bei
SOAR das Gitter für jeden Frame komplett neu generiert. Das Kernstück des Verfahrens ist
der Verfeinerungs-(Refinement)Algorithmus. Dieser basiert auf der „longest edge bisection“,
wobei ein Dreieck beim Verfeinern mittig durch seine Hypotenuse in zwei gleich große,
rechtwinklige Teil-Dreiecke aufgeteilt wird (Abbildung 22).
Abbildung 22 Longest edge bisection
Diese Teilung wird sehr häufig bei LOD-Verfahren angewendet und hat zwei entscheidende
Vorteile. Erstens, die Teilung kann völlig lokal und unabhängig vom gesamten Gitter
durchgeführt werden. Zweitens, die durch die Teilung entstandenen Punkte ergeben sich
wieder zu einem Quadrat; das Texture-Mapping beim Rendern wird dadurch vereinfacht.
Ausgangpunkt der Verfeinerung ist ein Gitter mit vier Dreiecken bestehend aus den vier
Eckpunkten und einem Mittelpunkt (siehe Abbildung 23a), welches die gesamte
Geländefläche repräsentiert. Ausgehend von dieser Stufe wird das Gitter rekursiv per EdgeBisection in kleineren Dreiecken aufgeteilt (Abbildung 23b,c). Die Eckpunkte der so
entstandenen Dreiecke werden als neue Punkte in das Gitter aufgenommen. Die Entscheidung
über die Teilung/Verfeinerung eines Teil-Quadranten erfolgt, wie bereits erwähnt, über die
20
vgl. [Lin01]
35
Auswertung des Vertex-Interpolation-Errors. Eine ausführliche Darstellung des Verfahrens ist
in [Lin01] beschrieben.
(a)
(b)
(c)
Abbildung 23 Rekursive Verfeinerung
Eine Besonderheit der eingesetzten Implementierung ist, dass die Heightmap-Daten direkt als
Vertex-Array (Array von 3D-Koordinaten) an OpenGL mit der Funktion glArrayPointer()
übergeben werden. Das Aufnehmen eines neuen Punktes bei der Verfeinerung in dem
generierten Gitter bedeutet einfach das Hinzufügen des Indizes dieses Punktes in die IndexListe. Diese Liste wird dann zum Rendern des Geländes an OpenGL übergeben21. Damit ist
eine Speicherung der Gitterpunkte etwa in Form von Quadtree nicht mehr erforderlich.
Der Ablauf des Verfeinerungsalgorithmus ist in Abbildung 24 schematisch dargestellt.
21
vgl. Kapitel 2.1.2
36
Abbildung 24 Schematischer Ablauf des Verfeinerungsalgorithmus
Die Schritte sind folgendermaßen:
•
Starten der Verfeinerung mit einem Gitter wie in Abbildung 23a.
•
In der Verfeinerungsfunktion wird zuerst der Vertex-Interpolation-Error des
Mittelpunktes r, siehe (1), ermittelt. Ist der Error kleiner als der Schwellenwert, dann ist
eine Verfeinerung nicht erforderlich; die Rekursion kann abgebrochen werden.
•
Beginn der Verfeinerung.
•
Ist eine Verfeinerung am Punkt p1 erforderlich? D.h. Fehler am Punkt p1 >
Schwellwert.
•
Ist eine Verfeinerung am Punkt p2 erforderlich (3)?
•
Wenn ja, Quadrant A rekursiv verfeinern (4). Nach der Verfeinerung
können hier zwei Fälle entstehen:
•
Wenn Quadrant A verfeinert wurde, dann sind alle darin enthaltenen Punkte
bereits in das Gitter aufgenommen (5). Keine weitere Aktion ist
erforderlich.
37
•
•
•
•
Wurde aber die Verfeinerung nicht durchgeführt, weil der Fehler zu klein
ist, so ist es auf jeden Fall sicher, dass der Quadrant A, wie in (6)
dargestellt, gerendert wird.
Ist Verfeinierung am Punkt p3 erforderlich (7)?
•
Quadrant B rekursiv verfeinern (11). Nach der Verfeinerung können
hier zwei Fälle entstehen ...
Wenn am Punkt p2 nicht verfeinert wird (3), dann erfolgt die Abbildung der
Dreiecke wie in (10).
•
Verfeinerung am Punkt p3 ...
Am Punkt p1 wird nicht verfeinert. D.h. die Darstellung erfolgt wie in (8).
•
Fortfahren mit der Prüfung am Punkt p2...(9)
Es gibt somit insgesamt 16 Fall-Unterscheidungen für das Verfeinern eines Quadranten.
3.2 Implementierung
Die als Basis herangezogene Experiment-Implementierung liegt als lauffähiges C/C++Programm unter Windows vor. Für die Anbindung von OpenGL an das Window-System
wurde SDL eingesetzt. Die Heightmap liegt in der Auflösung von 1025 x 1025 in BMP
Format vor. Eine Texture-Grafik für das Gelände mit einer Auflösung von 1024 x 1024 (siehe
Abbildung 25) sowie Bilder für den Hintergrund (Skybox) sind ebenfalls vorhanden. Eine
Skybox ist ein überdimensionierter Würfel, welcher mit sechs vordefinierten Texturen um die
gesamten 3D-Szene herum gezeichnet wird und bildet damit einen nahtlosen
Panoramahintergrund für die 3D-Szene.
(a)
(b)
Abbildung 25 Für die Geländedarstellung: (a) Textur. (b) Heightmap
Bei der Portierung eines OpenGL Programm auf das Target-System ist grundsätzlich zu
beachten:
•
Sicherstellen, dass der Code unter QNX syntaktisch und vor allem inhaltlich korrekt
arbeitet.
•
Die Window-System abhängigen Funktionen auf QNX-Photon umstellen. In diesem
Fall sind es die Funktionen aus der SDL-Library, die nicht auf dem Target verfügbar
sind. Diese Funktionen sind durch die in Kapitel 2.2.2 entwickelten Funktionen zu
ersetzen.
38
Abbildung 26 zeigt den Ablauf des Programms in der Initialisierungsphase (a) und in der
Renderingsphase (b).
soar
soa
init()
OpenGL
display()
initOpenGL()
drawTerrain()
refineTerrain()
loadTexture()
calculateTree()
loadHeightMap()
rekursiver Aufruf
glDrawElements()
processTerrain()
drawSkybox()
glVertex3f()
(a) Initialisierungsphase
(b) Darstellungsphase
Abbildung 26 Ablaufdiagramm des Gelände-Darstellungsprogramms
3.2.1 Funktionsbeschreibung
Im Folgenden werden die wichtigsten Funktionen beschrieben.
•
Funktionen display(void)
ist die Haupt-Display-Funktion, hier werden die „draw“-Funktionen für Skybox,
Terrain usw. der Reihe nach aufgerufen.
•
Funktionen drawTerrain(void)
Hier wird zuerst die Funktion refineTerrain() zum Generieren des Gitters
aufgerufen. Das Ergebnis wird in dem Array guiTerrainIndexList gespeichert.
Die zu zeichnenden Dreiecke werden jeweils durch drei Einträge (drei Eckpunkte)
aus diesem Array dargestellt. Ein Element dieses Arrays enthält nicht die
39
Koordinaten eines Punktes, sondern einen Index auf das eigentliche Vertex-Array.
Mit dem OpenGL-Befehl
glDrawElements(GL_TRIANGLES, iNmbPrimitiveNodes,
GL_UNSIGNED_INT, guiTerrainIndexList);
wird das Gitter schließlich gerendert. Durch das Flag GL_TRIANGLES wird OpenGL
mitgeteilt, dass es sich hier um Dreiecke handelt. Die Anzahl der Dreiecke wird in
dem Variable iNmbPrimitiveNodes gespeichert.
•
Funktionen refineTerrain(void)
Die Verfeinerungsfunktion calculateTree() wird hier mit den Initialwerten
gestartet. Für cI ist der Initialwert der Mittelpunkt des gesamten Geländes.
•
Funktionen calculateTree(int cI, int deltaX, int deltaY)
Der bereits erläuterte Verfeinerungsalgorithmus ist hier implementiert. Dabei ist cI
der Index des Mittelpunktes R, deltaX und deltaY der Index-Offset bis zum
Mittelpunkt der Teil-Quadranten. Durch das Programmieren der 16
Fallunterscheidungen ist diese Funktion ziemlich groß und unübersichtlich geraten.
•
Funktionen preprocessTerrain(void)
Die für die Fehler-Ermittlung zum Teil benötigten Daten werden hier in der
Initialisierungsphase einmalig vorgerechnet und in den folgenden Arrays
float *fErrorMatrix;
float *fRadiiMatrix;
abgespeichert.
•
Funktionen loadHeihtMap(void)
In dieser Funktion wird das Heightmap eingelesen. Die zum Lesen der BMP-Datei
verwendete SDL-Funktion SDL_LoadBMP wurde durch eine selbst entwickelte
Version ersetzt (SDL war auf dem Target-System nicht verfügbar). Nach dem
Einlesen der Daten werden Speicher für die globalen Arrays
GLfloat *gfTerrainVertexList;
GLfloat *gfTerrainColorList;
GLfloat *gfTerrainTexCoordList;
reserviert. Das Array gfTerrainVertexList enthält die x, y und z Koordinaten des
Originalgitters. Während die y Werte direkt aus dem Heighmap abgegriffen werden
können, muss für die x und z Werte eine Skalierung mit einem konstanten Faktor
TERRAIN_SCALE_FLAT durchgeführt werden. Das Array gfTerrainColorList
bekommt die Farbinformation zugewiesen und wird sichtbar wenn das TextureMapping ausgeschaltet ist. Im Array gfTerrainTexCoordList sind schließlich die
2D-Koordinaten für das Texture-Mapping gespeichert22.
•
22
Funktionen loadTextures(void)
vgl. Kapitel 2.1.2.8
40
Die Bilder für die Textures werden hier eingelesen (txtImg[])und mit den
folgenden OpenGL Befehlen aktiviert.
glTexImage2D(GL_TEXTURE_2D, ... GL_RGBA,
GL_UNSIGNED_BYTE, txtImg[run1]->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
•
Funktionen drawSkybox(void)
Die Panorama-Hintergrundbilder werden hiermit gezeichnet. Eine Skybox wird in
OpenGL als ein Würfel um das darzustellende Objekt (hier das Gelände) herum
modelliert. Die Hintergrundbilder werden als Texture auf die sechs Innenseite des
Würfels projiziert. Die Skybox dient hier nur der besseren Optik und kann optional
abgeschaltet werden.
•
Funktionen main()
Im Hauptprogramm kann schließlich ein Flug oder eine Fahrt durch das Gelände
simuliert werden. Dabei werden in einer Schleife die Koordinaten der KameraPosition frameweise verändert. Eine typische Steuerung sieht wie folgt aus.
for(i=0; i<15; i++) {
moveCamera(0.0, 0.0, -10.0, 0.0, 0.0, 0.0);
display();
glICMSwapBuffer();
}
void moveCamera(float X, float Y, float Z,
float angle_X, float angle_Y, float angle_Z) {
sCamera.angle_X += angle_X;
sCamera.angle_Y += angle_Y;
sCamera.angle_Z += angle_Z;
sCamera.coord_X_fly += X;
sCamera.coord_Y_fly += Y;
sCamera.coord_Z_fly += Z;
}
display() {
...
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90.0f, ratio, 0.1f, 100000.0f);
// Rotete and translate the View
glRotatef(sCamera.angle_X, 1.0f, 0.0f, 0.0f);
glRotatef(sCamera.angle_Y, 0.0f, 1.0f, 0.0f);
glRotatef(sCamera.angle_Z, 0.0f, 0.0f, 1.0f);
glTranslatef(-sCamera.coord_X_fly, -sCamera.coord_Y_fly,
sCamera.coord_Z_fly);
...
}
C-Code für die Kamera-Steuerung
41
Die folgende Abbildungen zeigen die Ergebnisse des implementierten Programms.
Abbildung 27 Blick auf das Gelände mit einem Symbol für die Navigation und
einer eingezeichneten virtuellen Strasse.
(a)
(b)
Abbildung 28 Screenshot aus unterschiedlichen Winkeln.
Mit Texturen und Skybox. (b) Als Gittermodell
3.2.2 Probleme bei der Implementierung
Eine Portierung verläuft selten ohne Probleme. Insgesamt ist es schwierig bei einem derart
komplexen Algorithmus den Überblick zu behalten. Stößt man auf einen Fehler, so stellt sich
als erstes die Frage, welche Komponente diesen Fehler verursacht hat. Die Mesa-Library ist
zwar ausgereift, hat aber offiziell keine QNX-Unterstützung. Die selbstentwickelten glICM –
Funktionen ist auch eine mögliche Fehlerquelle, ebenso der LOD-Algorithmus, der hier nur in
einer experimentellen Form vorliegt.
Selbst wenn alles auf einem X86-basierten QNX-Rechner fehlerfrei läuft, muss das
Programm noch auf dem Target getestet werden. Auch hier treten oft Probleme auf. Das
Fehlen von bestimmten Runtime-Libraries beispielsweise ist noch einfach zu beheben.
42
Komplizierter ist es, wenn sich die Grafikhardware anders als auf einem X86-PC verhält. Im
Folgenden wird auf einige dieser Probleme eingegangen.
•
Einlesen des Heightmap
Auch wenn ein Heighmap in einem Bild-Format vorliegt (.BMP), ist das nicht mit
der Existenz eines Bildes gleichzusetzen. Auf der Suche nach der passenden
Gittergröße werden Heightmaps mit Hilfe eines Zeichenprogramms vergrößert,
verkleinert oder bearbeitet. Hat man beim Abspeichern ein anderes Format oder eine
andere Farbauflösung (Graustufe, RGB, RGBA, 16, 24, 32 Bit) aus Versehen
gewählt, entstehen „verfälschte“ Landschaften.
•
Falsche Skalierung des Geländes
Das in Abbildung 29 gezeigte Bild war das erste sichtbare Ergebnis. Lange wurde
gerätselt, ob das nun ein Gelände ist oder doch nur ein zufälliges Bit-Muster im
Framebuffer. Nach einer langen Debug-Session hat sich herausgestellt, dass die
Skalierung auf der X-Achse bei diesem Bild verstellt war.
Abbildung 29 Fehlerhaft dargestelltes Gelände
•
Verhalten der Target-Grafikhardware
Auf dem Target muss der Offscreen-Context23 im Direct-Mode mindestens um eine
Pixel-Zeile größer als der Image-Buffer definiert werden. D.h. bei einer ImageBuffer-Größe von 320 x 96 Pixel muss der Offscreen-Context mit 320 x 97 Pixel
initialisiert werden. Dieses Verhalten ist auf der QNX-X86 Plattform nicht zu
beobachten. Die Ursache dafür ist bisher unbekannt.
3.3 Evaluierung und Diskussion
Um die Leistungsfähigkeit des Algorithmus zu überprüfen, wurden einige Tests durchgeführt.
•
Laufzeitverhalten
Die Laufzeiten bezüglich der Größe des Gitters und des eingestellten Fehlers werden
bei diesem Test gemessen. Dabei wird das Gelände 30 mal gerendet und jeweils der
Mittelwert für Frames pro Sekunden (FPS), Anzahl der benötigten Dreiecke
(Triangles) und Anzahl der verfeinerten Quadranten (Nodes) gebildet. Die Kamera
wird mittig im Gelände positioniert und bei jedem Frame um 5 Grad nach links
gedreht. Diese Messung wird jeweils für den Fehler 8, 16, 32 und 64 durchgeführt.
Die Ergebnisse für ein 256x256 Gitter sind in Tabelle 2 und für ein 512x512 Gitter in
Tabelle 3 aufgeführt.
23
Vgl. Kapitel 2.2.2
43
Error
8
16
32
64
Gitter: 256 x 256
FPS
Triangles
4,274
1647
4,295
1055
4,407
824
4,467
630
Nodes
508
326
255
197
Tabelle 2 Messergebnisse bei einem 256x256 Gitter
Error
8
16
32
64
Gitter: 512 x 512
FPS
Triangles
3,863
2056
4,087
1396
4,278
1007
4,377
761
Nodes
644
438
313
235
Tabelle 3 Messergebnisse bei einem 512x512 Gitter
Die Ergebnisse zeigen, dass durch das Erhöhen der Fehler-Schranke die Anzahl der
Dreiecke und Rekursionen verringert und gleichzeitig die Frame-Rate erhöht wird.
Das war zu erwarten. Der Performance-Gewinn an Frame-Rate fällt aber
verhältnismäßig gering aus. Vergleicht man in Tabelle 3 die Werte aus der ersten und
vierten Zeile, so stellt man Folgendes fest: Obwohl das System 63% weniger
Dreiecke zu rendern hat, ist die Frame-Rate nur um 14% gestiegen. D.h. die GrafikAusgabe auf dem Display verursacht prinzipiell eine hohe Grundlast.
•
Speicherverbrauch
Der Speicherverbrauch des Algorithmus ist nicht von der Laufzeit-Komplexität
(Anzahl der Rekursionen und Verfeinerungsstufen) abhängig. Daher kann eine
Abschätzung analytisch erfolgen. Bei einer n x n Gitter werden benötigt:
// xyz pro vertex
VertexList:
(n+1)*(n+1)*3 * sizeof(GLfloat)
// rgb pro vertex
ColorList:
(n+1)*(n+1)*3 * sizeof(GLfloat)
// uv pro vertex
TexCoordList: (n+1)*(n+1)*2 * sizeof(GLfloat)
// 1 pro vertex
ErrorMatrix:
(n+1)*(n+1) * sizeof(float)
// 1 pro vertex
RadiiMatrix:
(n+1)*(n+1) * sizeof(float)
// max Quads * 2 * 3, (2 tri pro quad, 3 vertices pro tri)
IndexList:
n*n*2*3 * sizeof(GLuint)
Zusammengefasst ergibt sich:
44
= ( 10 · (n + 1)2 + 6 · n2 ) · sizeof(float)
= ( 16 · n2 + 20 · n + 10 ) · sizeof(float)
Der Speicherbedarf beträgt bei einer Float-Variable-Größe von 4 Byte:
Gitter-Größe 512 x 512 ≈ 16 MB.
Gitter-Größe 1024 x 1024 ≈ 67 MB .
Ein Test mit einem 1024x1024 Gitter auf dem Target ist daher aufgrund von
Speicherproblemen gescheitert.
Zusammenfassend lässt sich sagen, dass das Programm erfolgreich auf dem Target portiert
wurde und zufrieden stellend arbeitet. Die Ergebnisse bestätigen die eingangs erwähnte
Notwendigkeit des LOD-Verfahrens. Die Feineinstellung der Detaillierungsgenauigkeit durch
den Vertex-Interpolation-Error führt zu den erwarteten Veränderungen der zu verarbeiteten
Dreiecke. Diese tragen allerdings nicht entscheidend zum Gesamtergebnis bei, da die GrafikAusgabe auf dem Bildschirm verhältnismäßig lang dauert.
Ein vollwertiges 3D-Navigationssystem lässt sich dadurch aber noch nicht realisieren. Soll
eine 2D-Strassenkarte (3D-Strassenkarte sind im Moment noch nicht erhältlich) auf einem
derartig gerenderten Gelände abgebildet werden, so treten einige Probleme auf. Abbildung 30
zeigt eine vektorisierte Strasse auf einem Geländegitter. Hier wird deutlich, dass neue Punkte
auf dem Gelände entstehen. Die Höhe (y-Koordinaten in OpenGL) an diesen Punkten lässt
sich bei einem statischen Gitter noch verhältnismäßig einfach mathematisch bestimmen. Aber
bei einem dynamischen LOD-Verfahren ändert sich die Geometrie des Gitters ständig.
Außerdem müssen die neuen Punkte in dem Verfeinerungsalgorithmus berücksichtigt werden.
Vektorisierte Strasse
Neue Punkte
Abbildung 30 Vektorisierte Strasse auf einem Gelände-Gitter. Die neu entstandenen Punkte sind in blau
gekennzeichnet.
Generell müssen 3D-Objekte, wie Städte und Gebäude bei der Visualisierung berücksichtigt
werden. Bei zunehmender Anzahl und Komplexität von Objekten ist der Einsatz von SzenenGraphen sinnvoll und erforderlich. Ob diese Techniken auf dem Target anwendbar sind, ist
allerdings ungeklärt.
45
Diese und einige andere Probleme müssen noch untersucht werden. Geeignete oder
spezialisierte Algorithmen sind zu evaluieren oder müssen noch entwickelt werden.
46
4 OpenGL Java-Binding
Bisherige Systeme im Automotive-Bereich werden in der Regel in C/C++ entwickelt. Mit
zunehmender Leistungsfähigkeit der eingesetzten Hardware hat die Programmiersprache Java
auch im Embedded-Bereich Einzug gehalten. So schreiben manche Automobil-Hersteller
beispielsweise vor, dass die HMI komplett in Java zu entwickeln ist. Es existieren einige
OpenGL Java-Binding Implementierungen; sie sind aber bisher nur auf Windows- und UnixPlattformen verfügbar. Für die Evaluierung auf der SH4-QNX Plattform ist daher eine
Portierung erforderlich. Die wichtigsten OpenGL Java-Binding sind:
•
JOGL24 (JSR25 231)
JOGL wurde ursprünglich von Ken Russel und Chris Kline entwickelt und im
August 2003 veröffentlich. Die Firma Sun und SGI haben durch eine
Zusammenarbeit JOGL als die offizielle Referenz-Implementierung für den Standard
JSR231 aufgenommen. Bis zu endgültiger Verabschiedung von JSR231 wird JOGL
wohl als quasi Standard angesehen.
•
Java3D26 (JSR 189)
Java3D ist sei Jahren die von SUN offiziell favorisierte 3D-API. Im Gegensatz zu
anderen „Eins zu Eins“ OpenGL-Binding bietet Java3D ein vollständig
objektorientiertes Programmiermodell für 3D-Objekte mit umfangreichen HighLevel Funktionen. Seit 2004 ist Java3D Open-Source, damit ist unklar, ob SUN die
Entwicklung wie bisher weiter vorantreiben wird.
•
SWT Experimental OpenGL Plug-in (SWTOGL)27
Speziell für das Java GUI-Framework SWT wurde dieses OpenGL-Binding als
Unterprojekt der Entwicklungsumgebung Eclipse entwickelt. SWTOGL ist sowohl
als Eclipse-Plugin als auch für den Standalone-Einsatz konzipiert. Der
Entwicklungsstand wird aber noch als experimentell eingestuft.
Weitere Implementierungen wie LWJGL (Lightweight Java Game Library), JavaGL,
GL4Java (OpenGL for Java Technology) und Magician haben sich aufgrund unterschiedlicher
Probleme nicht durchgesetzt. Hinzu kommt, dass diese Implementierungen teilweise nicht frei
verfügbar sind oder nicht weiter entwickelt werden.
Im Bereich Java und 3D-Grafik gibt es daneben noch einige interessanten Alternativen, wie
z.B. Java Bindings for OpenGL ES (JSR 239), Mobile 3D Graphics API for J2ME (JSR 184),
die allerdings in der vorliegenden Arbeit aus Zeitgründen nicht behandelt wurden.
In Kapitel 4.1 werden die Prinzipien und Techniken der einzelnen Java-BindingImplementierungen sowie die Grundlage der Java-Native-Schnittstelle JNI erläutert.
Insbesondere zwischen JOGL und Java3D gibt es große konzeptionellen Unterschiede. Die
konkrete Portierung der ausgesuchten Java-Binding, SWTOGL, wird in Kapitel 4.2
beschrieben. Die Evaluierungsergebnisse werden in Kapitel 4.3 zusammenfassend aufgeführt
und diskutiert.
24
vgl [JOGL]
JSR Java Specification Request, vgl [JCP]
26
vgl [Java3D]
27
vgl [SWTOGL]
25
47
4.1 Grundlage
Vereinfacht ausgedrückt muss man für ein OpenGL Java-Binding nichts anderes tun, als eine
Wrapper-Klasse in Java zu schreiben, die die C-Funktionen von OpenGL eins zu eins
abbilden. Auf den ersten Blick eine einfache Aufgabe; Native-Libraries kann man von Java
heraus über die JNI-API aufrufen. Nun stellt sich aber die Frage, wohin mit dem gerenderten
Bild? Die Grafik-Ausgabe soll logischerweise wieder auf einem Fenster (Canvas, Widget) der
Java-Anwendung erscheinen. Das bedeutet, dass das Java-Binding die Verbindung zwischen
dem zugrunde liegenden Java-GUI-Framework und dem plattformabhängigen WindowSystem herstellen muss. Bekannte Java-GUI-Frameworks sind:
•
AWT (Abstract Window Toolkit)
AWT ist das erste Java-GUI-Framework von SUN und ist fester Bestandteil der
Java-Foundation-Classes (JFC). Für die Darstellung der GUI-Komponenten
(Widgets) werden jeweils die Window-System eigenen Komponenten verwendet.
Für den Zugriff auf die nativen Window-System-Elemente dienen sog. Peer-Codes,
d.h. zu jeder Widget-Klasse existiert auch eine entsprechende Native-Peer-Funktion,
die das entsprechende Window-System-Widget ansteuert. Jede Java-RuntimeEnvironment-Distribution (JRE) einer bestimmten Plattform enthält jeweils einen
Satz von Window-System abhängigen Peer-Libraries. Da nicht alle Plattformen den
gleichen Umfang an GUI-Elementen besitzt, bildet AWT nur eine gemeinsame Basis
der unterstützten Plattformen ab. Aus diesem Grund werden moderne JavaApplikation nur ganz selten AWT als GUI-Framework verwenden.
•
Swing
Seit Java 1.2 (1998) ist Swing zusätzlich zu AWT als GUI-Framework verfügbar.
Bei Swing werden die GUI-Elemente selbst in Java gerendert und sind somit nicht
von Window-System abhängig. Das Ergebnis ist ein einheitliches „Look and Feel“
auf allen Plattformen. Das Framework selbst ist modular und objektorientiert
aufgebaut. Als reine Java-Implementierung sind Swing-Applikationen in der Regel
langsamer als AWT oder SWT.
•
SWT (Standard Widget Toolkit)
SWT wurde ursprünglich von IBM entwickelt und ist inzwischen zusammen mit dem
Eclipse-Projekt als Open-Source freigegeben. Ziel der Entwicklung ist es einen
effizienten Zugriff auf plattformabhängige Widget-Funktionalitäten durch eine
plattformunabhängige API zu ermöglichen. Wie bei AWT werden bei SWT auch die
Window-System spezifischen GUI-Komponenten verwendet. Der Zugriff auf die
Native-GUI-Funktionen erfolgt über JNI. Für jede unterstützte Plattform werden
jeweils spezifischen Java- und C-Funktionen implementiert, dadurch ist eine
optimale Umsetzung möglich. Gegenüber AWT gilt SWT als umfangreicher und
leistungsfähiger. Die Virtual-Machine J9 von IBM verwendet SWT als Basis GUIFramework; so bauen sich bei J9 die AWT Klassen auf SWT auf, um damit die für
die Zertifizierung erforderliche AWT Klassen-Kompatibilität zu erreichen.
In den folgenden Abschnitten werden die Konzepte der anfangs erwähnten Java-Bindings
näher erläutert.
4.1.1 JOGL
JOGL, aktuell in der Version 1.1, ist eine auf AWT und Swing basierte OpenGL JavaBinding und bietet Unterstützung von OpenGL bis zu der aktuellen Version 2.0. Eine Java48
VM in der Version 1.4 (oder höher) wird von JOGL strikt vorausgesetzt. Vom Konzept her
entspricht JOGL einer Eins-zu-Eins-Abbildung der Native-OpenGL-Funktionen. Zusätzlich
werden Klassen für die Anbindung an das AWT-Event-Konzept zur Verfügung gestellt.
Die Namenskonventionen der OpenGL-Funktionen28 werden auch in JOGL beibehalten,
obwohl das Problem bei einer objektorientierten Programmiersprache durch Überladen der
Funktionen mit unterschiedlichen Signaturen gelöst werden kann.
Um Plattformunabhängigkeit zu erreichen, verwendet OpenGL eigene Datentypen. Das ist bei
Java nicht erforderlich. Deshalb werden bei JOGL nur die vorgegebene Java-Standardtypen
wie in Tabelle 4 aufgelistet benutzt.
Java-Typ
OpenGL-Type
byte
GLbyte
short
GLshort
int
GLint
float
GLfloat
double
GLdouble
Größe in Bit
8
16
32
32
64
Tabelle 4 Zuordnung der Java- und OpenGL-Datentypen
Das Beispiel in Listing 3 zeigt den Unterschied zu einem C-Programm.
(a) Java Code
public void draw() {
gl.glClear(
GL.GL_COLOR_BUFFER_BIT
| GL.GL_DEPTH_BUFFER_BIT);
gl.glColor3f(1.0f, 0.3f, 0.3f);
gl.glBegin(GL.GL_QUADS);
gl.glVertex3f(0.2f, 0.2f, 0.0f);
gl.glVertex3f(0.7f, 0.2f, 0.0f);
gl.glVertex3f(0.7f, 0.7f, 0.0f);
gl.glVertex3f(0.2f, 0.7f, 0.0f);
gl.glEnd();
}
(b) C Code
void draw() {
glClear(
GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT);
glColor3f(1.0f, 0.3f, 0.3f);
glBegin(GL_QUADS);
glVertex3f(0.25f, 0.25f, 0.0f);
glVertex3f(0.75f, 0.25f, 0.0f);
glVertex3f(0.75f, 0.75f, 0.0f);
glVertex3f(0.25f, 0.75f, 0.0f);
glEnd();
}
Listing 3 OpenGL Code in (a) Java/JOGL, (b) C/OpenGL
Die wichtigsten Klassen der JOGL-API sind GL bzw. GLU. Diese Klassen sind die zentralen
Wrapper-Klassen der Native-OpenGL-Funktionen. Diese Klassen sind als Interface ausgelegt
und werden durch plattformabhängige Implementierungen ergänzt. Sämtliche OpenGLFunktionen werden in den beiden Implementierungsklassen als native deklariert. In der
aktuelle JOGL Version ist die GLU-Funktionalitäten komplett als Java-Klassen ausgeführt,
die aber per Einstellung auf die „gewrappte“ Native-Version umgeschaltet werden kann. Eine
Besonderheit von JOGL ist, dass die Sourcecodes (Java und C) zum Großteil durch ein eigens
entwickeltes Tool zur Buildzeit generiert werden. Bei der Generierung werden die HeaderDateien der auf dem System installierten OpenGL-Libraries gescannt und die Codes
entsprechend erstellt. Damit ist eine optimale OpenGL-Unterstützung gewährleistet.
Abbildung 31 zeigt die Beziehung der wichtigsten Klassen von JOGL.
28
vgl. Kapitel 2.1.1
49
JPanel
DrawableFactory
GLCapabilities
<<use>>
<<create>>
GLJpanel
«interface»
Drawable
X11GLImpl
«interface»
GLEventListener
Canvas
X11GLUImpl
«interface»
GL
GLCanvas
myGLEventListener
<<has>>
>
e>
us
<
<
«interface»
GLU
<<use>>
Abbildung 31 Klassendiagramm der wichtigsten JOGL Klasse
Weitere Basisklassen sind GLCanvas, GLJpanel, GLCapabilities, GLDrawableFactory,
GLDrawable und GLEventListener.
Die Klasse GLCanvas ist eine Spezialisierung der AWT-Klasse Canvas und repräsentiert
einen Grafik-Ausgabebereich mit garantierter Hardware-Beschleunigungsunterstützung.
Durch die Vererbung bleibt das AWT-Event-Konzept vollständig erhalten. Die Klasse
GLJpanel ist die Swing-Version davon, die zwar Swing-Kompatibilität bietet, jedoch
Performance-Nachteile bringt. Beide Klassen implementieren das Interface GLDrawable und
können mit der Klasse GLDrawableFactory instantiiert werden. Ein Objekt der Klasse
GLCapabilities kann dabei als Parameter übergeben werden. Damit werden
Grundeinstellungen wie Farbtiefe oder Anzahl der Framebuffer von OpenGL konfiguriert.
Das Listing 4 zeigt ein lauffähiges Beispielprogramm.
import java.awt.*;
import java.awt.event.*;
import net.java.games.jogl.*;
public class HelloWorld {
public static void main(String[] args) {
Frame frame = new Frame("Hello World");
GLCanvas canvas =
GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities());
canvas.addGLEventListener(new MyRenderer());
frame.add(canvas);
frame.setSize(300, 300);
canvas.display();
frame.show();
}
static class MyRenderer implements GLEventListener {
public void init(GLDrawable drawable) {
GL gl = drawable.getGL();
50
gl.glMatrixMode(gl.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}
public void display(GLDrawable drawable) {
GL gl = drawable.getGL();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glColor3f(1.0f, 0.3f, 0.3f);
gl.glBegin(GL.GL_QUADS);
gl.glVertex3f(0.25f, 0.25f, 0.0f);
gl.glVertex3f(0.75f, 0.25f, 0.0f);
gl.glVertex3f(0.75f, 0.75f, 0.0f);
gl.glVertex3f(0.25f, 0.75f, 0.0f);
gl.glEnd();
gl.glFlush();
}
public void displayChanged(GLDrawable drawable, boolean modeChanged,
boolean deviceChanged) {
}
public void reshape(GLDrawable drawable, int x, int y,
int width, int height) {
}
}
}
Listing 4 JOGL „HelloWorld“-Programm
In diesem Beispiel ist das Event-Konzept deutlich erkennbar. Das Objekt MyRenderer
implementiert die Interface-Klasse GLEventListener und wird an dem Event-Listener des
GLCanvas Objektes übergeben.
canvas.addGLEventListener(new MyRenderer());
Je nach Event und Situation werden die entsprechende Methode init (), display(),
und displayChanged() aufgerufen.
reshape()
JOGL bietet keine Unterstützung für die High-Level 3D-Programmierung. Soll eine
Anwendung objektorientiert modelliert werden, so ist es Aufgabe des Entwicklers, das
System entsprechend zu gestalten.
4.1.1.1 Native-Anbindung
Von höherer Bedeutung für die Portierung und Performance ist die Art und Weise, wie
OpenGL Native angebunden wird. Die Funktion glColor3f() ist beispielsweise wie folgt
implementiert.
package net.java.games.jogl.impl.x11
public class X11GLImpl implements GL
{
...
native public void glColor3f(float red, float green, float blue);
JNIEXPORT void JNICALL
Java_net_java_games_jogl_impl_x11_X11GLImpl_glColor3f(
JNIEnv *env, jobject _unused, jfloat red, jfloat green, jfloat blue) {
51
glColor3f((GLfloat) red, (GLfloat) green, (GLfloat) blue);
}
Java- und C-Code aus X11GLImpl.java und X11GLImpl_JNI.c
Bei dieser einfachen Funktion können die Parameter ohne Umwandlung direkt von jfloat
auf GLfloat „gecastet“ und weiter verwendet werden.
Komplexe Anwendungen wie die in Kaptitel 3 vorgestellte Geländevisualisierung arbeiten
aus Performance-Gründen mit Vertex-Array29. Eine effiziente Implementierung der
entsprechenden Funktionen ist daher für eine gute Gesamt-Performance unerlässlich. Die
folgenden Code-Fragmente zeigen die Implementierung der Funktion glVertexPointer.
public void glVertexPointer(int size, int type,
int stride, java.nio.Buffer ptr)
{
if (!BufferFactory.isDirect(ptr))
throw new GLException("Argument \"ptr\" was not a direct buffer");
glVertexPointer0(size, type, stride, ptr);
}
native private static void glVertexPointer0(int size, int type,
int stride, java.nio.Buffer ptr);
JNIEXPORT void JNICALL
Java_net_java_games_jogl_impl_x11_X11GLImpl_glVertexPointer0(
JNIEnv *env,
jobject _unused, jint size,
jint type, jint stride, jobject ptr)
{
GLvoid * _ptr3 = NULL;
if (ptr != NULL) {
_ptr3 = (GLvoid *) (*env)->GetDirectBufferAddress(env, ptr);
} else {
_ptr3 = NULL;
}
glVertexPointer((GLint) size, (GLenum) type, (GLsizei) stride,
(GLvoid *) _ptr3);
}
Java- und C-Code aus X11GLImpl.java und X11GLImpl_JNI.c
Hier ist zu erkennen, dass JOGL mit dem seit J2SE 1.4 eingeführten NIO (Native IO)30
Konzept arbeitet. Das Vertex-Array java.nio.Buffer ptr wird auf der Java-Seite als
Direct-Buffer deklariert und an die korrespondierte C-Funktion übergeben. Die C-Funktion
leitet diesen Pointer direkt an OpenGL als GLvoid „gecastet“ weiter. OpenGL hat damit ohne
Zeitverzögerung den vollen Zugriff auf das gesamte Array.
Die Anbindung von OpenGL an AWT soll exemplarisch auf einer X-Window/LinuxPlattform gezeigt werden. Abbildung 32 zeigt den Ablauf beim Aufruf der Methode reshap().
Ein Objekt der Klasse GLCanvas hat u.a. ein GLContex Objekt, in diesem Fall eine konkrete
X11-Implementierung X11OnscreenGLContext. In dieser Klasse sind alle WindowSystem/Plattform abhängige Funktionen gekapselt (siehe Abbildung 33).
29
30
vgl. Kapitel 2.1.1
vgl. Kapitel 4.1.4
52
Abbildung 32 Sequenz-Diagramm beim Aufruf der Methode GLCanvas.reshape()
GLContext
X11GLContext
WindowsGLContext
...
X11OnscreenGLContext
X11OffscreenGLContext
...
...
Abbildung 33 Klassendiagramm GLContext Hierarchie
Wie bereits in Kapitel 2.1.2 erläutert, benötigt OpenGL für die Initialisierung einen GrafikContext, damit OpenGL das fertig gerenderte Bild anzeigen kann. Das geschieht bei GLX mit
der Funktion glXMakeCurrent(Display* display, XID drawable, GLXContext ctx).
Die benötigten Parameter display und drawable müssen aus dem AWT Grafik-Context
abgeleitet werden. Diese können entweder aus der GraphicDevice-Klasse abgefragt werden31
31
vgl. Kapitel 4.1.2.1
53
oder native wie bei JOGL praktiziert über die AWT-C API. Diese Abfrage wird bei JOGL in
der Klasse JAWT realisiert. Die Methode GetDrawingSurface zeigt die Funktionsweise.
Der Parameter target ist dabei ein Objekt der AWT Component-Klasse.
public JAWT_DrawingSurface GetDrawingSurface(java.lang.Object target)
{
ByteBuffer _res;
_res = GetDrawingSurface0(getBuffer(), target);
if (_res == null) return null;
return new JAWT_DrawingSurface(_res.order(ByteOrder.nativeOrder()));
}
native private java.nio.ByteBuffer GetDrawingSurface0(
java.nio.Buffer jthis0, java.lang.Object target);
#include <jawt.h>
...
JNIEXPORT jobject JNICALL
Java_net_java_games_jogl_impl_JAWT_GetDrawingSurface0(
JNIEnv *env, jobject _unused, jobject jthis0, jobject target)
{
JAWT * this0 = NULL;
JAWT_DrawingSurface * _res;
if (jthis0 != NULL) {
this0 = (JAWT *) (*env)->GetDirectBufferAddress(env, jthis0);
} else {
this0 = NULL;
}
_res = this0->GetDrawingSurface(env, (jobject) target);
if (_res == NULL) return NULL;
return (*env)->NewDirectByteBuffer(env, _res, 24);
}
Java- und C-Code aus JAWT.java und JAWT_JNI.c
Das JAWT-Objekt wird hier mit der Funktion (*env)->GetDirectBufferAddress()ermittelt.
Anschließend wird die Funktion GetDrawingSurface() aufgerufen, um einen Zeiger auf die
JAWT_DrawingSurface Struktur zu bekommen. Interessant ist hier zu sehen, dass diese
Struktur mit der Funktion NewDirectByteBuffer in Direct-ByteBuffer umgewandelt und
zurückgeliefert wird32. Als Direct-ByteBuffer wird diese Struktur nicht mehr vom GarbageCollector beeinflusst, wodurch ein konstant schneller Zugriff gewährleistet wird.
In der Methode lockSurface des X11OnscreenContext Objektes werden für die X11
wichtigen Informationen wie display, drawable und visualID aus dem
JAWT_DrawingSurface Objekt extrahiert. Anschließend werden diese Informationen als
Parameter von der Methode makeCurrent() an die Wrapper-Klasse GLX.glXMakeCurrent()
weitergeleitet.
32
vgl. Kapitel 4.1.4
54
private boolean lockSurface() throws GLException {
...
ds = getJAWT().GetDrawingSurface(component);
...
dsi = ds.GetDrawingSurfaceInfo();
...
x11dsi = (JAWT_X11DrawingSurfaceInfo) dsi.platformInfo();
display = x11dsi.display();
drawable = x11dsi.drawable();
visualID = x11dsi.visualID();
...
}
protected synchronized boolean makeCurrent(Runnable initAction)
throws GLException
{
...
if (!lockSurface()) {
return false;
}
...
if (!GLX.glXMakeCurrent(display, (int) drawable, context)) {
throw new GLException("Error making context current");
}
...
}
Java -Code aus X11OnscreenContext.java
Die Methode GLX.glXMakeCurrent() wird Java- und C-seitig wie folgt implementiert:
native static public boolean glXMakeCurrent(
long dpy, long drawable, long ctx);
JNIEXPORT jboolean JNICALL
Java_net_java_games_jogl_impl_x11_GLX_glXMakeCurrent(
JNIEnv *env, jclass _unused, jlong dpy, jlong drawable, jlong ctx)
{
Bool _res;
_res = glXMakeCurrent((Display *) (intptr_t) dpy,
(XID) (intptr_t) drawable, (GLXContext) (intptr_t) ctx);
return _res;
}
Java- und C-Code aus GLX.java und GLX_JNI.c
4.1.2 JAVA3D
Java3D bietet eine vollständig objektorientierte Klassenbibliothek um 3D-Applikationen zu
erstellen. Dabei werden die 3D-Objekte als sog. Szenen-Graph dargestellt. Ziel der
Entwicklung ist High-Level-Konzepte eines Szenen-Graphen basierten Systems mit einer
Low-Level-3D-Engine wie OpenGL zu kombinieren. Die API bietet vielfältige Möglichkeiten
zum Erstellen und Manipulieren von 3D- und Szenen-Graph-Objekten. Loader-Klassen zum
Einlesen von 3D Objekten in den gängigsten Formaten (VRML, 3DS, usw.) sind ebenfalls
55
vorhanden. Aktuell liegt Java3D in der Version 1.4 vor. Eine JVM in der Version 1.4 wird
vorausgesetzt.
Die Funktionalitäten von Java3D sind in drei Packages aufgeteilt:
•
Java3D-Core Classes (javax.media.j3d)
Basis Klassen von Java3D, um das „virtual universe“ aufzubauen und zu bearbeiten.
•
Mathematik Klassen für (javax.vecmath)
Mathematik-Klassen für Vektoren, Matrizen und sonstige 3D Berechnungen.
•
Utility-Klassen (com.sun.j3d.utils)
Die Utility-Klassen bieten zusätzliche Hilfsfunktionen, beispielsweise für das
Einlesen von 3D Objekte oder eine vereinfachte Handhabung des Szenen-Graphen
Objektes (z.B. SimpleUniverse).
Das zentrale Konstrukt in Java3D ist der Szenen-Graph33. Eine 3D-Welt wird als eine BaumStruktur in einem sog. Virtual Universe dargestellt. 3D-(Szenen-Graph)-Objekte in diesem
3D-Raum werden als Knoten in diesem Baum angehängt. Die Kanten dieses Baumes
repräsentieren Beziehungen zwischen den Knoten. Zwei Arten von Beziehungen sind
definiert:
•
Vater-Kind-Beziehung (Parent-Child-Relationship)
Die Knoten-Objekte Virtual Universe, Locale, Group und Leaf stehen in einer VaterKind-Beziehung zueinander. D.h. die Hierarchie der Knoten wird durch diese Art
von Beziehung festgelegt.
•
Referenz-Beziehung (Reference-Relationship)
Knoten-Objekte von Typ Node Component und „Weitere Objekte“ (vgl. weiter
unten) werden von den anderen Knoten-Objekten referenziert und stehen somit in
einer Referenz-Beziehung zu denen.
Ein Vater-Knoten kann viele Kind-Knoten haben, umgekehrt darf aber ein Kind-Knoten nur
einen Vater-Knoten haben. Außerdem sind Zyklen nicht erlaubt. Jeder Szene-Graph
entspricht somit einen Directed Acyclic Graph (DAG). Abbildung 34 zeigt den Aufbau eines
Szenen-Graphen in Java3D.
33
engl. scene graph
56
VirtualUniverse
Parent-Child link
Reference
Locale
Content branch graph
View branch graph
BG
BG
Group (BranchGroup)
TG
TG
Group (TransformGroup)
Leaf
(Shape3D) S
TG
V
View
Canvas3D
Screen3D
Leaf
(ViewPlatform)
Appearance
NodeComponent
Physical
Body
Physical
Env.
Abbildung 34 Aufbau eines Szenen-Graphen in Java3D aus [Sun01]
Die Klassen des Szenen-Graphen sind:
•
Virtual Universe
Das Virtual Universe ist die Wurzel des gesamten Szenen-Graphen und enthält
Locale-Objekte als Kind-Knoten. Eine typische Java3D-Applikation hat
normalerweise nur ein Virtual Universe. Bei einem sehr großen Virtual Universe
kann der Szenen-Graph in unterschiedlichen Locale aufgeteilt werden.
•
Locale
In einem Locale-Objekt sind u.a. die „High-Resolution-Coordinates“ des TeilBaumes definiert. Die Position der 3D-Objekte innerhalb des Teil-Szenen-Graphen
sind immer relativ zu der Koordinaten des Locale-Objektes definiert.
•
Group
Objekte der Klasse Group werden an dem Locale Objekt als Kind-Konten
zugeordnet und repräsentieren Teilbäume im Szenen-Graph-Modell. Es sind zwei
Group-Typen definiert: TransformGroup und BranchGroup. TransformGroup ist für
die Positionierung der 3D-Objekte im Raum zuständig. BranchGroup enthält
Referenzen auf weitere Objekte (Group oder Leaf). Es gibt unterschiedliche Typen
von Teil-Graphen, die jeweils mit einem Group-Objekt eingeleitet wird. Der
„Content Branch Graph“ repräsentiert den Inhalt wie z.B. Geometrie des 3DObjektes und der „View Branch Graph“ enthält die Ansichtsparameter.
•
Leaf
Leaf-Objekte bilden die Blatt-Knoten des Baumes. Sie referenzieren die 3DElemente, die gerendert werden sollen.
57
•
Node Component
und dessen Subklassen Appearance und Geometry enthalten
letztendlich Darstellungsattribute, wie Geometrie und Lichtverhältnisse.
NodeComponent
•
Weitere Objekte
Weitere Objekte sind Canvas3D-, Screen3D- und View-Objekte. Sie sind alle für die
Ausgabe der Grafik zuständig.
Abbildung 35 zeigt die Hierarchie der wichtigsten Klassen.
BranchGroup
Canvas3D
Group
TransformGroup
SharedGroup
...
Node
Shape3D
Leaf
ViewPlatform
SceneGraphObject
Behavior
...
Apperance
Geometry
NodeComponent
Material
...
Texture
Abbildung 35 Hierarchie der wichtigsten Szenen-Graphen-Klassen
Anhand eines aus [Sun01] modifizierten HelloUniverse-Beispiels soll gezeigt werden, wie die
Programmierung konkret aussieht. Dabei soll der Szenen-Graph aus Abbildung 36
implementiert werden.
58
Abbildung 36 Beispiel HelloUniverse Szenen-Graph
Um das Beispiel möglichst einfach zu halten, wird die Utility-Klasse SimpleUniverse zum
Erzeugen des Szenen-Graphen verwendet. Der Vorteil von SimpleUniverse ist, dass der
komplette „View Branch Graph“ Teilbaum automatisch erzeugt wird34. Damit kann der
Entwickler sich auf die Inhalte des Teilbaums „Content Branch Graph“ konzentrieren.
public HelloMyUniverse() {
SimpleUniverse simpleUniverse = null;
setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas3D = new Canvas3D(config);
add("Center", canvas3D);
simpleUniverse = new SimpleUniverse(canvas3D);
simpleUniverse.getViewingPlatform().setNominalViewingTransform();
BranchGroup sceneCube = createCubeSceneGraph();
BranchGroup sceneText = createTextSceneGraph();
simpleUniverse.addBranchGraph(sceneCube);
simpleUniverse.addBranchGraph(sceneText);
}
public static void main(String[] args) {
MainFrame frame = new MainFrame(new HelloMyUniverse(), 600, 400);
}
Hauptprogramm von HelloMyUniverse
34
vgl. Abbildung 36
59
public BranchGroup createCubeSceneGraph() {
BranchGroup objRoot = new BranchGroup();
ColorCube colorCube = new ColorCube(0.4);
Transform3D xRotation = new Transform3D();
Transform3D yRotation = new Transform3D();
xRotation.rotX(Math.PI/4.0d);
yRotation.rotY(Math.PI/5.0d);
xRotation.mul(yRotation);
TransformGroup objRotation = new TransformGroup(xRotation);
objRoot.addChild(objRotation);
objRotation.addChild(colorCube);
objRoot.compile();
return objRoot;
}
public BranchGroup createTextSceneGraph() {
BranchGroup objRoot = new BranchGroup();
Text2D text2D = new Text2D("Hello Universe",
new Color3f(0.2f, 0.2f, 1.0f), "Helvetica", 40, 0);
Transform3D translation = new Transform3D();
translation.setTranslation(new Vector3d(-0.5, 0.1, 0.5));
TransformGroup objTranslation = new TransformGroup(translation);
objRoot.addChild(objTranslation);
objTranslation.addChild(text2D);
objRoot.compile();
return objRoot;
}
Methode zum Erstellen der Teil-Szenen-Graphen
Im Konstruktor der Klasse HelloMyUniverse()wird das Canvas3D Objekt mit einer DefaultKonfiguration erzeugt. Das Canvas3D-Objekt stellt hier den Kontext für die Grafikausgabe
dar. Mit dem Canvas3D-Objekt als Parameter wird anschließend das SimpleUniverse Objekt,
den eigentlichen Szenen-Graphen, erstellt. Ab hier kann man die zwei Teil-Bäume (ContentBranch-Graph), wie in Abbildung 36 dargestellt, dem Szenen-Graph mit der Methode
addBranchGraph() hinzufügen. Diese BranchGroup-Objekte werden in den Methoden
createCubeSceneGraph() und createTextSceneGraph() erzeugt. Das BranchGroupObjekt von der Methode createCubeSceneGraph enthält einen Würfel (ColorCube) sowie
entsprechende Transformationsobjekte (TransformGroup mit Rotationen auf der X- und YAchse). Die Methode createTextSceneGraph() liefert dagegen ein BranchGroup-Objekt
mit einem Text-Objekt (Text2D) inklusive einer einfachen Translation als Knoten zurück. Das
Ergebnis des Beispielprogramms ist in Abbildung 37 abgebildet.
60
Abbildung 37 Screenshot Java3D-Beispielanwendung
4.1.2.1 Native-Anbindung
Bei Java3D ist es aufgrund der höheren Komplexität insgesamt schwierig, die NativeAnbindung zu bewerten. Dennoch findet man bei der Analyse der Sourcecodes einige
bekannte Muster, die auf eine effiziente Implementierung schließen.
•
GeometrieArray
Ein Objekt der Klasse GeometrieArray sowie Objekte der Subklassen enthalten
Geometrie-Daten von 3D-Objekten. Der Sourcecode zeigt, dass diese Klasse für die
Speicherung der Vertex-Koordinaten die Klasse J3DBuffer verwendet. Wie das
folgende Code-Fragment zeigt, benutzt J3DBuffer für die eigentliche Datenhaltung
NIO-Buffer.
public class J3DBuffer {
...
private java.nio.Buffer originalBuffer = null;
...
public J3DBuffer(java.nio.Buffer buffer) {
...
setBuffer(buffer);
}
}
Java-Code aus javax.media.j3d.J3DBuffer.java
Die Methode execute der GeometryArray Klasse wird aufgerufen, um u.a. die
verschiedene Vertex-Arrays an OpenGL zu übergeben. Die Übergabe geschieht per
JNI, wie es der nächste Programmabschnitt zeigt.
abstract class GeometryArrayRetained extends GeometryRetained{
// Used for NIO buffer geometry
J3DBuffer coordRefBuffer = null;
FloatBufferWrapper floatBufferRefCoords = null;
...
void execute(...) {
Object vcoord = null;
vcoord = floatBufferRefCoords.getBufferAsObject();
61
...
executeVABuffer(cv.ctx, this, ..., vcoord, ...);
...
}
JNIEXPORT
void JNICALL Java_javax_media_j3d_GeometryArrayRetained_executeVABuffer(
JNIEnv *env, jobject obj, ..., jobject vcoords, ... )
{
...
fverts= (jfloat *)(*(table->GetDirectBufferAddress))(env, vcoords );
...
coordoff = 3 * initialCoordIndex;
glVertexPointer(3, GL_FLOAT, 0, &(fverts[coordoff]));
...
}
Java- und C-Code aus javax.media.j3d.GeometryArrayRetained.java bzw. …/native/d3d/
GeometryArrayRetained.cpp
•
Canvas3D
Das Konfigurieren der OpenGL-Engine erfolgt hauptsächlich in der Klasse
Canvas3D. In der Methode createNewContext() wird beispielsweise den OpenGLContext per JNI mit der glXCreateNewContext() Funktion erstellt. Hier sind die
bekannte GLX-Funktionen gut zu erkennen.
native long createNewContext(long display, int window,
int vid, long fbConfig,
long shareCtx, boolean isSharedCtx,
boolean offScreen);
JNIEXPORT
jlong JNICALL Java_javax_media_j3d_Canvas3D_createNewContext(
JNIEnv *env,
jobject obj,
jlong display,
jint window,
jint vid,
jlong fbConfigListPtr,
jlong sharedCtxInfo,
jboolean isSharedCtx,
jboolean offScreen)
{
jlong gctx;
jlong sharedCtx;
...
ctx = glXCreateNewContext((Display *)display, fbConfigList[0],
GLX_RGBA_TYPE, (GLXContext)sharedCtx, True);
...
if (!glXMakeCurrent((Display *)display,
(GLXDrawable)window,(GLXContext)ctx)) {
...
}
...
}
Java- und C-Code aus javax.media.j3d.Canvas3D.java bzw. …/native/ogl/Canvas3D.c
62
Im Gegensatz zu JOGL wird die JAWT-API bei Java3D nicht verwendet. Die für die GLXFunktionen erforderlichen Parameter, wie display und screen35, werden aus der konkreten
AWT –GraphicsDevice-Implementierungsklasse X11GraphicsDevice abgefragt.
NativeScreenInfo(GraphicsDevice graphicsDevice) {
// Open a new static display connection if one is not already opened
getStaticDisplay();
// Get the screen number
screen = ((X11GraphicsDevice)graphicsDevice).getScreen();
}
Java-Code aus javax.media.j3d.NativeScreenInfo.java
Interessant ist auch die Feststellung, dass Java3D für die Grafik-Ausgabe teilweise XlibFunktionen direkt verwendet. Der folgende Codeausschnitt zeigt ein Beispiel dafür.
JNIEXPORT jlong JNICALL
Java_javax_media_j3d_NativeScreenInfo_openDisplay(
JNIEnv *env,
jclass cls)
{
Display* dpy;
dpy = XOpenDisplay(NULL);
return (jlong)dpy;
}
C-Code aus .../native/ogl/NativeScreenInfo.c
4.1.3 SWTOGL
Ähnlich wie bei JOGL ist SWTOGL ein klassisches Eins-zu-Eins-OpenGL-Binding. Das
Konzept bezüglich Namenskonvention und Datentypen sind bei beiden Bindings fast gleich.
Das Listing 5 zeigt eine Gegenüberstellung zwischen Java/SWTOGL und C/GLX.
(a) Java Code
public void draw() {
GL.glClear(
GL.GL_COLOR_BUFFER_BIT
| GL.GL_DEPTH_BUFFER_BIT);
GL.glColor3f(1.0f, 0.3f, 0.3f);
GL.glBegin(GL.GL_QUADS);
gl.glVertex3f(0.2f, 0.2f, 0.0f);
gl.glVertex3f(0.7f, 0.2f, 0.0f);
gl.glVertex3f(0.7f, 0.7f, 0.0f);
gl.glVertex3f(0.2f, 0.7f, 0.0f);
GL.glEnd();
(b) C Code
void draw() {
glClear(
GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT);
glColor3f(1.0f, 0.3f, 0.3f);
glBegin(GL.GL_QUADS);
glVertex3f(0.25f, 0.25f, 0.0f);
glVertex3f(0.75f, 0.25f, 0.0f);
glVertex3f(0.75f, 0.75f, 0.0f);
glVertex3f(0.25f, 0.75f, 0.0f);
glEnd();
glContext.swapBuffers();
}
glXSwapBuffers(display, drawable);
}
Listing 5 Code-Beispiele (a) Java/SWTOGL, (b) C/GLX
35
vgl. Kapitel 2.1.2.2
63
Die Basis-Klassen von SWTOGL sind GL, GLU und GLContext. Im Gegensatz zu JOGL
werden die OpenGL Methode in der GL- und GLU Klassen als static final native
deklariert. Eine Applikation kann die gerenderte Grafik direkt durch den Aufruf der Methode
swapBuffer() des GLContext-Objektes auf dem Bildschirm darstellen. Eine feste Integration
in das Event-Framework, wie z.B. der GLEventListener von JOGL, ist nicht zwingend
erforderlich. Diese einfache Vorgehensweise entspricht am ehesten der klassischen OpenGLProgrammierung, beispielsweise mit GLX.
4.1.3.1 Native-Anbindung
Analog zu JOGL wird die Anbindung von OpenGL in diesem Kapitel anhand der
Implementierung an einigen signifikanten Stellen näher erläutert.
•
Funktion glColor3f()
public static final native void glColor3f (
float red, float green, float blue);
JNIEXPORT void JNICALL GL_NATIVE(glColor3f)
(JNIEnv *env, jclass that, jfloat arg0, jfloat arg1, jfloat arg2)
{
GL_NATIVE_ENTER(env, that, glColor3f_FUNC);
glColor3f(arg0, arg1, arg2);
GL_NATIVE_EXIT(env, that, glColor3f_FUNC);
}
Java- und C-Code aus GL.java bzw. gl.c
Die verwendeten Makros sind wie folgt definiert (die Marcos GLU_NATIVE_ENTER
werden nur für Statistik-Zwecke benutzt und sind per Default ausgeschaltet):
#define GL_NATIVE(func) Java_org_eclipse_swt_opengl_GL_##func
extern int GLU_nativeFunctionCallCount[];
#define GLU_NATIVE_ENTER(env, that, func)
GLU_nativeFunctionCallCount[func]++;
#define GLU_NATIVE_EXIT(env, that, func)
Macro Definition aus gl.h
Wie zu erwarten ist bei dieser einfachen Funktion kein Unterschied festzustellen.
•
Funktion glVertexPointer():
public static final native void glVertexPointer (
int size, int type, int stride, int[] pointer);
JNIEXPORT void JNICALL GL_NATIVE(glVertexPointer)
(JNIEnv *env, jclass that,
jint arg0, jint arg1, jint arg2, jintArray arg3)
{
jint *lparg3=NULL;
64
GL_NATIVE_ENTER(env, that, glVertexPointer_FUNC);
#ifdef JNI_VERSION_1_2
if (IS_JNI_1_2) {
if (arg3) lparg3 = (*env)->GetPrimitiveArrayCritical(env, arg3, NULL);
} else
#endif
{
if (arg3) lparg3 = (*env)->GetIntArrayElements(env, arg3, NULL);
}
glVertexPointer(arg0, arg1, arg2, lparg3);
#ifdef JNI_VERSION_1_2
if (IS_JNI_1_2) {
if (arg3) (*env)->ReleasePrimitiveArrayCritical(env, arg3, lparg3, 0);
} else
#endif
{
if (arg3) (*env)->ReleaseIntArrayElements(env, arg3, lparg3, 0);
}
GL_NATIVE_EXIT(env, that, glVertexPointer_FUNC);
}
Java- und C-Code aus GL.java bzw. gl.c
Hier ist deutlich zu erkennen, dass SWTOGL für die Native-Anbindung JNI bis zu
Version 1.2 unterstützt. Das von JOGL eingesetzte NIO-Package findet hier keine
Verwendung. Das ist gut für die Portierbarkeit, aber hinsichtlich der
Geschwindigkeit nicht optimal.
•
Anbindung SWT-OpenGL
Die Anbindung von SWT an OpenGL soll am Beispiel der GLContext-Klasse
analysiert werden. Die Abbildung 38 verdeutlicht den Verlauf.
Abbildung 38 Sequenz-Diagramm beim Erzeugen des GLContext Objektes
65
Im Konstruktor der GLContext-Klasse sind die erforderlichen Schritte zum Erstellen
des OpenGL-Context ersichtlich. Es wird zunächst ein SWT-Grafik-Context (gc) mit
der Methode drawable.internal_new_GC() erzeugt. Diese Methode des
drawable-Objektes ist in diesem Fall in der Klasse Control definiert. Verfolgt man
den Aufruf, so kann man feststellen, dass schließlich die C-Funktion gdk_gc_new()
aufgerufen wird. gdk_gc_new() ist eine Funktion aus der GTK-Toolkit Library.
GTK (Gimp Toolkit) ist ein weit verbreitetes Open-Source GUI-Framework. Die XWindow Implementierung von SWT basiert darauf. Die Klasse OS ist aus dem
Standard-Package von SWT, in der plattformabhängige Native-Funktionen gekapselt
sind.
Offensichtlich liefert die OS -Klasse standardmäßig nur den GTK-Grafik-Context
zurück. Um die erforderlichen XLib-Parameter, wie xDisplay und info, für die
Funktion glXCreateContext() zu ermitteln, wurde eine weitere Native-Klasse
XGTK entwickelt. Die Methode XGTK.gdk_x11_gc_get_xdisplay() ruft die
entsprechende GTK C-Funktion gdk_x11_gc_get_xdisplay() auf, um den XWindow-Handler xDisplay zu referenzieren.
public GLContext (Drawable drawable) {
if (drawable == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
this.drawable = drawable;
data = new GCData ();
gc = drawable.internal_new_GC (data);
if (gc == 0) SWT.error (SWT.ERROR_NO_HANDLES);
int xDisplay = XGTK.gdk_x11_gc_get_xdisplay (gc);
int screen = XGTK.XDefaultScreen (xDisplay);
...
handle = XGL.glXCreateContext (xDisplay, info, 0, false);
}
GLContext.java
public int internal_new_GC (GCData data) {
checkWidget ();
int window = paintWindow ();
if (window == 0) SWT.error (SWT.ERROR_NO_HANDLES);
int gdkGC = OS.gdk_gc_new (window);
if (gdkGC == 0) error (SWT.ERROR_NO_HANDLES);
...
return gdkGC;
}
Control.java
public static final native int _gdk_gc_new(int window);
JNIEXPORT jint JNICALL OS_NATIVE(_1gdk_1gc_1new)
(JNIEnv *env, jclass that, jint arg0)
{
jint rc = 0;
OS_NATIVE_ENTER(env, that, _1gdk_1gc_1new_FUNC);
66
rc = (jint)gdk_gc_new((GdkDrawable *)arg0);
OS_NATIVE_EXIT(env, that, _1gdk_1gc_1new_FUNC);
return rc;
}
OS.java und os.c
public static final synchronized native int
gdk_x11_gc_get_xdisplay(int gc);
JNIEXPORT int JNICALL XGTK_NATIVE(gdk_1x11_1gc_1get_1xdisplay)
(JNIEnv *env, jclass that, jint arg0)
{
jint rc;
NATIVE_ENTER(env, that, "gdk_1x11_1gc_1get_1xdisplay\n")
rc = (jint)gdk_x11_gc_get_xdisplay((GdkGC *)arg0);
NATIVE_EXIT(env, that, "gdk_1x11_1gc_1get_1xdisplay\n")
return rc;
}
XGTK.java und xgtk.c
4.1.4 JNI und NIO
Im folgenden Abschnitt werden die grundlegende Technik und Funktionsweise von JNI und
NIO hinsichtlich für OpenGL-Binding relevante Aspekte erläutert. Eine ausführliche
Beschreibung befindet sich in [Sun02] und [Hit02].
Um von einem Java-Programm heraus Native-Library Funktionen aufzurufen, hat die Firma
SUN das Java Native Interface (JNI) entwickelt. JNI stellt damit die Brücke zwischen der
Native- und Java-Seite dar. Anhand eines Beispiels soll die Vorgehensweise verdeutlicht
werden. Soll die statische Methode glColor3f () der Klasse GL im net.java.games.jogl
Package als Native-Funktion realisiert werden, muss auf der Java-Seite die Methode als
native deklariert werden.
package net.java.games.jogl;
public class GL
{
...
native public void glColor3f(float red, float green, float blue);
Native Methode Definition in Java
Dann muss eine C-Funktion als „Gegenstück“ entwickelt werden. Dabei sind die definierten
Datentypen und Makros zu verwenden und die JNI-Namenkonventionen einzuhalten. Der
Programmcode dazu sieht folgendermaßen aus:
JNIEXPORT void JNICALL
Java_net_java_games_jogl_GL_glColor3f(
JNIEnv *env, jobject _this, jfloat red, jfloat green, jfloat blue) {
glColor3f((GLfloat) red, (GLfloat) green, (GLfloat) blue);
}
Native Methode Definition in C
67
Die Parameter JNIEnv *env und jobject _this sind bei JNI obligatorisch. JNIEnv *env
ist die Referenz auf die VM-Umgebung. Über diesen Pointer können weitere JNI-Funktionen
aufgerufen werden. jobject _this repräsentiert das aufrufende Objekt selbst. Der Header
dieser C-Funktion kann auch mit dem Java-Tool javah
> javah –jni GL.java
generiert werden. Die Makros JNIEXPORT und JNICALL sorgen für die nötige Portierbarkeit
zwischen den Plattformen. Für die Benennung der C-Funktion gilt folgende Regel (Abbildung
39):
Der voll-qualifizierte
Klassenname, getrennt durch _
statt .
Prefix, ist immer
Java_
Java_
+
net_java_games_jogl_GL_
Methodename
+
glColor3f
Abbildung 39 Namenskonvention der Native-Funktion bei JNI
Die erstellte C-Funktion wird anschließend kompiliert und ggf. mit anderen C-Objekt-Dateien
zu einer Shared-Library zusammengefasst. Auf QNX kann eine Shared-Library namens
libmylib.so beispielsweise wie folgt erzeugt werden.
> cc –o libmylib.so –shared –lGL –lGLU –lm gl.o
Vor der Benutzung der Native-Funktion muss die Shared-Library noch geladen werden. Dies
geschieht mit der Methode System.loadLibrary () und soll typischerweise im staticBlock der Java-Klasse aufgerufen werden.
package net.java.games.jogl;
public class GL
{
static {
System.loadLibrary(“mylib”);
}
...
native public void glColor3f(float red, float green, float blue);
Beispiel zum Laden der externen Library mit der loadLibrary() Methode
Im oben gezeigten Beispiel werden float Variablen als Parameter an die C-Funktion
übergeben. Prinzipiell können alle Java-Primitive-Datentypen (Tabelle 5) sowie -Objekte
(Abbildung 40) als Parameter und Return-Wert übergeben werden.
68
Java-Typ
C-Type
boolean
byte
char
short
int
long
float
double
void
jboolean
jbyte
jchar
jshort
jint
jlong
jfloat
Jdouble
Void
Grösse in Bit
8, unsigned
8
16 unsigned
16
32
64
32
64
-
Tabelle 5 Zuordnung der Java- und C-Datentypen
jobject
...
jstring
jarray
jintArray – int arrays
jclass
jobjectArray – arrays of objects
jfloatArray – float arrays
...
Abbildung 40 Vordefinierte C-Typen auf Java-Objekte
Java-Objekte werden über „passed by reference“ übergeben. D.h. auf der Native-Seite wird
ein Java-Objekt über einen Pointer referenziert. JNI hat dazu einige Pointer-Typen definiert
(Abbildung 40). Obwohl es ausreichen würde, alle Java-Objekte über den jobject-Pointer zu
referenzieren, sollen laut Konvention stattdessen die dazugehörigen Sub-Typen der Objekte
verwendet werden.
Bei einer typischen OpenGL-Applikation werden große Datenmengen in einem Vertex-Array
abgespeichert und an die Native-OpenGL-Funktionen weitergeleitet. Es ist daher wichtig zu
wissen, wie JNI Arrays verwaltet. Hierzu ein Beispiel mit einem Float-Array als Parameter:
native public float sum(float[] aFloatArray);
JNIEXPORT jfloat JNICALL
Java_net_java_games_jogl_GL_sum(
JNIEnv *env, jobject _this, jfloatArray aFloatArray) {
...
}
Java- und C-Code für ein Beispiel JNI Aufruf
Der Zugriff auf die Java-Objekte erfolgt über Funktionen, die von JNI zur Verfügung gestellt
werden. Auf ein Float-Array-Objekt kann beispielsweise wie folgt zugegriffen werden.
69
JNIEXPORT jfloat JNICALL
Java_net_java_games_jogl_GL_sum(
JNIEnv *env, jobject _this, jfloatArray aFloatArray) {
int i;
float sum = 0;
jsize len = (*env)->GetArrayLength(env, aFloatArray);
jfloat *arr = (*env)->GetFloatArrayElements(env, aFloatArray, 0);
for (i=0; i<len; i++) {
sum += arr[i];
}
...
(*env)->ReleaseFloatArrayElements (env, aFloatArray, arr, 0);
Return sum;
}
C-Code für ein Beispiel mit Array bei JNI
Die Länge eines Arrays muss immer getrennt mit der Funktion GetArrayLength() ermittelt
werden. Beim Aufruf der Funktion GetFloatArrayElements() wird das Array je nach JVM
entweder als nicht verschiebbar markiert (pin) oder in einen nicht verschiebbaren
Speicherbereich (Nonmoveable Memory) der JVM kopiert, damit der Speicher nicht vom
Garbage-Collector zufällig während der Ausführung der Native-Funktion umorganisiert wird.
Es ist daher wichtig, nach der Ausführung diesen Speicherbereich mit der Funktion
ReleaseFloatArrayElements() wieder freizugeben. Die JVM kopiert dann das Array aus
dem Nonmoveable Memory zurück und gibt den Speicherbereich frei. Bei nicht Beachtung
dieser Regel kann es zu einem Memory-Leak und sogar zum Absturz der JVM führen.
Insbesondere das Kopieren der Arrays, obwohl es im Speicher erfolgt, kann bei großen
Datenmengen zu Performance-Verlust führen. Um hier Abhilfe zu schaffen, hat die Firma
SUN mit Java 1.2 zwei neue Funktionen eingeführt: GetPrimitiveArrayCritical() und
ReleasePrimitiveArrayCritical(). Die Semantik der beiden neuen Funktionen sind
vergleichbar mit den bisherigen Get/ReleaseArrayElements() Funktionen. Wenn möglich
liefert die JVM einen Pointer auf die „uncopied“ Version des Arrays, unabhängig davon, ob
die JVM das „pinning“ (Festnageln) von Speicher unterstützt.
Der Name „...Critical“ deutet aber schon an, dass die Verwendung dieser Funktion einige
Einschränkungen hat. Um einen konsistenten Zugriff auf den Speicher zu gewährleisten, muss
die JVM je nach Implementierung den Garbage-Collector vorübergehend abschalten oder
sogar alle anderen Threads stoppen. D.h. der Code zwischen
Get/ReleasePrimitiveArrayCritical() bildet eine Art „Critical-Region“. Die
Ausführung der Code-Zeilen innerhalb dieser Critical-Region soll deshalb nicht zu lange
dauern. Außerdem sind weitere JNI- und System-Calls, die zu Blockaden führen könnten,
dringend zu vermeiden. Aufgrund dieser Problematik werden diese Funktionen eher als eine
Not-Lösung für akute Performance-Probleme angesehen.
Um Abhilfe zu schaffen, hat die Firma SUN mit der Einführung von Java 1.4 das neue NIODirekt-Buffer Konzept sowie entsprechende neue JNI-Funktionen eingeführt.
Ein Direkt-Buffer repräsentiert einen von der Speicherverwaltung der JVM nicht betroffenen
Speicherbereich. Dieser Speicherbereich kann sowohl innerhalb der JVM als auch außerhalb
der JVM vom Native-Code erzeugt werden. Der Zugriff auf diesen Speicherbereich ist
70
sowohl auf der Java-Seite (gekapselt als Direct-Buffer-Objekt) als auch auf der Native-Seite
transparent. So ist es beispielsweise möglich, Video-Memory oder Register-Mapping-Bereich
eines Device-Controllers als Direct-Buffer zu deklarieren und nach Java zu exportieren. In
Verbindung mit dem ebenfalls in NIO neu eingeführten Channel-Konzept ist es zum ersten
mal möglich, hoch performante oder Hardware nahe Anwendungen mit Java zu realisieren.
Die Klassen-Hierarchie des NIO-Buffers ist in Abbildung 41 dargestellt.
Abbildung 41 Klassen-Hierarchie von Java-NIO-Buffer. Quelle [Hit02].
Ein Buffer-Objekt ist ein Array von primitiven Datentypen und hat zusätzlich vier
Eigenschaften:
•
Capacity
Die maximale Kapazität des Buffers wird beim Erzeugen des Buffer-Objektes
festgelegt und kann nicht verändert werden.
•
Limit
Das erste Element, das nicht gelesen oder geschrieben werden kann.
•
Position
Der Index des nächsten Elements, das gelesen oder geschrieben wird. Die Position
wird automatisch beim Aufruf der relativen get() und put() Methoden aktualisiert.
•
Mark
Eine Markierung der Index-Position: beim Aufruf der Methode mark() wird die
Markierung auf die aktuelle Position gesetzt. Bei reset() dagegen wird die aktuelle
Position auf den Wert der Markierung zurückgesetzt.
Abbildung 42 zeigt den schematischen Aufbau eines Buffer-Objektes.
Abbildung 42 Schematische Aufbau eines Buffer-Objektes. Quelle [Hit02].
71
Für den Zugriff und die Bearbeitung von Buffer-Objekten stehen effiziente Methoden zur
Verfügung. Hardwarenahe Aspekte wie Byte-Order (Big- / Little-Endian) werden ebenfalls
berücksichtigt. Ausführliche Beschreibungen zu dieser Thematik sind in [Hit02] beschrieben.
Für schnelle I/O-Operationen oder für direkten Speicher-Zugriff ist der Direct-Buffer
vorgesehen. Ein Direct-Buffer ist keine eigene Java-Klasse, sondern eine spezielle Form von
ByteBuffer. Das folgende Beispiel zeigt einen typischen Ablauf.
Public ByteBuffer getString() throws Exception {
CharBuffer chars = CharBuffer.allocate(100);
chars.put(“Hello World\n”);
chars.put(“bye bye\n”);
chars.flip();
Charset charset = Charset.forName(“US-ASCII”);
ByteBuffer buffer = charset.newEncoder().encode(chars);
ByteBuffer directBuffer = ByteBuffer.allocateDirect(buffer.limit());
directBuffer.put(buffer);
return directBuffer;
}
...
Beispiel Direct ByteBuffer
Es wird zunächst ein CharBuffer-Objekt erzeugt und mit Daten gefüllt. Mit der Methode
flip() wird der Buffer auf die Übertragung vorbereitet. Dabei werden die internen Zeiger
wie Limit und Position auf die richtigen Stellen gesetzt. Anschließend wird ein
ByteBuffer-Objekt erzeugt, welches als Inhalt den ASCII-Code des CharBuffer-Objektes
(Java-String enthält Unicode Zeichen) enthält. Das eigentliche Direct-Buffer Objekt wird mit
ByteBuffer.allocateDirect() erzeugt und bekommt als Inhalt den ByteBuffer mit den
ASCII-Zeichen. Das Direct-Buffer-Objekt kann nun beispielsweise über JNI direkt auf ein
Low-Level-Device geschrieben werden.
Wie bereits oben erwähnt, sind seit Java 1.4 folgende neue JNI Funktionen für das Handling
mit Direct-Buffer hinzugekommen:
•
NewDirectByteBuffer()
Diese Funktion konstruiert ein Direct-Buffer-Objekt aus einem (evtl. außerhalb der
JVM reservierten) Speicherbereich. Das Direct-Buffer Objekt wird in der Regel an
die Jave-Seite für die Weiterverarbeitung zurückgeliefert.
•
GetDirectBufferAddress()
Der Zugriff auf ein Direct-Buffer Objekt, das innerhalb der JVM erzeugt wurde,
kann mit Hilfe dieser Funktion erfolgen.
•
GetDirectBufferCapacity()
Die Größe des Direct-Buffer Objektes wird mit dieser Funktion ermittelt.
Das folgende Beispiel zeigt, wie der Zugriff auf einen Direct-Buffer in JNI-Native-Code
erfolgen kann.
native private java.nio.ByteBuffer getBuffer(
java.nio.Buffer ptr);
72
JNIEXPORT jobject JNICALL
example_Example_GetBuffer(
JNIEnv *env, jobject _unused, jobject ptr)
{
...
buffer = (*env)->GetDirectBufferAddress(env, ptr);
...
return (*env)->NewDirectByteBuffer(env, mem, 100);
}
Beispiel JNI Funktionen für Direct ByteBuffer
4.2 Implementierung
Auf dem Target liegt eine IBM J9 Java-Virtual-Machine in der Version 1.3 vor. D.h. das von
JOGL eingesetzte NIO Package ist nicht vorhanden. Ein weiteres Problem ist die fehlende
Native-AWT-Kompatibilität von IBM J9. Diese Kompatibilität wird bei der Zertifizierung
nicht gefordert. Beide Faktoren zusammen führen dazu, dass eine Portierung von JOGL in
einen vernünftigen Zeitrahmen ausgeschlossen ist. Des Weiteren ist SWTOGL auch für Java
1.3 ausgelegt und SWT ist auf QNX-Photon verfügbar. Aus diesen Gründen wurde für die
Portierung SWTOGL ausgewählt.
Wie bereits in Kapitel 4.1.3.1 gezeigt, besteht die Hauptaufgabe bei der Portierung darin, den
Grafik-Context des SWT-Fensters zu ermitteln und an die Native-OpenGL-Funktionen
weiterzuleiten. Analog wie bei der X-Window/GTK Version von SWT wird der GrafikContext unter Photon durch entsprechende Methoden exportiert. Ein Blick in den Sourcecode
von SWT-Photon verdeutlicht das Konzept.
Der Grafik-Context wird von der Methode internal_new_GC der Interface-Klasse Drawable
zurückgeliefert. In der Klassenhierarchie der GUI-Komponente wird diese Methode von der
Widget-Klasse Control implementiert:
Widget
Control
+internal_new_GC ()
«interface»
Drawable
+internal_new_GC ()
Abbildung 43 Klassendiagramm SWT Widget
73
aGLContext
aControl
OS
Photon Lib
new
internal_new_GC
pgCreateGC
pgCreateGC
Abbildung 44 Sequenz-Diagramm beim Instantiieren des GLContext Objektes
Die Klassen Control und OS sehen folgendermaßen aus:
public int internal_new_GC (GCData data) {
checkWidget();
int phGC = OS.PgCreateGC(0);
if (phGC == 0) SWT.error(SWT.ERROR_NO_HANDLES);
...
return phGC;
}
Control.java
public static final native int PgCreateGC(int size);
JNIEXPORT jint JNICALL OS_NATIVE(PgCreateGC)
(JNIEnv *env, jclass that, jint arg0)
{
jint rc = 0;
OS_NATIVE_ENTER(env, that, PgCreateGC_FUNC);
rc = (jint)PgCreateGC(arg0);
OS_NATIVE_EXIT(env, that, PgCreateGC_FUNC);
return rc;
}
Java- und C-Code aus OS.java und os.c
Aus den oben gezeigten Sourcecode-Teilen wird ersichtlich, dass der Photon-Grafik-Context
von SWT fehlerfrei exportiert wird.
Für eine prototypische Portierung sind mindesten drei Funktionen anzupassen:
•
•
•
CreateContext()
MakeCurrent()
SwapBuffers()
74
Es ist also eine neue Java-Klasse sowie die dazugehörige C-Library mit den drei Funktionen
zu realisieren. Auf der Java-Seite wird dazu ein neues Package parallel zu der X11/GTK
Version erstellt.
aGLContext
aControl
OS
Native Photon Lib
PhotonGL
Native OSMesa Lib
new
internal_new_GC
pgCreateGC
pgCreateGC
glPhotonCreateContext
createOSContext
Abbildung 45 Sequenz-Diagramm CreateContext
Die neue PhotonGL-Klasse sieht wie folgt aus:
package org.eclipse.swt.opengl.internal.photon;
import org.eclipse.swt.opengl.Library;
public class PhotonGL {
static {
Library.loadLibrary("photonGL");
}
public static final native int glPhotonCreateContext(int gc);
public static final native boolean glPhotonMakeCurrent();
public static final native void glPhotonSwapBuffers();
}
PhotonGL.java
Der äquivalente C-Code ist nachfolgend aufgelistet:
JNIEXPORT jint JNICALL GL_NATIVE(glPhotonCreateContext)
(JNIEnv *env, jclass that, jint gc)
{
DEBUG_CALL("glPhotonCreateContext\n")
return glICMCreateContext(SCREEN_WIDTH_3D, SCREEN_HEIGHT_3D, 0, gc);
}
JNIEXPORT jboolean JNICALL GL_NATIVE(glPhotonMakeCurrent)
(JNIEnv *env, jclass that)
{
75
DEBUG_CALL("glPhotonMakeCurrent \n")
return glICMMakeCurrent();
}
JNIEXPORT void JNICALL GL_NATIVE(glPhotonSwapBuffers)
(JNIEnv *env, jclass that)
{
DEBUG_CALL("glPhotonSwapBuffer \n")
glICMSwapBuffer();
}
PhotonGL.c
Aus dem C-Code wird anschließend eine Shared-Library mit dem Name libphotonGL.so
erstellt. Diese Shared-Library muss zur Laufzeit vom System auffindbar sein, z.B. durch
Setzen der System-Variable LD_LIBRARY_PATH.
In der Klasse GLContext sind die drei Methoden wie folgt implementiert36.
public GLContext (Drawable drawable) {
if (drawable == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
this.drawable = drawable;
data = new GCData ();
gc = drawable.internal_new_GC (data);
PhotonGL.glPhotonCreateContext(gc);
}
public void setCurrent () {
PhotonGL.glPhotonMakeCurrent();
}
public void swapBuffers () {
PhotonGL.glPhotonSwapBuffers();
}
GLContext.java
Die neuen Methoden sind völlig transparent und können von jedem SWTOGL-Programm
ohne Code-Änderungen verwendet werden. Der folgende Code zeigt ein lauffähiges Beispiel.
public class HelloWorld {
private Canvas glCanvas;
private GLContext context;
public void run() {
// init SWT
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout( new GridLayout());
shell.setLocation(1,1);
36
Die Klasse GLContext ist in der vorliegenden Version von SWTOGL nicht als Interface oder abstrakte Klasse
ausgelegt, d.h. alle plattformabhängigen Codes werden hier fest kodiert. Für unterschiedliche Plattform werden
entsprechend angepasste Versionen ausgeliefert.
76
GridData gridData = new GridData();
gridData.heightHint = 97; // hack
gridData.widthHint = 320;
gridData.verticalAlignment = GridData.BEGINNING;
glCanvas = new Canvas(shell, SWT.NONE);
glCanvas.setLayout(new GridLayout());
glCanvas.setLayoutData(gridData);
glCanvas.setSize(320, 96); // needed for windows
shell.pack();
shell.open();
context = new GLContext(glCanvas);
context.setCurrent();
GL.glMatrixMode(GL.GL_PROJECTION);
GL.glLoadIdentity();
GL.glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 100.0);
GL.glClearColor(0.7f, 0.7f, 0.7f, 0.7f);
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
GL.glLoadIdentity();
GL.glBegin(GL.GL_POLYGON);
GL.glVertex3f(0.25f, 0.25f, 0.0f);
GL.glVertex3f(0.75f, 0.25f, 0.0f);
GL.glVertex3f(0.75f, 0.75f, 0.0f);
GL.glVertex3f(0.25f, 0.75f, 0.0f);
GL.glEnd();
GL.glFlush();
context.swapBuffers();
while( !shell.isDisposed()) {
}
display.dispose();
}
Beispiel Programm mit SWTOGL/Photon auf dem Target
Zum Ausführen des Programms auf dem Target sind für unterschiedliche Komponenten
einige Java- und C-Libraries erforderlich:
•
JRE
Die Basis-Klassen der Java Runtime Environment von J9 sind:
o JclMax/classes.jar
o runtime.jar
o jface.jar
•
SWT
Die Basis SWT-Klassen sind in den folgenden Libraries zusammengefasst:
o Java: swt.jar
o C: libswt-photon-3138.so
•
SWTOGL
Das potierte OpenGL-Binding sowie die Shared-Libraries dazu sind hier zu finden:
o Java: swtopengl.jar
o C: libgl-photon.so
o C: libphotonGL-photon.so
77
•
OpenGL
Die OpenGL-Mesa-Libraries:
o OSMesa.so
o GL.so
o GLU.so
•
Anwendung
Die Java-Klassen der eigentlichen Anwendung:
o Gears.class
All diese Libraries müssen zur Laufzeit von der JVM auffindbar sein. Ein möglicher JVMAufruf kann wie folgt aussehen:
export JAVAROOT=/usr/qnx630/host/qnx6/x86/usr/qde/eclipse/jre/
export CLASSPATH=.:$JAVAROOT/lib/jclMax/classes.zip:\
$JAVAROOT/lib/eclipse/swt.jar:\
$JAVAROOT/lib/eclipse/jface.jar:\
$JAVAROOT/lib/eclipse/runtime.jar:\
/root/java/swt-opengl-photon/swtopengl.jar
export LD_LIBRARY_PATH=$JAVAROOT/bin:\
/root/Mesa-6.0-OS/lib:/root/java/swt-opengl-photon
$JAVAROOT/bin/j9 Gears
Beispiel-Aufruf von Java-Gears-Demo
4.3 Evaluierung und Diskussion
Um die Funktionsfähigkeit der durchgeführten Portierung zu prüfen, wurde die Java-Version
des Gears-Demoprogramm aus dem JOGL-Beispiel für Tests auf dem Target adaptiert.
Abbildung 46 zeigt einen Screenshot des lauffähigen Gears-Demoprogramms. Aus dem
Blickwinkel der Grafikdarstellung ist kein Unterschied zu der C-Implementierung
festzustellen.
Abbildung 46 Gears Demo in Java mit SWTOGL
Zur Beurteilung der Ergebnisse wurde ein einfacher Performance-Test durchgeführt, bei dem
jeweils die Framerate (FPS) der Java-Version und einer C-Version auf dem Target gemessen
wurde. Und zwar jeweils mit und ohne Bildschirm-Ausgabe. Tabelle 6 zeigt die Ergebnisse
78
Mit Ausgabe
Ohne Ausgabe
FPS mit Java
32
44
FPS mit C
50
59
Tabelle 6 Messergebnisse Gears-Demo mit Java/SWTOGL und C auf dem Target
Im direkten Vergleich ist die Framerate der Java-Version erheblich schlechter. Ziel der
Portierung war allerdings nicht primär die Performance zu beurteilen, sondern viel mehr einen
Einblick über die Machbarkeit zu gewinnen.
Die erfolgreich durchgeführte Portierung von SWTOGL bestätigt, dass die Entwicklung von
3D-Applikationen für das Target mit Java möglich ist. Eine fundierte Leistungsbewertung ist
bei dem derzeitigen Software-Stand nicht möglich. Die schlechte Performance bei den
durchgeführten Tests ist auf die noch nicht optimal implementierten OpenGL-Binding
zurückzuführen. Für eine gute Gesamt-Performance sind folgende Punkte erforderlich:
•
eine leistungsfähige JVM,
•
ein effizientes Java-Binding und
•
eine (evtl. hardwarebeschleunigte) OpenGL-Library.
Nur durch das optimale Zusammenwirken dieser Komponenten wird Java im Bereich 3DGrafik auf Embedded-Systemen konkurrenzfähig sein.
Die lange Startzeit der JVM beim Hochfahren des Systems ist ein Hauptargument gegen den
Einsatz von Java im Automotive-Bereich. Dieser Nachteil fällt hier nicht so sehr ins Gewicht.
Ein Navigationssystem, das 3D-Karte in Java rendert, muss nicht sofort beim Start verfügbar
sein. Auch eine kleine Verzögerung bis zum Erscheinen des ersten Bildes kann durchaus
akzeptiert werden.
JOGL in Verbindung mit Java 1.4 stellt im Moment das beste OpenGL-Binding dar.
Insbesondere der konsequente Einsatz von NIO-Buffer führt zur performanten Verarbeitung
auch mit komplexen und großen 3D-Modellen. Sollte die IBM J9 endgültig in der Version 1.4
vorliegen, so ist es sicherlich eine interessante Aufgabe, JOGL auf die J9- / SWT-Plattform zu
portieren. Der Aufwand dürfte sich in Grenzen halten, da die Sourcecodes zum Großteil durch
intelligente Generator-Tools erstellt werden.
SWTOGL ist ein schlankes OpenGL-Binding, welches noch in der Entwicklungsphase
befindet und noch keine Produktions-Qualität erreicht hat. Gerade durch das einfache Design
ist es für eine Portierung ideal geeignet.
Fällt die Design-Entscheidung für einen objektorientierten Ansatz nach dem Szenen-GraphModell, so ist der Einsatz von Java3D aus softwaretechnischen Sicht die beste Alternative.
Laufzeitzeitverhalten und Portierbarkeit auf dem Target müssen aber noch eingehender
untersucht werden.
Dank der Open-Source-Politik der oben evaluierten Produkte ist es möglich, Einblicke in die
interne Funktionsweise zu gewinnen und eine erste Bewertung vorzunehmen. Das
Analysieren des Sourcecodes war sehr lehrreich; es weckt Interesse auf weiterführende
Untersuchungen.
79
5 3D-Modelle und Fisheye View
Echtzeit-3D-Darstellung von POI-(Point of Interest)-Objekten oder z.B. eine Navigationsfahrt
durch Städte mit realitätstreuer 3D-Darstellung der Umgebungsgebäude sind Funktionen, die
die Navigationssysteme der nächsten Generation anbieten werden. Bereits heute sind einige
Großstädte in Deutschland komplett digitalisiert. Auch die POI-Datenbank der
Kartenzulieferfirmen wächst von Tag zu Tag. Für die Weiterverarbeitung werden die
eingescannten Daten in 3D-Modellierungsprogramme importiert und in entsprechende
Formaten abgespeichert. Ein Navigationssystem muss also in der Lage sein, 3D-Modelle aus
der Datei zu lesen und zu visualisieren.
3D-Viewer sind im PC-Bereich für verschiedene 3D-Formate verfügbar. Ob sich allerdings
ein solcher 3D-Viewer auch auf dem Target mit OpenGL realisieren lässt, ist noch ungeklärt.
Im Rahmen dieser Arbeit soll daher ein 3D-Viewer implementiert werden, der sowohl
kleinere POI-Objekte als auch größere 3D-Stadt-Modelle visualisieren kann. Als weiteres
Feature soll eine Fisheye-View realisiert werden. Dabei soll ein 3D-Stadt-Modell ähnlich wie
bei einem Globus auf eine Halb-Kugel projiziert werden.
5.1 Grundlagen
5.1.1 3D-Formate
In den Kapitel 2, 3 und 4 wurden bereits mehrfach gezeigt, wie ein 3D-Objekt oder gar ein
komplettes Gelände aus Vertices und Texturen mit einem OpenGL-Programm erzeugt wird.
In der Praxis werden 3D-Objekte jedoch mit einem Modellierungsprogramm erstellt und
anschließend in einer Datei eines bestimmten 3D-Formates abgespeichert. Sowohl ein
einzelnes Objekt als auch eine komplette 3D-Szene können so wieder eingelesen und
dargestellt werden. Wie in anderen Gebieten der Informatik existieren auch hier eine Menge
von proprietären, wie offenen, 3D-Formate.
Grundsätzlich gibt es verschiedene Möglichkeiten, 3D-Objekte zu beschreiben. Neben den
von OpenGL verwendeten Gitter- und Flächen-Modellen, bei denen ein 3D-Objekt durch
Kanten zwischen einzelnen Punkten sowie von Kanten eingeschlossene Flächen
(Polygonzügen und –Flächen37) repräsentiert werden, gibt es noch weitere Modelle. Auch
wenn diese alternativen Modelle nicht von den gängigen 3D-Modellierungstools verwendet
werden, werden diese nachfolgend kurz erwähnt:
•
Punktwolke
Eine sehr einfache Form der Darstellung. Dabei werden 3D-Objekte durch eine
Menge von einzelnen Punkten im Raum beschrieben. Es gibt keine Informationen
über Kanten, Flächen oder Volumen des Objektes.
•
Volumenmodell
Bei diesem Modell wird ein 3D-Objekt durch geometrische Körper beschrieben. Ein
Objekt kann entweder aus einfachen Grund-Objekten wie Würfel, Kugel, Pyramide
usw. oder aus komplexeren Grund-Objekten wie Freiformkörper oder Fraktale
37
vgl. Kapitel 2.1.2
80
zusammengesetzt werden. Bekannter Vertreter dieses Modells ist das Voxel-Modell.
Dabei wird ein Objekt aus einer Menge von kleineren Objekten gleicher Größe (in
der Regel Würfel) approximiert. Weitere Vertreter des Volumen-Modells sind
beispielsweise Octree, Bintree oder CSG-Tree. Ausführliche Informationen darüber
sind in [Wat02] verfasst.
Neben dem zugrunde gelegten Darstellungsmodell lassen sich 3D-Formate je nach
Einsatzgebiet und Anforderungen in verschiedene Kategorien unterteilen. Die wichtigsten
sind Folgende:
•
CAD/CAM Formate
CAD-Systeme werden typischerweise in der Konstruktion und Fertigung eingesetzt.
Nicht die realistisch gerenderte Darstellung, sondern die präzise und detaillierte
Beschreibung des Objektes steht hier im Vordergrund. Gängige Formate sind:
o DXF: Ein von der Firma Autodesk (AutoCAD) spezifiziertes Format zum
Austausch von CAD-Daten.
o IGES: ANSI Standard
•
Modellierung- und Animationsformate
Im Gegensatz zu CAD-Formate enthalten Modellierungsformate zusätzlich zu den
Objekt-Daten auch die komplette Beschreibung der Szene inklusiv Effekte, Lichter,
Kamera-Einstellung usw. Einsatzgebiete von Modellierungsprogrammen reichen von
High-End-Simulatoren über die Film-Industrie bis zu der Computerspiel-Branche.
Beispiele dieses Formates sind:
o 3DS: Dieses Format wird von dem Modellierungstool 3D Studio der Firma
Autodesk verwendet. Obwohl die Spezifikation nicht offen gelegt ist, wird das
Format aufgrund der weiten Verbreitung des Programms sehr häufig als
Austausch-Format eingesetzt.
o OBJ: Das Format von dem Programm Advance-Visualizer der Firma Alias
Wavefront, in der Praxis auch als Austausch-Format sehr beliebt.
o MB: Das Format des High-End Modellierungsprogrammes Maya von Alias.
o BLEND: Das Open-Source Modellierungsprogramm Blender erfreut sich
immer größerer Beliebtheit. Obwohl es durch die offene Architektur praktisch
mit allen gängigen 3D-Formaten umgehen kann, hat das Programm auch ein
eigenes Format.
o U3D: Das Universal-3D Format wurde von der ECMA als ECMA-363
standardisiert und dient als herstellerübergreifendes 3D-Austausch-Format.
•
Internet-basierte Formate
Diese Formate wurden speziell für die Übertragung und Darstellung von 3D-Szenen
über Internet entwickelt. Durch entsprechende Client-Programme oder BrowserPlug-In werden die übertragene 3D-Szene beim Anwender sichtbar. Aspekte wie
Kompression oder Streaming werden bei diesen Formaten aufgrund der oft geringen
Bandbreite des Netzes mitberücksichtigt. Typische Vertreter sind:
o WRL: Datei dieses Formates enthält Szenenbeschreibung gemäß der VRML
Spezifikation. VRML (Virtual Reality Modeling Language) ist eine
standardisierte (ISO/IEC 14772) Sprache zur Beschreibung interaktiver 3DSzene.
o X3D: Das Nachfolger-Format von WRL, ist XML basiert und weist gegenüber
WRL einige Verbesserungen auf.
81
In den folgenden Abschnitten wird der Aufbau für zwei häufig verwendete Formate (3DS und
OBJ) der 3D-Modellierung näher erläutert.
5.1.1.1 Das OBJ-Format
Das OBJ-Format des Modellierungsprogrammes Advanced Visualizer der Firma Wavefront
ist ein ASCII Format und daher besonders als Austausch-Format geeignet. Die Spezifikation
ist öffentlich zugänglich. Das Format unterstützt Linien, Polygone, Freiform-Kurven (z.B.
Bezier, B-Spline) und Oberflächen. Linien und Polygone werden durch ihre Punkte
beschrieben. Bei Kurven und Oberflächen dagegen sind es die Kontrollpunkte oder
geometrische Parameter des jeweiligen Objektes.
Daten werden zeilenweise interpretiert. Eine Zeile beginnt mit einem Schlüsselwort gefolgt
von schlüsselwortabhängigen Attributen. Um beispielsweise ein Polygon zu definieren,
werden zunächst die Eckpunkte mit dem Schlüsselwort v spezifiziert und anschließend die
entstandene Fläche mit dem Schlüsselwort f angegeben. Dabei legen die Attribute von f die
Reihenfolge der zuvor definierten Eckpunkte fest. Das folgende Beispiel beschreibt ein
einfaches Dreieck.
v
v
v
f
0.0
0.0
0.0
1 2
0.0 0.0
1.0 0.0
0.0 1.0
3
Farb- und Material-Informationen werden nicht in dem OBJ-Format definiert, sondern in
einer externen Material-Library (Material Library File, *.MTL). Diese Material-Library kann
mit dem Schlüsselwort mtllib geladen werden. Die Material-Library enthält Informationen
wie Farbe, Material-Beschaffenheit oder Eigenschaften wie Transparenz und Lichtbrechung.
In der OBJ-Datei kann ein Material mit dem Schlüsselwort usemtl referenziert werden. Alle
folgenden Flächen bekommen dieses Material bis zum nächsten usemtl Befehl zugeordnet.
Szene-Beschreibung ist in dem OBJ-Format nicht explizit vorgesehen. Es können allerdings
Objekt-Gruppen und somit Hierarchien definiert werden.
5.1.1.2 Das 3DS-Format
Ein sehr weit verbreitetes 3D-Format ist das 3DS von Autodesk 3D Studio. Die Spezifikation
dieses Formats ist nicht offen gelegt. Es existieren jedoch einige inoffiziellen Beschreibungen
zu dem Format. Im Gegensatz zum einfach aufgebauten OBJ-Format ist 3DS ein binäres
Format und von der Struktur her etwas komplexer. 3DS-Dateien bestehen aus sog. Chunks
(Blöcken). Jeder Chunk beginnt mit einer ID, die die Art der in diesem Chunk enthaltenen
Daten festlegt. Darauf folgt die Länge des Chunks. Ein 3DS-Loader-Programm kann somit
ein Chunk mit unbekannter ID ignorieren und einfach zum nächsten Chunk überspringen.
Chunks sind in Hierarchie-Ebene eingeordnet. In dieser Hierarchie können die meisten
Chunks nur an bestimmten Stellen vorkommen. Einige wenige Chunks dagegen, wie etwa
Farbwerte, können an unterschiedlichen Positionen auftreten. Abbildung 47 zeigt den
vereinfachten Aufbau schematisch.
82
Abbildung 47 Schematische Darstellung des 3DS-Formats
Der MAIN3DS38 Chunk leitet die Hierachie in der Datei ein. Die Konfigurationen des 3D
Studio Desktops zum Zeitpunkt der Speicherung werden in den Chunks wie
EDIT_CONFIGS1 oder EDIT_VIEW_P1 festgehalten. Material-Informationen stehen global
in dem EDIT_MATERIAL Chunk und können von Polygon-Flächen individuell referenziert
werden. Mehr von Bedeutung für einen 3DS-Viewer ist der EDIT_OBJECT Chunk. Hier sind
sowohl die geometrischen Objekte als auch Objekte wie Licht und Kamera definiert.
Geometrische Objekte werden in 3DS als Dreieck-Gitter (Triangle-Mesh) unter dem Chunk
OBJ_TRIMESH gespeichert. Der Sub-Chunk TRI_VERTEXL beinhaltet die Anzahl der
Punkte sowie deren Koordinaten des Triangle-Mesh Objektes. Die Oberflächen des TriangleMesh Objektes werden in dem Sub-Chunk TRI_FACEL1 definiert, dabei wird jede PolygonFläche durch 3 Punkte (Index auf TRI_VERTEXL) beschrieben.
Die im EDIT_OBJECT Chunk definierten Objekte werden der Reihe nach hintereinander
gespeichert. Die Strukturen und Hierarchien der gesamten 3D-Szene sind im KEYF3DS
Chunk abgelegt. Dieser Chunk ist eigentlich für die Definition von sog. Keyframes
(Schlüsselpositionen von Objekten für Animationen) vorgesehen und bietet aber auch
Möglichkeiten, Objekt-Hierarchien zu definieren.
5.1.2 Fisheye View
In der Computergrafik versteht man unter einer klassischen Fisheye View eine wie in
Abbildung 48 (a) gezeigten Projektion. Diese Bilder entstehen entweder durch Aufnahme mit
einer entsprechenden Kamera und Linse oder durch Nachbearbeitung mit einem
Bildverarbeitungsprogramm.
38
Die Benennung der Chunks sind aus [Bou96] übernommen.
83
Abbildung 48 (a) Eine klassische Fisheye-Projektion. (b) Eine normale 2D-Karte Darstellung.
(c) Vergrößerung mit einer Fisheye-Linse
Solche Visualisierungseffekte werden auch als eine Art Vergrößerungslinse (Fisheye-Linse)
beispielsweise bei der Darstellung von Landkarten verwendet (siehe Abbildung 48 (b) (c)).
Im 3D-Bereich wird diese Vergrößerungstechnik (3D-Fisheye Zoom nach [Raab96]), u.a.
auch für die Visualisierung von medizinischen Daten eingesetzt.
Abbildung 49 Hervorhebung durch 3D-Fisheye Zoom bei der Visualisierung
von medizinischen Daten nach [Raab96]
Überträgt man diese Darstellungseffekt auf eine 3D-Karte mit plastischen Häuser-Modellen,
so entsteht eine interessante Perspektive. Zum Beispiel stehen Häuser auf einem
kugelförmigen Grundriss wie auf der Erdkugel. In der Mittel der Kugel werden die Objekte
vergrößert und mit mehr Details dargestellt als Objekte am Rande der Kugel. Eine mögliche
Projektion dieser Art ist in Abbildung 50 als Schnitt dargestellt.
84
z
x
(a)
(b)
Abbildung 50 (a) Objekte/Häuser auf der Ebene.
(b) Projektion der Objekten/Häuser auf eine Kugelfläche.
Die in der Literatur beschriebenen Verfahren von Fisheye-Projektionen beschränken sich
meist auf die Vergrößerung von 2D-Bilder. Auch das in [Raab96] vorgestellte Verfahren lässt
sich hierfür nur bedingt übertragen. Im Bereich der Kartografie existieren zwar eine Menge
von Verfahren zum Thema Kugel-Projektion39. Sie behandeln jedoch den Umgekehrten Fall,
nämlich die Projektion der Daten von der Kugeloberfläche (Erde) auf eine Ebene (Landkarte).
Ausserdem sind diese Daten zwei dimensional.
Die Grundlage für eine derartige Projektion kann aber auch aus einfachen geometrischen
Überlegungen hergeleitet werden. Ein Punkt p(x, y, z) auf der Kugelfläche mit dem Radius r
kann durch die Winkel θ und φ wie folgt ausgedrückt werden (siehe Abbildung 51).
x = r · sin θ · sin φ
y = r · sin θ · cos φ
z = r · cos θ
39
(1)
(0 ≤ θ ≤ π und 0 ≤ φ < 2π)
Vgl. [Sosna]
85
z
180°
p(r, θ, φ)
θ
r
x
270°
90°
φ
y
0°
Abbildung 51 Winkel θ und φ in Beziehung zu Punkt p
Um die Koordinaten von Punkt p’ auf der Kugelfläche nach (1) zu berechnen, müssen
zunächst die Winkel θ und φ bestimmt werden. Der Punkt p aus der original Darstellung wird
dabei auf die Ebene bei z=0 projiziert (siehe Abbildung 52). Ausgehend von dieser Position
kann θ und φ ermittelt werden.
z
p’
x’
x
z’
p
z
Ebene bei z=0
θ
Projizierte Stelle auf der
Ebene von p
h
r
r+z
x
Abbildung 52 Herleitung der Projektion von p nach p’ bei φ = 90° oder 270°
86
Abbildung 53 zeigt die geometrische Beziehung zwischen dem Punkt p und den Winkel θ und
φ. Daraus lässt sich die Formel (2) konstruieren.
z
p’
p
x
φ
z
y
h
x
θ
y
Abbildung 53 Bestimmung von θ und φ
θ = arctan( sqrt(x 2 + y2) / h)
φ = arctan( x / y )
(2)
Die Formel für die Koordinaten von Punkt p’(x’, y’, z’) auf der Kugeloberfläche kann aus (1)
hergeleitet werden.
x’ = (r+z) · sin θ · sin φ
y’ = (r+z) · sin θ · cos φ
z’ = ((r+z) · cos θ ) - h
(3)
Die Größe h und r können je nach gewünschtem Effekt (Krümmung und Verzerrung)
angepasst werden. Dabei soll h logischerweise kleiner als r gewählt werden.
5.2 Implementierung
Das für die Evaluierung zur Verfügung gestellte 3D-Stadt-Modell liegt in 3DS-Format vor. Es
ist daher nahe liegend, ein 3DS-Viewer zu implementieren. Es existieren aufgrund der weiten
Verbreitung dieses Formates einige Tutorials und Open-Source-Projekte im Internet. Das
Beispielprogramm aus [GamTut] wurde als Basis der Implementierung herangezogen. Wie
bereits im Kapitel 3.2 dargestellt, werden auch bei diesem Programm die SDL-Funktionen
durch entsprechende glICM-Funktionen ersetzt.
Wie bei der OpenGL-Programmierung üblich, teilt sich der Ablauf des Programms in zwei
Phasen. In der Initialisierungsphase wird die 3DS-Datei eingelesen. In der Darstellungsphase
87
wird die 3D-Szene in einer Schleife abhängig von der Kamera-Einstellung gerendert und
angezeigt.
Das Kernstück des Programms ist die 3DS-Import-Funktion Import3DS(). Hier wird die
3DS-Datei gemäß der Formatbeschreibung in Kapitel 5.1.1.2 importiert. In der Funktion
ProcessNextChunk() der Klasse CLoad3DS wird ein Chunk mit der Funktion ReadChunk()
eingelesen. Je nach Chunk-Typ werden weitere Funktionen wie ProcessNextObjectChunk()
oder ProcessNextMaterialChunk() rekuriv aufgerufen. Ist ein Objekt-Chunk gefunden, so
werden die Vertex-Koordinaten dieses Objektes mit den Funktionen wie ReadVertices()
oder ReadVertexIndices() eingelesen und in entsprechende Arrays abgespeichert.
In der Darstellungsphase wird die Koordinaten-Transformation für die Fisheye-Projektion
gemäß Formel (3) optional in der Funktion RenderScene() durchgeführt. Bei der
Berechnung der Winkel φ sind die Vorzeichen der x- und y-Koordinaten zu beachten und
Korrekturen ggf. vorzunehmen. Anschließend werden die Vertices mit der bekannten
Funktion glVertexf() an OpenGL weitergeleitet und somit die 3D-Szene gerendet.
5.3 Diskussion
Die mit dem Viewer eingelesenen und gerenderten POI-Objekte sind in Abbildung 54
dargestellt. Die Kenndaten der Objekten: (a) 437 Vertices, 336 Faces, 2 Objects. (b) 437
Vertices, 336 Faces, 2 Objects.
Abbildung 54 Ein gerendertes POI Objekt.
Ein Screenshot des Stadt-Modells ist in Abbildung 55 zu sehen. Die Kenndaten des Objektes:
48859 Vertices, 55887 Faces, 2435 Objects. Im direkten Vergleich zum POI-Objekt machen
sich die fehlenden Texturen des Stadt-Modells optisch bemerkbar.
Abbildung 55 Stadt-Modell als normale Perspektivendarstellung
88
Abbildung 56 zeigt das Stadt-Modell in Fisheye-View aus verschiedenen Blick-Richtungen.
Hier sind an manchen Stellen Sprünge zu erkennen. Das liegt daran, weil die langen Linien in
dem vorgelegten Modell nur durch einige wenige Stützpunkte modelliert wurden. Die später
für die Produktion vorgesehenen Modelle werden mit mehr Punkten ausgestattet sein.
Abbildung 56 Stadt-Modell in Fisheye-View aus verschiedenen Blick-Richtung
Als Vergleich wurde ein kommerzielles Straßen-Modell40 mit dem Viewer auf einem X86 PC
gerendert. Das Modell entspricht etwa den Szenen aus Computerspielen. Navigationssysteme
der nächsten Generation könnten eine solche Ansicht darstellen. Das Rendern dieser Bilder
nimmt aber erheblich mehr Zeit in Anspruch. Ohne Hardwarebeschleunigung ist eine solche
Anwendung daher nicht realisierbar.
40
Quelle [TurboS]
89
Abbildung 57 Darstellung einer künstlichen 3D-Straßen-Szene
90
6 Zusammenfassung und Ausblick
Ziel dieser Arbeit war anhand von ausgesuchten Themen die Möglichkeit der Integration von
3D-Grafik in einem Automotive-Embedded-System zu evaluieren. Dabei wurden in vier
Kapiteln unterschiedliche Untersuchungen durchgeführt.
In Kapitel 2 wurde eine mit 3D-Grafik animierten GUI zum Demonstrationszweck erörtert.
Im Grundlagenteil dieses Kapitels wurden zunächst die Möglichkeiten der Integration von
GUI mit 3D-Unterstützung in der HMI-Komponente sowie in das gesamte Automotive
Framework diskutiert. Ausserdem wurde auf einige Basiskonzepte der OpenGLProgrammierung eingegangen, die auch im Rahmen dieser Arbeit eingesetzt wurden.
Für die Realisierung der GUI wurde die Open-Source OpenGL-Implementierung Mesa auf
dem Target portiert. Für die OpenGL-Anbindung zum QNX-Window-System Photon wurde
eine eigene Entwicklung als Ersatz für das fehlende kommerzielle Produkt von QNX
prototypisch implementiert. Die Ergebnisse bezüglich der Funktionalität waren sehr
zufriedenstellend. Die Performance war dagegen, nicht zuletzt wegen der fehlenden
Hardware-Beschleunigung, weniger überzeugend. Anzumerken bleibt, dass PerformanceOptimierung kein Gegenstand dieser Arbeit ist und somit als Thema für ergänzende
Ausarbeitungen vergeben werden könnte. Für solche Ausarbeitungen würde sogar eine neue
Target-Hardware mit einem hardwarebeschleunigten 3D-Grafik-Chipsatz und mit der
OpenGL-Library ausgestattet zur Verfügung stehen. Dadurch lassen sich zusammen mit der
Multi-Layer-Technik interessante 3D-GUI und 3D-Anwendungen realisieren.
Im Kapitel 3 wurde ein Algorithmus mit LOD-Unterstützung für die Darstellung von 3DGeländen auf dem Target portiert. Das Ziel der Arbeit war, zu untersuchen, ob ein derartiges
Verfahren, das normalerweise für High-End-Simulationen konzipiert ist, auch auf die
verfügbare Target –Hardware mit beschränkten Ressourcen eingesetzt werden kann. Auch die
Portierbarkeit eines komplexeren OpenGL-Programm sollte in diesem Rahmen gezeigt
werden.
Der eingesetzte SOAR-Algorithmus ist ein weit verbreitetes Verfahren. Varianten davon
werden sowohl für Simulatoren in der Industrie als auch in Computerspielen verwendet. Im
Grundlagenteil dieses Kapitels wurde kurz auf dieses Verfahren eingegangen. Für die
Implementierung wurde ein auf dem Windows-Plattform lauffähiges OpenGL-Programm
portiert. Die Portierung erfolgte weitest gehend problemlos, es waren hauptsächlich CodeAnpassungen in der Verbindungsschicht zu QNX-Photon erforderlich. Die eingesetzte MesaLibrary auf der SH4-Target-Plattform hat sich als sehr ausgereift erwiesen.
Für die Evaluierung des Verfahrens wurden einige Tests durchgeführt. Die korrekte
Arbeitsweise des LOD-Algorithmus konnte durch Einstellung der Parametern gezeigt werden.
Weiterhin wurde gezeigt, dass Gittergröße von 512 x 512 Punkten derzeit die Obergrenze für
das Target-System darstellt.
Der implementierte Prototyp kann nur mit Geländen umgehen. Für ein richtiges 3DNavigationssystem müssen noch Strassen, Landschaftslayer und weitere Objekte, wie z.B.
Häuser oder gar Stadt-Modelle, die wiederum in unterschiedlichen Formaten vorliegen,
integriert werden. Für den produktiven Einsatz sind daher alternative Algorithmen zu
evaluieren.
91
OpenGL Java-Bindings wurden im Kapitel 4 behandelt. Dabei wurden drei Open-Source
OpenGL Java-Binding bezüglich Konzeption und Native-Anbindung eingehend untersucht.
Diese Produkte haben jeweils ihre Stärken und Schwächen. JOGL ist die ReferenzImplementierung von Sun und ist konzeptionell ein „Eins zu Eins“-Binding. Bezüglich der
Performance ist JOGL zurzeit das beste Open-Source Java-Binding auf dem Markt. Java3D
verfolgt dagegen ein High-Level-Ansatz, hier werden 3D-Objekte in einer virtuellen Welt
nach dem Szenen-Graph-Modell abstrahiert. Java3D ist nicht so performant wie JOGL, bietet
aber viel mehr High-Level-Funktionalitäten. SWTOGL schliesslich ist ein experimentelles
Java-Binding für das SWT-GUI-Framework.
Aufgrund der fehlenden AWT-Unterstützung bei der installierten Java Virtual Machine J9
wurde SWTOGL für die Portierung ausgewählt. Die Portierung konnte mit den in Kapitel 2
beschriebenen Funktionen für die Photon-Anbindung erfolgreich durchgeführt werden. Die
einwandfreie Funktionalität konnte durch anschließende Tests nachgewiesen werden. Die
Performance ist wie erwartet nicht zufriedenstellend. Hier sind sowohl auf Seite der Virtual
Machine (J9 ist noch nicht in der Version 1.4 verfügbar) als auch auf Seite der OpenGLAnbindung Verbesserungspotentiale gegeben.
In Kapitel 5 wurde ein 3D-Viewer zum Visualisieren von 3D-Modellen auf dem Target
portiert. Zusätzlich ist eine Fisheye-Projektion als Visualisierungseffekt implementiert.
Ebenso wurde ein kurzer Überblick über die gängigen 3D-Formate gegeben. Ausserdem
wurde die geometrische Herleitung der verwendeten Fisheye-Projektion dargestellt. Die
Ergebnisse der Implementierung wurden anschließend gezeigt und diskutiert.
Die vorliegende Arbeit zeigt, dass 3D-Grafik auf der getesteten Target-Hardware sowohl
durchführbar als auch für einfache Anwendungen nutzbar ist. Benutzeroberflächen mit 3DAnimationen sind schon selbst mit einer 3D-Library auf Softwarebasis ohne Einschränkungen
möglich. Die im Rahmen dieser Arbeit gewonnenen Erkenntnisse können als Basis bzw. als
Einstieg für die Entwicklung zukünftiger 3D-Applikationen im Automotive-EmbeddedBereich herangezogen werden.
92
Literatur
[Bou96]
3D-Studio File Format:
http://astronomy.swin.edu.au/~pbourke/dataformat/3ds/. Stand 01.09.2005
[Feh04]
J. Fehr: Who Gives a Blit; QNX Technical Articles;
http://www.qnx.com/developers/articles/article_291_2.html. Stand
01.09.2005
[GamTut]
Game Tutorials http://www.GameTutorials.com. Stand 01.09.2005
[GLUT]
The OpenGL Utility Toolkit:
http://www.opengl.org/resources/libraries/glut.html.
Stand 01.09.2005
[Hit02]
R. Hitchens: Java NIO; 2002; O’Reilly
[ICM04]
S.Freitag: Projektbericht InCarMultimedia; Wintersemester 03/04; FHDarmstadt
[J3DTut]
Java3D Tutorial:
http://java.sun.com/developer/onlineTraining/java3d/index.html. Stand
01.09.2005
[Java3D]
Java3D: https://java3d.dev.java.net/. Stand 01.09.2005
[JNITut]
JNI Tutorial: http://java.sun.com/docs/books/tutorials/native1.1/. Stand
01.09.2005
[JOGL]
JOGL: https://jogl.dev.java.net/. Stand 01.09.2005
[JCP]
Java Community Process: http://www.jcp.org. Stand 01.09.2005
[Lin01]
P. Lindstrom, V.Pascucci: Visualization of Large Terrains made Easy;
2001; Proceedings of IEEE Visualization 2001
[Mesa]
Mesa 3D Library: http://www.mesa3d.org. Stand 01.09.2005
[MOST01]
Multimedia and Control Networking Technology, Function Catalog MOST
Cooperation, V1.0; 2001
[Nis04]
A.Nischwitz, P. Haberäcker: Masterkurs Computergrafik und
Bildverarbeitung; 2004; Vieweg Verlag
[QNX3D]
QNX: 3D Graphics Technology Development Kit:
http://www.qnx.com/products/tech_dev_kits/3d_graphics.html. Stand
01.09.2005
[Photon04]
QNX: Photon microGUI for QNX Neutrino 6.3, Programmer’s Guide;
2004; QNX Software System Ltd
93
[Raab96]
Andreas Raab, Michael Rüger: D-ZOOM: Interactive Visualization of
Structures and Relations in Complex Graphics; infix-Verlag; 1996
[SDL]
Simple Directmedia Layer: http://www.libsdl.org. Stand 01.09.2005
[Shr04]
D. Schreiner, M. Woo, J. Neider, T. Davis: OpenGL Programming Guide,
4rd edition; 2004; Addison-Wesley
[SMLynx]
Lynx3DM+ DataBook: Silicon Motion, Inc.
ftp://ftp.siliconmotion.com.tw/databooks/SM722_Rev08.pdf. Stand
01.09.2005
[Sosna]
D. Sosner: Vorlesungskript Geoinformationssysteme I, Universität Leipzig;
http://www.informatik.uni-leipzig.de/~sosna/karten/netze.html. Stand
01.10.2005
[SWTOGL]
SWT Experimental OpenGL Plug-in:
http://www.eclipse.org/swt/opengl/opengl.html. Stand 08.10.2005
[TurboS]
Turbo Squid Inc:
http://www.turbosquid.com/FullPreview/Index.cfm/ID/168319/SID/250762/
blFP/1; Stand 01.09.2005
[Ulr00]
T. Ulrich: Continuous LOD Terrain Meshing Using Adaptive Quadtrees;
2000; Gamasutra;
http://www.gamasutra.com/features/20000228/ulrich_01.htm. Stand
01.09.2005
[Wat02]
A.Watt: 3D-Computergrafik; 2002; Pearson Studium
[Wie05]
J. Wietzke, M.T. Tran: Automotive Embedded System; 2005; Springer
Verlag
94
Anhang A: Technische Daten der Target-Hardware
Abbildung 58 Fotografische Abbildung der Target-Hardware
CPU
Grafik-Chip
RAM
Flash
ATAPI-Laufwerk
Diaplay
Schnittstelle
Erweiterungsslot
Netzwerk
Angeschlossene Geräte
Stromanschluss
Stromaufnahme
Betriebssystem
Hitachi SH4, 200 MHz
Silicon Motion Lynx3DM
32 MB
32 MB
20 GB 2,5“ Harddisk, DVD Laufwerk
Farbdisplay, Auflösung 320 x 96 Pixel
2 x Seriell, MOST und CAN
1 x PCI, 1 x PCMCIA
PCMCIA-Ethernet-Karte
GPS-Antenne, Tuner und Verstärker über MOST
12V
4A
QNX 6.3
Tabelle 7 Technische Daten der Target-Hardware. Quelle [ICM04].
95
Gateway-Box
Head-Unit
Abbildung 59 Schematische Darstellung der Hardware. Quelle [ICM04].
96
Selbständigkeitserklärung
Ich versichere, dass ich die vorliegende Masterarbeit selbständig verfasst und keine anderen
als die angegebenen Quellen und Hilfsmittel verwendet habe.
____________________________________
Tsz Kwong Yung
97