Augmented Reality mit korrekter Beleuchtung durch Light Probe
Transcrição
Augmented Reality mit korrekter Beleuchtung durch Light Probe
Augmented Reality mit korrekter Beleuchtung durch Light Probe Tracking Studienarbeit vorgelegt von Fabian Scheer Institut für Computervisualistik Arbeitsgruppe Computergraphik Betreuer: Dipl.-Inform. Thorsten Grosch Prüfer: Prof. Dr. Stefan Müller Koblenz, Februar 2006 INHALTSVERZEICHNIS 1 Inhaltsverzeichnis 1 Einführung 3 2 Grundlagen 4 2.1 2.2 2.3 2.4 2.5 2.6 2.7 ARToolkit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.1.1 Grundlagen ARToolkit . . . . . . . . . . . . . . . . . . . . 4 2.1.2 Das Tracking Verfahren im ARToolkit . . . . . . . . . . . 6 2.1.3 Begrenzungen des Trackingverfahrens . . . . . . . . . . . 8 2.1.4 Applikationsentwicklung . . . . . . . . . . . . . . . . . . . 9 Stereokamera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2.1 Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Umgebungsmapping . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.3.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.3.2 Blinn/Newell Methode . . . . . . . . . . . . . . . . . . . . 18 2.3.3 Sphärisches Mapping . . . . . . . . . . . . . . . . . . . . . 19 Multitexturing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.4.1 Multitexturing in OpenGL . . . . . . . . . . . . . . . . . 25 Beleuchtungsmodell . . . . . . . . . . . . . . . . . . . . . . . . . 27 2.5.1 OpenGL Beleuchtungsmodell . . . . . . . . . . . . . . . . 27 2.5.2 Verwendetes Beleuchtungsmodell . . . . . . . . . . . . . . 28 Schatten Volumen . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.6.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.6.2 Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Verfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 3 Implementierung 39 4 Ergebnisse 53 4.1 Bewertung und Performanz . . . . . . . . . . . . . . . . . . . . . 5 Fazit 5.1 Schlußfolgerungen 53 57 . . . . . . . . . . . . . . . . . . . . . . . . . . 1 57 INHALTSVERZEICHNIS 5.2 2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 57 1. Einführung 1 3 Einführung Die Verschmelzung von Realität und Virtualität hat in den letzten Jahren in einem erstaunlichen Maße zugenommen. Fiktive Wesen die sich früher nur in Science-Fiction oder Fantasy Romanen finden ließen, bevölkern nun die Bildschirme der Menschen zuhause. Gerade die Filmindustrie zeigt in beeindruckenden Maße, wie sehr die vorherige, intuitiv klare Trennung zwischem realem Schauspieler oder Gegenstand und virtueller Erschaffung von Charakteren und Welten verschwimmt. Leider ist der damit verbundene Aufwand, diese Verschmelzung in einem realistischen Maße zu betreiben, enorm hoch. Besonders die Beleuchtung der virtuellen Objekte spielt in diesem Zusammenhang eine bedeutende Rolle, denn wer kennt es nicht, daß ein virtuelles Objekt in einer realen Szene von einer anderen Richtung beleuchtet wird, als es die realen Begebenheiten erwarten lassen würden. An diesem Punkt setzt meine Arbeit an. Die Zielsetzung besteht darin, die Beleuchtung virtueller Objekte an die aktuelle Beleutungssituation der realen Szene in Echtzeit anzupaßen, um störende Faktoren, die die Immersion des Betrachters beeinträchtigen könnten, auszuschließen. Da viele gängige Systeme und Methoden daran ansetzen, Beleuchtungen virtueller Objekte in realen Szenen durch Nachbearbeitungsprozeße zu erreichen, wird diese Arbeit die Bestrebung verfolgen, dies in Echtzeit zu erreichen. Dabei wird die zentrale Problemstellung der realistischen Darstellung in zwei Kernprobleme unterteilt: Zum einen wird versucht die aktuelle Beleuchtungssituation in jedem Schritt der Darstellung zu erfaßen, um das Objekt korrekt zu beleuchten. Zum anderen soll das Objekt mit der realen Umgebung in dem Maße interagieren können, als das es einen Schatten auf die Objekte der realen Szene wirft. 3 2. Grundlagen 4 Abbildung 1: Marker 2 Grundlagen 2.1 ARToolkit Um die oben genannten Ziele zu erreichen wurde die Softwarebibliothek ARToolkit eingesetzt, die im folgenden vorgestellt wird. 2.1.1 Grundlagen ARToolkit ARToolkit ist eine Softwarebibliothek zur Entwicklung von Augmented Reality Applikationen. Die Bibliothek stellt Funktionalitäten zur Verfügung, um Bilder einer realen Szene, die mit einer Kamera aufgenommen wurden, mit virtueller Information anzureichern. Um dies zu gewährleisten werden Marker eingesetzt, aus deren Position und Ausrichtung zum Betrachter Daten gewonnen werden können, die es ermöglichen virtuelle Objekte perspektivisch korrekt in Abhängigkeit zum Marker darzustellen. Abbildung 1 zeigt exemplarisch solche Marker. Abbildung 2 zeigt ein solches System im Einsatz1 . Das um virtuelle Informationen angereicherte Bild kann dabei am Bildschirm oder über Brillen, die der Benutzer trägt dargestellt werden. In der Abbildung wird ein Head Mounted Display, ein sogenannter HMD, verwendet, der über winzige LCD Bildschirme in der Brille und über eine Kamera zur Aufnahme der Szenerie verfügt. Eines der Hauptprobleme bei Augmented Reality besteht darin, die Orientierung und die Position des Benutzer, d.h. der Kamera in Relation zur beobachteten Szenerie 1 Billinghurst: ARToolkit, http://www.hitl.washington.edu/artoolkit/, [Stand: 22.12.2005] 4 2.1 ARToolkit 5 Abbildung 2: Head Mounted Display, Vgl. Billinghurst: ARToolkit festzustellen, um virtuelle Objekte relativ zu dieser Position und Orientierung anzeigen zu können. ARToolkit nutzt Algorithmen aus dem Bereich Computer Vision, um die Position und Orientierung der realen Kamera in Relation zu den betrachteten Markern in Echtzeit zu errechnen. Die Features des ARToolkit bestehen im wesentlichen aus den Folgenden2 : • Ein einfaches Framework um Echtzeit AR Applikationen zu erstellen • Eine Multiplattformbibliothek (Windows, Linux, Mac OS X, SGI) • Überlagerung von Markern im Bild mit virtuellen 3D Objekten • Multiplattform Videobibliothek mit: – Unterstützung mehrerer Quellen (USB, Firewire, capture card) – Unterstützung mehrerer Formate (RGB/YUV420P, YUV) 2 Billinghurst: ARToolkit, http://www.hitl.washington.edu/artoolkit/documentation/features.htm, [Stand: 02.01.2006] 5 2.1 ARToolkit 6 Abbildung 3: Diagramm ARToolkit, Vgl. Billinghurst: ARToolkit – Unterstützung für ein Tracking mehrerer Kameras • schnelles und kostengünstiges 6D Marker Tracking • Erweiterbarer Marker Ansatz (eigene Marker können erstellt werden, doch die Performanz nimmt mit steigender Zahl an zu trackenden Markern ab) • einfacher Kalibrierungsmethode • einfacher Graphikbibliothek basierend auf GLUT • schnellem Rendering basierend auf OpenGL • 3D VRML Unterstützung • einfacher und modularer API, geschrieben in C • Unterstützung anderer Programmiersprachen wie Java oder Matlab 2.1.2 Das Tracking Verfahren im ARToolkit Die prinzipielle Technik des ARToolkit die Position und Orientierung der realen Kamera relativ zu einem beobachteten Marker zu berechnen, funktioniert 6 2.1 ARToolkit 7 folgendermaßen3 : 1. Zuerst wird das reale Videobild von einer Kamera aufgenommen und an den Rechner gesandt 2. Dann wird jedes aufgenommene Bild des Videostreams anhand eines einstellbaren Treshholds binarisiert, der sich mit variierenden Lichtverhältnissen auch ändern sollte, um ein stabiles Tracking zu gewährleisten 3. Anschließend wird das binarisierte Bild mit Methoden der Bildverarbeitung nach quadratischen Regionen bzw. Formen durchsucht. Da jedoch viele quadratische Regionen bzw. Formen im Bild auftreten können, werden alle gefundenen quadratischen Formen mit vortrainierten Mustern verglichen. Bei diesem Vergleich wird das Muster mit Markern verglichen, die sich der Benutzer erstellt hat. Diese Erstellung geschieht in einem Schritt der Vorverarbeitung, bei der der Benutzer seine erstellten Marker mit einer Kamera aufnimmt und sie in einem eigenen Format abgelegt. 4. Hat das ARToolkit nun eine Übereinstimmung eines detektierten Musters mit einem vortrainierten Marker gefunden, so wird die Position und Orientierung der realen Videokamera relativ zum detektierten Marker berechnet. Die Berechnung basiert im Prinzip darauf, daß die Originalgröße des Markers anhand der Trainings bekannt ist, sich somit ein Distanzwert abschätzen lässt, und die Orientierung anhand der Rotation des Markers im Bild wiedergewonnen werden kann. 5. Als Ergebnis des oben beschriebenen Prozeßes erhält man eine 3x4 Matrix, welche die Position und Orientierung der realen Videokamera relativ zu dem detektierten Marker angibt. Diese Matrix wird dann verwendet um die Position und Orientierung der virtuellen Kamera in OpenGL zu setzen. Da die virtuelle und die reale Kamera nun die gleiche Position aufweisen, 3 Billinghurst: ARToolkit, http://www.hitl.washington.edu/artoolkit/documentation/userarwork.htm, 02.01.2005] 7 [Stand: 2.1 ARToolkit 8 werden die virtuellen Objekte präzise auf dem detektierten Marker im Videobild gezeichnet. Um das Verfahren stabiler zu halten, sollte man möglichst keine symmetrischen Gebilde als Muster für die zu erkennenden Marker wählen, damit die Detektion der Marker, die Positions-, und die Orientierungsberechnung eindeutige Ergebnisse ermitteln können. Desweiteren sollte man sich der Tatsache bewusst sein, daß der Treshold nicht dynamisch berechnet wird und sich das Verfahren verschlechtert bzw. auch ganz ausfällt, wenn sich die Lichtverhältnisse aufgrund von Kameraschwenks stark ändern. Dies ist z.B. der Fall wenn man auf ein Fenster mit Gegenlicht schwenkt. Die von mir verwendete ARToolkit Version ARToolKitDirectShow2.52VRML besaß zumindest nicht diese Fähigkeit. Allerdings existiert mittlerweile eine Version, die mit einem dynamischen Verfahren zur Bestimmung des Tresholds arbeitet4 .Dabei handelt es sich um eine Weiterentwicklung der Originalsoftware. Die Originalsoftware kann man unter der folgenden Adresse beziehen: http://sourceforge.net/projects/artoolkit 2.1.3 Begrenzungen des Trackingverfahrens Das Trackingverfahren des ARToolkits funktioniert nur, wenn ein Marker im Bild zu detektieren ist. Die Objekte sind in ihrer Bewegung durch die Größe des Bildes eingeschränkt und sie werden gar nicht angezeigt, sobald die Marker verdeckt sind5 . Eine weitere Einschränkung der Markererkennungsfähigkeit des Systems ist die Abhängigkeit zwischen der Größe der Marker und der Entfernung des Betrachters zum Marker. 4 Schmalstieg: Studierstube, http://studierstube.org/handheld ar/download/ARToolKitPlus Autothresholding.avi, [Stand: 02.01.2006] 5 Billinghurst: ARToolkit, http://www.hitl.washington.edu/people/grof/SharedSpace/Download/ARToolKit2.33doc.pdf, [Stand: 02.01.2006] 8 2.1 ARToolkit 9 Marker Größe(cm) Reichweite(cm) 6,98 40,6 8,89 63,5 10,79 86,7 18,72 127 Tabelle1: TrackingReichweite Die Komplexität der zu erkennenden Muster beeinträchtigt die Erkennung und führt dazu, daß die Reichweite in der die Marker noch korrekt erkannt werden, proportional mit der Komplexität abnimmt. Auch die Orientierung des Benutzers zum Marker spielt bei der Erkennung eine wichtige Rolle, denn die Markererkennungsfähigkeit nimmt mit dem Betrachtungswinkel ab. Je flacher der Benutzer den Marker betrachtet, desto mehr an sichtbarer Fläche des Markers verschwindet und ein Tracking wird unmöglich6 . 2.1.4 Applikationsentwicklung Um nun ein Programm mit dem ARToolkit zu entwickeln sind folgende Schritte notwendig7 : 1. Initialisierung: Initialisierung des Video Grabbings“, Einlesen der zu er” kennenden, vortrainierten Markermuster und Einlesen der per Kalibrierung ermittelten Kameraparameter 2. MainLoop: (a) Grabbing eines Bildes des Videostreams (b) Erkennen der Markermuster im Videobild 6 Billinghurst: ARToolkit, http://www.hitl.washington.edu/people/grof/SharedSpace/Download/ARToolKit2.33doc.pdf, [Stand: 02.01.2006] 7 Billinghurst: ARToolkit, http://www.hitl.washington.edu/artoolkit/documentation/devprinciple.htm, 02.01.2006] 9 [Stand: 2.1 ARToolkit 10 (c) Berechnung der Kameratransformation relativ zu den erkannten Markern (d) Zeichnen der virtuellen Objekte auf bzw. relativ zu den erkannten Markern 3. Beenden: Schließen des Videostreams Im folgenden werden die oben genannten Schritte weiter erläutert: • Initialisierung: init() Methode: • Setzen der initialen Kameraparameter: if( arParamLoad(cparam_name, 1, &wparam) < 0 ) { printf(\"Camera parameter load error !!\"); exit(0); } • Transformation der Parameter für die entsprechende Bildgröße(hier 320x240): arParamChangeSize( &wparam, xsize, ysize, &cparam ); • Setzen der an die Bildgröße angepassten Kameraparameter: arInitCparam( &cparam ); • öffnen des Graphik Fensters mit den entsprechenden Parametern: argInit( &cparam, 2.0, 0, 0, 0, 0 ); • Einlesen der VRML Objekte aus den Dateien, objectnum gibt die dabei zu ladende Anzahl an: 10 2.1 ARToolkit 11 if( (object=read_VRMLdata(model_name, &objectnum)) == NULL ) exit(0); Main Loop: • Ein Bild von der Kamera grabben: camera.GrabFrame(); if((dataPtr = (ARUint8 *)camera.GetBuffer()) == NULL ) { arUtilSleep(2); return; } • Zeichnen des Videobildes: argDrawMode2D(); argDispImage(dataPtr, 0,0); • Erkennen der Marker im Videobild: if(arDetectMarker(dataPtr,thresh, &marker_info,&marker_num)<0){ cleanup(); exit(0); } Der Vertrauenswert eines Markers(cf) gibt die Zuverlässigkeit des Trackings an, und die Marker IDs werden entsprechend diesen Werten vergeben. Eine Modifikation des Originalprogramms merkt sich gleichzeitig den höchsten Vertrauenswert und die damit verbundene Marker ID, um später mit dem am besten getrackten Marker arbeiten zu können. Gleichzeitig wird mit der Funktion arGetT ransM at und arGetT rasM atCont die Matrix bestimmt, die die Transformation zwischen dem Marker und der Kamera beinhaltet. Dies ist davon abhängig ob das Objekt sichtbar ist oder nicht: 11 2.1 ARToolkit 12 for( i = 0; i < objectnum; i++ ) { k = -1; for( j = 0; j < marker_num; j++ ) { if(marker_info[j].cf > cf ) { cf=marker_info[j].cf; id=marker_info[j].id; } if( object[i].id == marker\_info[j].id ) { if( k == -1 ) k = j; else { if( marker_info[k].cf < marker_info[j].cf ) k = j; } } } if( k == -1 ) { object[i].visible = 0; continue; } if( object[i].visible == 0 ) { arGetTransMat(&marker_info[k], object[i].marker_center, object[i].marker_width, object[i].trans); } else{ arGetTransMatCont(&marker_info[k], object[i].trans, object[i].marker_center, object[i].marker_width, object[i].trans); } object[i].visible = 1; } 12 2.1 ARToolkit 13 Die Position und Orientierung der realen Kamera finden sich in der 3x4 Matrix object[i].trans. Nach diesen Schritten können endlich die virtuellen Objekte auf den Markern gezeichnet werden: draw(); argSwapBuffers(); Das Zeichnen der Obejekte unterteilt sich wiederum in drei Bearbeitungsschritte. Erstens muss das 3D Zeichnen initialisiert werden durch8 : argDrawMode3D(); argDraw3dCamera( 0, 0 ); glClearDepth( 1.0 ); glClear(GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); Anschließend wird die 3x4 Matrix in ein Array mit 16 Werten konvertiert, um diese Matrix in OpenGL laden zu können: argConvGlpara(patt_trans, gl_para); glMatrixMode(GL_MODELVIEW); glLoadMatrixd( gl_para ); Nun können beliebige Transformationen ausgeführt werden, um das zu zeichnende Objekt in die gewünschte Position und Ausrichtung zu bringen: glTranslatef( 0.0, 0.0, 25.0 ); glRotatef(45,0.0,1.0,0.0); glutSolidCube(50.0); 8 Billinghurst: ARToolkit, http://www.hitl.washington.edu/artoolkit/documentation/devstartup.htm, 02.01.2005] 13 [Stand: 2.2 Stereokamera 14 In einem letzten Schritt wird die Cleanup() Methode aufgerufen: argCleanup(); Dies war eine grobe Zusammenfassung zur Erstellung einer Applikation mit dem ARToolkit. Der Leser sollte sich jedoch bewusst sein, daß sich von Version zu Version des ARToolkits Änderungen in den oben beschriebenen Schritten ergeben können. Informationen zum ARToolkit und verschiedene Versionen zum Herunterladen lassen sich unter http://artoolkit.sourceforge.net/ finden. 2.2 Stereokamera Zur Ermittlung der Tiefeninformation der aufgenommenen Szene wird eine Stereokamera eingesetzt. Im Lieferumfang der Kamera ist ein Software Development Kit(SDK) enthalten, mit dem es möglich ist, sowohl normale Farbbilder mit einer Auflösung von 1024x768 bei 15fps zu grabben, als auch ein schwarz/weiß Tiefenbild mit einer Bildrate von 4fps. 2.2.1 Probleme Leider hat sich im Laufe der Entwicklung herausgestellt, daß ein rein optisches Verfahren nicht ausreicht, um verlässliche Tiefenwerte der Szene zu ermitteln. Das Hauptproblem hierbei war, daß für die Anwendung ein Tiefenbild der gesamten Szene benötigt wurde. Um dies zu erhalten, musste die Kamera allerdings so parametriert werden, daß am Ende nicht mehr validierte Tiefenwerte pro Pixel gewonnen werden konnten, und das Bild somit fehlerhafte Tiefeninformationen lieferte, wie in Abbildung 4 besonders am Rand und an den Objektkanten ersichtlicht ist. Parametrierte man die Kamera dermaßen, daß man nur validierte Tiefenwerte erhalten hat, so erhielt man nicht mehr für jeden Pixel des Bildes einen Tiefenwert, was allerdings von der Anwendung gefordert wurde. Desweiteren war die Begrenzung der Darstellung der Tiefenbilder auf 255 Grauwerte ein Problem. Somit wurden die Abstände eng beieinander liegender Objekte, die sich in dem Tiefenbild lediglich durch ein paar wenige Grauwerte 14 2.2 Stereokamera 15 Abbildung 4: Szene mit Tiefenbild unterschieden, in der realen Szene jedoch einige Zentimeter weit auseinanderlagen, verfälscht und konnten nicht mehr maßstabsgetreu in den OpenGL Tiefenbuffer geschrieben werden. Um dies zu verbessern musste man die Parametrierung der Kamera ändern, was allerdings wieder zu oben genanntem Problem führte, und die Unzulänglichkeit der Auflösung der Tiefenwerte auch nicht zufriedenstellend beseitigte. Auch die Anpassung der OpenGL Tiefenwerte an die Tiefenwerte der Stereokamera bereitete Probleme. Um die beiden Systeme aneinander anzupassen, wurden Referenzpunkte in beiden Systemen gewählt, die den Mittelpunkten der Markern entsprachen. Leider unterlagen die Tiefenwerte der Referenzpunkte der Stereokamera großen Schwankungen aufgrund von Bildrauschen und fehlerhaft ermittelten Tiefenwerten, wodurch sich bei starken Schwankungen erhebliche Fehler der Tiefenwerte ergaben und somit die Szene nicht realistisch wiedergegeben werden konnte. In den Kapiteln 4 und 5 wird auf diese Sachverhalte genauer eingegangen und es wird eine Möglichkeit vorgestellt, um die Tiefenbilder in ihrer Qualität zu verbessern. 15 2.3 Umgebungsmapping 2.3 16 Umgebungsmapping Um die Objekte realistisch zu beleuchten, wurden Umgebungsmaps eingesetzt, die im folgenden näher beschrieben werden. 2.3.1 Grundlagen Umgebungsmapping, auch Reflexionsmapping genannt, vereinfacht das Rendern von verspiegelten Objekten, die ihre Umgebung reflektieren. Die Umgebungsmapping Methoden bieten besonders bei der Darstellung gekrümmter Flächen eine gute Möglichkeit, die Reflexionen der Umgebung des Objekts anzunähern. Die verschiedenen Verfahren des Umgebungs-Mappings beginnen alle mit der Bestimmung des Blickstrahls, der vom Betrachter zum Objekt führt. Dieser Blickstrahl wird unter Berücksichtigung der Normalen des Oberflächenpunkts reflektiert. Die Richtung des dadurch gewonnenen Reflexionsvektors fungiert als Index für die Umgebungsmap (s. Abb.5). Die Näherung der Reflexionen des Umgebungsmapping setzen voraus, das sich die Objekte der Umgebung weit entfernt vom darzustellenden Objekt befinden. Dies ist der Fall wenn sich z.B. ein kleines Objekt in einem großen Raum befindet. Eine weitere Vorraussetzung ist, daß sich der Reflektor nicht selbst reflektiert, d.h. das Objekt darf nicht konkav sein9 , denn dies ist mit einer Umgebungsmap nicht zu erfassen. Es gibt zwei Arten eine Umgebungsmap zu erstellen: 1. Syntethisch: Die Umgebungsmap wird virtuell erzeugt. Zum einen kann man eine reflektierende Kugel in seine virtuelle Szene setzen und diese mit der Ray Tracing Methode beleuchten. Somit hat man die Beleuchtungssituation der gesamten Szene in der Kugel fesgehalten und kann diese gerenderte Kugel als Umgebungsmap für sphärisches Umgebungsmapping verwenden. Man kann sich aus Texturen für kubisches Umgebungsmapping auch per Image Warping eine Umgebungsmap für sphärisches Umgebungsmapping generieren10 . Will man hingegen kubisches Umgebungsmapping 9 Vgl. Watt, 2002: 275 Generating 10 Blythe: a Sphere 16 Map for Specular Reflection, 2.3 Umgebungsmapping 17 abgetasteter Pixel Betrachter n r b Umgebungs Map Die Projektionsfunktion erzeugt aus dem Reflexionsvektor (x,y,z) Texturkoordniaten (u,v) Reflektierende Oberfläche Abbildung 5: Prinzip der Umgebungsmap verwenden, das Texel über das Verfolgen des Reflektionsvektors auf sechs Seiten eines Würfels indiziert, so muß man die Szene aus sechs Blickrichtungen rendern, um die Texturen für die sechs Seiten des Würfels zu erhalten. 2. Real : Hierbei wird die Umgebungsmap direkt aus einer realen Szene gewonnen, indem man eine reflektierende Kugel fotografiert. Mathemathisch betrachtet gilt hierbei die Anforderung, daß die Kamera über eine unendliche Brennweite verfügen sollte und sich auch unendlich weit vom Objekt entfernt befinden sollte11 , um möglichst die gesamte Beleuchtungssituation der Szene zu erfaßen. Da sich die Arbeit mit der augmentierten Realität auseinandersetzt und sich somit an realen Begebenheiten und nicht an synthetisch erzeugten orientiert, wird hier im weiteren die Methode der Erzeugung der realen Umgebungsmap verwendet. http://www.opengl.org/resources/tutorials/sig99/advanced99/notes/node179.html, [Stand: 16.12.2005] 11 Vgl. Shreiner [et al.], 2002: 428 17 2.3 Umgebungsmapping 18 Abbildung 6: Kugel Koordinatenverlauf, Vgl. Möller/Haines, 2002:155 2.3.2 Blinn/Newell Methode 1976 entwickelten Blinn und Newell12 den ersten Umgebungsmapping Algorithmus. In ihrem Algorithmus berechnen sie für jeden Pixel des Objekts den Reflexionsvektor, ausgehend vom Blickstrahl des Betrachters zu diesem Punkt. Der erhaltene Reflexionsvektor wird dann in sphärische Koordinaten (ρ, φ) transformiert. Das Verfahren wird auch manchmal als Längen/Breiten-Mapping bezeichnet, denn ρ, variierend zwischen 0 und 2π, stellt bei der sphärischen Projektion den Längengrad dar und φ, variierend zwischen 0 und π, den Breitengrad. ρ und φ berechnen sich dabei folgendermaßen: ρ = arccos(−rz ) (1) φ = atan2(ry , rx ) (2) Der Reflexionsvektor r in den Gleichungen berechnet sich durch: r = b − 2(n · b)n (3) b ist dabei der Blickstrahl und n ist die Oberflächennormale in dem durch den Blickstrahl festgelegten Punkt auf der Oberfläche. Um nun gültige (u, v) Texturkoordinaten zu erhalten, werden die sphärischen Koordinaten (ρ, φ) in den Wertebereich [0, 1) transformiert. Aufgrund der Transformation in spärische Koordinaten kann man sich die Umgebungsmap auch als Bild einer aufgefalteten Kugel vorstellen 12 Vgl. 13 Vgl. 13 (s. Abb.6). Es wird klar, warum diese Projektion manchmal Möller/Haines, 2002:154 Möller/Haines, 2002:155 18 2.3 Umgebungsmapping 19 auch Längen/Breiten-Mapping genannt wird, denn die beiden Texturkoordinaten (u, v) entsprechen dem Längen/Breitengrad der Kugel. Es folgen nun einige charakteristische Punkte der Projektion14 : Name Koordinate Winkel N(Nordpol) (0,0,1) ρ = π,φ ist nicht definiert S(Südpol) (0,0,-1) ρ = 0,φ ist nicht definiert M (1,0,0) ρ = π/2,φ = 0 O (0,1,0) ρ = π/2,φ = π/2 P (-1,0,0) ρ = π/2,φ = π Q (0,-1,0) ρ = π/2,φ = 3π/2 Tabelle2: Wertebereichstabelle Leider weist diese Methode einige Schwachpunkte auf: Zum einen befindet sich bei φ = 0 ein Rand und desweiteren konvergiert die Umgebungsmap an den Polen. Aufgrund des Wertebereichs der (u, v) Koordinaten von [0, 1) ist φ an den Polen nicht bestimmt. Daher kann es an den Polen zu Verzerrungsproblemen kommen, da die vertikalen Nahtstellen der Map nicht übereinstimmen15 . Ein anderer Schwachpunkt ist, daß bei der Interpolation zwischen zwei Texturkoordinaten Probleme auftreten können. Geht man z.B. von einer zu rendernden Linie aus, die durch die Punkte P und Q definiert ist, wobei im Punkt P die u Koordinate bei 0.97 liegt und im Punkt Q bei 0.02. Dann wird ein Fehler (wir vernachläßigen in diesem Bespiel die Nahtstellen der Textur) auftreten, weil sich die Interpolation von 0.97 abwärts hin zu 0.02 bewegen würde ( 0.96, 0.95 ..... 0,01, 0,02)16 . 2.3.3 Sphärisches Mapping Sphärisches Mapping war das erste Umgebungsmapping, das von kommerzieller Graphikhardware unterstützt wurde17 . Der normalisierte Blickvektor vom Be14 Vgl. Möller/Haines, Möller/Haines, 16 Vgl. Möller/Haines, 17 Vgl. Möller/Haines, 15 Vgl. 2002:155 2002:156 2002:156 2002:158 19 2.3 Umgebungsmapping 20 trachter zum Vertex auf der Oberfläche des Objekts wird als b bezeichnet. Die Berechnung findet im Kamerakoordinatensystem statt, wobei sich die Kamera im Ursprung an der Stelle des Betrachters befindet, und b die Position des Vertex auf der Oberfläche im Kamerakoordinatensystem darstellt. Die Normale n wird ebenfalls ins Kamerakoordinatensystem durch Multiplikation mit der transponierten inversen Modelviewmatrix gebracht, und anschließend normalisiert. Um den Reflektionsvektor zu berechnen, wird die folgende Gleichung verwendet: r = b − 2(n · b)n (4) Da b und n normalisiert sind, erhält man mit dem Refklektionsvektor r ebenfalls einen normalisierten Vektor und kann ihn sich als 3D Punkt auf der Einheitskugel vorstellen. Der Normalenvektor n ist genau der halbierte Winkel zwischen Reflektionsvektor und Blickstrahl. Letzterer entspricht (0,0,1) im lokalen Koordinatensystem der Einheitskugel. Also ergibt sich der Normalenvektor einfach aus der Summe des Blickstrahls und des Reflektionsvektors: (rx , ry , rz ) + (0, 0, 1) = (rx , ry , rz + 1) (5) Diesen Vektor gilt es anschließend noch zu normalisieren: m= q n= rx2 + ry2 + (rz + 1)2 rx ry rz + 1 , , m m m (6) (7) Die Koordinaten dieser Einheitsnormalen entsprechen dem Punkt h der Normalen auf der Einheitskugel (Vgl. Abb.7). Nun verfügen wir also über den Reflektionsvektor, der uns unsere Umgebungsmap indizieren kann, müssen ihn aber noch in den 2D Raum transfomieren, damit wir korrekte Koordinaten für die Umgebungsmap erhalten. Die Reduktion auf den 2D Raum entspricht einer Projektion auf die Einheitskugel in der rz = 0 Ebene: m= q rx2 + ry2 + (rz + 1)2 u= rx + 0, 5 2m 20 (8) (9) 2.3 Umgebungsmapping 21 y h_y v n r h Zum Auge Ursprung rad ius z =1 Unterer Rand der spärischen Map Abbildung 7: Schaubild Umgebungsmap, Vgl. Möller/Haines, 2002:160 v= ry + 0, 5 2m (10) Da r normalisiert ist, wird auch rx und ry normalisiert sein und daher im Wertebereich [−1, 1] liegen. Um korrekte Texturkoordinaten zu erhalten, benötigen wir aber Koordinaten im Bereich [0, 1]. Die Transformation des Wertebereichs wird in Gleichung (9) und (10) durch die Skalierung von 0,5 und durch die Addition von 0,5 erreicht. Sphärisches Mapping bietet im Gegensatz zu dem Verfahren von Blinn und Newell diverse Vorteile: Zum einen existiert keine Texturnaht, über die man interpolieren müßte und es liegt auch nur eine Singularität an den Rändern der Map vor, die aber wenig Probleme bereitet, da sich alle gültigen Texturbereiche im Innern des Kreises der Umgebungsmap befinden, und so die Ränder nicht überschritten werden18 . Leider hat das Verfahren auch einige Nachteile: 1. Der Abstand zwischen zwei Punkten der spärischen Map ist nicht linear. Wie in der Methode von Blinn und Newell ist die lineare Interpolation auf der Textur lediglich eine Approximation und Fehler treten umso häufiger auf, je mehr man sich den Rändern der Map nähert19 . 2. Die Spärische Map ist nur gültig für eine bestimmte Blickrichtung, d.h. wenn der Betrachter seine Position verändert und die Map nicht aktua18 Vgl. 19 Vgl. Möller/Haines, 2002:160 Möller/Haines, 2002:161 21 2.4 Multitexturing 22 lisiert wird, so wird das virtuelle Objekt unter der neuen Blickrichtung dennoch so gerendert, als würde der Betrachter an seiner unsprünglichen Position stehen. Dies kann, da sich der Blickstrahl ändert, zu visuellen Artefakten führen. Es ist zwar möglich die Texturkoordinaten für jede neue Blickrichtung in jedem Frame berechnen zu lassen, jedoch kann auch dies zu visuellen Artefakten führen, weil manche Teile der spärischen Map aufgrund der neuen Blickrichtung vergrößert erscheinen, und die Singularitäten an den Rändern auch zu einem Problem werden20 . Sphärisches Umgebungsmapping ist also von der Blickrichtung abhängig. Um speziell dieses Problem zu umgehen, wird in dem hier vorgestellten Verfahren, in jedem Frame eine sphärische Umgebungsmap erstellt, so daß die oben genannten Probleme vermieden werden. 2.4 Multitexturing Unter einfachem Texturmapping versteht man die Abbildung einer zweidimensionalen Textur auf ein dreidimensionales Objekt. Multitexturing hingegen erlaubt die Kombination mehrerer Texturen um ein dreidimensionales Objekt zu texturieren. Man unterscheidet zwischen Single-, und Multipass Multitexturing. Singlepass Multitexturing verwendet die Graphikhardware um mehrere Tex” ture Units“ in einem einzigen Renderingschritt darzustellen. Als Textur Unit“ ” bezeichnet man hierbei eine Textur in der Pipeline“ des Multitexturings (Vgl. ” Abb.8). Leider unterstützt erst die modernere Graphikhardware Multitexturing. Ob die eigene Graphikhardware Multitexturing unterstützt kann man durch einen einfachen OpenGL Befehl herausfinden: const GLubyte *glGetString(GLenum name); Als Parameter name ist dabei GL EXT EN SION S anzugeben. Lässt sich GL ARB M ultitexture im Rückgabestring finden, so wird Multitexturing unterstützt. Wie viele einzelne Textureinheiten man dabei verwenden kann, hängt von der jeweiligen Graphikhardware ab und kann mit: 20 Vgl. Möller/Haines, 2002:161 22 2.4 Multitexturing 23 glGetIntegerv (GL_MAX_TEXTURE_UNITS_ARB, &num) herausgefunden werden. Multipass Multitexturing hingegen zeichnet ein 3D Objekt n-mal an der gleichen Stelle, wobei n-mal eine andere Textureinheit verwendet wird. Die n Textureinheiten werden über Blending vermischt. Diese Arbeit wird im folgenden das Singlepass Multitexturing verwenden, um sich der Vorteile der Graphikhardware in Bezug auf die Geschwindigkeit zu bedienen. Wie oben bereits erwähnt, verläuft das Multitexturing in einer eigenen Pipeline“ (s. Abb.8). In jeder Textur Unit verbinden sich die Farbwerte des ” aktuell darzustellenden Fragments mit den Farbwerten der bereits in den vorherigen Textur Units vermischten Fragmente. Dabei kann man wie gewohnt die Texturingsumgebungen GL REP LACE, GL M ODU LAT E, GL DECAL, GL BLEN D, GL ADD und GL COM BIN E verwenden. Singlepass Multi- Abbildung 8: Multitexturing, Vgl. Shreiner [et al.], 2002:432 texturing erspart dem Benutzer somit nicht nur zusätzliche Renderingschritte, sondern es lassen sich auch sehr komplexe Shading Models“ darstellen. Zum ” Beispiel lässt sich ein Beleuchtungsmodell, welches die Gleichung (A·B)+(C ·D) verwendet, mit einfachem Texturemapping nicht darstellen. Ein Multipass Algorithmus könnte A·B in zwei Renderingschritten kombinieren und C im nächsten Schritt hinzufügen. Jedoch würde nicht C · D hinzugefügt, denn es kann jeweils 23 2.4 Multitexturing 24 nur eine Farbe im Color Buffer“ gespeichert werden. Mit Multitexturing hin” gegen, könnte der erste Renderschritt A · B berechnen, der zweite könnte C · D berechnen und das Ergebnis zu A · B im Color Buffer addieren21 . Somit lassen sich mit Multitexturing fortgeschrittene Renderingtechniken wie z.B. Beleuchtungseffekte, Decals“, Compositing“ und detailierte Texturen im” ” plementieren22 . 21 Vgl. 22 Vgl. Möller/Haines, 2002:146 f. Shreiner [et al.], 2002:432 24 2.4 2.4.1 Multitexturing 25 Multitexturing in OpenGL Multitexturing ist in OpenGL eine Erweiterung, die vom Architecture Review Board (ARB) spezifiziert wird. Sie wird ferner nicht von jeder Graphikkarte unterstützt (s.o.). Wird Microsoft Win32 verwendet, so müssen um Multitexturing anzuwenden, erst einmal die Zeiger auf die Extensionfunktionen mit wglGetProcAddress() gesetzt werden: PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL; PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL; PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL; glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress(glMultiTexCoord2fARB); glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress(glActiveTextureARB); glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress(glClientActiveTextureARB); Es ist ferner die Header Datei glext.h“ einzubinden, und es müssen Texturko” ordinaten vergeben werden. Mit glMultiTexCoord{1234}{sifd}(GLenum texUnit, TYPE coords) kann man jedem Vertex mehrere Texturkoordinaten zuweisen. Verwendet man stattdessen glTexCoord*(...) wie beim normalen Texturmapping, entspricht dies dem Aufruf23 : MultitexCoord*(GL_TEXTURE0,....) Anschliessend defininert man sich die anzuzeigenden Texturen: glGenTextures(2, texNames); glBindTexture(GL_TEXTURE_2D, texNames[0]); 23 Vgl.Shreiner [et al.], 2002:436 25 2.4 Multitexturing 26 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXTRES, TEXTRES, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture1); //------------------------------------------------------------glBindTexture(GL_TEXTURE_2D, texNames[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXTRES, TEXTRES, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture2); Die Texturen sind nun erzeugt und stehen zur Anwendung bereit: glActiveTextureARB(GL_TEXTURE0_ARB); glEnable(G_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texNames[0]); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); /-----------------------------------------------------------glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texNames[1]); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); Obiges Texturmapping addiert die Farbwerte der einzelnen Texturen. glActiveT extureARB(GLenumtexU nit) gibt dabei die im Moment zu aktivierende Textur an. Jetzt kann das zu texturierende Objekt mit der Multitexturingtextur gezeichnet werden. 26 2.5 2.5 Beleuchtungsmodell 27 Beleuchtungsmodell Im Folgenden wird das klassische OpenGL Beleuchtungsmodell erklärt und dem verwendeten Beleuchtungsmodell gegenübergestellt. 2.5.1 OpenGL Beleuchtungsmodell Das Beleuchtungsmodell, das in OpenGL verwendet wird, drückt sich durch folgende Beleuchtungsformel aus: L = M e + M a · La + Lq X (Ma · La,i + Md · cos ϕi · Ld,i + Ms · cosn ψi · Ls,i ) (11) i=1 Diese Formel stellt allerdings keine physikalisch korrekte Form der Beleuchtung Abbildung 9: Beleuchtung, Vgl.Müller: Licht und Materialien dar, sondern kann lediglich als eine Annäherung an die menschliche Wahrnehmung des Lichtes betrachtet werden. In der Formel vereinigen sich der Emissionsterm des Objektes Me , der ambiente Term, der sowohl global Ma · La als auch pro Lichtquelle Ma · La,i in die Berechnung mit eingeht, der diffuse Term Md · cos ϕi · Ld,i und der spiegelnde Term Ms · cosn ψi · Ls,i . Damit stellt sich das Licht in OpenGL als Summe aus den oben genannten Komponenten dar, wobei der ambiente Term, der diffuse Term und der spiegelnde Term über alle Lichtquellen aufsummiert werden und dieses Ergebnis der Summe aus emissions und globalem ambienten Term hinzugefügt werden. Die einzelnen Komponenten sind in Abb.9 und 12 nochmals zur graphischen Veranschaulichung dargestellt 24 . 24 Müller: Licht und Materialien, http://geri.uni-koblenz.de/ws0203/cg2folien/9 lichtmat 6.pdf, [Stand: 04.01.2006] 27 2.5 Beleuchtungsmodell 2.5.2 28 Verwendetes Beleuchtungsmodell Abbildung 10: Aufbau Während bei OpenGL die Eckpunkte anhand obiger Formel, unter der Voraussetzung, daß die Lichtquellen und Materialien der Objekte entsprechend gesetzt wurden, beleuchtet werden, verfolgt diese Arbeit den Ansatz, die Beleuchtung aus einer realen Szene abzuleiten und die Objekte letztlich durch Texturierung zu beleuchten. Es sollen Texturen aus der realen Szene gewonnen werden, welche die Beleuchtungssituation der Szene in der Textur speichern und durch Kombination der Texturen ein virtuelles Objekt so ”beleuchten”bzw. texturieren, als wäre es ein Bestandteil der realen Szene. Um dies zu erreichen, wurden zwei Kugeln eingesetzt, die die Beleuchtungssituation des Raumes einfangen. Den Aufbau der Kugeln mit den entsprechenden Markern um die Kugeln zu tracken sieht man in Abbildung 10. Mit einem weiter unten vorgestellten Verfahren wurden diese beide Kugeln getrackt und aus dem Videobild ausgeschnitten, um sie als Textur für die virtuellen Objekte zu verwenden. Die ausgeschnittenen Texturen sieht man in Abbildung 11. Aus der reflektierenden Kugel wurde ferner eine Textur gewonnen, die die Glanzlichter der Szene darstellen sollte. Dazu wurde die Textur der reflektierenden Kugel binarisiert, wobei der Schwellschwert 28 2.5 Beleuchtungsmodell 29 Abbildung 11: Diffuse und reflektierende Kugel einstellbar ist, so dass sich die Menge und Größe der Glanzlichter eines Objektes parametrieren lassen, entsprechend der Glanzzahl in OpenGL. Mit der Textur des 3D Objekts ergeben sich so 4 Texturen, mit denen im weiteren gearbeitet wird. Wie man die einzelnen Texturen letztlich miteinander kombiniert, ist dem Benutzer bzw. dem gewünschten Ergebnis überlassen, doch für eine realistische Darstellung machen nicht alle Kombinationen Sinn. Um das Beleuchtungsmodell von OpenGL annähernd abzubilden, kann man beispielsweise alle 4 Texturen gewichtet aufsummieren, wobei die Gewichte der jeweiligen Textur parametrierbar sind, ähnlich der Festlegung der Parameter der Lichtquellen und der Materialien, die in OpenGL vorzunehmen sind. Abbildung 12 und 13 veranschaulichen dieses Prinzip. Die erste Kugel stellt dabei die Textur des Objekts dar. Die zweite Kugel hält den diffusen Anteil des Lichts in der Szene fest, ähnlich dem diffusen Anteil jeder Lichtquelle und des Materials in OpenGL. Durch die dritte Abbildung 12: Texturierungsverfahren Kugel wird die komplette Szene eingefangen, da diese Kugel verspiegelt ist. In Analogie zu OpenGL, könnte man diese Kugel als den ambienten Anteil sowie den spekularen Anteil betrachten. Um aber die Glanzlichter unabhängig von der verspiegelten Kugeln parametrieren zu können, wird die vierte Kugel wie oben bereits beschrieben aus der dritten Kugel gewonnen. Somit erhält man ei29 2.5 Beleuchtungsmodell 30 Abbildung 13: Beleuchtungstexturen ne ungefähre Analogie zu OpenGL, wobei in diesem Modell weit mehr ermittelt bzw. festgehalten wird, als es mit der Standardbeleuchtung in OpenGL möglich ist. Durch Verwendung einer verspiegelten Kugel kann man Objekte verspiegelt darstellen, was in OpenGL standardmäßig nicht möglich ist. Ferner erhält man in einem Pixel der reflektierenden Kugel alle Lichtinformation der Szene, d.h. es wird auch indirektes Licht wiedergegeben. Drückt man den Weg, den das Licht von der Lichtquelle zum Auge nehmen kann durch einen Formalismus wie LA aus, so kann in OpenGL maximal eine diffuse Fläche dazwischenliegen, also LDA. Die reflektierende Kugel gibt das komplette Licht der Szene, daß in diesem Pixel eintrifft wieder. Damit sind also auch Wege des Lichtes wie LD*A, oder Wege mit dazwischenliegenden spiegelnden Objekten möglich, also LS*D*A, wobei S* angibt, daß beliebig viele spiegelnde Flächen dazwischenliegen können. Es wird also jeder mögliche Weg, den das Licht gehen kann, in der reflektierenden Kugel erfasst. In rein virtuellen Umgebungen stellt gerade diese Erfassung des Lichts ein großes Problem dar, daß nur über sehr rechenaufwändige Verfahren wie Radiosity oder Ray Tracing gelöst werden kann und für alle möglichen Wege des Lichts noch nicht in Echtzeit berechnet werden kann. In 30 2.5 Beleuchtungsmodell 31 der erweiterten Realität kann man hingegen den Vorteil nutzen, das man über die verspiegelte, sowie die diffuse Kugel bereits alle notwendigen Informationen gewinnen kann, um die virtuellen Objekte so zu beleuchten, als würden sie sich in der realen Szene befinden. Man setzt die beiden Kugeln quasi als Lichtmesser ein, um die realistische Darstellung der Beleuchtung nicht mehr aufwendig simulieren zu müssen, sondern aus der realen Szenerie abzuleiten. Die realistische Darstellung von Licht muss dabei die Beleuchtungsgleichung erfüllen: Z ρ (dAe , d$i , d$0 ) · Li (dAe , d$0 ) · cos θi · d$i L0 (dAe , d$0 ) = Le (dAe , d$0 )+ 2π (12) Diese Gleichung25 wird durch verschiedene Verfahren wie Ray Tracing und Radiosity simuliert angenähert, um eine realistische Darstellung der Objekte zu ermöglichen. Durch erfassen der beiden Kugeln im Bild, welche die aktuelle Beleuchtungssituation des Raumes einfangen, erhält man L0 (dAe , d$0 ) (13) Um dieses Ergebnis wieder in die einzelnen Bestandteile der obigen Formeln zu zerlegen, bzw. die einzelnen Bestandteile anzunähern, um Materialeigenschaften von Objekten modellieren zu können, wurden 2 Kugeln eingesetzt, wobei es durchaus auch möglich gewesen wäre, die Textur der diffusen Kugel rechnerisch aus der reflektierenden Kugel zu gewinnen, wie Ramamoorthi und Hanrahan in ihrer Arbeit zeigen26 . Leider ist mit dem Verfahren keine realistische Darstellung transparenter Objekte möglich, da Lichtbrechungen innerhalb des virtuellen Objektes nicht berücksichtigt werden. Man könnte diese Brechungen zwar innerhalb des Objektes berechnen, müsste aber darauf verzichten, daß dieses Licht wieder in der realen Szene dargestellt wird. Um die verschiedenen Texturen zu kombinieren, werden Techniken des Multitexturing angewendet, die 25 Müller: Photorealistische Computergraphik, http://www.uni-koblenz.de/FB4/Institutes/ICV/AGMueller/Teaching/SS05/PCG, [Stand: 04.01.2006] 26 Ramamoorthi, Hanrahan: An Efficient Representation for Irradiance Environment Maps, http://graphics.stanford.edu/papers/envmap/, [Stand: 10.12.2005] 31 2.5 Beleuchtungsmodell 32 Abbildung 14: Konkrete Beleuchtungen oben bereits erwähnt wurden. Abbildung 13 zeigt die oben beschriebenen ermittelten Texturen. Abbildung 14 demonstriert die Anwendung der Texturen zur Beleuchtung. In dem linken Bild wurden dabei folgende Parameter gesetzt: OriginalTextur: 1.0 Diffuse Kugel: 0.0 Spiegelnde Kugel: 0.3 Glanzeffekt: 0.2 Schwellwert: 235 Bei der Darstellung des rechten Bildes wurden hingegen diese Parameter verwendet: OriginalTextur: 0.9 Diffuse Kugel: 0.3 Spiegelnde Kugel: 0.0 Glanzeffekt: 0.5 Schwellwert: 215 32 2.6 2.6 Schatten Volumen 33 Schatten Volumen Im folgenden Abschnitt wird das verwendete Schattierungsverfahren vorgestellt. Es soll zur Schattierung der realen Szene eingesetzt werden. 2.6.1 Grundlagen Schattenvolumen stellen ein robustes Verfahren dar, um beliebige Objekte zu schattieren. Sie verwenden dazu den Stencilbuffer, um den schattierten Bereich in einem Bild auszumaskieren. Das Verfahren basiert im Prinzip auf der Tatsache, daß man sich für jedes Objekt und jede Lichtquelle der darzustellenden Szene ein eigenes Schattenvolumen berechnet. Um das Schattenvolumen effizient zu berechnen, werden für die Berechnung des Volumens nur die Vertices der Polygone herangezogen, die in Richtung der betrachteten Lichtquelle ausgerichtet sind. Für jeden dieser Vertices errechnet man sich den Vektor li,j von der Lichtquelle zum Vertex, wobei i = 1, ...n der Anzahl der Lichtquellen und j = 1, ..., n der Anzahl der den Lichtquellen zugewandten Vertices entspricht. Diese Vektoren sollte man sich als unendliche Vektoren vorstellen, denn durch die Verlängerung der Vektoren wird das eigentliche Schattenvolumen aufgespannt (Vgl. Abb.15). Stellt man sich ferner alle Polygone die der Lichtquelle zugeneigt sind, als eine Region und alle die der Lichtquelle abgeneigt sind, als eine andere Region vor, so liegt an der Grenze dieser beiden Regionen die Schattenkante des betrachteten Objekts. Eine effizientere Methode das Schattenvolumen zu berechnen, ergibt sich dadurch, daß man nur die Vertices einer Schattenkante betrachtet und diese verlängert, um das Schattenvolumen aufzuspannen. Um nun herauszufinden ob sich ein Objekt des Bildes innerhalb des Schattens eines anderen Objekts liegt oder nicht, nutzt man den Stencilbuffer. Zuerst wird der Stencil Buffer gelöscht, um etwaige Einträge zurückzusetzen. Anschließend wird die darzustellende Szene in den Frame Buffer gezeichnet. Da das Schattenvolumen zur reinen Ermittlung des Schattens verwendet wird und nicht Bestandteil der darzustellenden Szene ist, wird im nächsten Schritt das Schreiben in den Tiefen,- und Colorbuffer abgeschaltet. Nun erhöht man an allen Stellen des Bildes, an denen 33 2.6 Schatten Volumen 34 Abbildung 15: Prinzip des Schattenvolumen sich eine dem Betrachter zugewandte Seite des Schattenvolumens befindet, den Wert des Stencil Buffers um 1. Darauf folgend erniedrigt man an allen Stellen des Bildes, an denen sich eine dem Betrachter abgewandte Seite des Schattenvolumens befindet, den Wert des Stencil Buffers um 1. Es bleibt nur der Bereich im Stencil Buffer maskiert, an dem sich der Schatten des Objektes befindet. Abbildung 16 veranschaulicht dieses Prinzip. Um den schattierten Bereich dann darzustellen, zeichnet man ein Polygon das den gesamten Bildbereich überdeckt und sich orthogonal zum Betrachtungspunkt befindet. Dieses wird genutzt um die Szene genau an den vom Stencil Buffer ausmaskierten Stellen per Blending abzudunkeln. Zusammenfassend kann man also sieben Schritte des Verfahrens festhalten: 1. Schattenvolumen aus den errechneten verlängerten Vektoren bilden 2. Stencil Buffer zurücksetzen 3. Zeichnen der darzustellenden Szene ohne Schattenvolumen 4. Ausschalten des Tiefen-, sowie des Color Buffers 5. Zum Betrachter ausgerichtete Polygone des Schattenvolumens zeichnen 34 2.6 Schatten Volumen 35 Abbildung 16: Berechnung des Schattenvolumen und den Stencil Buffer dabei um 1 erhöhen 6. Vom Betrachter abgewandte Polygone des Schattenvolumens zeichnen und den Stencil Buffer dabei um 1 erniedrigen 7. Polygon zeichnen, daß den gesamten Bildbereich abdeckt und orthogonal zum Betrachter ausgerichtet ist. Per Blending dann die Schattenbereiche, d.h. die durch den Stencil Buffer ausmaskierten Bildbereiche abdunkeln. Es ist ferner möglich nicht nur harte Schatten mit dem Verfahren darzustellen, sondern es ist auch möglich weiche Schatten abzubilden. Stellt man sich vor, man verwendet mehrere Lichtquellen in einer Szene, die für jedes beleuchtete Objekt, daß sich im Raum befindet ein Schattenvolumen erzeugen, so gibt es Stellen an denen sich mehrere Schattenvolumen überschneiden. Damit erhöht sich auch der Wert des Stencil Buffers an den betreffenden Stellen und man erhält einen aus heterogenen Werten bestehenden Stencil Buffer. Anhand der verschiedenen Werte ist es nun möglich weiche Schatten über Blending Methoden zu zeichnen. 35 2.6 Schatten Volumen 36 Abbildung 17: Problem mit dem Schattenvolumen 2.6.2 Probleme Ein Problem des Verfahrens besteht darin, ersichtlich in Abbildung 17, daß der Schatten nicht nur auf dem in der Abbildung dargestellten Quader liegen bleibt, sondern auch in dem Bereich hinter dem Quader. Die korrekte Darstellung findet sich in Abbildung 18. Ein weiteres Problem besteht in der Darstellung von Objekten, die die Farbe des Lichtes verändern, wie z.B. verschmutztes Glaß 27 . Letztendlich hängt die Performanz des Verfahrens von der Anzahl der Polygone des Schatten werfenden Objekts ab. Dennoch lässt sich das Verfahren durch die oben dargestellte Verbesserung, d.h. durch die Reduktion der Eingangsdaten auf die Schattenkanten des Objekts, erheblich optimieren, im Vergleich zu der brute-force“ Methode, bei der alle Vertices, der zu der Lichtquelle ausgerichte” ten Polygone in die Berechnung des Schattenvolumens mit eingehen. 27 Vgl. Möller/Haines, 2002:267 36 2.7 Verfahren 37 Abbildung 18: Lösung des Problems mit dem Schattenvolumen 2.7 Verfahren Das in dieser Arbeit erarbeitete Verfahren teilt sich in verschiedene Bearbeitungsschritte auf, die im folgenden beschrieben und auf die im nächsten Abschnitt näher eingegangen wird. Generell besteht das erste Problem darin, die Texturen der beiden Kugeln aus dem Videobild auszuschneiden, d.h. die Position der beiden Kugeln im Bild zu verfolgen und immer an der richtigen Stelle die Pixel, die die Kugeln darstellen, aus dem Bild zu extrahieren. Dies lässt sich mit den Markern und den Funktionen des ARToolkit implementieren. Wie aus Abbildung 10 hervorgeht werden jeder Kugel zwei Marker zugewiesen. Es sollten zwei Marker sein, damit das System eine gewisse Ausfallsicherheit gewährleisten kann, denn es kommt vor, daß das Trackingverfahren des ARToolkits aus Gründen, die bereits im Kapitel 2.1 erläutert wurden, versagt. Hat man nun die Kugeln erfolgreich getrackt, muss man die 2D Koordinaten des Kugelmittelpunktes und des Kugelrandes bestimmen, um die Kugel aus dem 2D Bild auszuschneiden. Dazu programmiert man einen Teil der OpenGL Renderingpipeline nach, um aus den 3D Koordinaten die projezierten 2D Koordinaten zu gewinnen. Hat man die zwei Ausschnitte mit den Kugeln erhalten, so müssen diese auf eine 2er Potenz skaliert werden, um als Textur dargestellt werden zu 37 2.7 Verfahren 38 Marker Erkennung Texturierung/ Beleuchtung Tracken der Kugeln Textur der Kugeln im Bild ermitteln Kombination der Texturen per Multitexturing Schattierung Tiefenbild grabben Tiefenwerte in OpenGL z-Buffer schreiben Schatten zeichnen durch Shadow Volumes virtuelle Objekte in reale Szene zeichnen Abbildung 19: Übersicht über das Verfahren können. Die ermittelten Texturen werden anschließend wie im Kapitel 2.4 beschrieben per Multitexturing in beliebiger Weise kombiniert und auf das Objekt gebracht. Als nächstes wird von der Stereokamera ein Tiefenbild der Szene genommen. Ein mögliches Tiefenbild ist in Abbildung 4 zu sehen. Die Tiefenwerte werden dabei als Grauwerte dargestellt, also im Bereich von 0...255. Objekte, die sich nah an der Kamera befinden, werden dabei heller dargestellt, als Objekte die weiter entfernt sind. Es gilt also, daß das am weitesten entfernte Objekt den Farbwert Schwarz mit dem Grauwert 0 und das Objekt mit der geringsten Distanz zur Kamera den Farbwert Weiß mit dem Grauwert 255 zugewiesen bekommt. Diese Tiefenwerte gilt es nun in den Tiefenbuffer von OpenGL zu schreiben, wobei die Werte aus dem Bild an den Wertebereich der Tiefenwerte im OpenGL Tiefenbuffer angepasst werden müssen, damit einheitlich auf den Werten gearbeitet werden kann. Nachdem die Tiefenwerte der realen Szene im Tiefenbuffer von OpenGL stehen, kann man mit der Berechnung des Schattens beginnen. Hierzu spannt man sich ein Schattenvolumen zwischen dem darzustellenden Objekt und einer Lichtquelle auf. Befinden sich mehrere Lichtquellen in der virtuellen Szene, so werden mehrere Schattenvolumen aufgespannt. Durch das Schattenvolumenverfahren erhält man einen Schatten, der zusammen mit dem darzustellenden Objekt gerendert wird. Der Schatten kommt dabei nicht 38 3. Implementierung 39 nur auf virtuell dargestellten Objekten zum liegen, sondern auch auf realen Objekten, weil die Tiefenwerte der realen Szene in den Tiefenbuffer von OpenGL übernommen wurden. Die Schwierigkeit bei dem ganzen Verfahren besteht darin, die erwünschte Darstellung in Echtzeit erhalten zu können, damit die Immersion des Betrachters nicht beeinträchtigt wird. Auf die erwähnten Aufgaben und Probleme, die sich aus der obigen Beschreibung ergeben, wird im nächsten Kapitel detailierter eingegangen. Abbildung 19 veranschaulicht die verschiedenen Bearbeitungsschritte. 3 Implementierung Während dieser Arbeit sind letztlich zwei verschiedene Softwareversionen entstanden, die sich lediglich in dem Punkt unterscheiden, daß die eine Version mit einer normalen Webcam zu betreiben ist, und die andere mit der Stereokamera arbeitet. Im folgenden wird die Softwareversion, die mit der Stereokamera arbeitet, vorgestellt. Diese Version unterscheidet sich von der anderen Version nur in Bezug auf die Verwendung von der Stereokamera ermittelten Tiefenwerte. Die Version, die mit der Webcam arbeitet, simuliert lediglich Tiefenwerte der realen Szene, in dem sie nachmodellierte Objekte der realen Szene als 3D Modell in den OpenGL Tiefenbuffer zeichnet. Da die Stereokamera auf 4 Tiefenbilder pro Sekunde beschränkt ist und dies damit den Flaschenhals der Applikation darstellt, werden mit der Webcamversion weit höhere fps Raten erzielt. Durch Erstellung beider Softwareversionen ist es ferner möglich, einen genaueren Vergleich bzw. eine genauere Analyse bezüglich der Echtzeitanforderung der Applikation zu erstellen. Da die verwendete Stereokamera Bilder in einer Auflösung von 1024x768 liefert, das ARToolkit als Eingabe jedoch Bilder mit einer Auflösung von 320x240 erwartet, muss das Eingangsbild auf 320x240 mittels: intz = gluScaleImage(GL RGBA, 1024, 768, GL U N SIGN ED BY T E, data, 320, 240, GL U N SIGN ED BY T E, buf ) herunterskaliert werden. Leider ist dieser Funktionsaufruf im Vergleich zu allen anderen in dieser Arbeit verwendeten Algorithmen der am rechenintensivste und somit der teuerste. Es wird 39 3. Implementierung 40 im Kapitel Performanz weiter darauf eingegangen. Nach der Initialisierung der Applikation, in der die notwendigen Einstellungen des ARToolkits, der 3D Kamera, und der für die Applikation wichtigen Variablen initialisiert werden, sowie die VRML Modelle geladen werden, folgen die bereits in Kapitel 2.7 beschriebenen Schritte des MainLoops. Zu Beginn wird jeweils ein Bild der Kamera genommen, aus dem mit den Routinen des SDKs der Stereokamera ein Tiefenbild gewonnen wird. Dieses Tiefenbild gilt es nun in den Tiefenbuffer von OpenGL zu schreiben, und die Wertebereiche des Bildes und des OpenGL Tiefenbuffers anzupassen. Die Variable depthV alue stellt dabei den Tiefenwert des Mittelpunkts des aktuell zum Zeichnen der Objekte verwendeten Markers in OpenGL dar. Die Variablen depthDif f.x, depthDif f.y hingegen den Tiefenwert des Markermittelpunkts im Tiefenbild der Stereokamera. float *depthImageTemp; if(depthValue != 0) { int w,h; w = 320; h=240; depthImageTemp = new float[w*h]; for(int i=0; i<w*h; i++) { // scale to range 0...1 and invert // to ensure the values range from 0...1 not from 1...0 depthImageTemp[i] = 1.0 -(depthImage.data[i]/255.0); } int t1 = depthDiff.x;int t2 = depthDiff.y; // Only if a marker is detected, the factor is set correctly, // otherwise set to 1 double factor = 0; factor = depthImageTemp[t1*ROW+ t2] / depthValue; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glClear(GL_DEPTH_BUFFER_BIT); glDepthFunc(GL_ALWAYS); glDrawPixels(w,h, GL_DEPTH_COMPONENT,GL_FLOAT, depthImageTemp); 40 3. Implementierung 41 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); Es werden zuerst die Pixel aus dem Bereich [0...255] in den Bereich [0...1] umgerechnet. Mittels glDrawP ixels() werden dann die von der Stereokamera gewonnenen Tiefenwerte in den Tiefenbuffer von OpenGL geschrieben. Wichtig hierbei ist das Schreiben in den Colorbuffer zu deaktivieren, um nicht das angezeigte Videobild zu überzeichnen. Damit das normale Farbbild der Stereokamera korrekt angezeigt wird, ist die oben erwähnte Skalierung auf eine Auflösung von 320x240 Pixeln notwendig. Anschließend werden die Marker in dem Videobild mit den Funktionen des ARToolkits detektiert. Die Informationen der VRML Objekte bezüglich der Marker werden im ARToolkit getrennt von den Informationen der Marker selbst abgelegt: typedef struct { int area; int id; int dir; double cf; double pos[2]; double line[4][3]; double vertex[4][2]; } ARMarkerInfo; In der ARM arkerInf o Struktur werden alle notwendigen Informationen, wie die ID, der Vertrauenswert, die Position des Markerzentrums in idealen Bildschirmkoordinaten, die Eckpunkte des Markers in idealen Bildschirmkoordinaten und die den Marker umgebenden Linien als Werte der Liniengleichung a · x + b · y + c = 0 in idealen Bildschirmkoordinaten abgespeichert: typedef struct { char name[256]; int id; int visible; 41 3. Implementierung 42 double marker_coord[4][2]; double trans[3][4]; double marker_width; double marker_center[2]; } ObjectData_T; In der ObjectData T Struktur werden hingegen die Informationen der geladenen VRML Objekte bezüglich der Marker angegeben, d.h. der Name und die Referenz ID des Markers des Objekts, ob der Marker des Objekts sichtbar ist, die Transformationsmatrix, um das Objekt so in OpenGL zu transformieren, daß es auf dem Marker gezeichnet wird und die Breite und das Zentrum des Markers. Werden die Marker getrackt, so wird eine Referenz auf die ID des Markers gesetzt so dass klar ist, auf welchen Marker sich die Transformationsmatrix trans[3][4] bezieht. Um sicher zu sein, dass der referenzierte Marker auch im Bild sichtbar ist, sollte man prüfen, ob das Objekt bzw. der Marker überhaupt im aktuellen Bild sichtbar ist, wobei die Information der Sichtbarkeit in der Variable visible enthalten ist und bei Sichtbarkeit den Wert 1 enthält. Im nächsten Schritt wird geprüft, ob überhaupt einer der beiden Marker, der der jeweiligen Kugel zugeordnet wurde, sichtbar ist. Sind beide sichtbar, so wird sich der Marker gemerkt, der den höheren Vertrauenswert hat, d.h. der am besten getrackt wurde. Dies wird durchgeführt, um immer mit dem am besten getrackten Marker zu arbeiten, um zu große Abweichungen der Trackingwerte zu vermeiden. Das folgende Beispiel zeigt das Verfahren, um den Marker mit dem höchsten Vertrauenswert der reflektierenden Kugel zu bestimmen: //----------------- Reflektierende KUGEL -----------------// Marker der reflektierenden Kugel detektieren // die Objektinformationen werden entsprechend den Referenzids // der Marker eingelesen und es wird sich die Markerid mit // dem h"ochsten cf gemerkt um die Kugeln richtig auszuschneiden int patt0,patt1; patt0 = patt1 = -1; for(int i=0; i < marker_num; i++) { 42 3. Implementierung 43 if(marker_info[i].id == 0){ patt0 = i;} if(marker_info[i].id == 1){ patt1 = i;} } //Reflektierende Kugel, nur die Reflektierende detektieren //Nur ein einzelner Marker detektiert? ids entsprechend setzen if (patt1 > -1 \&\& patt0 == -1) { argConvGlpara(object[1].trans, gl_paraRefl); id1 = 1; } else if (patt0 > -1 && patt1 == -1) { argConvGlpara(object[0].trans, gl_paraRefl); id1 = 0; } else if (patt1 != -1 && patt0 != -1) { if ( marker_info[patt0].cf < marker_info[patt1].cf ) { argConvGlpara(object[1].trans, gl_paraRefl); id1 = 1; } else { argConvGlpara(object[0].trans, gl_paraRefl); id1 = 0;} } Mittels argConvGlpara(object[i].trans, gl paraRef l) wird schließlich die Transformationsmatrix des am besten getrackten Markers, die die Transformation von der Kamera zum Marker angibt, in einem 1D Array abgespeichert. Das gleiche wird auch für die diffuse Kugel durchgeführt und man hat die beiden Transformationsmatrizen, falls die entsprechenden Marker im Bild sichtbar sind, für beide Kugeln erhalten. Nun gilt es die beiden Kugeln aus dem Videobild auszuschneiden. 43 3. Implementierung 44 Dazu wurde eine kleine Hilfsstruktur definiert, welche 2D Pixelinformationen speichern kann: struct pixpos{ double x; double y; }; Es wird zuerst die Funktion getM id2Edge() aufgerufen, welche zwei pixpos Strukturen zurückliefert, in denen die 2D Bildschirmkoordinaten des Kugelmittelpunkts und des Kugelrandes enthalten sind. Anhand dieser Information kann man sich den Radius der Kugel errechnen, der zusammen mit dem Kugelmittelpunkt an die Funktion getScaledT ext() übergeben wird, welche schließlich die gewünschte Kugel aus dem Bild ausschneidet und eine auf eine 2er Potenz skalierte Textur erzeugt. Der folgende Programmausschnitt zeigt das Verfahren zum Ausschneiden der verspiegelten Kugel: //------------------// Verspiegelte Kugel //------------------pixpos mid2edge[2]; getMid2Edge(gl_paraRefl, mid2edge, id1); pixpos middle = mid2edge[0], edge = mid2edge[1]; // Radius der Kugel bestimmen int rad = (int)fabs(edge.x - middle.x); getScaledText(rad, textureRefl, buf, middle); In der Funktion getM id2Edge() wird zuerst die Matrix für den entsprechenden Marker geladen. Anschließend werden relativ zu dieser Position Transformationen angegeben, um auf die Stelle zwischen den Markern der realen Szene zu verweisen, d.h. auf den Mittelpunkt der Kugel im Videobild. Die Transformationen werden entsprechend der ID des betrachteten Markers gesetzt. Sind die Transformationen gesetzt, erfolgt noch eine Korrektur des Blickwinkels, damit 44 3. Implementierung 45 der Bildausschnitt nicht entsprechend dem Blickwinkel der Orientierung zum Marker verkürzt wird. Dies ist erforderlich, um die Kugel im Videobild korrekt auszuschneiden. Danach wird die Pixelposition des Kugelmittelpunktes durch get2DChoords() berechnet. Dies wird sowohl für den Kugelmittelpunkt als auch für den Rand der Kugel berechnet. Um den Rand der Kugel zu bestimmen, wird vor der Berechnung der 2D Bildschirmkoordinaten noch die erforderliche Tranlation gesetzt. Letztlich gibt die Funktion dann die 2D Bildschirmkoordinaten des Kugelmittelpunktes und des Kugelrandes zurück: getMid2Edge(double gl_para[16], pixpos midedge[2], int &id){ //Transformationsmatrix fuer den aktuellen Marker laden //um an die Kugelposition zu verschieben glLoadMatrixd( gl_para ); if(id==0) { glTranslatef(140.0,0.0,120.0); glScalef(3.0,3.0,3.0); } else if(id==1) { glTranslatef(-140.0,0.0,120.0); glScalef(3.0,3.0,3.0); } else if(id==2) { glTranslatef(140.0,0.0,90.0); glScalef(3.0,3.0,3.0); } else if(id==3) { glTranslatef(-140.0,0.0,90.0); glScalef(3.0,3.0,3.0); } //Blickwinkelkorrektur um die Textur korrekt auszuschneiden correctAngle(gl_para[4]); 45 3. Implementierung 46 //Kugelmittelpunkt zum Ausschneiden bestimmen pixpos middle = get2DCoords(); midedge[0] = middle; //um den Kugelradius verschieben glTranslatef(27.3,0.0,0.0); // 2D coord des Kugelrandpunkts ermitteln pixpos edge = get2DCoords(); midedge[1] = edge; } Die Funktion get2DChoords() bildet einen Teil der OpenGL Renderingpipeline ab, um aus den Werten der aktuellen Modelviewmatrix ,d.h. der Matrix zur Transformation des Objekts auf den Marker und den anschließenden Transformationen zur Abbildung auf den Kugelmittelpunkt und den Kugelrand, die 2D Bildschirmkoordinaten des Kugelmittelpunktes und Kugelrandes zu ermitteln. Hierzu wird zuerst die aktuelle Projektionsmatrix mit der aktuellen Modelviematrix multipliziert und darauf folgend die perspektivische Division durchgeführt. Dann wird die aktuell durch die Transformationen referenzierte Position und Orientierung der Viewporttransformation übergeben und man erhält die gewünschten 2D Bildschirmkoordinaten: static pixpos get2DCoords() { double gl_proj[16], gl_screen[16], gl_model[16]; glMatrixMode(GL_MODELVIEW); glGetDoublev(GL_MODELVIEW_MATRIX, gl_model); glGetDoublev(GL_PROJECTION_MATRIX, gl_proj); // mult proj * modelview glLoadMatrixd(gl_proj); glMultMatrixd(gl_model); glGetDoublev(GL_MODELVIEW_MATRIX, gl_screen); // homogenous division for(int i=0;i<16;i++) 46 3. Implementierung 47 gl_screen[i] = gl_screen[i]/gl_screen[15]; pixpos tpos; tpos.x = gl_screen[12]; tpos.y = gl_screen[13]; //Viewport Transformation pixpos middle = viewport(tpos); glLoadMatrixd(gl_model); return middle; } Mit den beiden auf diese Weise erhaltenen Texturen lassen sich die Objekte wie bereits in Kapitel 2.7 beschrieben texturieren. Dazu werden die Texturen initialisiert und in einer Display Liste gespeichert. Das folgende Beispiel zeigt die Texturierung, die sich dem Beleuchtungsmodell von OpenGL annähert. Die Funktion weightT ext() gewichtet dabei die jeweilige Textur: weightText(textureRefl, ambient); weightText(textureSpec, specular); weightText(textureDiff, diffuse); weightText(textImage, emission); init4Text( textureRefl, textureSpec, textImage, textureDiff); textures = glGenLists(1); glNewList(textures, GL_COMPILE); setUpMultLightOpenGL(textImage, textureDiff, textureRefl, textureSpec); glEndList(); Um sich den Overhead zu sparen, der immer entsteht wenn man eine neue Textur anlegt, werden die Texturen während der Initialisierung der Applikation erstellt. Im laufenden Programm werden die Texturen dann nur per glT exSubImage2D() aktualisiert, anstatt sie jedesmal neu anzulegen und später zu löschen. Nachdem die Texturen ausgeschnitten sind und zur Verfügung stehen, wird der Schatten berechnet, noch bevor das Objekt gerendert wird. Dazu wird zuerst 47 3. Implementierung 48 die Transfomationsmatrix des Markers geladen, zu dem man das Objekt in Relativität darstellen will. glLoadMatrixd( gl_paraRefl ); Anschließend erzeugt man sich sein Schattenvolumen, ShadowVolume volume(polys); wobei man dem Konstruktor der Klasse ShadowVolumes einen V ector der Struktur P oly übergibt. struct Poly { Point p1; Point p2; Point p3; Vektor n1; Vektor n2; Vektor n3; bool facesLight; Vektor faceNormal; }; Im Zusammenhang dieses Projekts ist eine Klasse entstanden um VRML Objekte zu laden. In dieser Klasse wird der V ector erzeugt, der alle Polygone des Objektes mit ihren Normalen pro Vertex und pro Fläche enthält. Diese Polygone werden später zur Berechnung des Schattenvolumens benötigt. Als nächstes wird der Lichtvektor gesetzt, der die Position der Lichtquelle angibt und es wird mittels setF acesLight() für jedes Polygon des Objekts errechnet, ob es der Lichtquelle zu-, oder abgewandt ist. Die von der Lichtquelle abgewandten Polygone werden bei der Berechnung des Schattenvolumens nicht berücksichtigt. Vektor light(lx,ly,lz); volume.setLightPos(light); 48 3. Implementierung 49 void ShadowVolume::setFacesLight(){ for (int i = 0 ; i < polys.size() ; i++) { Vektor lightVec = lightPos - polys[i].p1.getPosition(); if (lightVec.dot(lightVec, polys[i].faceNormal) > 0){ polys[i].facesLight = true; } else { polys[i].facesLight = false; } } } Nun kann der Schatten gezeichnet werden: createShadow(int x, int y, int xend, int yend){ glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glDepthMask( GL_FALSE ); glDepthFunc( GL_LEQUAL ); glEnable( GL_STENCIL_TEST ); glDisable( GL_BLEND ); glStencilFunc( GL_ALWAYS, 1, 0xffffffff );newline //erhoehe, bei dem Betrachter zugewandten Flaechen glCullFace(GL_BACK); glStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); drawShadowVolume(); // erniedrige, bei dem Betrachter abgewandten Flaechen glCullFace(GL_FRONT); glStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); drawShadowVolume(); // Culling ausschalten glDisable(GL_CULL_FACE); 49 3. Implementierung 50 // Zeichne wieder in den Colorbuffer glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); //Zeichne den Schatten drawShadow(x,y,xend,yend); glDepthMask( GL_TRUE ); glDisable( GL_STENCIL_TEST ); } In der Methode drawShadowV olume() wird das Schattenvolumen gezeichnet: void ShadowVolume::drawShadowVolume(){ int i,j; glColor4f(0,0,0,0.2); for (i = 0 ; i < polys.size() ; i++) { VrmlBuilder::Poly poly = polys[i]; if (poly.facesLight == true) { // Dreieck zeigt zur Lichtquelle //Skalierungsfaktor damit der Vektor //sich dem Unendlichen annaehert float scale = 10000; // Vektor von der Lichtquelle zum Vertex bestimmen Vektor lightVec1 = poly.p1.getPosition() - lightPos; Vektor lightVec2 = poly.p2.getPosition() - lightPos; Vektor lightVec3 = poly.p3.getPosition() - lightPos; // den Vektor ins Unendliche verlaengern Vektor v1End = poly.p1.getPosition() + (lightVec1 * scale); Vektor v2End = poly.p2.getPosition() + (lightVec2 * scale); Vektor v3End = poly.p3.getPosition() + (lightVec3 * scale); // Die Polygone des Schattenvolumens zeichnen //p1,end1,end2,p2 glBegin(GL_QUADS); glVertex3f(poly.p1.getX(),poly.p1.getY(),poly.p1.getZ()); 50 3. Implementierung 51 glVertex3f(v1End.getX(), v1End.getY(), v1End.getZ()); glVertex3f(v2End.getX(), v2End.getY(),v2End.getZ()); glVertex3f(poly.p2.getX(),poly.p2.getY(),poly.p2.getZ()); glEnd(); //p2,end2,end3,p3 glBegin(GL_QUADS); glVertex3f(poly.p2.getX(),poly.p2.getY(),poly.p2.getZ()); glVertex3f(v2End.getX(), v2End.getY(), v2End.getZ()); glVertex3f(v3End.getX(), v3End.getY(),v3End.getZ()); glVertex3f(poly.p3.getX(),poly.p3.getY(),poly.p3.getZ()); glEnd(); //p3,end3,end1,p1 glBegin(GL_QUADS); glVertex3f(poly.p3.getX(),poly.p3.getY(),poly.p3.getZ()); glVertex3f(v3End.getX(), v3End.getY(), v3End.getZ()); glVertex3f(v1End.getX(), v1End.getY(),v1End.getZ()); glVertex3f(poly.p1.getX(),poly.p1.getY(),poly.p1.getZ()); glEnd(); } } } Jetzt hat man den Bereich des Schattens im Stencilbuffer ausmaskiert und kann diese Maske nutzen, um das Videobild zu überblenden und damit den Schatten zu zeichnen: void ShadowVolume::drawShadow(int x, int y, int xend, int yend) { glDisable( GL_LIGHTING ); // Stencil Test zum ausmaskieren aktivieren glEnable( GL_STENCIL_TEST ); // Tiefentest ausschalten damit die Tiefenwerte // nicht ueberzeichnet werden 51 3. Implementierung 52 glDisable( GL_DEPTH_TEST ); glColor4f( 0, 0, 0, 0.3); // Zeichne alles was != 0 ist glStencilFunc( GL_NOTEQUAL, 0, 0xffffffff ); glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadIdentity(); // Polygon "uber den gesamten Schirm zeichnen glBegin(GL_QUADS); glVertex2i(x, y); glVertex2i(xend, y); glVertex2i(xend, yend); glVertex2i(x, yend); glEnd(); glPopMatrix(); glEnable( GL_DEPTH_TEST ); glDisable( GL_STENCIL_TEST ); } Da wir zu diesem Zeitpunkt bereits die Texturen zur Beleuchtung und den Schatten des Objekts berechnet haben, können wir das Objekt nun zeichnen. Dazu wird das Schreiben in den Tiefenbuffer nochmals explizit erlaubt, um die alten Tiefenwerte durch die des neuen Objekts zu überschreiben, insofern das Objekt den Tiefentest besteht. glDepthMask(GL_TRUE); Nun kann die Display Liste mit den Texturen und dem zu zeichnenden Objekt geladen werden: glCallList(textures); //Display Liste des zu rendernden Objekts 52 4. Ergebnisse 53 glCallList(list[0]); 4 Ergebnisse In diesem Kapitel werden die Ergebnisse des Verfahrens beschrieben und bzgl. der Performanz analysiert. 4.1 Bewertung und Performanz Abschließend lässt sich feststellen, daß der in dieser Arbeit verfolgte Ansatz vielversprechend ist. Es ging in der Arbeit prinzipiell um die Entwicklung eines Prototyps und um eine Studie darüber, ob der gedachte Ansatz in Echtzeit zu verwirklichen ist. Diese Anforderungen wurden von der Applikation erfüllt, wobei darauf hingeweisen werden muß, daß die Applikation ihr volles Potential noch nicht ausgeschöpft hat, denn es sind noch einige Stellen offen, an denen optimiert und somit nochmal ein Mehr an Geschwindigkeit herausgeholt werden kann. Leider war es nicht möglich in der Kürze der Implementierungsphase, alle diese Optimierungsaspekte zu berücksichtigen. Ein großer Nachteil war die Verwendung der Funktion gluScaleImage(), die am teuersten in der Berechnung war, vor allem bei der Skalierung von Bildern mit großer Auflösung auf Bilder mit kleiner Auflösung, z.B. von 1024x768 auf 320x240. In beiden Versionen der Applikation müssen die Texturen auf 2er Potenzen skaliert werden, in der Version mit der Stereokamera muß jedoch zusätzlich das aufgenommene Bild von 1024x768 auf 320x240 herunterskaliert werden, damit das ARToolkit mit den Bildern arbeiten kann. Während die Applikation mit normalen Farbbildern der Stereokamera bei 6-8 fps lief, so lief sie mit der Webcam, die auf die teure Skalierung verzichten konnte, stabil mit 16 fps. Mit einer eigenen Skalierungsfunktion könnte man dort nochmal einen enormen Geschwindigkeitszuwachs herausholen, was ein Vergleich mit der Skalierungsfunktion einer speziellen Bibliothek zur Bildverarbeitung erahnen ließ. Eine weitere Optimierung besteht in der Ausnutzung der menschlichen Wahrnehmungsfähigkeit. Die Ausschnitte der beiden 53 4.1 Bewertung und Performanz 54 Kugeln müssen für einen realistischen Eindruck nicht in jedem Videobild berechnet werden. Im Gegensatz zu rein virtuellen Umgebungen, in denen sich eine niedrige Framerate durch Bildruckeln usw. ausdrückt, fiel es den Betrachtern in der Evaluation der Applikation keineswegs auf, daß die Texturen nur alle 4 oder 8 Bilder aktualisiert wurden. Hat man sogar eine Applikation, die auf begrenztem Raum arbeitet, d.h. der Marker auf dem das virtuelle Objekt dargestellt werden soll, ist nicht sehr weit von den beiden die Lichtsituation des Raumes festhaltenden Kugeln entfernt, so kann man mit statischen Environment Maps arbeiten, die zu Beginn der Applikation aufgenommen werden. Hierbei gilt aber, daß die Beleuchtung um so ungenauer wird, je mehr sich der Betrachter von der Position entfernt, an der die beiden Environmentmaps der Kugeln aufgenommen wurden. Wird ferner der Arbeitsfluß nicht durch eine Neuaufnahme der Environmentmaps gestört, so könnte der Benutzer bei einem Positionswechsel auch wiederholend die Environmentmaps aufnehmen, die dann wieder für diesen lokalen Kontext Gültigkeit besäßen. Dies hängt allerdings immer mit den von einer möglichen Anwendung gestellten Anforderungen zusammen, d.h. ob sie im lokalen Kontext mit statischen Environmentmaps arbeiten kann oder ob sie dynamische Environmentmaps mit der jeweils für die aktuelle Position gültigen Map benötigt. Bezüglich der Stereokamera lässt sich festhalten, daß in dieser Arbeit mit der verwendeten Kamera und Software die rein optische Verfahren zur Berechnung der Tiefenwerte der Szene verwenden, keine korrekten flächendeckenden Tiefenwerte für die gesamte Szene ermitteln ließen. Um Tiefenwerte für jeden Pixel des Bildes zu erhalten, musste die Kamera so parametriert werden, daß sie keine validierten Tiefenwerte mehr geliefert hat und somit die Tiefenwerte zum groen Teil zu sehr fehlerhaft waren (Vgl. Abb.4). Parametrierte man die Kamera hingegen so, daß sie nur validierte Tiefenwerte lieferte, so waren in dem Tiefenbild zu viele Bereiche ohne eine Tiefeninformation vorhanden. Leider fehlte in dieser Arbeit die Zeit, um die validierten Tiefenbilder zu verbessern, oder mit einem Aufbearbeitungsverfahren die Löcher in den Tiefenbildern zu schließen. Eine Idee, die nicht der Verbesserung der Erfassung der Tiefeninformation, 54 4.1 Bewertung und Performanz 55 sondern lediglich der Korrektur des Ergebnisses dient, wäre möglicherweise ein Verfahren, daß sich die Beziehung zwischen Kanten-, und Tiefenbild zunutze macht, denn die Stereokamera ist auch in der Lage, ein Kantenbild des aktuellen Bildes zurückzugeben. Mit dieser zusätzlichen Information wäre es evtl. möglich, die Löcher in den Bilder zu schließen. Da die optischen Verfahren zur Ermittlung eines Tiefenbildes meistens dort scheitern, wo sie keine Punktkorrespondenzen zwischen den beiden aufgenommenen Bildern finden können, könnte man sich die Kanteninformationen zu nutze machen und zwischen den Kanten die Tiefenwerte z.B. linear interpolieren, denn an den Kanten von Objekten werden meistens Tiefenwerte von den optischen Verfahren gefunden. Ein weiterer Nachteil der Stereokamera war, da sie nur 4 Tiefenbilder pro Sekunde liefern konnte, aufgrund der Rechenaufwändigkeit des optischen Verfahrens. Dadurch war die Applikation bereits auf 4 fps beschränkt und lief am Ende nur noch mit ca 1,5 fps. Dies stellte leider den Flaschenhals des Verfahrens dar, denn ohne die Stereokamera und mit Simulation von Tiefenwerten realer Objekte lief die Applikation stabil mit 16 fps. Eine bessere Alternative wäre der Einsatz eines physikalischen Verfahrens, wie der Einsatz einer Laser-, oder Infrarotkamera, um die Tiefenwerte zu bestimmen. Diese Verfahren sind vor allem in der Lage, für jeden Pixel des Bildes eine korrekte Tiefeninformation zu liefern, sofern die Objekte nicht allzuweit von der Kamera entfernt sind und sich in einem geschlossenen Raum befinden. Würden diese Kameras dazu noch höhere Frameraten bei der Erzeugung der Tiefenbilder liefern, so wäre die Verschattung der realen Objekte durch das virtuelle Objekt in Echtzeit berechenbar. Ferner war die Anpassung der Tiefenwerte der Stereokamera an die Tiefenwerte von OpenGL ein großes Problem, denn zum einen lieferte die Stereokamera falsche Tiefenwerte des Referenzpunkts durch Rauschen und durch die Ungenauigkeit des Verfahrens zur Gewinnung der Tiefenwerte aufgrund der Parametrierung, und zum anderen lieferte das ARToolkit für den Referenzpunkt nicht immer stabile Werte aufgrund des im ARToolkit verwendeten Trackingverfahrens. Somit war es recht schwierig die Angleichung der beiden Wertebereiche vorzunehmen, was den größten Nachteil darstellte. Auch die ungenügende Auflösung der Tie55 4.1 Bewertung und Performanz 56 Abbildung 20: Ergebnis fenwerte der Stereokamera durch die Beschränkung auf ein Grauwertbild mit 255 Grauwerten verstärkte dieses Problem. Die Applikation läuft auf einem Mobile Athlon Prozessor 3000+, mit einer ATI Mobility Radeon 9700 Pro/128 MB Graphikkarte, und 1 GB RAM bei einer stabilen Framerate von 16 fps, wobei die Environmentmaps nur alle 4 Frames geändert werden und die 3D Tiefenwerte durch direktes Zeichnen von Objekten in den Tiefenbuffer simuliert werden. Das zu zeichnende Objekt, daß für den Test verwendet wurde, besteht aus 12888 Vertices und 4296 Polygonen. Abbildung 20 zeigt das Ergebnis des Tests als Screenshot, wobei sowohl der Boden als auch die Box in den OpenGL-Tiefenbuffer geschrieben wurden. Hierbei ist nochmals anzumerken, daß bei der für die Entwicklung der Applikation zur Verfügung stehenden Zeit kein besonderes Augenmerk auf die Optimierung gelegt wurde. Beispielsweise wurden nicht die Möglichkeiten moderner Graphikhardware durch Vertex oder Pixelshader genutzt, die nochmals einen Geschwindigkeitszuwachs verbuchen könnten. 56 5. Fazit 5 5.1 57 Fazit Schlußfolgerungen Kombiniert man den vorgestellten Ansatz mit Verfahren und Geräten zur Gewinnung der 3D Tiefeninformation, die korrekte, validierte Tiefenwerte für jeden Pixel des aktuellen Bildes liefern, so sollte man in der Lage sein, virtuelle Objekte entsprechend der aktuellen Beleuchtunssituation in einem Raum durch Kombination von Environmentmaps korrekt zu beleuchten, und die virtuellen Objekte die reale Szene verschatten zu lassen. Die Qualität des Ergebnisses lässt sich dabei noch durch Verwendung einer höheren Auflösung des Bildes und der Environmentmaps, sowie durch Verwendung eines Schattenverfahrens erzielen, welches nicht die oben genannten Nachteile aufweist. 5.2 Ausblick Der verwendete Ansatz ist in vielerlei Hinsicht erweiterbar. Eine mögliche Erweiterung wäre die Berechnung der Position der Lichtquellen, wie sie bereits von Kanbara und Yokoya in einem Verfahren verwendet wurde28 , das aus einer statischen Aufnahme einer verspiegelten Kugel die Position der Lichtquellen berechnet, um in OpenGL an den entsprechenden Stellen Lichtquellen zu setzen. Ferner könnte man auch versuchen die Position der Lichtquellen in Echtzeit dynamisch aus der verspiegelten Kugel zu berechnen und diese in OpenGL setzen, um weiche Schattierungen zu ermöglichen und den Schatten korrekt zu werfen. Eine andere Erweiterung bestände darin, High Dynamic Range Kameras zur Aufnahme der Szene zu verwenden, um vor allem auch grelles Licht, welches außerhalb des durch den Monitor darstellbaren Bereichs liegt, durch Angleichung der darzustellenden Farbwerte an den darstellbaren Bereich des Monitors darstellen zu können. Wenn man diesen Ansatz weiterverfolgt, kann man sich auch die Kamerakurve, über die Pixelwerte und die Belichtungszeit, für die 28 Kanbara, Yokoya: Geometric and Photometric Registration for Real-time Augmented Rea- lity, http://csdl2.computer.org/comp/proceedings/ismar/2002/1781/00/17810279.pdf, [Stand: 20.11.2005] 57 5.2 Ausblick 58 verwendete Kamera bestimmen und die Kamera somit als Lichtmessgerät einsetzen29 . Allerdings sind heutige Graphikkarten noch nicht in der Lage mit 32 bit pro Farbkanal umzugehen, wie sie von High Dynamic Range(HDR) gefordert werden30 . Stattdessen arbeiten modernste Graphikkarten mit 16 bit pro Farbkanal und können somit echtes HDR lediglich simulieren. Ein weiterer interessanter Ansatz wäre das vorgestellte Verfahren mit einer Fischaugenkamera durchzuführen, so daß es überflüssig wäre, bei dynamischen Umgebungsmaps ständig die beiden Kugel aufnehmen zu müssen. Man könnte stattdessen einfach das anzuzeigende Bild aus dem Fischaugenbild zurückrechnen und die reflektierende als auch die diffuse Map aus dem Fischaugenbild gewinnen. Die vorgestellte Technik kann in vielen Anwendungsfeldern eingesetzt werden. Vor allem im Bereich des Films und der Beleuchtungstechnik, wie z.B. der Innenausstattung würden sich gute Einsatzmöglichkeiten finden, wenn der Kunde in Echtzeit durch seinen Rohbau laufen und seine bereits möblierte, korrekt beleuchtete Wohnung betrachten könnte. 29 Debevec: Paul Debevec Home Page, www.debevec.org, [Stand:03.01.2006] High Dynamic Range Rendering, 30 Wikipedia: http://de.wikipedia.org/wiki/High Dynamic Range Rendering, [Stand: 02.01.2006] 58 LITERATUR 59 Literatur [1] Möller, Tomas Akenine; Haines, Eric: Real-Time Rendering, 2nd edition, A K Peters, Ltd., Natick, 2002 [2] Shreiner, Dave; Woo, Mason; Neider, Jackie; Davis, Tom: OpenGL Programming Guide, 4th edition, Addison-Wesley, Boston, 2004 [3] Watt, Alan: 3D-Computergraphik, 3.Auflage, Pearson Studium, München, 2002 [4] Billinghurst, Mark: ARToolkit, http://www.hitl.washington.edu/artoolkit/, [Stand: 22.12.2005] [5] Müller, Stefan: Licht und Materialien, http://geri.uni- koblenz.de/ws0203/cg2folien/9 lichtmat 6.pdf, [Stand: 04.01.2006] [6] Müller, Stefan: Photorealistische Computergraphik, http://www.unikoblenz.de/FB4/Institutes/ICV/AGMueller/Teaching/SS05/PCG, [Stand: 04.01.2006] [7] Schmalstieg, Dieter: Studierstube, http://studierstube.org/, [Stand: 02.01.2006] [8] Blythe, David: Generating a Sphere Map for Specular Reflection, http://www.opengl.org/resources/tutorials/sig99/advanced99/ notes/node179.html, [Stand: 16.12.2005] [9] Ramamoorthi, cient Ravi; Representation Hanrahan, for Irradiance Pat: An Effi- Environment Maps, http://graphics.stanford.edu/papers/envmap/, [Stand: 10.12.2005] [10] Kanbara, Masayuki; Yokoya, Naokazu: Geometric and Photometric Registration for Real-time Augmented Reality, http://csdl2.computer.org/comp/proceedings/ismar/ 2002/1781/00/17810279.pdf, [Stand: 20.11.2005] 59 ABBILDUNGSVERZEICHNIS [11] Debevec, Paul: Paul 60 Debevec Home Page, www.debevec.org, [Stand:03.01.2006] [12] Wikipedia: High Dynamic Range http://de.wikipedia.org/wiki/High Dynamic Range Rendering, Rendering, [Stand: 02.01.2006] Abbildungsverzeichnis 1 Marker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2 Head Mounted Display, Vgl. Billinghurst: ARToolkit . . . . . . . 5 3 Diagramm ARToolkit, Vgl. Billinghurst: ARToolkit . . . . . . . . 6 4 Szene mit Tiefenbild . . . . . . . . . . . . . . . . . . . . . . . . . 15 5 Prinzip der Umgebungsmap . . . . . . . . . . . . . . . . . . . . . 17 6 Kugel Koordinatenverlauf, Vgl. Möller/Haines, 2002:155 . . . . . 18 7 Schaubild Umgebungsmap, Vgl. Möller/Haines, 2002:160 . . . . . 21 8 Multitexturing, Vgl. Shreiner [et al.], 2002:432 . . . . . . . . . . 23 9 Beleuchtung, Vgl.Müller: Licht und Materialien . . . . . . . . . . 27 10 Aufbau 28 11 Diffuse und reflektierende Kugel . . . . . . . . . . . . . . . . . . 29 12 Texturierungsverfahren . . . . . . . . . . . . . . . . . . . . . . . . 29 13 Beleuchtungstexturen . . . . . . . . . . . . . . . . . . . . . . . . 30 14 Konkrete Beleuchtungen . . . . . . . . . . . . . . . . . . . . . . . 32 15 Prinzip des Schattenvolumen . . . . . . . . . . . . . . . . . . . . 34 16 Berechnung des Schattenvolumen . . . . . . . . . . . . . . . . . . 35 17 Problem mit dem Schattenvolumen . . . . . . . . . . . . . . . . . 36 18 Lösung des Problems mit dem Schattenvolumen . . . . . . . . . . 37 19 Übersicht über das Verfahren . . . . . . . . . . . . . . . . . . . . 38 20 Ergebnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60