GPU-basiertes Raycasting Studienarbeit Vorgelegt von Frank Sawitzki Institut für Computervisualistik Arbeitsgruppe Computergraphik Betreuer: Dipl.-Inform. Matthias Biedermann Prüfer: Prof. Dr.-Ing. Stefan Müller September 2005 Inhaltsverzeichnis 1 Einleitung 4 1.1 Motivation und Anforderung . . . . . . . . . . . . . . . . . . 4 1.2 Ziel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3 Gliederung der Arbeit 5 . . . . . . . . . . . . . . . . . . . . . . 2 Volumenvisualisierung 6 2.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Die Visualisierungs-Pipeline . . . . . . . . . . . . . . . . . . . 6 2.2.1 Aquisition . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2.2 Sampling . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2.3 Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.2.4 Klassikaton 9 2.3 2.4 . . . . . . . . . . . . . . . . . . . . . . . Indirektes Volume Rendering . . . . . . . . . . . . . . . . . . 9 2.3.1 Marching Cubes . . . . . . . . . . . . . . . . . . . . . 10 2.3.2 Marching Tetrahedra . . . . . . . . . . . . . . . . . . . 10 2.3.3 Surface from Contours . . . . . . . . . . . . . . . . . . 10 Direktes Volume Rendering . . . . . . . . . . . . . . . . . . . 11 . . . . . . . . . . . . . . . . . . . . . . . . 11 . . . . . . . . . . . . . . . . . . . . . . . . . 15 Shear-Warp . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4.1 Raycasting 2.4.2 Splatting 2.4.3 3 Raycasting auf der GPU 17 3.1 GPU Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2 GPU Programmiersprachen . . . . . . . . . . . . . . . . . . . 21 OpenGL Shader Language . . . . . . . . . . . . . . . . 21 Bisherige Ansätze zum GPU-Raycasting . . . . . . . . . . . . 23 3.3.1 Acceleration Techniques for GPU-based VR . . . . . . 23 3.3.2 Advanced GPU Raycasting 26 3.2.1 3.3 . . . . . . . . . . . . . . . 4 Implementierung 30 4.1 Ziel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.2 Aufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.2.1 30 4.3 Das Hauptprogramm . . . . . . . . . . . . . . . . . . . Die verwendeten Shader . . . . . . . . . . . . . . . . . . . . . 31 4.3.1 colorcube.vert + colorcube.frag . . . . . . . . . . . . . 32 4.3.2 emabs-kw.frag . . . . . . . . . . . . . . . . . . . . . . 33 4.3.3 mip.frag . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.3.4 mean.frag . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.3.5 m.frag 34 4.3.6 emabs-tf.frag . . . . . . . . . . . . . . . . . . . . . . . 34 4.3.7 emabs-ert.frag . . . . . . . . . . . . . . . . . . . . . . . 34 4.3.8 emabs-ess.frag . . . . . . . . . . . . . . . . . . . . . . . 34 . . . . . . . . . . . . . . . . . . . . . . . . . . 1 4.3.9 4.4 emabs-sm.frag . . . . . . . . . . . . . . . . . . . . . . . 34 Die verwendeten Volumendaten . . . . . . . . . . . . . . . . . 35 5 Visuelle Beurteilung 5.1 5.2 44 Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 5.1.1 Emission-Absortion . . . . . . . . . . . . . . . . . . . . 44 5.1.2 Maximum-Intensity-Projection . . . . . . . . . . . . . 44 5.1.3 Mittelwert . . . . . . . . . . . . . . . . . . . . . . . . . 44 5.1.4 First-Local-Maximum 44 5.1.5 Emission-Absortion mit Transferfunktion . . . . . . . . . . . . . . . . . . . . . . . . . 45 Vergleich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 5.2.1 Emission-Absortion . . . . . . . . . . . . . . . . . . . . 45 5.2.2 Maximum-Intensity-Projection 45 . . . . . . . . . . . . . 6 Beurteilung der Geschwindigskeit 53 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Vergleich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 6.1 Frameraten 6.2 Analyse 6.3 7 Fazit und Ausblick 56 2 Abbildungsverzeichnis 1 Visualisierungs Pipeline [10] . . . . . . . . . . . . . . . . . . . 7 2 Voxel Objekt [2] . . . . . . . . . . . . . . . . . . . . . . . . . 7 3 Topologien [10] . . . . . . . . . . . . . . . . . . . . . . . . . . 8 4 Rekonstruktions-Filter [2] 5 Marching Cubes [12] 6 Marching Tetrahedra [12] . . . . . . . . . . . . . . . . . . . . 10 7 Raycasting [2] . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 8 Emission und Absorption [2] . . . . . . . . . . . . . . . . . . . 13 9 Shear-Warp bei paralleler Projektion [2] 16 10 Shear-Warp bei perspektivischer Projektion [2] 11 OpenGL Processing Pipeline mit xen per-Vertex Operati- . . . . . . . . . . . . . . . . . . . . 9 . . . . . . . . . . . . . . . . . . . . . . . 10 . . . . . . . . . . . . . . . . . . . . ons(2) und xem Fragment Processing(6).[9] . . . . . . . . . . 12 16 17 OpenGL Processing Pipeline mit programmierbarem Vertex(1) und Fragment-Processor(2)[9]. . . . . . . . . . . . . . . . . 18 13 Front- und Backfaces[4] . . . . . . . . . . . . . . . . . . . . . 24 14 Bounding Geometrie[5] . . . . . . . . . . . . . . . . . . . . . . 27 15 First front-face und last back-face[5] . . . . . . . . . . . . . . 28 16 Interleaved Sampling[7] . . . . . . . . . . . . . . . . . . . . . . 28 17 Dithering[5] . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 18 Color-Cube Vertex-Shader . . . . . . . . . . . . . . . . . . . . 32 19 Color-Cube Fragment-Shader . . . . . . . . . . . . . . . . . . 33 20 Emission-Absorption Fragment-Shader . . . . . . . . . . . . . 36 21 Maximum Intensity Projection . . . . . . . . . . . . . . . . . 37 22 Mittelwert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 23 First Local Maximum . . . . . . . . . . . . . . . . . . . . . . 39 24 Transfer-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . 40 25 Early-Ray-Termination . . . . . . . . . . . . . . . . . . . . . . 41 26 Einfaches Empty-Space-Skipping 42 27 Modizierter Emission-Absorbtion Fragment-Shader 28 Ausgaben des Emissions-Absorptions Shaders 29 Ausgaben des Maximum-Intensity-Projection Shaders 30 Ausgaben des Mean-Value Shaders 31 Ausgaben des First-Local-Maximum Shaders 32 . . . . . . . . . . . . . . . . . . . . . 43 . . . . . . . . . 46 . . . . 47 . . . . . . . . . . . . . . . 48 . . . . . . . . . 49 Ausgaben des Emissions-Absorptions Shaders mit aktivierter Transferfunktion . . . . . . . . . . . . . . . . . . . . . . . . . 33 Ausgaben des Emission-Absorbtions Verfahrens (Vergleich) 34 Ausgaben des Maximum-Intensity-Projection Verfahrens (Ver- 50 . 51 gleich) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3 1 Einleitung 1.1 Motivation und Anforderung Für die Visualisierung von volumetrischen Daten, wie sie beispielsweise in der Medizin oder Simulation verarbeitet werden, gab es bislang die Entscheidung zwischen hoher Geschwindigkeit oder hoher Qualität. Doch gerade medizinische Anwendungen benötigen aufgrund ihrer hohen Anforderungen an die visuelle Qualität und Korrektheit bei gleichzeitiger Interaktivität bessere Lösungen. Die rasante Entwicklung im Bereich der Grakhardware hat in den letzten Jahren die Darstellung von Volumendaten insgesamt sehr beschleunigt. Aufgrund der hohen Rasterisierungsleistung waren texturbasierte Verfahren mit Hilfsgeometrie (sog. slicing) bisher die einzige Möglichkeit, selbst umfangreichere Datensätze aus der medizinischen Bildgebung in interaktiven Geschwindigkeiten darzustellen. Dennoch war es nur mit erheblichem Aufwand möglich, die Daten ohne störende Schichtartefakte zu rendern. Ebenso wie Raytracing bei der Darstellung von Oberächenmodellen selbst auf einfachen PCs mittlerweile in interaktive Geschwindigkeitsbereiche vorstöÿt, machen aktuellste Entwicklungen programmierbarer Grak-prozessoren das Verfahren auch für Volumendaten interessant; als Bezeichnungen werden dabei meist Raycasting oder Raymarching verwendet. Neben der höheren Flexibilität und visuellen Qualität bietet dieses Verfahren zudem die Möglichkeit, auch volumetrische Eekte wie Rauch, Feuer oder Wolken sehr leicht und überzeugend darstellen zu können - und somit zukünftige Spiele noch realistischer aussehen zu lassen. 1.2 Ziel Ziel der Arbeit ist es, sich in die Thematik der Shaderprogrammierung anhand einer modernen Hochsprache einzuarbeiten. Auf der Basis verschiedener optimierender Ansätze soll das Raycasting-Verfahren auf aktueller Grakhardware implementiert und getestet werden. Der Schwerpunkt soll dabei auf (statischen) Volumendaten aus der medizinischen Bildgebung liegen und hinsichtlich der Renderingleistung und visuellen Qualität beurteilt werden. Schwerpunkte dieser Arbeit sind: • Auswahl und Einarbeitung in eine Shaderhochsprache • Konzeption und Implementierung von interaktivem Raycasting für Volumendatensätze auf aktueller Grakhardware • Demonstration der Ergebnisse in einer Beispielanwendung mit verschiedenen volumetrischen Daten • Dokumentation und Beurteilung der Ergebnisse 4 1.3 Gliederung der Arbeit Im zweiten Kapitel dieser Arbeit werden die Grundsätze des Volumen Rendering erläutert, die sich in indirekte und direkte Verfahren aufteilen. Dabei werden kurz die einzelnen Verfahren vorgestellt. Der Hauptteil wird dabei vom Raycasting eingenommen. Das dritte Kapitel beschreibt die Möglichkeiten, die mit heutiger Hardware zur Verfügung stehen, um das Volumen Rendering ezient umzusetzen. Die OpenGL Shader Sprache wird in ihren Grundzügen vorgestellt. Anschlieÿend werden bereits bestehende Lösungen des Volumen Rendering mittels Grakhardware vorgestellt. Im vierten Kapitel wird die Implementierung eines hardwarebeschleunigten Volumen Renderers vorgestellt, der im Rahmen dieser Studienarbeit entwickelt wurde. Das fünfte Kapitel zeigt die Visuellen Ergebnisse der Implementierung auf und vergleicht diese mit Ergebnissen aus anderen Raycasting Anwendungen. Im sechsten Kapitel wird die Geschwindigkeit der Implementierung gemessen und mit Benchmarks aus anderen Raycasting Anwendungen verglichen. Im siebten Kapitel wird ein Fazit erstellt und ein Ausblick auf weitere Möglichkeiten gegeben. 5 2 Volumenvisualisierung 2.1 Einleitung In der Computergrak werden dreidimensionale Objekte überwiegend durch Oberächendarstellungen visualisiert. Diese Visualisierung bieten sich in den Bereichen an, in denen regelmäÿige Strukturen auftreten. Diese können dann recht einfach in Form von Polygonächen, Meshes, Nurbs, usw. dargestellt werden. Bei Daten mit unregelmäÿigen Strukturen z.B. aus numerischen Simulationen oder Scans von dreidimensionalen Volumen 1 ist es schwie- rig diesen eine eindeutige Oberäche zuzuordnen, weil die Strukturen ieÿend ineinander übergehen. Bei einer Oberächendarstellung würden feine Strukturen verloren gehen. Deshalb geschieht die Visualisierung dieser Daten durch Volume Rendering. Unter diesem Begri werden verschiedene Techniken zusammengefasst, bei denen Bilder aus diesen Volumendaten erzeugt werden. Mittlerweile gewinnen diese Volumen Rendering Verfahren auch bei der Visualisierung von Eekten in Computerspielen immer mehr an Bedeutung. Mit ihnen lassen sich Objekte wie Flüssigkeiten, Gase oder andere Naturphänomene realistisch darstellen. Im Gegensatz zur Oberächenvisualisierung bieten diese Verfahren den weiteren Vorteil, mehrschichtige oder transparente Informationen, z.B. aus dem Inneren eines Körpers, darstellen zu können. Allerdings war bisher durch eine aufwendige Berechnung für das Volume Rendering die Bildwiederholungsrate für eine üssige Animation zu gering. Deswegen wurden unterschiedliche Verfahren entwickelt, die durch eine Vereinfachung der Berechnungen eine schnelle Darstellung ermöglichten, 2 für die Darstellung oder es wurde spezielle und somit auch teure Hardware eingesetzt. Gerade für eine optimale Interpretation von medizinischen Aufnahmen ist eine Interaktion mit den Bildmaterial wichtig. Erst durch die Entwicklung von programmierbaren Grakprozessoren ist es möglich eine gute Bildqualität in Echtzeit auch kostengünstig umzusetzen. 2.2 Die Visualisierungs-Pipeline Bei dem Volumen Rendering sind mehrere Schritte notwendig, die in Form einer Pipeline angeordnet werden können (Abbildung 1). 2.2.1 Aquisition Als Volumen versteht man ein kontinuierliches dreidimensionales Signal das durch eine Aquisition, 3, z.B. durch einen CT-Scan eines menschlichen 1 Diese stammen z.B. aus der Medizin, und werden durch Computer Tomographie (CT) oder Magnet Resonanz Tomographie (MRI) erzeugt. 2 z.B. VolumePro 1000. Eine Hardwarelösung von Terarecon. 3 Das Signal ist allerdings durch das Abtasttheorem bandlimitiert mit einer cutt-o Frequenz νs , bei der für eine exakte Rekonstruktion die Abtastrate mehr als das Doppelte der cut-o Frequenz betragen muss (Nyquist Rate). 6 Abbildung 1: Visualisierungs Pipeline [10] Körpers, als Datensatz auf dem Computer vorliegt. f (~x) ∈ R mit ~x ∈ R3 (1) 2.2.2 Sampling Zur Rekonstruktion von Volumen Daten wird ein Gitter durch den Datensatz gelegt, welches das Volumen in ein dreidimensionales Array aufteilt. Abbildung 2: Voxel Objekt [2] Die Abtastwerte werden entweder an den Knoten (Voxel 4 ) oder im Zen- trum eines von den Schnittpunkten des Gitters gebildeten Würfels bestimmt (Voxelzelle) (Abbildung 2). Die Anordnung der Voxel bzw. Voxelzellen kann dabei unterschiedliche Formen aufweisen (Abbildung 3). 4 VOXEL = VOlume (X) ELement, in Anlehnung an PIXEL = PIcture (X) ELement 7 (a) kartesisch (b) gleichmässig (c) rechtwinklig (d) strukturiert (e) unstrukturiert (f) gestreut Abbildung 3: Topologien [10] 2.2.3 Filtering Zwei Probleme ergeben sich bei der Rekonstruktion des abgetasteten Volumens, die durch eine • Filterung der Daten ausgeglichen werden: Nach dem Abtasttheorem benötigt die exakte Rekonstruktion des Signals eine Faltung durch eine Sinus Funktion (Abbildung 4, C), die für den eindimensionalen Fall lautet: sinc(x) = sin(πx) πx (2) 5 Im dreidimensionalen Fall geschieht die Faltung über ein Tensor-Produkt . • Es entstehen Aliasing Artefakte, falls ein Signal rekonstruiert wird, welches nicht bandlimitiert gewesen ist. Um das kontinuierliche Signal mittels eines Arrays von Voxel rekonstruieren zu können, wird die Sinc-Faltung durch einen Box- oder Tent-Filter ersetzt (Abbildung 4, A und B). • Der Box-Filter interpoliert nach dem Nearest-Neighbor-Verfahren. Dieses erzeugt allerdings Unterbrechnungen zwischen Nachbarwerten und eine insgesamt blockartige Erscheinung. 5 Es werden die gesamten Abtastpunkte berücksichtigt. Dies ist rechnerisch aufwendig zu lösen. 8 Abbildung 4: Rekonstruktions-Filter [2] • Der Tent-Filter interpoliert trilinear. Dadurch erhält man ein gutes Verhältnis zwischen Rechenzeit und Qualität des rekonstruierten Signals. 2.2.4 Klassikaton Das Volumen bezeichnet eine Verteilung von Licht emittierenden Partikeln mit einer bestimmten Dichte. Diese Dichte wird für die Abbildung auf dem Bildschirm in RGBA Werte umgewandelt. Die Klasskation beschreibt die Auswirkung des Lichts auf das Voxel. Dabei wird der Skalarwert mit Hilfe einer Transferfunktion von einer physikalische Gröÿe (z.B. Materialdich- te) in eine optische Gröÿe (z.B. Farbe (RGB), Absorptionseigenschaften und Opazität (a)) umgewandelt. Es wird zwischen Pre- oder Post-Klassikation unterschieden, je nachdem ob die die Klassikation vor oder nach der Interpolation erfolgt. 2.3 Indirektes Volume Rendering Die Verfahren des Volumen Rendering werden grob in zwei Klassen aufgeteilt. In das indirekte Volume Rendering und das direkte Volume Rendering. Bei dem indirekten Volume Rendering werden Flächen innerhalb des Volumens bestimmt, die eine identische Dichte besitzen. Diese Flächen werden als Isoächen bezeichnet. Die Schwierigkeit besteht darin, diesen Isowert zu bestimmen. Dieses Verfahren setzt eine aufwendige Vorverarbeitung voraus und besitzt den Nachteil, das viele im Volumen enthaltene Informationen verloren gehen können, da die Darstellung auf die gefunden Isoächen beschränkt ist. Es existieren mehrere Techniken zur Bestimmung der Isoächen, die im folgenden kurz dargestellt werden[12]. 9 2.3.1 Marching Cubes Bei dem Marching-Cubes Verfahren wird die Voxelzelle als ein Würfel betrachtet, bei dem die 8 Eckpunkte in + oder - klassiziert werden, abhängig davon, ob der Eckpunkt einen höheren oder niedrigeren Wert als ein gesetzter Isowert besitzt. Falls sich die Werte der Eckpunkte in einem Würfel in + und - unterscheiden lassen, werden in diesem Würfel Ebenen aufgespannt, die zwischen dieser + und - Klassikation verlaufen. Diese Ebenen ergeben dann über mehrere Zellen betrachtet die Isoäche (Abbildung 5). Das Problem der Marching Cubes ist allerdings ein Auftreten von Mehrdeutigkeiten. Abbildung 5: Marching Cubes [12] 2.3.2 Marching Tetrahedra Bei den Marching Tetrahedra werden Tetraeder anstatt der Würfel betrachtet, um so das Problem der Mehrdeutigkeiten zu beseitigen (Abbildung 6). Abbildung 6: Marching Tetrahedra [12] 2.3.3 Surface from Contours Bei dem Surface-from-Contours Verfahren werden Schichtweise durch Segmentierungsverfahren die Konturen des Volumens bestimmt um auf diese 10 Weise die Oberäche zu rekonstruieren. 2.4 Direktes Volume Rendering Bei dem direkten Volumenrendering Verfahren lässt sich das Volumen ohne eine Repräsentation der Oberäche anzeigen. Bei den Verfahren wird das Volumen als Skalarfeld mit transparenten Werten aufgefasst, bei dem mit Hilfe von physikalischen Modellen die Lichtausbreitung nachgebildet wird. Dadurch lassen sich alle im Volumen enthaltenen Informationen verlustfrei darstellen. Die direkten Verfahren werden aufgeteilt in Bildraum-, Objektraum und hybride Verfahren. Bei dem Bildraumverfahren wird die Darstellung vom Bild aus gesehen erzeugt, während bei dem Objektraumverfahren die Betrachtungsrichtung von jeder Zelle des Volumen zum Bild führt. Bei dem Hybriden Verfahren wird das Bild aus beiden Richtungen zusammengesetzt. 2.4.1 Raycasting Abbildung 7: Raycasting [2] Ein Vertreter des Bildraumverfahrens ist das Raycasting. Es ist einfach zu implementieren und erzielt dabei die eine gute Bildqualität, erfordert aber dafür auch eine hohe Rechenleistung. Dieses Verfahren wurde im medizinischen Bereich erstmals 1988 von Paolo Sabella vorgestellt [6]. Der in diesem Kapitel vorgestellte Algorithmus stammt von Markus Hadwiger und Christof Rezk-Salama [2]. Beim Raycasting wird vom Auge des Betrachters durch jeden Pixel des Bildschirms ein simulierter Sehstrahl bildung 7). t ~x(t) durch das Volumen geschickt (Ab- bezeichnet in diesem Fall die zurückgelegte Strecke auf dem Sehstrahl. Nun wird in gleichmäÿigen Intervallen der Skalarwert der getroenen Voxel s(~x(t)) im Volumen ermittelt und mit der Distanz zur aktuellen Position 11 gewichtet. Dies kann entweder über einen Box-Filter mit Nearest-NeighborInterpolation oder einen Tent-Filter mit trilinearer Interpolation geschehen. Compositing: Basierend auf einem optischen Modell wird die Lichtver- breitung entlang des Sehstrahls im Volumen berechnet. Dieses optische Modell beschreibt, ob die Voxel des Volumens das Licht emittieren, reektieren, streuen, absorbieren oder verdecken. Folgende Compositing Verfahren werden z.B. angewendet: • Integration von Absorption und Emission: Das Licht wird von den Partikeln emittiert und auch absorbiert. Dieses Modell ist am weitesten verbreitet. • Integration von Emission und Absorption über eine Transferfunktion: Die Transferfunktion ermöglicht eine farblich unterschiedliche Darstellung der verschiedenen Dichten im Volumen (siehe Klassikation). Bestimmte Bereiche lassen sich damit auch transparent darstellen. • Mittelwert: Es wird der Mittelwert über alle auftretenden Farbwerte entlang eines Sehstrahls gebildet und dieser dargestellt. • Maximum Intensity Projection: Es wird nur der Maximalwert aller aufgetretenen Farbwerte entlang eines Sehstrahls ermittelt und dieser dargestellt. • First Local Maximum: Es wird der Farbwert ermittelt und dargestellt, der das erste lokale Maximum entlang des Sehstrahls bildet. Das Raycasting basiert im Allgemeinen auf dem Absorptions-Emissions Modell und man erhält dabei für die Absorption an der Position k(s) und die Emission c(s) t: k(t) := k(s(~x(t))) und c(t) := c(s(~x(t)) (3) In Abbildung 8 wird die einmalige Emission und anschlieÿende Absorption des Lichts verdeutlicht, dass mit einer Strahlungsenergie (c) am Punkt (t=d) ausgesendet wird und auf dem Weg bis zum Auge (t=0) kontinuierlich 0 an Energie (c ) verliert. Für c0 gilt: c0 = c · e−kd 12 (4) Abbildung 8: Emission und Absorption [2] Sollte die Absorption nicht kontinuierlich verlaufen, z.B. durch eine Emission von Licht an einem Punkt entlang des Strahls, wird die Strahlungsenergie d c0 durch eine Integration der Absorptionskoeezienten entlang der Strecke berechnet: Z ∞ C= c(t) · e−τ (0,t) dt̂ (5) 0 Optische Tiefe: Als optische Tiefe bezeichnet man das Integral der Absorptions- Koezienten im Exponenten. Z d2 τ (d1 , d2 ) = k(t̂)dt̂) (6) d1 Die Menge der Strahlungsenergie c die das Auge aus einer Richtung er- reicht wird durch Integration der gesamten Strahlungsenergie aller Punkte t entlang des Strahls berechnet. Z C= ∞ c(t) · e−τ (0,t) dt (7) 0 Die Auswertung des Integrals erfolgt numerisch durch back-to-front oder front-to-back Faltung (Alpha blending). Durch die Bestimmung einer Riemann Summe lässt sich die optische Tiefe τ approximieren. Diese beschreibt dann die anwachsende Absorption bis zu einer bestimmten Position ~x(t). ∆t ist die Distanz zwischen den Resampling Punkten. t/∆t τ (0, t) ≈ ~τ (0, t) = X k(i · ∆t)∆t (8) i=0 Über eine Multiplikation kann die Summierung der Exponenten substituiert werden: 13 e−τ̃ (0,t) = t/d Y e−k(i·∆t)∆t (9) i=0 Opazität: Nun kann die Opazität A berücksichtigt werden, mit der die Lichtundurchlässigkeit des Voxels angegeben wird: Ai = 1 − e−k(i·∆t)∆t (10) Ai Als Vereinfachung kann durch eine Umformung die Opazität als Ap- proximation für den gesamten i-ten Strahl angenommen werden: −τ̃ (0,t) e t/d Y = (1 − Aj ) (11) i=0 Die Farbe des i-ten Strahls lässt sich schlieÿlich approximieren über: Ci = c(i · ∆t)∆t (12) Nachdem nun die Skalarwerte mit Hilfe des optischen Modells in RGBA Werte umgewandelt wurden, kann durch eine numerische Lösung das Volume Rendering Integral bestimmen: C̃ = n X i=0 Alpha Blending: Ci i−1 Y (1 − Ai ) (13) j=0 Die Auswertung des Integrals erfolgt iterativ durch Alpha Blending, bei der zwischen back-to-front oder front-to-back Reihenfolge unterschieden wird. Mit einem Stepping i mit Schrittweiten von n - 1 bis 0 erhält man für die back-to-front Reihenfolge: 0 Ci0 = Ci + (1 − Ai )Ci+1 Der Wert Farbe Ci , Ci0 (14) wird dabei unter der Berücksichtigung von der aktuellen Opazität Ai und des Wertes an der vorherigen Position 0 rechnet. Startwert ist immer der Farbwert Cn 14 er- = 0. Alternativ läÿt sich das Alpha-Blending mit dem Stepping in front-to-back Reihenfolge ausführen: 0 Ci+1 i von 1 bis n 0 Ci0 = Ci−1 + (1 − A0i )Ci A0i = A0i−1 + (1 − A0i )Ai Der Farbwert Ci0 (15) wird dabei unter der Berücksichtigung der aktuellen 0 Ci−1 errechnet. 0 Das gleiche gilt für die Opazität Ai , die sich auch aus der aktuellen Opazität Ai und des vorherigen Opazitätswertes A0i−1 errechnet. Startwert ist auch 0 0 hier der Farbwert Cn = 0 und der Opazitätswert A0 = 0. Farbe Ci , der Opazität Ai Early-Ray-Termination: und des vorherigen Farbwertes Da die Berechnung des Sehstrahls im Fall der front-to-back Reihenfolge vom Auge aus in das Volumen führt, lässt sich mit der Early-Ray-Termination ein Verfahren nutzen, das es ermöglicht, die Berechnung entlang eines Sehstrahls abzubrechen. Sobald ein bestimmter Opazitätswert A0i bei der Integration erreicht wird, bei dem angenommen wird, dass kein Licht mehr durchdringen kann, wird der Algorithmus gestoppt. D.h. bei allen dahinter liegenden Voxeln kann angenommen werden, dass sie nicht mehr sichtbar sind und deshalb auch nicht mehr berechnet werden müssen. 2.4.2 Splatting Ein Vertreter des Objektraumverfahrens ist das Splatting. Dieses Verfahren basiert darauf, dass die optischen Eigenschaften eines Raumes in einem bestimmten Bereich durch jeden diskreten Datenpunkt im Volumen beeinusst werden. Der beeinusste Bereich ist gekennzeichnet durch seine charakteristische Funktion. In back-to-front Reihenfolge werden nun für jeden Voxel dessen Mittelpunkt auf die Bildebene projiziert und der Beitrag mit einem Emissionswert gewichtet und mit einem Over-Operator verknüpft. 2.4.3 Shear-Warp Ein Vertreter des Bild- sowie Objektraum-Verfahrens ist das Shear-WarpVerfahren, bei dem die Berechnung grob in zwei Schritten abläuft. Im ersten Schritt wird das Volumen in mehrere Schichten unterteilt, die alle parallel zur Sehachse verlaufen. Im Falle einer Parallelprojektion wird nun das Volumen so verzerrt (3D-Scherung), dass die Entfernung der Abtastpunkte auf diesen Schichten zu einer bestimmten Ebene gleich ist (Abbildung 9). Im Falle einer Perspektivischen Projektion erfolgt zusätzlich eine Skalierung (Abbildung 10). Auf dieser Zwischenebene werden nun in beiden Fällen die Abtastwerte von den Schichten projiziert. In einem weiteren Schritt wird das Ergebnis von dieser Zwischenebene für eine korrekte Darstellung wieder entzerrt (2DVerzerrung). 15 Abbildung 9: Shear-Warp bei paralleler Projektion [2] Abbildung 10: Shear-Warp bei perspektivischer Projektion [2] Dieses Verfahren ermöglicht auf diese Weise die Implementierung eines schnellen Berechnungsalgorithmus, der allerdings bei starker Vergröÿerung zu Fehlern in der Darstellung führt, da die Zwischenräume zwischen den Abtastungs-Schichten sichtbar werden. Auÿerdem müssen je nach Betrachtungsrichtung unterschiedlich ausgerichtete Schichtaufnahmen vorliegen, was zu einem höherem Speicherbedarf führt. 16 3 Raycasting auf der GPU 3.1 GPU Hardware Heutige Grakprozessoren liefern in bestimmten Bereichen ein vielfaches an Leistung im Vergleich zu gängigen CPUs. So liegt bspw. die Leistung einer Floating-Point Berechnung einer zwei Jahren alten GeForce 5900 Ultra bei 20 Milliarden Multiplikationen pro Sekunde. Im Vergleich dazu kommt eine mit 3 GHz getaktete Pentium 4 CPU auf 6 Milliarden Multiplikationen pro Sekunde. Die Floating-Point Leistung dieser GPU entspricht somit eines mit 10 GHz getakteten Pentium 4 Prozessors [1]. Und die Rechenleistung von GPUs wächst im Vergleich zu der von CPUs schneller an und überschreitet 6 dabei sogar die Prognosen von Moores Law . In der Vergangenheit waren allerdings die Möglichkeiten der Programmierung durch eine Fixed-Function Rendering-Pipeline beschränkt (Abbildung 11). Mittels dieser Pipeline werden aus den 3D Koordinaten von Polygonen und zusätzlichen Informationen wie Farbe, zugehöriger Textur, Transparenz, usw. eine Darstellung der Daten auf dem Bildschirm erzeugt. Abbildung 11: OpenGL Processing Pipeline mit xen per-Vertex Operations(2) und xem Fragment Processing(6).[9] Die Pipeline stellt dabei eine Reihe von Funktionen zur Verfügung. Diese waren allerdings bei der xed-function Bauweise nicht erweiterbar. Dies änderte sich mit der Einführung von frei programmierbaren Vertex- und Fragment-Einheiten, mit denen die xen Vertex- und Fragment-Operationen 6 Gordon Moore war Mitbegründer von Intel und stellte 1965 eine nach ihm benannte Faustregel auf, die beschreibt, dass sich die Anzahl der Transistoren in Prozessoren in 24 Monaten jeweils verdoppelt[8]. 17 7 ausgetauscht werden konnten (Abbil- durch selbst programmierte Shader dung 12). Abbildung 12: OpenGL Processing Pipeline mit programmierbarem Vertex(1) und Fragment-Processor(2)[9]. Die Vertex-Shader ersetzen alle Berechnungen, die die Verticies betreen. Dazu zählen z.B.:[9] • Vertex-Transformation • Normalen-Transformation and Normalisierung • Erzeugen von Texturkoordinaten • Texturekoordinaten-Tansformation • Beleuchtung Die Fragment-Shader ändern die Fragment-Daten, die nach der Rasterisierung entstanden sind. Funktionen eines Fragment-Shaders sind z.B.:[9] • Operationen auf interpolierten Werten • Texturzugrie • Nebel • Farbsummen 7 Als Shader wird der Code bezeichnet, der in der Vertex- oder Fragment-Einheit ausgeführt wird. Dessen Möglichkeiten gehen weit über das i.A. bezeichnete Shaden hinaus. 18 Fast jeder heutzutage verkaufte Grakprozessor besitzt diese frei programmierbare Shader-Einheiten. Die derzeit leistungsfähigste Grakkarte im Consumer Bereich ist die 7800 GTX von Nvidia, die auf der G70 Architektur basiert. Sie besitzt 8 Vertex-Einheiten und eine Fragment-Einheit mit 24 Pipelines, die es erlauben, Berechnungen gleichzeitig parallel auf mehreren Daten auszuführen. Durch die Einführung von programmierbaren Shader-Einheiten, lässt sich diese Leistung nicht nur alleine für die graphische Darstellung von Dreiecken nutzen, sondern auch für viele weitere Anwendungen. Diese Anwen- General Purpose dungen werden unter dem Begri GPGPU ( ons on Graphic Processor Units) computati- zusammengefasst. Die Umsetzung eines Raycasting-Algorithmus für die Berechnung auf der Grakhardware ist ein Beispiel für eine dieser Anwendungen. Es gibt aber weiterhin noch zahlreiche Einschränkungen, die bei der Programmierung beachtet werden müssen, auch wenn diese meistens durch kommende Hardwaregenerationen aufgehoben werden. Diese Arbeit wurde kurz vor dem Erscheinen der G70 Architektur begonnen und die Implementierung in dieser Arbeit orientiert sich aus diesem Grund noch an den Features, die Nvidia seit der Vorherigen, der NV4x Architektur eingeführt hat. Für die Nvidia 6800 GT Ultra Grakkarte z.B. sind es [11]: • 256Bit Prozessor • 256Bit Speicherbus zu GDDR3 Speicher • 6 Vertex-Shader Einheiten in MIMD • 16 Pixel-Pipelines mit 32Bit Floating-Point Präzision in SIMD 8 Bauweise 9 Bau- weise • 1 Textur-Einheit/Pipe • 10 Bit/Farbkanal • Vertex-Shader Version 3.0 Unterstützung • Pixel-Shader Version 3.0 Unterstützung • DirectX Version 9.0c Unterstützung Im Detail sind für Pixel-Shader 3.0 folgende Eigenschaften festgelegt: • 8 9 kein Dependent Texture Limit Multiple Input, Multiple Data. Single Instruction, Multiple Data. 19 • unbegrenzte Texture Instruktionen • Position Register • >= 512 Instruktion Slots • 65535 Executed Instruktionen • 10 Interpolated Registers • Instruction Predication • Indexed Input Registers • 32 Temp Registers • 224 Constant Registers • Arbitrary Swizzling • Gradient Instructions • Loop Count Register • Face Register (2-sided lighting) • Dynamische Flusskontrolle mit einer Tiefe von 24 Für Vertex-Shader 3.0 gelten folgende Eigenschaften: • >= 512 Instruction slots • 65535 Maximal ausführbare Instructionen • Instruction Predication • 32 Temp. Register • >= 256 Konstanten Registers • Static Flow Control • Dynamische Flusskontrolle mit einer Tiefe von 24 • Vertex Texture Fetch • 4 Texture Samplers • Geometry Instancing Support Durch diese Fülle an Funktionen bietet sich die NV40 Hardware geradezu für das Raycasting an. Sie bietet einen Fragment-Prozessors mit vielen Pipelines für das parallele Ausführen von Operationen, eine Rechengenauigkeit von 32Bit Floating Point Werten und die Möglichkeit der Flusskontrolle im Shader (Branching). Die sich dadurch ergebenden Möglichkeiten werden in einem späteren Kapitel bei der Implementierung erläutert. 20 3.2 GPU Programmiersprachen Zuerst mussten die Shaderprogramme in Maschinensprache programmiert werden. Vereinfacht wurde die Programmierung durch Einführung von ShaderHochsprachen wie Nvidias c for graphics (cg), Microsofts High Level Shader Language (HLSL) oder 3Dlabs OpenGL Shader Language (GLSL), die sich allgemein sehr ähnlich sind. Nach einer kurzen Einarbeitung in die genannten Sprachen wurde GLSL für die Implementierung gewählt. 3.2.1 OpenGL Shader Language Die OpenGL Shader Language wurde als Erweiterung von OpenGL entwickelt, um die bisherigen Beschränkungen durch die xed-functionality der traditionellen OpenGL Rendering-Pipeline aufzuheben. Die Sprache ist in der Syntax an C orientiert worden. Dies ermöglicht es zukünftigen GLSL Entwicklern einen schnellen Einstieg zu nden. Zusätzlich wurde die Sprache um mitgelieferte Funktionen erweitert, die gerade bei Algorithmen zu Berechnung von Grak häug verwendet werden. Die Sprache wird gemeinsam für den Vertex- oder Fragment-Shader verwendet, unterscheidet sich aber in der Verwendung in wenigen Details. Gemeinsamkeiten zu C sind[9]: • Shader starten bei void main() • Geschweifte Klammern um Funktionen abzugrenzen • Konstanten, Bezeichnern, Operatoren, Expressions und Statements • Flusskontrolle für Schleifen, if-then-else und Funktionsaufrufe Erweiterungen zu C sind[9]: • Vektor Typen für Floation-Point-, Integer- und Boolean Werte • Floating-Point Matrizen • Sampler Typen um auf den Textur-Speicher zuzugreifen • Qualier für die Ein- und Ausgaben und für die Kommunikation zwischen den Shadern • Traditionelle OpenGL States als mitgelieferte Variablen zur Kommunikation zur xed-functionality. • Eine Vielzahl an mitgelieferten Funktionen um die Programmierung zu vereinfachen. Dazu zählen die Berechnungen von: 21 trigonometrischen Funktionen (Sinus, Cosinus, Tangenz, ...) Exponentialfunktionen (Quadrat, Exponent, Logarithmus, Quadratwurzel) allgemeinen mathematischen Funktionen (Betrag, Ober- und Untergrenze, Modulo, ...) geometrischen Funktionen (Länge, Distanz, Skalarprodukt, Kreuzprodukt, Normalisierung, ...) Vergleichsoperatoren auf Vektoren (grösser, kleiner, gleich, ..) Aus C++ wurden übernommen[9]: • Überladen von Funktionen • Deklaration von Variablen, wenn sie benötigt werden. Nicht aus C enthalten sind[9]: • Automatische Umwandlung von Datentypen • Zeiger, Strings- und Character-Datentypen • Double-precision Floats, Byte, Short, Long-Integer • Referenzen auf Dateien wie z.B. • Casting von Datentypen ohne eine Umwandlung #include Mittels dieser Sprache für die frei programmierbaren Shader-Einheiten ergeben sich für den Programmierer eine Vielzahl neuer Möglichkeiten. Dazu zählen z.B.[9]: • Realistische Materialoberächen, wie Metall, Stein, usw. • Realistische Lichteekte, wie Area-Lights, Soft-Shadows, usw. • Darstellung natürlicher Phenomäne, wie Feuer, Rauch, Wasser, Wolken, usw. • Nicht photorealistisches Rendering • Verwenden des Textur-Speichers zum Speichern von Normalen, Glanzwerten, Polynomialen Koezienten, usw. • Reduzieren von Textur-Zugrien durch Erzeugen von prozeduralen Texturen 22 • Bildbearbeitungfunktionen, wie Faltung, unscharf Maskieren, Blending, usw. • Animationseekte, wie Key-Frame-Interpolation, Partikelsysteme, prozedural denierte Bewegungen • Vom Benutzer programmierbare Antialiasing Methoden Es muss allerdings beachtet werden, dass durch einen Shader die gesamte Berechnung betreend der Verticies oder Fragmente ersetzt wird. Es ist nicht möglich, eine Transformation über die xed-function Pipeline durchführen zu lassen und dann einen Vertex-Shader für die Beleuchtung zu nutzen. Allerdings lassen sich in einem Shader die traditionellen OpenGl-States nutzen. Auch werden durch den Fragment-Prozessor nicht die xed-functions am Ende der OpenGL-Rendering-Pipeline wie z.B. der Alpha-, Tiefen- oder Stencil Test ersetzt. 3.3 Bisherige Ansätze zum GPU-Raycasting Es existieren bereits mehrere Ansätze das Raycasting zu beschleunigen oder auch speziell auf GPUs anzupassen, um deren Rechenleistung zu nutzen. Beispiele hierfür sind: • Smart Hardware-Accelerated Volume Rendering [3] • Acceleration Techniques for GPU-based Volume Rendering [4] • Advanced GPU Raycasting [5] Die oben genannten Arbeiten vertreten alle die Auassung, dass gerade die besondere Fähigkeit der GPUs, mehrere unabhängige Prozesse in den mehrfach vorhandenen Pipelines parallel ausführen zu können, die Berechnung des Raycasting mit seinen vielen voneinander unabhängigen Lichtstrahlen, beschleunigt. Die Arbeit Acceleration Techniques for GPU-based Volume Rendering wird im Folgenden ausführlicher vorgestellt, da sie einige Ideen beinhaltet, wie das Raycasting einfach, aber auch ezient auf einer GPU umgesetzt werden kann. Die Arbeit Advanced GPU Raycasting baut auf der bereits genannten Arbeit auf und erweitert diese um weitere interessante Ansätze. 3.3.1 Acceleration Techniques for GPU-based VR Die Implementierung dieser Studienarbeit soll mit der Early-Ray-Termination eine Methode umsetzen, die von J.Krüger und R.Westermann in [4] für 23 die Anwendung auf einer GPU entwickelt wurde. Diese Methode soll dabei auf die verbesserten Möglichkeiten der aktuellen Grakhardware angepasst werden, die es im Gegensatz zu der von Krüger und Westermann verwendeten Hardware erlaubt, Schleifen und Conditional-Breaks zu benutzen. Um die Beschränkungen der nicht verfügbaren Schleifen zu umgehen, wurden die Algorithmen in der Arbeit von Krüger und Westermann in einem Multipass-Verfahren umgesetzt, bei denen die Ergebnisse zwischen den Rendering Durchläufen in Texturen gespeichert wurden. Algorithmus Zuerst werden die Volumendaten als eine 3D Textur im Spei- cher der Grakkarte abgelegt. Nun werden die Sehstrahlen auf das Volumen geschickt, und die Koordinaten der Schnittpunkte zwischen dem Sehstrahl und dem Volumen errechnet. So lassen sich später die Richtungsvektoren der Sehstrahlen sehr einfach bestimmen. Dazu wird eine Bounding-Box in Form eines Würfels um das Volumen erzeugt (Abbildung 13). Die TexturKoordinaten der Front-Faces dieser Box, die gleichzeitig auch die Eintrittspunkte des Strahls in das Volumen sind, werden als RGB-Farbwerte temporär in eine 2D Textur geschrieben. Ebenso werden die Textur-Koordinaten der Back-Faces dieser Box bestimmt, also die Austrittspunkte des Strahls aus dem Volumen. Anschlieÿend werden die Farbwerte der vorher ermittelten Font-Faces subtrahiert. Das Ergebnis der Subtraktion wird normalisiert und ergibt den Richtungsvektor des Sehstrahls, der nun in den RGB-Werten einer weiteren 2D Textur geschrieben wird. Die Länge der Strahls, also der des nicht normalisierten Richtungsvektors, wird in die Alpha-Komponente dieser Textur geschrieben. Abbildung 13: Front- und Backfaces[4] Beim eigentlichen Raycasting werden nun die Farbwerte für jeden Pixel des Ausgabebildes bestimmt. Dazu werden die Front-Faces nochmals berechnet und nun für jeden Pixel solange entlang des ermittelten Richtungsvektors das Volumen abgetastet, bis der Opazitätswert einen bestimmten Grenzwert überschreitet oder der Strahl das Volumen verlässt. Die Länge des Vektor überschreitet dabei den in der Alpha-Komponente gespeicherten Wert. Die 24 Abtastwerte werden für das Compositing in einer weiteren 2D Textur gespeichert. Empty-Space-Skipping Eine weitere Berechnungsvereinfachung ist das Überspringen von leeren Regionen oder die Vergröÿerung der Schrittweiten bei einheitlichen Regionen im Volumen. Dadurch wird die Anzahl der durchzuführenden Operationen verringert und die Geschwindigkeit der Berechnung kann beträchtlich gesteigert werden. Für die Realisierung des EmptySpace-Skipping wird eine zusätzliche Datenstruktur für die Speicherung der Positionen der leeren Regionen im Volumen verwendet, wie z.B. eine OctreeHierachie. Dabei werden in den inneren Knoten statistische Informationen über die äuÿeren Knoten gespeichert, z.B. die Grenzen der Region, die von einem Knoten abgedeckt werden. Mit Hilfe dieser Informationen kann die Sampling-Distanz entlang des Strahls erhöht werden, sobald der Strahl auf eine leere Region trit. Im verwendeten Beispiel von J.Krüger und R.Westermann wurde eine Rastergröÿe von 83 Zellen gewählt. Der minimal- und maximal- Wert der in diesem Block enthaltenen Skalarwerte wird nun in den RGWerten einer 3D-Textur gespeichert, die folglich nur noch 1/8 der Grösse des Volumens besitzt. Die RG-Werte indexieren eine weitere 2D-Textur CT, die bei jedem existierenden min/max Wertepaar kontrolliert, ob der enthaltene Wert von 0 abweicht. Sollte der Raum leer sein wird der gesamte Bereich übersprungen. Implementierung Die Implementierung der Arbeit von J.Krüger und R.Westermann basiert auf der Pixel Shader 2.0 API von Microsoft und wurde für die Ausführung auf einer ATI 9700 angepasst. 10 . Diese Hardware ermöglicht: • Per-fragment texture fetch operations: Zugri eines Pixel-Shaders auf bis zu 8 verschiedene Texturen • Texture-render-target: Erzeugen einer 2D-Textur, in die direkt auf den Viewport ausgerichtet gerendert, und auf die in folgenden Rendering Schritten zugegrien werden kann. Diese dient der Zwischenspeicherung der Rendering Ergebnisse. Die Textur kann Floating-Point Werte, sowie negative Werte beinhalten. • Texture-coordinate generation: Es ist möglich mit einem Shader-Programm direkt über Textur-Koordinaten auf Texturen zuzugreifen oder diese zu manipulieren. 10 Die Implementierung sollte auch auf allen neueren Grakkarten lauähig sein 25 • Per-fragment arithmetic: Es können im Shader Programm arithmetische Operationen auf Skalaroder Vektor-Variablen ausgeführt werden 11 . • Depth replace: Es können beliebige Werte im z-Buer als Tiefenwerte für ein Fragment geschrieben werden. Ergebnis J.Krüger und R.Westermann konnten in ihrer Arbeit die Anzahl der benötigten Fragment-Operationen durch die Verwendung von Early-RayTermination und Empty-Space-Skipping verringern. Allerdings ergab sich durch die Tatsache, dass bei ihnen hardwarebedingt ein Shader-Programm nicht abgebrochen werden konnte, ein Problem. Der eigentliche RenderingSchritt musste in mehrere Durchläufe mit einer festen Anzahl an Abtastpunkten aufteilt werden, um so nach einem Durchlauf zu prüfen, ob weitere Durchläufe nötigt sind, um das Volumen zu durchqueren. Schwierig war ein optimales Verhältnis zwischen der Anzahl der Durchläufe und Anzahl der Abtastschritte pro Durchlauf zu nden. Durch das Festlegen der Arbeitsschritte konnte sichergestellt werden, dass die Anzahl der Berechnungen in einem Durchlauf diesen Wert nicht überschreiten würde. Bei dem relativ ungünstigen Fall, dass direkt im ersten Schritt, entweder der Opazitätswert das Maximum erreicht oder der Strahl das Volumen verlässt, ist immer sichergestellt, dass nach diesen Schritten der Durchlauf beendet wird. Es wäre daher sinnvoll diesen Wert relativ klein zu wählen. Allerdings erhöht sich dadurch aber auch die Anzahl der Durchläufe und damit ein gewisser Overhead, der durch das Rendern der Front-Faces und der Zugrie auf die Texturen entsteht. Optimaler ist es, die Anzahl der Durchläufe und die der Arbeitsschritte gleichzusetzen. Im Falle der Implementation auf der ATI 9700 Karte war dies allerdings nicht möglich, da die Anzahl der Arbeitsschritte durch die Anzahl der Hardware-Shader auf 8 limitiert wurde. Des Weiteren versagt die Optimierung, wenn alle Abtastpunkte entlang des Strahls bis zum Verlassen des Volumens berechnet werden müssen, weil sie z.B. stark transparent sind oder eine hohe Dichte aufweisen. 3.3.2 Advanced GPU Raycasting Die Ansätze von J.Krüger und R.Westermann wurden in der Arbeit von H.Scharsach [5] um weitere Techniken erweitert. Bounding Geometrie Während für die eigentliche Berechnung der Seh- strahlen nur die Fragment-Einheit der GPU genutzt wird, lässt sich die 11 d.h. Operationen wie +, -, *, dot oder Quadratwurzel 26 Vertex-Einheit z.B. dazu nutzen, die Bounding-Box der Struktur des Volumens anzupassen, indem die Bounding-Box um existierende Freiräume verkleinert wird (Abbildung 14). Abbildung 14: Bounding Geometrie[5] Bei dem von J.Krüger und R.Westermann vorgestellten Empty-SpaceSkipping wurde von Bounding-Box in Form eines Würfels mit 6 Flächen ausgegangen. Die Rechenleistung heutiger Vertex-Einheiten erlaubt es mühelos ein Vielfaches an Flächen zu verwalten 12 . Mit dem von J.Krüger und R.Westermann vorgestellten Blocking-Verfahren wird für jeden Block überprüft, ob der Inhalt für das Rendering interessant ist. Allerdings muss nebem dem eigentlichen Block auch dessen unmittelbares Umfeld betrachtet werden, da sich sonst durch die im Volumen verwendeten Filter Fehler ergeben können. Da nun diese Bounding-Geometrie als Grundlage für das Raycasting verwendet wird, gestaltet sich die Unterscheidung in Front- und Back-Faces etwas schwieriger, da nicht unbedingt von einer konvexen Struktur ausgegangen werden kann. Es wird somit zwischen dem ersten Front-Face und dem letzten Back-Face unterschieden (Abbildung 15). Interleaved Sampling Die Berechnung von regulären Sampling-Pattern sind einfach und schnell, führt aber zu dem Problem, dass über diesen Weg auch Aliasing-Fehler entstehen können, die das Resultat verfälschen, z.B. in Form von Ringstrukturen (Abbildung 16). Das Interleaved Sampling basiert auf einer Arbeit von A. Keller and W. Heidrich[7] und versucht dieses Problem durch die Verwendung von irregulären Pattern zu umgehen, die mehrere Pixel abdecken. Dabei wird ein kleiner Oset in z-Richtung zwischen den Pixeln verwendet, die in unmittelbarer Nähe zueinander stehen, ähnlich dem des Supersamplings. Allerdings kann das Ergebnis des Interleaved-Sampling auch zu ungewünschten Eekten führen, z.B. wenn sich unmittelbar hintereinander zwei sehr dünne Strukturen im Volumen benden sollten. Das Interleaved-Sampling führt nun dazu, dass im 12 Die Anzahl sollte sich aber an der Shadereinheit der verwendeten Hardware orientieren um die Gesamtleistung des Systems nicht auszubremsen 27 Abbildung 15: First front-face und last back-face[5] Abbildung 16: Interleaved Sampling[7] Ergebnisbild bei benachbarten Pixeln der Wert einmal aus der einen Struktur, und mal aus der anderen Struktur entnommen wird. Dies resultiert dann in einem Dithermuster in den Farben der beiden Strukturen (Abbildung 17). Abbildung 17: Dithering[5] Umgangen werden kann der Eekt durch Verringern des Osets. Sollte dies nicht möglich sein, weil der Oset schon den kleinstmöglichen Wert besitzt, zeigt dies, dass die Samplingrate des vorliegende Datensatz erhöht werden sollte, um solch feine Strukturen unter Verwendung des Interleaved Sampling korrekt anzeigen zu können. Das Interleaved Sampling kann 28 im Fragment Shader einfach durch eine Modulo-Funktion der Bildschirmkoordinaten realisiert werden. Dieses Verfahren führt nicht zu wesentlichen Einbuÿen in der Geschwindigkeit, und erzeugt dafür aber ein korrektes Ausgabebild. 29 4 Implementierung 4.1 Ziel Das Ziel der Implementierung war es ein Programm zu entwickeln, mit dem es möglich ist, den berechnungsintensiven Algorithmus des Raycastings auf einer GPU zu berechnen. Der Ablauf sollte sich dabei an der bereits im Punkt 3.3.1 beschriebenen Arbeit Acceleration Techniques for GPU-based Volume Rendering von J.Krüger und R.Westermann orientieren und die darin beschriebenen Ansätze, wie z.B. das Verfahren zur Richtungsbestimmung der Sehstrahlen oder das Early-Ray-Termination berücksichtigen. Weiterhin ging es darum, bei der Entwicklung den aktuellen Stand der Technik zu berücksichtigen um so das beschriebene Verfahren evtl. weiter zu beschleunigen. Das in dieser Studienarbeit entwickelte Programm basiert auf OpenGL und der OpenGL Shader Language. Die Wahl von OpenGL ermöglicht theoretisch den Einsatz auf unterschiedlichen Betriebssystemen und Hardwareumgebungen. Die Entwicklung der Anwendung erfolgte unter WindowsXP mit einem OpenGL 2.0 unterstützendem Grakkartentreiber. Auf eine Bedienungsoberäche wurde verzichtet, stattdessen wurde GLUT für die Interaktion mittels der Maus oder Tastatur eingesetzt. Bei nur einer geringen Anzahl an Interaktionsmöglichkeiten war dies absolut ausreichend. 4.2 Aufbau Die Implementierung ist in ein Hauptprogramm und mehrere Shader-Programme gegliedert. 4.2.1 Das Hauptprogramm Der Ablauf des Hauptprogramms lässt sich folgendermaÿen darstellen: 1. Initialisieren von OpenGL und GLUT 2. Initialisieren und Laden von Texturen: • Eine 1D-Textur zur Speicherung einer Lookup-Tabelle für die Transferfunktion. • Zwei 2D-Texturen, in denen jeweils die Front- und Back-Faces des Würfels zwischengespeichert werden. • Eine 3D-Textur, in welche die Skalardaten des darzustellenden Volumens geladen werden. 3. Laden, Kompilieren und Linken der zu verwendenden Shader. 4. Darstellung des Farbwürfels mittels der OpenGL xed-function Pipeline oder mittels Shader-Programm. 30 5. Zwischenspeichern der Front- und Backface-Ansichten des Würfels in den 2D-Texturen. 6. Starten von Fragment-Shadern für die Berechnung des Raycastings. 7. Interaktionen: • Rotation und Skalierung des Würfels. • Wechseln zwischen unterschiedlichen Volumen. • Wechseln zwischen verschiedenen Optischen Modellen (in Form unterschiedlicher Fragment-Shader). • Ändern der Anzahl von Abtastungen (Steps). • Gewichtung der Opazität (nur beim Emission-Absorptions Modell). • Verschieben der Farben bei der Transfer-Funktion (nur beim EmissionAbsorptions Modell mit Transfer-Funktion). • Messen der Berechnungsgeschwindigkeiten bei einer 360 ◦ Drehung des Volumens. 4.3 Die verwendeten Shader Es wurden folgende Shader benutzt oder entwickelt: • colorcube.vert + colorcube.frag 13 : Shader-Programme um einen RGB-Farbwürfel zu erzeugen. • emabs-kw.frag: Fragment-Shader für die Darstellung des Emission-Absorbtions Compositings. • mip.frag: Fragment-Shader für die Darstellung des Maximum-Intensity-Projection Compositings. • mean.frag: Fragment-Shader für die Darstellung des Mittelwert Compositings. • m.frag: Fragment-Shader für die Darstellung des Raycastings mit erstem lokalem Maximum. 13 Übernommen aus den OpenGL Shading Language Tutorials von M. Christen[14] 31 • emabs-tf.frag: Fragment-Shader für die Darstellung des Emission-Absorbtions Compositings mit Transfer-Funktion. • emabs-ert.frag: Fragment-Shader für die Darstellung des Emission-Absorbtions Compositings mit Early-Ray-Termination. • emabs-ess.frag: Fragment-Shader für die Darstellung des Emission-Absorbtions Compositings mit vereinfachtem Empty-Space-Skipping. • emabs-sm.frag: Fragment-Shader für eine alternative Darstellung des Emission-Absorbtions Compositings. 4.3.1 colorcube.vert + colorcube.frag Das Färben des Würfels in die Farben seiner Koordinaten geschieht über eine Kombination eines Vertex- und Fragment-Shaders und lässt sich mit wenigen Programmzeilen realisieren (Abbildung 18, Abbildung 19). varying float xpos; varying float ypos; varying float zpos; void main(void){ xpos = clamp(gl_Vertex.x,0.0,1.0); ypos = clamp(gl_Vertex.y,0.0,1.0); zpos = clamp(gl_Vertex.z,0.0,1.0); } gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; Abbildung 18: Color-Cube Vertex-Shader Zunächst werden die Vertex-Positionen des Würfels im Vertex-Shader bestimmt und auf einen Wertebereich von 0-1 übertragen (geclampt). Die Koordinaten werden mittels varying-Variablen an den Fragment-Shader übergeben, der diese Koordinaten als Farbinformationen für die Pixel der Oberäche des Würfels ausgibt. 32 varying float xpos; varying float ypos; varying float zpos; void main (void){ } gl_FragColor = vec4 (xpos, ypos, zpos, 1.0); Abbildung 19: Color-Cube Fragment-Shader 4.3.2 emabs-kw.frag Dieses Fragment-Shader Programm zeigt eine Implementation des Raycastings nach dem von J.Krüger und R.Westermann vorgestellten Verfahren in der OpenGL Shader Language (Abbildung 20). Die weiteren FragmentProgramme basieren auf dieser Struktur und wurden entweder um einige Code-Zeilen gekürzt oder erweitert. Die Änderungen werden in den jeweiligen Listings farblich kenntlich gemacht. Zu Beginn des Fragment-Shaders werden aus der 2D-Textur mit der Darstellung der Front-Faces des Farbwürfels die Eintrittspunkte der Sehstrahlen in das Volumen ausgelesen und in einem Vektor texFront gespeichert. Eben- so werden die Daten der Austrittspunkte über die 2D-Textur der Back-Faces in einem weiteren Vektor texBack abgelegt. Über eine Subtraktion des ersten Vektors von dem Zweiten lässt sich die Richtung der Sehstrahlen ermitteln. Diese wird normalisiert in einem weiteren Vektor direction gespeichert. In dem Sehstrahl wird jetzt über die nicht normalisierte Länge des Richtungsvektors hinweg jeweils der Abtastpunkt ermittelt und der enthaltene Voxelwert in ses alpha alpha gespeichert. Die Opazität wird anschlieÿend über die- und einem gewählten Faktor opacityfac bestimmt. Im folgenden Schritt erfolgt nun die Berechung des Ausgabewertes output unter Berück- sichtigung des aktuellen und der bereits ermittelten alpha-Werte. Danach wird der nächste Abtastpunkt bestimmt. Sobald alle Abtastpunkte entlang des Sehstrahls ausgewertet wurden, wird der für den aktuellen Pixel bestimmte Farbwert ausgegeben. 4.3.3 mip.frag Bei diesem Programm wird eine Maximum-Intensity-Projektion ausgeführt, bei der kein Blending, sondern eine Anzeige der Abtastpunkte mit der höchsten Ausprägung erfolgt (Abbildung 21). 33 4.3.4 mean.frag In diesem Programm wird der Mittelwert über alle Abtastpunkte eines Sehstrahls errechnet und anschliessend dargestellt. Den Unterschied zu dem Emission-Absorptions Modell sieht sieht man in Abbildung 22. Die Werte der Abtastpunkte werden dazu aufaddiert (output += alpha) und vor der Ausgabe als Fragmentfarbe durch die Anzahl der abgetasteten Punkte dividiert (output /= alphacount). 4.3.5 m.frag Bei diesem Programm wird entlang eines Sehstrahls das erste auftretende Maximum bestimmt (Abbildung 23). Die Schleife wird sofort verlassen, sobald es einen Abtastwert mit einem niedrigeren Skalarwert geben sollte. 4.3.6 emabs-tf.frag Bei diesem Programm wurden zu dem Emission-Absorptions Shader eine Transfer-Funktion hinzugefügt (Abbildung 24). Dem aus dem Volumen ermittelten Abtastwert wird der Farbwert aus einer Lookup-Tabelle texUnit1D alphaTF zugeordnet. 4.3.7 emabs-ert.frag Bei diesem Programm wurde dem bereits vorgestellten Emission-Absorptions Shader nur eine Programmzeile für die Early-ray-Termination hinzugefügt (Abbildung 25). Das Programm springt aus der Schleife sobald der Abtastwert sein Maximum erreicht hat. 4.3.8 emabs-ess.frag Bei diesem Programm wurde dem bereits vorgestellten Emission-Absorptions Shader eine Bedingung für die Berechnung des Ausgabewertes hinzugefügt (Abbildung 26). Die Berechnung wird übersprungen, falls an der Abtaststelle kein Skalarwert vorhanden ist (alpha = 0). 4.3.9 emabs-sm.frag Dieser Shader unterscheidet sich vom bereits vorgestellten Verfahren in der Längenbestimmung des Sehstrahls (Abbildung 27). 34 Hierbei wird nicht die in den anderen Verfahren ermittelte Distanz zwischen Eintritts- und Austrittspunkt des Sehstrahls in das Volumen berücksichtigt, sondern für jeden Abtastpunkt über jeweils zwei if-Abfragen bestimmt, ob er sich innerhalb des Volumens bendet. Der Durchlauf über die Abtastpunkte erfolgt über eine for-Schleife mit int-Werten. Diese Maÿnahme erhöht die Geschwindigkeit ein wenig, obwohl dies eine zusätzliche Division im Shader erfordert. 4.4 Die verwendeten Volumendaten Die folgenden Volumendatensätze wurden bei der Implementierung geladen um die Darstellungsqualität, die Funktionen und die Geschwindigkeit der Implementierung zu testen. Die Datensätze sind jeweils auf eine Auösung von 8bit beschränkt. Andere Auösungen wurden in der Implementierung nicht berücksichtigt. • head256.raw[15]: Zeigt eine CT-Aufnahme eines Kopfes. Auösung: 256x256x225 Pixel; 1,1,1 Schicht-Dicke; 8Bit Auösung. • BostonTeapot.raw[13]: Zeigt eine CT-Aufnahme des Boston-Teapots mit einem innenliegenden Hummer. Auösung: 256x256x178 Pixel; 1,1,1 Schicht-Dicke; 8Bit Auösung. • engine.raw[13]: Zeigt eine CT-Aufnahme eines Motorblocks. Auösung: 256x256x128 Pixel; 1,1,1 Schicht-Dicke; 8Bit-Auösung. • backpack8.raw[13]: Zeigt eine CT-Aufnahme eines mit Gegenständen gefüllten Rucksacks. Auösung: 512x512x373 Pixel, 0.9766, 0.9766, 1.25 Schicht-Dicke, 8Bit Auösung. 35 //GPU EMISSION-ABSORPTION - KRUEGER WESTERMANN uniform sampler2D texUnit1; uniform sampler2D texUnit2; uniform sampler3D texUnit3D; uniform float stepsize; uniform float opacityfac; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); float output = 0.0; float alpha; float opacity; for (float dirlength = length(texBack - texFront); dirlength > 0.0; dirlength - stepsize){ alpha = (texture3D(texUnit3D, texFront)).g; //weight opacity opacity = (alpha/opacityfac); //raycast output = opacity * alpha + (1.0 - opacity)*output; //next position texFront = texFront + (stepsize * direction); } } gl_FragColor = vec4 (output,output,output,1.0); Abbildung 20: Emission-Absorption Fragment-Shader 36 //GPU EMISSION-ABSORPTION - KRUEGER WESTERMANN uniform sampler2D texUnit1; uniform sampler2D texUnit2; uniform sampler3D texUnit3D; uniform float stepsize; uniform float opacityfac; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); float output = 0.0; float alpha; float opacity; for (float dirlength = length(texBack - texFront); dirlength > 0.0; dirlength - stepsize){ alpha = (texture3D(texUnit3D, texFront)).g; if (alpha > output) output = alpha; //next position texFront = texFront + (stepsize * direction); } } gl_FragColor = vec4 (output,output,output,1.0); Abbildung 21: Maximum Intensity Projection 37 //GPU FIRST LOCAL MAXIMUM uniform sampler2D texUnit1; uniform sampler2D texUnit2; uniform sampler3D texUnit3D; uniform float stepsize; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); float output = 0.0; float alpha; float opacity; int alphacount; for (float dirlength = length(texBack - texFront); dirlength > 0.0; dirlength - stepsize){ alpha = (texture3D(texUnit3D, texFront)).g; ++alphacount; output += alpha; //next position texFront = texFront + (stepsize * direction); } output /= alphacount; } gl_FragColor = vec4 (output,output,output,1.0); Abbildung 22: Mittelwert 38 //GPU FIRST LOCAL MAXIMUM uniform sampler2D texUnit1; uniform sampler2D texUnit2; uniform sampler3D texUnit3D; uniform float stepsize; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); float output = 0.0; float alpha; float opacity; for (float dirlength = length(texBack - texFront); dirlength > 0.0; dirlength - stepsize){ alpha = (texture3D(texUnit3D, texFront)).g; if (output <= alpha){ output = alpha; } else break; //next position texFront = texFront + (stepsize * direction); } } gl_FragColor = vec4 (output,output,output,1.0); Abbildung 23: First Local Maximum 39 //GPU EMISSION-ABSORPTION - TRANSFER FUNCTION uniform uniform uniform uniform sampler1D sampler2D sampler2D sampler3D texUnit1D; texUnit1; texUnit2; texUnit3D; uniform float stepsize; uniform float opacityfac; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); vec3 output = vec3(0.0, 0.0, 0.0); float alpha; float opacity; vec3 alphaTF; for (float dirlength = length(texBack - texFront); dirlength > 0.0; dirlength - stepsize){ alpha = (texture3D(texUnit3D, texFront)).g; alphaTF = (texture1D(texUnit1D, alpha).rgb); //weight opacity opacity = (alpha/opacityfac); //raycast output = opacity * alphaTF + (1.0 - opacity)*output; //next position texFront = texFront + (stepsize * direction); } gl_FragColor = vec4 (output,1.0); } Abbildung 24: Transfer-Funktion 40 //GPU EMISSION-ABSORPTION - EARLY-RAY-TERMINATION uniform sampler2D texUnit1; uniform sampler2D texUnit2; uniform sampler3D texUnit3D; uniform float stepsize; uniform float opacityfac; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); float output = 0.0; float alpha; float opacity; for (float dirlength = length(texBack - texFront); dirlength > 0.0; dirlength - stepsize){ alpha = (texture3D(texUnit3D, texFront)).g; //weight opacity opacity = (alpha/opacityfac); //raycast output = opacity * alpha + (1.0 - opacity)*output; if (output >= 1.0) break; //next position texFront = texFront + (stepsize * direction); } } gl_FragColor = vec4(output,output,output,1.0); Abbildung 25: Early-Ray-Termination 41 //GPU EMISSION-ABSORPTION - EMPTY-SPACE-SKIPPING uniform sampler2D texUnit1; uniform sampler2D texUnit2; uniform sampler3D texUnit3D; uniform float stepsize; uniform float opacityfac; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); float output = 0.0; float alpha; float opacity; for (float dirlength = length(texBack - texFront); dirlength > 0.0; dirlength - stepsize){ alpha = (texture3D(texUnit3D, texFront)).g; if (alpha > 0.0){ //weight opacity opacity = (alpha/opacityfac); //raycast output = opacity * alpha + (1.0 - opacity)*output; } //next position texFront = texFront + (stepsize * direction); } } gl_FragColor = vec4 (output,output,output,1.0); Abbildung 26: Einfaches Empty-Space-Skipping 42 //GPU EMISSION-ABSORPTION - MODIFIED uniform sampler2D texUnit1; uniform sampler2D texUnit2; uniform sampler3D texUnit3D; uniform int steps; uniform float opacityfac; void main (void){ vec3 texFront = texture2D(texUnit1, gl_TexCoord[0].st); float output = 0.0; if ((texFront.r > 0.0) ||( texFront.g > 0.0) || (texFront.b > 0.0)){ vec3 texBack = texture2D(texUnit2, gl_TexCoord[0].st); vec3 direction = normalize(texBack - texFront); float alpha; float opacity; float stepsize = 1.73/steps; for (int step = 0; step < steps; ++step){ alpha = texture3D(texUnit3D, texFront).g; //weight opacity opacity = alpha/opacityfac; //raycast output = opacity * alpha + (1.0 - opacity)*output; //next position texFront = texFront + (stepsize * direction); } } //ray is outside volume if ((texFront.r >= 1.0) || (texFront.g >= 1.0) || (texFront.b >= 1.0)){ break; } } gl_FragColor = vec4 (output,output,output,1.0); 43 Abbildung 27: Modizierter Emission-Absorbtion Fragment-Shader 5 Visuelle Beurteilung Im Folgenden werden die Bildausgaben der Implementierung je nach gewähltem Volumendatensatz bewertet. Die Screenshots wurden auf einem 14 und einem WindowsXP System mit einer Nvidia 6600 GT Grakkarte OpenGL2.0 kompatiblen Treiber mit der Version 77.76 erstellt. 5.1 Ergebnisse 5.1.1 Emission-Absortion Alle Emission-Absortions-Shader (emabs-kw.frag, emabs-ert.frag, emabs-ess.frag, emabs-sm.frag) liefern für die ausgewählten Volumendatensätze die in Abbildung 28 dargestellten Ergebnisse. 5.1.2 Maximum-Intensity-Projection Die Ergebnisse des Maximum-Intensity-Projection Shaders kann man in Abbildung 29 sehen. Bei den Aufnahmen der Maximum-Intensity-Projection werden die Bereiche im Volumen mit der höchsten Dichte hervorgehoben. 5.1.3 Mittelwert Die Ergebnisse des Mittelwert Shaders werden in Abbildung 30 dargestellt. Die Darstellungen sehen denen von Röntgenaufnahmen ähnlich, da sich jedes Voxel entlang eines Sehstrahls zu gleichen Teilen auf die Ausgabe auswirkt. 5.1.4 First-Local-Maximum Die Ergebnisse des First-Local-Maximum sind in Abbildung 31 zu sehen. Die Ergebnisse sind auf den ersten Blick verwirrend und es entsteht der Eindruck, als würden sehr viele Fehler in den Bildern auftreten. Die Berechnung sollte aber korrekt sein und die unbrauchbare Visualisierung ergibt sich durch die Tatsache, dass in den Volumen ein Rauschen enthalten ist. Sobald der Sehstrahl nur auf einen einzigen verrauschten Punkt treen sollte, wird die Berechnung abgebrochen. Der einzige Volumensatz, der weitestgehend ohne Fehler dargestellt wird, ist der des Teapots(b). Dieser Shader eignet sich gut zur Oberächenerkennung, jedoch nur, wenn die Aufnahmen frei von Störungen sind. 14 Diese ist zu der in Abschnitt 3.1 vorgestellten Hardware der Nvidia 6800 Ultra in den Funktionen voll kompatibel, allerdings in der Taktrate für Prozessor und Speicher und der Anzahl der Pipelines für die Vertex- und Fragmenteinheit beschränkt worden. 44 5.1.5 Emission-Absortion mit Transferfunktion Der Emission-Absortions Shader mit aktivierter Transferfunktion liefert die in Abbildung 32 gezeigten Ergebnisse. Die Ergebnisse verdeutlichen die Funktion der Lookup-Tabelle. Die Transferfunktion ist in diesen Beispielen in nur zwei Stufen eingeteilt und es erfolgt bei einem Dichtewert von 37,5 % vom Maximalwert eine farbliche Trennung. Getroenes Material mit einer hohen Dichte, wie z.B. die Knochenmasse(a) oder das Metall des Motorblocks(b) werden grün dargestellt, während Material mit einer niedrigen Dichte rot gefärbt wird, wie z.B. die Haut(a). 5.2 Vergleich Die Qualität der Bilder kann in einem direkten Vergleich mit den anderen, bereits vorgestellten Implementierungen bestimmt werden. Als Vergleichsapplikationen wurden zwei Programme verwendet. Zum einen mit einem CPURaycaster, der von C.Rezk-Salama in der Übung zu der Vorlesung Visualisierung erstellt wurde[12], zum Anderen mit einer Implementierung zu der Arbeit A Simple and Flexible Volume Rendering Framework for GraphicsHardware-based Raycasting, die Zeitgleich zur Studienarbeit von S. Stegmaier, M. Strengert, T. Klein, und T. Ertl vorgestellt wurde[15]. Bei allen Programmen wurden die Volumen in die gleiche Richtung rotiert und sofern möglich die gleichen Parameter gewählt. Der Vergleich beschränkt sich auf den Volumendatensatz head256.raw, mit einer Abtastrate von 128 Schritten. 5.2.1 Emission-Absortion In allen Bildern sind die gleichen Strukturen zu erkennen, so dass davon ausgegangen werden kann, dass die Darstellung des GPURaycasters korrekt ist (Abbildung 33). 5.2.2 Maximum-Intensity-Projection In der Maximum-Intensity-Projection Visualisierung sind die im Volumen enthaltenen Strukturen besser zu erkennen. Es fällt auf, dass die Bilder der beiden anderen Implementierungen eine feinere Auösung liefern (Abbildung 34). Dies hängt mit der Skalierung der Ausgabe zusammen, bei der die Werte nicht linear interpoliert werden. 45 (a) head256 (Opacity=8,Steps=256) (b) BostonTeapot (Opacity=8,Steps=256) (c) engine (Opacity=2,Steps=256) (d) backpack8 (Opacity=2,Steps=512) Abbildung 28: Ausgaben des Emissions-Absorptions Shaders 46 (a) head256 (Steps=256) (b) BostonTeapot (Steps=256) (c) engine (Steps=256) (d) backpack8 (Steps=512) Abbildung 29: Ausgaben des Maximum-Intensity-Projection Shaders 47 (a) head256 (Steps=256) (b) BostonTeapot (Steps=256) (c) engine (Steps=256) (d) backpack8 (Steps=512) Abbildung 30: Ausgaben des Mean-Value Shaders 48 (a) head256 (Steps=256) (b) BostonTeapot (Steps=256) (c) engine (Steps=256) (d) backpack8 (Steps=512) Abbildung 31: Ausgaben des First-Local-Maximum Shaders 49 (a) head256 (Opacity=8,Steps=256) (b) BostonTeapot (Opacity=8,Steps=256) (c) engine (Opacity=2,Steps=256) (d) backpack8 (Opacity=2,Steps=512) Abbildung 32: Ausgaben des Emissions-Absorptions Shaders mit aktivierter Transferfunktion 50 (a) CPU-Raycaster (Steps=128) (b) SPVolRen (Steps=128) (c) GPU-Raycaster (Steps=128) Abbildung 33: Ausgaben des Emission-Absorbtions Verfahrens (Vergleich) 51 (a) Raycaster (Steps=128) (b) SPVolRen (Steps=128) (c) GPU-Raycaster (Steps=128) Abbildung 34: Ausgaben des Maximum-Intensity-Projection (Vergleich) 52 Verfahrens 6 Beurteilung der Geschwindigskeit Die Güte der Implementierung läÿt sich nicht nur an der visuellen Qualität, sondern auch an der Performanz feststellen. Bei allen in dieser Implementierung enthaltenen Shadern wurde die durchschnittliche Framerate gemessen, die das Programm für eine Drehung des Volumens um 360 ◦ um die X-Achse benötigt. Diese Frameraten wurden ebenso mit den Frameraten der anderen Implementierungen verglichen. Als Testrechner wurde ein AMD Athlon 64 3200+ mit 1024MB Ram und einer Nvidia 6600GT Grakkarte verwendet. 6.1 Frameraten Bei der Implementierung dieser Studienarbeit wurden alle Compositing-Verfahren mit ihren Variationen mit den bereits vorgestellten Volumensätzen in ihrer Darstellungsgeschwindigkeit gemessen. Die Ergebnisse für die jeweiligen Volumensätze werden in den Tabellen 1-4 dargestellt. emabs mip mean m -kw emabs emabs emabs emabs -tf -ert -ess -sm 128 Steps 05.68 05.69 06.04 27.59 05.31 04.51 05.67 08.80 256 Steps 04.71 04.72 04.96 17.57 04.48 03.87 04.72 04.67 Tabelle 1: Frameraten für head256.raw emabs mip mean m -kw emabs emabs emabs emabs -tf -ert -ess -sm 128 Steps 06.39 06.40 06.89 09.96 05.95 04.96 06.40 09.83 256 Steps 05.49 05.51 05.76 08.93 05.17 04.40 05.51 05.28 emabs Tabelle 2: Frameraten für BostonTeapot.raw emabs mip mean m -kw emabs emabs emabs -tf -ert -ess -sm 128 Steps 10.05 10.05 10.05 18.52 08.61 06.70 10.05 15.13 256 Steps 09.45 09.42 10.77 15.70 08.52 06.85 09.42 08.07 Tabelle 3: Frameraten für engine.raw 6.2 Analyse Zunächst wird bei dieser Analyse für jeden Shader der Geschwindigkeitsunterschied in Bezug zu der Schrittgröÿe entlang des Sehstrahls ausgewertet (Tabelle 5). Im Anschluss wird der Geschwindigkeitsunterschied im Vergleich zum Emissions-Absorbtions Shader bestimmt (Tabelle 6). 53 emabs mip mean m emabs emabs emabs -tf -ert -ess -sm -kw emabs 256 Steps 02.58 02.58 02.66 06.50 02.51 02.31 02.58 02.69 512 Steps 01.89 01.89 01.93 05.12 01.85 01.74 01.89 01.78 Tabelle 4: Frameraten für backpack8.raw emabs mip mean m emabs emabs emabs -tf -ert -ess -sm -kw emabs 128-256 -17% -17% -18% -36% -16% -14% -17% -47% 256-512 -27% -27% -27% -21% -26% -25% -27% -34% Tabelle 5: Geschwindigkeitsunterschiede zwischen den Schrittgrössen mip mean m emabs emabs emabs -tf -ert -ess emabs -sm 128 Steps 0% +6% +385% -7% -21% 0% +54% 256 Steps 0% +5% +273% -5% -18% 0% +1% 512 Steps 0% +3% +170% -4% -8% 0% +6% Tabelle 6: Geschwindigkeitsunterschiede zu emabs-kw.frag 54 6.3 Vergleich Der Vergleich der Frameraten beschränkt sich auf das Emission-Absorbtions und das Maximum-Intensity-Projection Verfahren, da diese als einzige von allen drei Implementierungen zur Verfügung gestellt werden. Die SPVolRen Applikation bietet von sich aus eine Benchmark Funktion an, bei der CPURaycaster Anwendung musste diese erst im Quellcode hinzugefügt werden. Der Vergleich beschränkt sich ebenso wie der Darstellungsvergleich auf den head256.raw Datensatz (Vergleichsergebisse siehe Tabelle 7). Emisson-Abs. Emisson-Abs. MIP MIP 128 Steps 256 Steps 128 Steps 256 Steps CPU-Raycaster 00.08 00.08 SPVolRen 03.36 02.17 07.11 04.15 GPU-Raycaster 05.68 04.71 05.69 04.72 08.80 04.67 nach Krüger-W. GPU-Raycaster modiziert Tabelle 7: Frameraten für head256.raw (Vergleich) Durch die Implementierung des GPU-Raycasters konnte die Geschwindigkeit im Vergleich zu der des CPU-Raycasters erheblich gesteigert werden. Die Geschwindigkeit liegt in drei von vier Tests über der der SPVolRen Implementierung, die ebenso auf einem Pixel-Shader 3.0 basiert. Allerdings liefert SPVolRen ein feiner aufgelöstes Bild. Man kann davon ausgehen, dass die erzielte Geschwindigkeit des GPU-Raycaster auf Kosten der Darstellungsqualität geht. 55 7 Fazit und Ausblick Im Rahmen dieser Studienarbeit, ist es gelungen, einen GPU basierten Raycaster zu entwickeln, der sowohl visuell, als auch in der Geschwindigkeit mit anderen Lösungen zum Raycasting-Verfahren mithalten kann. Die Implementierung dieser Studienarbeit lies sich durch die Verwendung von aktueller Grakhardware in einem Single-PassVerfahren realisieren. Dabei wird das Ergebnis in nur einem einzelnen Shader-Durchlauf berechnet. Ein Zwischenspeichern von Shader-Ergebnissen und ein nochmaliges Aufrufen konnte somit entfallen. Mit der Unterstützung von Abbruchsanweisungen durch die Hardware und GLSL war es möglich eine Early-Ray-Termination zu realisieren, wie sie z.B. in Abbildung 25 zu sehen ist. Ein solcher Break ist auch für das First-Local-Maximum Verfahren zwingend notwendig (Abbildung 23). Weiterhin wurden von der Hardware und GLSL automatisch eine trilineare Interpolation der 3D-Textur, sowie weitere hilfreiche Funktionen, wie z.B. die Normalisierung und Längenbestimmung von Vektoren zur Verfügung gestellt. Allerdings hat sich auch gezeigt, dass die Flusskontrolle in den Shadern die Leistung stark mindert. So zeigt ein Geschwindigkeitsvergleich zwischen dem Emissions-Absorbtions Shader ohne Optimierung (Abbildung 20) und des Emissions-Absorbtions Shader mit Early-Ray-Termination (Abbildung 25), dass eine einzige hinzugefügte if-Abfrage incl. conditional break die Leistung um bis zu 21% mindert, obwohl eigentlich der Berechnungsaufwand durch dieses Hinzufügen minimiert werden sollte (Tabelle 6). So konnte die Early-Ray-Termination nicht den erhoten Erfolg erzielen. Verwunderlich ist auch die Tatsache, dass die Leistung des Emissions-Absorbtions Shaders mit einem einfachen Empty-Space-Skipping (Abbildung 26) sich von der des Emissions-Absorbtions Shader ohne Optimierung nicht unterscheidet, obwohl hier ebenfalls nur eine if-Anweisung hinzugefügt wurde, diese allerdings in diesem Fall ohne ein break. Das Raycasting-Verfahren konnte für die Darstellung mit grossen Schrittweiten (max. 256 Steps pro Sehstrahl) sogar noch optimiert werden (Abbildung 27) und lieferte Geschwindigkeiten, die im Vergleich zum Emissions-Absorbtions Shader ohne Optimierung um bis zu 54% höher lagen (Tabelle 6). Zunächst war auch angedacht, ein CPU-basiertes Raycasting Verfahren zu Vergleichszwecken in die Implementierung zu integrieren. Dieses sollte ebenso an das Verfahren von J.Krüger und R.Westermann angelehnt sein. Die Entwicklung wurde allerdings nach kurzer Zeit wieder verworfen, da der Umfang, dies zu realisieren, doch um einiges gröÿer war, als zunächst angenommen. Die Implementierung müsste um zahlreiche Methoden erweitert werden, die standardmäÿig nicht zur Verfügung gestellt werden. Dazu zählen z.B. die Unterstützung von Vektoren und die damit verbundenen Funktionen wie die Längenbestimmung oder die Normalisierung. 56 Die Gesamtgeschwindingkeit der Visualisierung wird hauptsächlich durch die der Grakkarte beschränkt. Die von Windows angezeigte CPU Auslastung lag bei dem verwendetem Testsystem bei 0 - 1%. Durch die Verwendung von leistungsfähigeren Grakprozessoren, wie z.B. des NV40 auf der Nvidia 6800 Ultra Grakkarte konnte eine nochmals gesteigerte Leistung erzielt wer- 15 . Interessant dürfte ein Test mit den aktuell vorgestellten Grakkarten den von Nvidia basierend auf G70 Generation sein. Auch soll demnächst ATI eine neue Reihe von Grakkarten basierend auf der R520 GPU auf den Markt bringen, die ebenso die in dieser Arbeit verwendeten Funktionen des Shader Model 3.0 vollständig unterstützen. Grosse Honung wird auch in den kommenden Cell-Prozessor von IBM, Toshiba und Sony gesetzt 16 . Das das Thema Raycasting auch noch in naher Zukunft aktuell sein wird, zeigen laufend neu veröentlichte Papers zu diesem Thema. Dies unterstreicht, wie wichtig dieses Gebiet in der Forschung und in der Industrie ist. 15 Die zugehörigen Testergebnisse mussten leider wegen einer Änderung des Berechnungsalgorithmus verworfen werden. 16 Die Firma Mercury Computer Systems plant z.B. den Cell Prozessor in der Medizinischen Bildverarbeitung für Diagnosesysteme einzusetzen[16]. 57 Literatur [1] Ian Buck and T. Purcell. A Toolkit for Computation on GPUs. In GPU Gems. Addison-Wesley. 2004. [2] M. Hadwiger and C. Rezk-Salama. Volume Rendering. In Course 28: Real-Time Volume Graphics. ACM Computer Graphics, Proc. SIGGRAPH 2004, 1828, August 2004. [3] S. Röttger, S. Guthe, D. Weiskopf, and T. Ertl. Smart Hardware-Accelerated Volume Rendering. In Procceedings of EG/IEEE TCVG Symposium on Visualization VisSym '03, pages 231-238, 2003. [4] J. Krüger and R. Westermann. Acceleration Techniques for GPU-based Volume Rendering. August 2003. [5] Henning Scharsach. Advanced GPU Raycasting. March 2005. [6] Paolo Sabella. A Rendering Algorithm for Visualizing 3D Scala Fields. ACM Computer Graphics, Proc. SIGGRAPH '84, 22(4):5158, August 1984. [7] A. Keller and W. Heidrich. Interleaved sampling. In Proceedings of the 12th Eurographics Workshop on Rendering Techniques, pages 269276, 2001. [8] Mooresches Gesetz, Mooresches_Gesetz http://de.wikipedia.org/wiki/ [9] R. Moore. OpenGL Shading Language. Addison Wesley. 2004. [10] M. Biedermann. Visualisierung und Volumenrendering. In Computergrak Institut für 2. Arbeitsgruppe Computervisualistik. Computergrak. Universität Koblenz- http://www.uni-koblenz.de/ FB4/Institutes/ICV/AGMueller/Teaching/WS0405/CG2 Landau. WS 2004/2005. [11] L. Weinand. Die Gröÿe machts: GeForce 6800 Ultra http://www.de.tomshardware.com/graphic/ 20040414/index.html (NV40). [12] C. Rezk-Salama. Visualisierung. Computergrak und Multimediasysteme. Universität Siegen. WS 2004/2005. http://www.cg.informatik.uni-siegen.de/Teaching/ Lectures/WS_04_05/VIS 58 [13] volvis.org. http://www.volvis.org http: //www.clockworkcoders.com/oglsl/tutorial4.htm [14] M. Christen. OpenGL Shading Language Tutorials. [15] S. Stegmaier, M. Strengert, T. Klein, and T. Ertl. A Simple and Flexible Volume Rendering Framework for Graphics-Hardware-based Raycasting, Proceedings of Volume Graphics 2005, Stony Brook, New York, USA, industrielle und medizinische pp.187-195, 2005. [16] Cell-Prozessoren Bildverarbeitung. meldung/61141 für die http://www.heise.de/newsticker/ 59