31.10.2013 Aufrufe

Download - Benjamin Granzow Portfolio

Download - Benjamin Granzow Portfolio

Download - Benjamin Granzow Portfolio

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

Bachelorarbeit<br />

GPU-basierte Volumendarstellung<br />

Verfasser<br />

<strong>Benjamin</strong> <strong>Granzow</strong><br />

Studiengang<br />

Medieninformatik<br />

Fachbereich<br />

FB VI - Informatik und Medien<br />

Mat.Nr. 764663<br />

Bearbeitungszeit 01. Nov 2012 - 21. Feb 2013<br />

Betreuer<br />

Prof. Dr.-Ing. Hartmut Schirmacher<br />

Gutachter<br />

Prof. Dr. rer. nat. Rüdiger Weis<br />

Beuth Hochschule für Technik Berlin<br />

Beuth Hochschule für Technik Berlin


Inhaltsverzeichnis<br />

Abbildungsverzeichnis<br />

Listings<br />

Vorwort<br />

v<br />

vi<br />

vii<br />

1 Einleitung 1<br />

1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2<br />

1.2 Zielsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />

1.3 Abgrenzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />

2 Fachliches Umfeld 4<br />

2.1 Theoretischer Hintergrund . . . . . . . . . . . . . . . . . . . . 4<br />

2.1.1 Lichttransportmodell . . . . . . . . . . . . . . . . . . . 4<br />

2.1.2 Volumen Rendering Integral . . . . . . . . . . . . . . . 6<br />

2.1.3 Volumen Rendering Verfahren . . . . . . . . . . . . . 7<br />

2.1.4 Volumen Rendering Pipeline . . . . . . . . . . . . . . 9<br />

2.2 Datensätze . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />

2.3 Verwendete Technologien . . . . . . . . . . . . . . . . . . . . 11<br />

2.3.1 C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

2.3.2 Qt-Framework . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.3.3 OpenGL ES . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.3.4 Shader . . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.4 OpenGL ES 2.0 Grafikpipeline . . . . . . . . . . . . . . . . . 13<br />

2.4.1 Vertex Processing . . . . . . . . . . . . . . . . . . . . 14<br />

2.4.2 Fragment Processing . . . . . . . . . . . . . . . . . . . 14<br />

2.4.3 Compositing . . . . . . . . . . . . . . . . . . . . . . . 14<br />

2.5 GPU Programmierung . . . . . . . . . . . . . . . . . . . . . . 16<br />

2.5.1 Vertexshader . . . . . . . . . . . . . . . . . . . . . . . 17<br />

2.5.2 Fragmentshader . . . . . . . . . . . . . . . . . . . . . 18<br />

3 Konzeption 19<br />

3.1 Auswahl des Rendering Verfahrens . . . . . . . . . . . . . . . 19<br />

3.2 Prototypischer Software-Entwurf . . . . . . . . . . . . . . . . 20


INHALTSVERZEICHNIS<br />

iii<br />

4 Umsetzung 23<br />

4.1 2D Texture-Sliced Volumen Rendering . . . . . . . . . . . . . 23<br />

4.1.1 Texturen Anordnung . . . . . . . . . . . . . . . . . . . 23<br />

4.1.2 Einzelne Textur . . . . . . . . . . . . . . . . . . . . . . 24<br />

4.1.3 Mehrfach Texturen . . . . . . . . . . . . . . . . . . . . 26<br />

4.1.4 Zusammensetzung im Framebuffer . . . . . . . . . . . 29<br />

4.2 Klassifizierung . . . . . . . . . . . . . . . . . . . . . . . . . . 30<br />

4.2.1 Transferfunktion . . . . . . . . . . . . . . . . . . . . . 30<br />

4.2.2 Pre- vs. Post-Klassifizierung . . . . . . . . . . . . . . . 30<br />

4.2.3 Post-Klassifizierung . . . . . . . . . . . . . . . . . . . 31<br />

4.3 Beleuchtung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />

4.3.1 Blinn-Phong . . . . . . . . . . . . . . . . . . . . . . . 33<br />

4.3.2 Gradient Berechnung . . . . . . . . . . . . . . . . . . . 35<br />

4.3.3 Integration . . . . . . . . . . . . . . . . . . . . . . . . 39<br />

5 Ergebnisse & Auswertung 42<br />

5.1 Datenqualität und Datengröße . . . . . . . . . . . . . . . . . 43<br />

5.2 Erstellung einer Transferfunktion . . . . . . . . . . . . . . . . 44<br />

5.3 Vergleich der eigenen Umsetzung mit Visage7 . . . . . . . . . 46<br />

6 Zusammenfassung & Ausblick 49<br />

6.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

6.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50<br />

Danksagung 53<br />

A CD-Inhalt 54<br />

Literatur- und Quellenverzeichnis 55


Abbildungsverzeichnis<br />

1.1 Verschiedene Volumengrafiken: Sparschwein, Orange, Brustkorb,<br />

Metall . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2<br />

2.1 Materialmodelle (a) Emission (b) in-scattering (c) out-scattering<br />

(d) Absorption . . . . . . . . . . . . . . . . . . . . . . . . . . 4<br />

2.2 Schematische Darstellung der Shear-Warp Volumen Rendering<br />

Variante. Umsetzung für orthografische und perspektivische<br />

Projektion . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

2.3 Verschiedene Schnitte erstellt von einem CT, aus den Beispieldatensätzen<br />

der Visage7 Demo . . . . . . . . . . . . . . . 11<br />

2.4 Programmierbare OpenGL ES pipeline . . . . . . . . . . . . . 14<br />

3.1 Prinzip der Texture slicing Volumendarstellung: a) Textur<br />

Anordnung b) 2D-Texturen c) Optische Interpretation; Entnommen<br />

Real-Time Volume Graphics [3] Seite 49, Figure 3.1. 20<br />

3.2 Programmablauf mit Benutzerinteraktion und Rendering Schritten<br />

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />

3.3 Komponenten und ihre angedachte Interaktion untereinander 22<br />

4.1 Darstellung des Rotationsproblems von Ebenen a) ohne Ebenenwechsel<br />

b) mit Ebenenwechsel . . . . . . . . . . . . . . . . 23<br />

4.2 Einfache Volumendarstellung mit konstanter Transparenz . . 25<br />

4.3 Mittels Transferfunktion dargestellte Ebenen Links: Einzelne<br />

Texturen (20 Ebenen) Rechts: Mehrfach Texturen (200 Ebenen) 29<br />

4.4 Schematische Darstellung von a) Pre-Klassifizierung: zuerst<br />

Klassifizierung dann Interpolation b) Post-Klassifizierung: zuerst<br />

Interpolation dann Klassifizierung . . . . . . . . . . . . . 30<br />

4.5 Vergleich von Pre-(Links) und Post-Klassifizierung(Rechts)<br />

eines CT-Datensatzes mit der selben Transferfunktion. Entnommen<br />

Real-Time Volume Graphics [3] Seite 90, Figure 4.2. 31<br />

4.6 Visualisierung von mikro- und makroskopischer Normalen . . 33<br />

4.7 Darstellung der Winkel des Blinn-Phong Modells . . . . . . . 34<br />

4.8 Berechnung des Gradienten (Steigung) an der Stelle x i . . . . 36


ABBILDUNGSVERZEICHNIS<br />

v<br />

4.9 Links: Berechnete Gradienten (Steigung), Rechts: Berichtigte<br />

Orientierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />

5.1 Ansicht der resultierenden Umsetzung . . . . . . . . . . . . . 42<br />

5.2 Histogramm eines Datensatzes a) Luft b) Haut c) Muskeln d)<br />

Knochen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

5.3 Beispiele für Transferfunktion mit Resultat . . . . . . . . . . 45<br />

5.4 Gegenüberstellung von Visage7 (links) und der eigenen Umsetzung<br />

(rechts). Dabei wurden die selben Volumendaten und<br />

Transferfunktion verwendet. . . . . . . . . . . . . . . . . . . . 48


Listings<br />

2.1 Minimum Vertexshader in GLSL . . . . . . . . . . . . . . . . 17<br />

2.2 Minimum Fragment Shader in GLSL . . . . . . . . . . . . . . 18<br />

4.1 Berechnung der Ebenenrichtung der negativen Z-Achse . . . . 24<br />

4.2 Fragmentshader für eine Textur mit konstanter Transparenz . 25<br />

4.3 Berechnung einer Ebene aus den Texturen auf der Z-Achse . 26<br />

4.4 Textureinstellung zum begrenzen des Wertebereichen . . . . . 27<br />

4.5 Fragmentshader für mehrere Texturen mit konstanter Transparenz<br />

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />

4.6 Fragment Shader mit Transferfunktion . . . . . . . . . . . . . 32<br />

4.7 Blinn-Phong Methode im Fragmentshader . . . . . . . . . . . 35<br />

4.8 Vorbrauserechnung des Gradienten auf dem Datensatz . . . . 38<br />

4.9 Fragmentshader mit Transferfunktion und Blinn-Phong Beleuchtung<br />

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40


Vorwort<br />

Zum Abschluss des Studiums ist es vorgesehen, dass eine Abschlussprüfung<br />

abgehalten wird. Die Regelungen, denen diese Prüfung unterliegt, sind in<br />

der Rahmenprüfungsordnung (RPO IV) festgelegt. Die Abschlussprüfung<br />

besteht nach §13 Abs. 2 aus einer Abschlussarbeit und einer mündlichen<br />

Abschlussprüfung. Für den Studiengang Medieninformatik ist in der Studienordnung<br />

Medieninformatik Bachelor §5 Abs. 7 festgelegt, dass für die<br />

schriftliche Ausarbeitung 12 Wochen Bearbeitungszeit zur Verfügung stehen.<br />

Während der gesamten Abschlussphase wird dem Prüfling eine betreuende<br />

Lehrkraft zugewiesen, welche diesem mit Anleitung und Beratung zur<br />

Verfügung steht. Die Bewertung der Abschlussprüfung wird vom Betreuer<br />

und einem zusätzlichen Gutachter vorgenommen.<br />

Die tatsächliche Bearbeitungszeit dieser Arbeit weicht um vier Wochen von<br />

den festgelegten 12 Wochen ab. Diese Verlängerung ist vom Prüfungsausschuss<br />

genehmigt. Das ist begründet aus einer zweiwöchigen krankheitsbedingten<br />

Prüfungsunfähigkeit und einem zweiwöchigen Zeitbonus, aufgrund<br />

eines Nachteilsausgleiches nach §12 (RPO IV) hinsichtlich einer amtlich anerkannten<br />

Legasthenie.


Kapitel 1<br />

Einleitung<br />

Die Computergrafik befasst sich mit der Generierung, Bearbeitung und Verarbeitung<br />

von Bildern. Am Anfang bezog sich die Computergrafik nur auf<br />

2D Raster- oder Vektorgrafiken, welche seit den 60er Jahren um die dritte<br />

Dimension ergänzt wurde 1 . Seitdem unterliegt die Computergrafik einer<br />

schnellen Entwicklung und bietet immer mehr Möglichkeiten. Da die Qualität<br />

immer fotorealistischer wird, hat die Computergrafik in vielen visuellen<br />

Medienbereichen Einzug gehalten. Dabei wird meistens der Bereich<br />

der Unterhaltungsindustrie wahrgenommen. Seien es Computergrafiken in<br />

Computerspielen, digitale Ergänzungen in der Filmtechnik oder komplett<br />

im Computer entstandene Animationsfilme. Sie wird auch immer häufiger<br />

verwendet, um die verschiedensten Informationen besser für ein Zielpublikum<br />

aufzubereiten.<br />

In der Wirtschaft und Forschung wird Computergrafik besonders zur Visualisierung<br />

von Daten verwendet. So werden zum Beispiel Messdaten aus dem<br />

CERN LHC-Teilchenbeschleuniger grafisch aufbereitet, um diese medienwirksam<br />

präsentieren zu können 2 . Eine alltägliche Anwendung dieser Visualisierung<br />

durch die Computergrafik geschieht in der Medizintechnik, wo für<br />

bildgebende Verfahren zB. Computertomographie (CT) oder Magnetresonanztomographie<br />

(MRT) mittels Computergrafik möglichst aussagekräftige<br />

Darstellungen erstellt werden. Diese Verfahren erstellen eine sehr große Anzahl<br />

von Bildschnitten, welche einzeln ausgewertet werden können. Um aber<br />

einen Überblick zu erhalten, kann man durch Volumengrafik ein Gesamtbild<br />

erzeugen. Volumengrafiken eignen sich besonders um Objekte darzustellen,<br />

die keine direkte Abgrenzung zu ihrer Umgebung besitzen, wie Wolken oder<br />

Nebel. Diese Methode erreicht durch Nachbildung des Lichttransportes eine<br />

hohe optische Wirklichkeitsnähe. In Abbildung 1.1 sind verschiedene Volumengrafiken<br />

aufgezeigt.<br />

1 http://de.wikipedia.org/wiki/Geschichte der Computergrafik [24.01.13]<br />

2 ALICE scrutinizes proton-lead run for quark-gluon plasma,<br />

http://home.web.cern.ch/about/updates/2013/01/alice-scrutinizes-proton-lead-run-


1.1. MOTIVATION 2<br />

Abbildung 1.1: Verschiedene Volumengrafiken: Sparschwein, Orange, Brustkorb,<br />

Metall<br />

Im Laufe dieser Arbeit wird eine Möglichkeit erläutert, wie Volumendarstellung<br />

mit Hilfe der Grafikkarte (GPU) umgesetzt werden kann. Dabei wird<br />

ein zusätzlicher Schwerpunkt auf Portabilität gesetzt, sodass die Implementierung<br />

auf so vielen Plattformen wie möglich funktioniert.<br />

1.1 Motivation<br />

Da die Thematik der Volumengrafik im Rahmen des Studiums nicht behandelt<br />

worden ist, ging es darum diese selbständig zu erarbeiten. Besonders<br />

interessant ist dieses Thema, da es eine vollkommen andere Sichtweise aufzeigt,<br />

als in der Computergrafik üblich ist. Bei den gängigen Techniken ist<br />

der Schwerpunkt auf die Beschreibung von Oberflächen gelegt, sodass das<br />

Objekt als ein geschlossener und hohler oder fester Körper modelliert wird.<br />

Dieses oberflächenbasierte Rendering (Surface Rendering) ist vor allem für<br />

geschlossene Objekte mit klar abzugrenzenden Oberfläche geeignet. Um mit<br />

dieser Technik transparente Objekte zu realisieren gibt es nur zwei Varianten.<br />

Die Erste ist das ganze Objekt mit einer Transparenz zu versehen. Die<br />

Zweite besteht darin mit Texturen zu arbeiten, wodurch auch einzelne Bereiche<br />

transparent gesetzt werden können. Dennoch kann bei beiden Varianten<br />

die klare Abgrenzung nicht wirklich aufgehoben werden. Mit Volumentechquark-gluon-plasma<br />

[24.01.13]


1.2. ZIELSETZUNG 3<br />

niken wird diese Begrenzung aufgehoben, des Weiteren kann das Innenleben<br />

eines Objektes miteinbezogen werden.<br />

Die Thematik der Volumendarstellung ist nicht neuartig. Doch in den letzten<br />

Jahren haben sich einige technische Entwicklungen ergeben, durch welche<br />

sich für die Volumendarstellung interessante neue Möglichkeiten ergeben.<br />

Die Erstellung eigener Shaderprogramme ermöglicht nun schnelle interaktive<br />

Manipulationen der anzuzeigenen Daten. Durch die Auslagerung einiger<br />

Berechnungen entstehen andere Anforderungen an die Hardware.<br />

1.2 Zielsetzung<br />

In der vorliegenden Arbeit geht es darum, die grundlegenden Techniken der<br />

Volumendarstellung aufzuzeigen und dabei einen praktisch verwendbaren<br />

Prototypen zu erstellen. Dieser soll medizinische Daten als Volumengrafik<br />

darstellen. Durch eine einfache grafische Oberfläche sollen die Daten manipuliert<br />

werden können. Zusätzlich soll ein Schwerpunkt auf Portabilität<br />

gesetzt werden, sodass die Implementierung mit wenig Aufwand auf möglichst<br />

vielen Plattformen realisiert werden kann. Für diese Arbeit werden<br />

die gängigsten Plattformen (Windows, Linux, Mac OS) berücksichtigt. Für<br />

eine bessere Performance soll die Hardwarebeschleunigung der Grafikkarte,<br />

in Form von Shadern, mit einbezogen werden. Dadurch ergibt sich die zuvor<br />

erwähnte Interaktivität mit den Daten.<br />

1.3 Abgrenzung<br />

Die Thematik der Volumendarstellung ist sehr umfangreich, wodurch nicht<br />

alle Aspekte des Themas bearbeitet werden können, da dieses den Rahmen<br />

der Bearbeitungszeit übersteigen würden. Aus diesem Grund schneidet diese<br />

Arbeit nur die Grundlagen der Thematik an.<br />

Bei der Implementierung beschränken sich die importierten Datensätze auf<br />

CT-Daten. Andere bildgebende Verfahren, wie MRT oder Sonographie, werden<br />

nicht umgesetzt. Die Datensätze liegen in Form von allgemein üblichen<br />

Bilddateien (JPEG, TIFF, etc.) vor. DICOM, dass Standardformat für radiologische<br />

Bilddaten, wird im Rahmen der Arbeit nicht unterstützt.<br />

Um die vorgestellten Techniken nachvollziehbar zu halten, wird nicht vertieft<br />

auf Speicheroptimierungen eingegangen, da der Code dadurch nur unnötig<br />

komplex und schwerer nachvollziehbar wird. Auch auf die neusten Entwicklungen<br />

im Bereich der Deformation und Animation wird in dieser Abhandlung<br />

nicht weiter eingegangen.


Kapitel 2<br />

Fachliches Umfeld<br />

2.1 Theoretischer Hintergrund<br />

2.1.1 Lichttransportmodell<br />

Die physikalische Grundlage des Volumen Renderings liegt in der geometrischen<br />

Optik. Diese nimmt an, dass Licht sich immer entlang eines geraden<br />

Strahles bewegt, außer es erfolgt ein Zusammentreffen mit anderen Materialien.<br />

Genau diese Interaktion zwischen Licht und anderen Materialien ist bei<br />

der Volumendarstellung von Interesse. In der optischen Geometrie werden<br />

drei Verschiedene Materialmodelle beschrieben.<br />

Für die nachfolgende Beschreibung wird von einem gasförmigen Material<br />

ausgegangen.<br />

Abbildung 2.1: Materialmodelle (a) Emission (b) in-scattering (c) outscattering<br />

(d) Absorption<br />

Emission<br />

Emission beschreibt, das ein aktives Material selbst Licht aussendet. In der<br />

Realität wandelt stark erhitztes Gas seine Wärmeenergie in Strahlung um,<br />

dabei wird die Strahlungsenergie erhöht.


2.1. THEORETISCHER HINTERGRUND 5<br />

Absorption<br />

Das Material kann Licht absorbieren. Physikalisch wird dabei Strahlung in<br />

Wärmeenergie umgewandelt. Hierbei ist zu beachten, dass ein Material nicht<br />

unbedingt die gesamte Strahlungsenergie umwandelt, diese jedoch reduziert.<br />

Scattering<br />

Scattering beschreibt, das Licht gestreut wird. Dieses kann man aber eher<br />

mit einer Richtungsänderung vergleichen. Es sind dabei noch zwei Verhalten<br />

zu unterscheiden. Ein Material kann aus anderen Richtungen einfallendes<br />

Licht sammeln und dieses selber in Strahlenrichtung aussenden, was als<br />

in-scattering bezeichnet wird. Oder es kann Licht absorbieren und in alle<br />

Richtungen aussenden, dies wird als out-scattering bezeichnet. Dadurch<br />

ist es durch Scattering möglich, die Strahlungsenergie zu erhöhen oder zu<br />

reduzieren.<br />

Es gibt verschiedenste Modelle, die unterschiedlich viele dieser Materialmodelle<br />

verwenden. Wobei die Königsdisziplin darin besteht, alle drei Modelle<br />

zu berücksichtigen. Dennoch ist das Emissions-Absorptions-Modell die am<br />

meisten verbreitetste Umsetzung. Aus diesem Grunde wird dieses Model in<br />

dieser Arbeit genauer dargestellt und für die Implementation auch verwendet.<br />

Um dieses Modell mathematisch umzusetzen, ist es nötig, sich vor Augen<br />

zu führen, was Licht überhaupt ist. Licht kann an seiner Bestrahlungsstärke<br />

(Irradiance) I beschrieben werden. Diese wird durch die Strahlenenergie Q,<br />

Fläche A und Raumwinkel Ω pro Zeiteinheit t berechnet.<br />

I =<br />

dQ<br />

dA ⊥ dΩdt<br />

Dabei steht ⊥ dafür, dass der Strahl parallel zur Fläche verläuft. A ⊥ =<br />

A cos θ, wobei θ für den Winkel zwischen Lichtrichtung und Normale der<br />

Fläche A steht. Ausführliche Spezifikationen und Grundlagen zum Thema<br />

Licht in der Bildsynthese können einem Buch von Glassner [1] entnommen<br />

werden.<br />

Die nachfolgende Formel beinhaltet alle vorher erwähnten Modelle:<br />

ω · ∇ x I(x, ω) = −χI(x, ω) + η<br />

Dabei steht der Term ω · ∇ x I für das Skalarprodukt zwischen Lichtrichtung<br />

ω und Strahlung I abhänig von der Position x. Der Nabla-Operator ∇ ist<br />

ein Vektor, welcher als Komponenten die partiellen Ableitungsoperatoren<br />

besitzt (∇ = ( ∂ ∂<br />

∂x 1<br />

. . .<br />

∂x n<br />

)). Dieser wird als Kurzschreibweise für den Gradientvektor<br />

verwendet. Der Term χ bezeichnet die absolute Absorption und η<br />

die absolute Emission.


2.1. THEORETISCHER HINTERGRUND 6<br />

Diese Darstellung des Lichttransportes ist so nicht ganz genau. Mit der<br />

Schreibweise von H.C.Hege [2] wird die Emission und Absorption noch weiter<br />

aufgespalten, sodass die Scattering-Varianten mit berücksichtigt werden.<br />

χ = κ + σ<br />

Dadurch wird ausgedrückt, dass die absolute Absorption aus dem Absorptions-<br />

Koeffizienten κ und dem out-scattering-Koeffizienten σ besteht.<br />

η = q + j<br />

Analog dazu, ist die absolute Emission aufgeteilt in Emission q und inscattering<br />

j. Zu beachten ist, dass die Koeffizienten alle von der Position x<br />

und der Strahlenrichtung ω abhängen. Mit diesen Schreibweisen erhält man<br />

nun:<br />

ω · ∇ x I(x, ω) = −κ(x, ω) + σ(x, ω)I(x, ω) + q(x, ω) + j(x, ω)<br />

Da, wie oben schon erwähnt, das Scattering außer acht gelassen werden soll,<br />

können die dazugehörigen Terme aus der Formel entfernt werden. Die resultierende<br />

Formel wird meistens als Volumen Rendering Formel bezeichnet.<br />

ω · ∇ x I(x, ω) = −κ(x, ω)I(x, ω) + q(x, ω) (2.1)<br />

Genaugenommen beschreibt die Formel den Lichttransport bei differenziellen<br />

Änderungen im Lichtstrahl. Wird nur ein einziger Strahl ausgewertet, so<br />

kann ω · ∇ x I auch als Ableitung ( dI<br />

ds<br />

) geschrieben werden.<br />

dI(s)<br />

ds<br />

= −κ(s)I(s) + q(s) (2.2)<br />

Dort wird die Position durch den Längenfaktor s angegeben.<br />

2.1.2 Volumen Rendering Integral<br />

Ausgehend von der Differenzialform der Volumen Rendering Formel 2.2,<br />

welche für einen Strahl gültig ist, kann mittels Änderungen der Variablen s<br />

dieser Strahl entlanggewandert werden. Mittels Integration der Formel kann<br />

dieses ausgedrückt werden. Dabei wird mit s = S 0 der Startpunkt und mit<br />

s = D der Endpunkt beschrieben. Daraus ergibt sich:<br />

I(D) = I 0 e<br />

D∫<br />

− k(t)dt<br />

∫ D<br />

s 0 +<br />

s 0<br />

D∫<br />

q(s)e − k(t)dt<br />

s ds (2.3)


2.1. THEORETISCHER HINTERGRUND 7<br />

In dieser Formel wird durch I 0 als die Bestrahlungsstärke des Hintergrundes<br />

an der Anfangsposition s 0 angenommen. Dabei ist I(D) die Bestrahlungsstärke,<br />

welche das Volumen an der Stelle s = D verlässt. Also repräsentiert<br />

der erste Term die Bestrahlungsstärke des Hintergrundes abgeschwächt<br />

durch das Volumen. Der zweite Term steht für die Bestrahlungsstärke, welche<br />

durch das Durchqueren der verschiedenen Materialien abgeschwächt<br />

wird. Das hochgestellte Integral<br />

r(s 1 , s 2 ) =<br />

∫ s 2<br />

s1<br />

k(t)dt<br />

definiert die Optische Tiefe im Bereich s 1 bis s 2 . Dieses beschreibt die Länge,<br />

die ein Lichtstrahl benötigt bis dieser vollständig zerstreut wird. Niedrigere<br />

Werte stehen also für ein transparenteres Material, Hohe analog dazu für undurchlässigere.<br />

Äquivalent dazu kann Transparenz auch anders ausgedrückt<br />

werden.<br />

s ∫2<br />

T (s 1 , s 2 ) ⇔ e −r(s 1,s 2 ) ⇔ e − k(t)dt<br />

s1<br />

Mit dieser kann das Volumen Rendering Integral 2.3 weiter vereinfacht werden.<br />

∫ D<br />

I(D) = I 0 T (s 0 , D) +<br />

s 0<br />

q(s)T (s, D)ds (2.4)<br />

Diese Volumen Rendering Integral Formel ist die am meisten verwendete<br />

Darstellung.<br />

2.1.3 Volumen Rendering Verfahren<br />

Die zugrundeliegende Idee hinter der Umsetzung von Volumendarstellung<br />

unterscheidet sich signifikant von der üblichen Vorgehensweise bei der oberflächenbasierten<br />

Darstellung. Volumengrafiken anzuzeigen, wird von den Grafikbibliotheken<br />

nativ nicht unterstützt, da diese darauf spezialisiert sind, mit<br />

Gitternetzen (Meshes) zu arbeiten. Um Volumendarstellungen realisieren zu<br />

können, muss daher auf übliche Mittel zurückgegriffen werden, diese werden<br />

ihrem eigentlichen Zweck entfremdet, um ein Volumen darzustellen. Für die<br />

Umsetzung existieren unterschiedlichste Herangehensweisen, welche unter<br />

andem im Buch Real-Time Volumen Graphics [3] erwähnt werden.<br />

Raytracing<br />

Diese Technik ist in der Computergrafik weit verbreitet um 3D-Szenen abzubilden.<br />

Dabei wird für jeden Pixel der zu erstellenden Ansicht ein Strahl in


2.1. THEORETISCHER HINTERGRUND 8<br />

die Szene gesendet. Das Volumen wird entlang jeden Strahls von vorne nach<br />

hinten (front-to-back) oder von hinten nach vorne (back-to-front) durchlaufen,<br />

und die Emission und Absorption ausgewertet und aufsummiert. Dieses<br />

geschieht solange, bis bewertet werden kann, dass die dahinter liegenden<br />

Teile des Strahl nicht mehr sichtbar sind.<br />

Texture slicing<br />

Bei dieser Variante handelt es sich um die, welche in die Volumendarstellung<br />

die häufigste Verwendung findet. Die Grundidee davon ist es, hintereinander<br />

liegende Ebenen sog. Slices, abzubilden. Dabei werden diese Ebenen mit<br />

Transparenzen dargestellt, was bei frontaler Betrachtung die Texturen auf<br />

den einzelnen Ebenen addiert. Dabei entsteht für den Betrachter der Anschein,<br />

dass es sich um ein einziges Objekt handelt.<br />

Shear-Warp<br />

Die Mechanik dieser Variante ist eng mit dem Konzept des Texture slicing<br />

verbunden. Hierbei wird das Volumen auch auf transparenten Ebenen<br />

dargestellt. Abhängig von der verwendeten Kameratransformation wird das<br />

Volumen geschert und skaliert. Bei der orthografischen Projektion wird das<br />

Volumen nur so geschert, dass die Entfernung der Abtastung auf den Ebenen,<br />

bezogen auf eine bestimmte Ebene, immer gleich ist. Bei der perspektivischen<br />

Projektion ist zusätzlich noch eine Skalierung notwendig. Beide<br />

Variationen sind in Abbildung 2.2 dargestellt.<br />

Abbildung 2.2: Schematische Darstellung der Shear-Warp Volumen Rendering<br />

Variante. Umsetzung für orthografische und perspektivische Projektion<br />

Da dieses für CPU-Berechnung vorgesehen ist, werden die einzelnen Ebenen<br />

in eine Ergebnisebene addiert. Diese Variante ist die performanteste Variante<br />

bei CPU Umsetzungen. Sie wurde das erste Mal von Lacroute und Levoy[4]<br />

beschrieben.


2.1. THEORETISCHER HINTERGRUND 9<br />

Splatting<br />

Das Splatting basiert auf der Annahme, dass jeder diskrete Punkt des Volumens<br />

das Resultat beeinflusst. Dafür wird das Volumen von hinten nach<br />

vorne durchlaufen und jeder abgetastete Punkt auf die Bildebene projiziert.<br />

Dabei geht der Punkt mit seiner Emission geschwächt um die Absorption<br />

in den Bildpunkt ein. Die verschiedenen Projektionen werden anschließend<br />

durch unterschiedliche Overlay-Operations kombiniert. Der Name des Verfahrens<br />

wurde durch Lee Westover geprägt, welcher den Ablauf mit einem<br />

Schneeball verglich, der auf eine Bildebene geworfen wird und zerplatzt. Eine<br />

genaue Beschreibung kann im Visualization Handbook[5] nachgeschlagen<br />

werden.<br />

Durch diese Konzepte muss man sich von der Vorstellung lösen, dass ein<br />

Volumen im Programm als wirkliches Objekt existiert. Erst im Auge des<br />

Betrachters wird dieses zu einer Volumendarstellung.<br />

2.1.4 Volumen Rendering Pipeline<br />

Auch wenn die verschiedenen Rendering Verfahren unterschiedliche Herangehensweisen<br />

haben, folgt die Erstellung von Volumengrafiken immer einem<br />

festen Ablauf. Ein typischer Ablauf einer Volumen Rendering Pipeline ist:<br />

Datensätze vorbereiten<br />

Die Datensätze werden so positioniert, dass die Abtastpunkte passend für<br />

das Verfahren liegen. Dazu gehören auch Transformationen, wie Translation,<br />

Rotation und Scherung. Dabei wird die Ausrichtung hergestellt, um die<br />

Verwendung des Volumen Rendering Integrals zu ermöglichen.<br />

Interpolation<br />

Dabei geht es darum, entlang der Datenwerte zu interpolieren, um die Zwischenwerte<br />

der abgetasteten Punkte zu erhalten. Am häufigsten findet hier<br />

die triliniare Interpolation Verwendung.<br />

Klassifizierung<br />

Transformiert die Werte des Datensatzes anhand einer Transferfunktion,<br />

um ihre optischen Eigenschaften zu ermitteln. Hier werden die optischen<br />

Eigenschaften (Absorption, Emission), welche für das Volumen Rendering<br />

Integral benötigt werden, zugewiesen.


2.2. DATENSÄTZE 10<br />

Beleuchtung<br />

Bei Volumendarstellung ist es Möglich, auch Beleuchtung in das Volumen<br />

Rendering Integral einzubringen. Dafür kann eine beliebige Beleuchtungsberechnung<br />

aufgerechnet werden.<br />

Zusammensetzen<br />

Zusammensetzten der einzelnen Daten zu einem Gesamtbild. Dieses repräsentiert<br />

die Aufsummierung und Iteration des Volumen Rendering Integrals.<br />

Dabei steht back-to-front oder front-to-back zur Auswahl.<br />

Auch die im Verlauf dieser Ausarbeitung dargestellte Umsetzung verwendet<br />

diesen Aufbau der Volumen Rendering Pipeline. Daher werden die einzelnen<br />

Schritte im weiteren Verlauf noch näher erläutert.<br />

2.2 Datensätze<br />

Es gibt verschiedenste bildgebende Verfahren, die bekanntesten darunter<br />

sind CT, MRT und Sonografie (Ultraschall). Diese Verfahren messen durch<br />

unterschiedliche Methoden die Dichte bzw. Materialbeschaffenheit eines Objektes.<br />

Durch eine Vielzahl von Schnitten entlang des Objektes wird damit<br />

Einblick in das Innenleben dieses ermöglicht. Dabei kann der Schichtabstand<br />

der Schnitte gezielt eingestellt werden, um die Abtastung passend<br />

zum Untersuchungszweck anzupassen. Solche Datensätze werden meistens<br />

im offenen DICOM-Format gespeichert, können aber auch in allgemein üblichen<br />

Bilddateien abgespeichert werden. Diese Bilddaten sind bei CT-Scans<br />

meistens 512x512 Pixel groß und beinhalten die Messdaten als Graufarbenbild.<br />

Die Messdaten werden dafür in einen Farbwert umgewandelt, wobei der<br />

Wert 0 keiner messbaren Dichte entspricht. Mit zunehmender Dichte steigt<br />

auch der Farbwert an. Abhängig vom Verfahren können Dichtewerte unterschiedlich<br />

präzise abgestuft werden. Daher gibt es Datensätze in 8, 12 und<br />

16 Bit Farbtiefe, welche eine unterschiedlich Anzahl an Abstufungen speichern<br />

können. Auf die Auswirkungen von Datengrößen wird im Abschnitt<br />

Datenqualität und Datengröße 5.1 eingegangen. In Abbildung 2.3 sind verschiedene<br />

CT Schnitte dargestellt.


2.3. VERWENDETE TECHNOLOGIEN 11<br />

Abbildung 2.3: Verschiedene Schnitte erstellt von einem CT, aus den Beispieldatensätzen<br />

der Visage7 Demo<br />

2.3 Verwendete Technologien<br />

Durch die zusätzliche Zielsetzung der Kompatibilität entstehen bestimmte<br />

Anforderungen an die Implementierung. Damit diese auf möglichst vielen<br />

Systemen funktioniert, wurde auf einheitliche Standards bzw. Schnittstellen<br />

zurückgegriffen.<br />

2.3.1 C++<br />

Typischerweise werden für die Kernfunktionalität von Computergrafik-Anwendungen<br />

die Programmiersprache C++ verwendet. Es ist oft nötig viele


2.3. VERWENDETE TECHNOLOGIEN 12<br />

Daten in der Computergrafik zu verwalten und durch die Eingenschaft von<br />

C++ ist es möglich, die Speicherverwaltung selbst zu kontrollieren. Deswegen<br />

kann eine sehr gute Performance für Anwendungen erzielt werden, da<br />

eine direkte Anbindung an die Hardware entsteht. Als Programmiersprache,<br />

welche direkt in Maschinencode kompiliert wird, entfällt der Laufzeit-<br />

Overhead, welchen interpretierte Sprachen durch ihre Laufzeitumgebung erzeugen.<br />

2.3.2 Qt-Framework<br />

Das Qt-Framework stellt dem Anwender Klassen zur Verfügung, um grafische<br />

Benutzeroberflächen zu erstellen. Dieses Framework funktioniert nicht<br />

nur für verschiedene Desktop-Betriebssysteme und eingebettete Systemen,<br />

sondern es ist auch für mobile Plattformen geeignet. Dabei werden zum jetzigen<br />

Zeitpunkt Windows Mobile, ältere Nokia-Systeme (Symbian, MeeGo,<br />

Maemo) und Blackberry unterstützt, dennoch sind Umsetzungen für Android<br />

und iOS mit dem im Herbst 2013 erscheinenden Qt 5.2 angekündigt.<br />

Des Weiteren wird zur Steuerung der Anwendung ein einfaches und effektives<br />

Eventsystem geboten. Durch die Nutzung dieses Frameworks, wird eine<br />

breite Kompatibilität der Anwendung zu verschiedensten Systemen garantiert.<br />

2.3.3 OpenGL ES<br />

Die Open Graphics Library (OpenGL) ist ein offener Standard im Multimediabereich,<br />

um plattformübergreifend die Darstellung von 2D und 3D<br />

Computergrafiken zu ermöglichen. OpenGL wird von der Khronos Group<br />

entwickelt, welche viele namenhafte Firmen zu ihren Mitgliedern zählt. Dabei<br />

sind AMD/ATI, Apple Inc., id Software, Google, Intel Coorporation,<br />

Mozilla und Oracle/Sun Microsystems, um nur einige zu nennen. OpenGL<br />

wird von allen handelsüblichen Grafikkarten nativ unterstützt, und ist nicht<br />

wie andere Bibliotheken, zB. DirectX nur auf Windows Systeme, beschränkt.<br />

Für die Implementierung wurde gezielt OpenGL ES 2.0 verwendet, da der<br />

genutzte Befehlssatz auch auf mobilen Plattformen und eingebetteten Systemen<br />

sicher zur Verfügung steht.<br />

OpenGL ES 2.0 umfasst 179 Befehle und bildet eine bereinigte Untermenge<br />

der 297 Standardbefehle, welche in OpenGL 4 verfügbar sind, um eine möglichst<br />

große Kompatibilität zu ermöglichen. Die Funktionsweise von OpenGL<br />

ES 2.0 und ihrer Grafik-Pipeline wird im Abschnitt 2.4 OpenGL ES 2.0 Grafikpipeline<br />

näher erläutert.<br />

2.3.4 Shader<br />

Shader sind Mini-Programme, die auf die Grafikkarte geladen werden können<br />

und auf die Szenendaten dann beliebige Effekte rauf rechnen. Zum Erstel-


2.4. OPENGL ES 2.0 GRAFIKPIPELINE 13<br />

len von Shaderprogrammen können verschiedene Sprachen gewählt werden.<br />

Anfangs wurden die Programme mittels einer Assemblersprache geschrieben.<br />

Da diese aber nicht intuitiv zu verwenden ist, wurden einige Alternativen<br />

entwickelt, welche sich an der Programmiersprache C orientieren. Die<br />

Verbreitetsten sind C for Graphics (Cg) von NVIDIA, High Level Shading<br />

Language (HLSL), die für DirextX entwickelt wurde oder die OpenGL Shading<br />

Language (GLSL) für OpenGL Anwendungen. An diese Shader können<br />

beliebige zusätzliche Informationen übertragen werden, was eine erhöhte Interaktivität<br />

mit der Darstellung ermöglicht. Da für die Implementierung die<br />

Wahl auf OpenGL ES gefallen ist wird auch GLSL im nachfolgenden verwendet.<br />

Im Abschnitt 2.4 OpenGL ES 2.0 Grafikpipeline wird im Detail auf<br />

die zur Verfügung stehenden Shader von OpenGL ES eingegangen.<br />

2.4 OpenGL ES 2.0 Grafikpipeline<br />

Eine Grafikpipeline ist ein Modell, dass die einzelnen Schritte beschreibt,<br />

welche beim Grafikrendern durchlaufen werden, um eine Grafik auf den Bildschirm<br />

darzustellen. Dabei sind einige Schritte dieser Pipeline auf der Hardware<br />

implementiert, um eine bessere Performance zu erhalten. Die Schritte<br />

der OpenGL ES 2.0 Pipeline sind in Abbildung 2.4 mit ihren drei Phasen<br />

dargestellt. Um die Grafikpipeline zu betrachten, werden einige Begriffe benötigt:<br />

ˆ Vertex Buffer Object (VBO)<br />

Ein VBO ermöglicht eine schnelle und performante Speicherung und<br />

Übertragung von Daten, zB. Vertices und Normalen, zur Grafikkarte.<br />

Dabei wird dennoch ein einfacher Zugriff ermöglicht, um die Daten zu<br />

aktualisieren.<br />

ˆ Vertex(Vertices)<br />

Vertices sind Knotenpunkte aus welchen eine geometrische Form zusammengesetzt<br />

werden.<br />

ˆ Primitive<br />

Sind eine einfache geometrische Form, welche durch die Verbindung<br />

von Vertices repräsentiert werden. Üblich sind Punkte, Linien, Dreiecke<br />

und Vierecke.<br />

ˆ Fragment<br />

Fragments repräsentieren eine Farbe auf einer Rasterposition und beinhalten<br />

alle Informationen, um die betreffende Pixel darstellen zu können.


2.4. OPENGL ES 2.0 GRAFIKPIPELINE 14<br />

Abbildung 2.4: Programmierbare OpenGL ES pipeline<br />

Wie in Abbildung 2.4 dargestellt, ist die OpenGL ES Pipeline in drei Phasen<br />

aufgeteilt: Das Vertex Processing, das Fragment Processing und das Compositing.<br />

2.4.1 Vertex Processing<br />

Während dem Vertex Assambly Schritt werden die Daten aus der Anwendung<br />

in den Grafikkartenspeicher geladen. Dies geschieht zB. über ein VBO,<br />

welches die Vertices von Primitiven mit ihren zugehörigen Attributen enthält.<br />

Diese Daten werden als Attribute an den Vertexshader weitergegeben.<br />

Im Vertexshader werden verschiedene Vertex-Attribute transformiert. Meistens<br />

wird dieses auf die Position angewendet, aber potentiell können alle<br />

Attribute wie die Normale oder andere Anwendungsspezifische Per-Vertex-<br />

Daten transformiert werden. Nachdem die Vertex-Attribute transformiert<br />

wurden, können weitere beliebige Werte über sogenannte Varyings an den<br />

Fragmentshader weitergegeben werden. Nähere Beschreibung zum Vertexshader<br />

folgt im Abschnitt 2.5.1 Vertexshader. Darauf folgt der Primitiv Assembly<br />

Schritt, welcher die einzelnen Vertices zu Primitiven zusammensetzt.<br />

Danach werden beim Clipping die Bereiche der Objekte abgeschnitten, welche<br />

nicht im sichtbaren Ausschnitt liegen. Dies reduziert die weiterzuverarbeitenden<br />

Daten und entlastet dadurch die nachfolgenden Schritte.<br />

2.4.2 Fragment Processing<br />

Der erste Schritt in dieser Phase ist die Rasterization. Dabei wird für jeden<br />

Pixel, der ein Teil des Primitiven abdeckt, ein Fragment erzeugt. Jeder<br />

dieser Fragments gehört dabei zu einer Rasterposition auf dem Bildschirm,<br />

repräsentiert diesen aber nicht, da üblicherweise mehrere Fragments von unterschiedlichen<br />

Objekten auf einer Rasterposition liegen. Diese Fragments,<br />

welche aus den interpolierten Werten der Vertices bestehen, durchlaufen anschließend<br />

den Fragmentshader, der den endgültige Farbwert des Fragments<br />

berechnet. Dies kann durch Texturzugriffe oder Funktionen geschehen. Nähere<br />

Beschreibung folgt im Abschnitt 2.5.2 Fragmentshader.<br />

2.4.3 Compositing<br />

Die Compositing Phase ist der letzte Verarbeitungsschritt bevor der Farbwert<br />

in den Framebuffer gespeichert wird. Der Framebuffer ist ein 2D-Array<br />

von Pixeln und ihren Attributen (Farbe, Transparenz, Tiefe), welches das


2.4. OPENGL ES 2.0 GRAFIKPIPELINE 15<br />

endgültige Bild enthält. Bevor ein Fragment in den Framebuffer übertragen<br />

wird, können verschiedene Tests auf diesen angewendet werden, um herauszufinden<br />

ob es ggf. verworfen oder angezeigt wird. Dafür sind folgende Tests<br />

verfügbar, welche nach Bedarf verwendet werden können:<br />

Alpha-Test<br />

Der Alpha-Test wertet das Alpha-Attribut a der Fragmentfarbe aus. Anhand<br />

einer vorher definierten Funktion wird bewertet, ob das Fragment als<br />

komplett transparent gilt. Wenn dies der Fall ist, werden die restlichen Tests<br />

und das Speichern in den Framebuffer übersprungen. Die Funktion wird mit<br />

glAlphaFunc(func,value) festgelegt. Dabei muss value im Bereich [0,1] liegen<br />

und bezeichnet den Referenzwert. Für func kann aus einem Aufzählungstyp<br />

(Enumeration) verschiedene Vergleichsoperationen gewählt werden. Diese<br />

Operation legt fest, wie value und das Alpha-Attribut a der Fragmentfarbe<br />

verglichen werden.<br />

Es ist zu beachten, dass der Befehl glAlphaFunc in OpenGL ES 2.0 nicht<br />

zu Verfügung steht, dieser Test ist nur zu Vollständigkeit mit aufgeführt.<br />

Ist diese Funktionalität dennoch gewünscht kann diese im Fragmentshader<br />

selbstständig umgesetzt werden.<br />

Stencil-Test<br />

Dieser Test besteht daraus, dass die im Stancilbuffer erstellte Maske auf<br />

das Fragment angewendet wird. Im Stancilbuffer können durch diese Maske<br />

Bereiche festgelegt werden, welche herausgefiltert werden oder welche sichtbar<br />

sind. Dies wird zB. dazu verwendet, wenn nur bestimmte Bereiche neu<br />

gerendet werden sollen. Durch die Möglichkeit die gespeicherten Werte des<br />

Buffers zu inkrementieren oder zu dekrementieren, wird dieser auch für die<br />

Berechnung von Shadow-Volumes benötigt. Mehr Informationen zu diesem<br />

Thema können dem von NVIDIA veröffentlichen GPUGems[6] entnommen<br />

werden.<br />

Depth-Test<br />

Der Tiefen-Test ist dafür da, um herauszufinden, welche Fragments der Kamera<br />

am Nächsten und dadurch sichtbar sind. Dabei existiert, ähnlich dem<br />

Stancilbuffer, ein Depthbuffer, welcher die Tiefenwert des sich momentan im<br />

Framebuffer befindlichen Fragments an dieser Stelle speichert. Zur Bewertung<br />

wird die Tiefe des neuen Fragments mit der des Alten verglichen. Ist das<br />

Neue näher an der Kamera, also der Wert kleiner, wird das neue Fragment<br />

in den Framebuffer übernommen und der neue Tiefenwert im Depthbuffer<br />

gespeichert. Ist dieses nicht der Fall, befindet sich das neue Fragment hinter<br />

dem alten und wird im endgültigen Bild nicht sichtbar sein, das Fragment


2.5. GPU PROGRAMMIERUNG 16<br />

wird daher verworfen.<br />

Nachdem ein Fragment alle Test durchlaufen hat, wird dieser in den Framebuffer<br />

übertragen. Bei komplett sichtbaren Objekten wird der neue Wert<br />

direkt in den Framebuffer übertragen. Wenn aber transparente Objekte verwendet<br />

werden, müssen diese noch durch den Alpha-Blending Schritt verarbeitet<br />

werden.<br />

Alpha-Blending<br />

Mittels Alpha-Blending wird genau festgelegt, wie ein neuer Fragment mit<br />

dem alten Wert im Framebuffer kombiniert wird, um den neuen Wert festzulegen.<br />

Dafür muss eine Blendfunktion festgelegt werden. Dies geschieht<br />

über glBlendFunc(srcFactor,fbFactor), wobei für die Faktoren Enumeration verwendet<br />

werden. Die möglichen Werte können der OpenGL ES 2.0 Referenz<br />

[7] entnommen werden. Diese Funktion beschriebt, mit welchen Anteilen<br />

welche Farbwerte eingehen. srcFactor wird dabei auf die neuen Fragment<br />

angewendet und fbFactor auf den alten Wert im Framebuffer.<br />

2.5 GPU Programmierung<br />

Heutzutage sind die meisten Computer mit leistungsfähiger Grafikhardware<br />

ausgestattet. Diese Hardware besitzt einen eigenen Rechenkern, der auf die<br />

Berechnung von Grafiken optimiert ist. Dieser wurde historisch dafür verwendet,<br />

Projektionen für die Darstellung zu berechnen und Objekte nachträglich<br />

zu transformieren und mit Lichteffekten zu versehen. Dabei wurden<br />

Bereiche einer Textur heller oder dunkler eingefärbt, um Schatten und Licht<br />

darzustellen. Diese Beleuchtungsfunktionen waren fest auf der Hardware<br />

verankert und konnten nur leicht konfiguriert werden. Aus diesem Grund<br />

wurde diese auch als Fixed-Function-Pipeline bezeichnet. Durch den Nachfolger<br />

dieser Architektur, die Programmable-Graphics-Pipeline, wurden die<br />

Grafikprozessoren immer leistungsfähiger. Diese neuen Graphic Processing<br />

Units (GPUs) können mit beliebigen Mini-Programmen, den sogenannten<br />

Shadern, ausgestattet werden, um beliebige Berechnungen auszuführen.<br />

Die OpenGL ES Pipeline unterstützt zwei Typen von Shadern, welche an<br />

unterschiedlichen Stellen der Pipeline durchlaufen werden. Jeder übernimmt<br />

davon eine bestimmte Aufgabe. Im Folgenden werden die angedachten Aufgaben<br />

kurz erwähnt. Da die Shader aber frei programmierbar sind, ist es<br />

dennoch möglich, diese für andere Zwecke zu verwenden.<br />

Bei Shadern gibt es eine Besonderheit für Variablen, welche Werte von außerhalb<br />

erhalten oder welche herausgeben. Diese müssen zusätzlich mit einem<br />

Typenqualifizerer versehen werden. Er ist der Typendeklaration vorangestellt<br />

und kommt am Anfang des Shaders.


2.5. GPU PROGRAMMIERUNG 17<br />

Es folgt eine Auflistung der wichtigsten Typenqualifizierer und ihre Bedeutung:<br />

ˆ attribute<br />

Attribute bezeichnen nur lesbare Werte, welche aus den Vertex Buffer<br />

Objekten kommen und beinhalten die fertig interpolierten Werte des<br />

speziellen Vertexes.<br />

ˆ uniform<br />

Typen, welche damit qualifiziert sind, können gezielt in der Anwendung<br />

gesetzt werden, um einen Shader zu steuern bzw. zu manipulieren.<br />

Hiermit wird eine Interaktivität gewährleistet.<br />

ˆ varying<br />

Damit werde Werte bezeichnet, welche vom Vertexshader an den Fragmentshader<br />

übergeben werden.<br />

2.5.1 Vertexshader<br />

Dieser Shader arbeitet strikt auf den Attributen des aktuellen Vertices. Es<br />

kann nicht auf andere zugreifen oder neue hinzufügen. Seine Aufgabe ist<br />

es, die Vertices zu transformieren. Dabei wird klassisch zwischen zwei unterschiedlichen<br />

Bedeutungen unterschieden. Zuerst betrifft es denn Vertex<br />

direkt, dabei kann auf dem Vertex Rotation, Translation und Skalierung angewendet<br />

werde. Zuletzt geht es um die Interpretation des Vertex.<br />

a t t r i b u t e vec3 textureCoord ;<br />

a t t r i b u t e vec3 vertexPos ;<br />

uniform mat4 modelViewMatrix ;<br />

uniform mat4 p r o j e c t i o n M a t r i x ;<br />

varying vec3 textCoord ;<br />

void main ( void )<br />

{<br />

// W e i t e r l e i t e n von Texturkoordinaten zum Fragment Shader<br />

textCoord = textureCoord ;<br />

// Transformieren der V e r t e x p o s i t i o n i n Bildschirm<br />

Koordinatensystem<br />

g l P o s i t i o n = p r o j e c t i o n M a t r i x * modelViewMatrix * vec4 ( vertexPos<br />

, 1 . 0 ) ;<br />

}<br />

Listing 2.1: Minimum Vertexshader in GLSL<br />

Durch verschiedene Matrix-Operationen kann das Koordinatensystem umgerechnet<br />

werden. Klassisch werden Lokalekoordinaten erst in Weltkoordinaten<br />

(Modelmatrix) danach in die Kamerakoordinaten (Viewingmatrix) und


2.5. GPU PROGRAMMIERUNG 18<br />

anschließend in Bildschirmkoordinaten (Projectionmatrix) umgerechnet. Im<br />

Verlauf des Shaders muss dabei gl Position mit den neuen transformierten<br />

Vertex-Attributen gefüllt werden. Das Vertexshader-Programm wird für jeden<br />

Vertex einzeln aufgerufen. Im Listing 2.1 ist ein simples Beispiel für ein<br />

Vertexshader Programm aufgeführt.<br />

2.5.2 Fragmentshader<br />

Der Fragmentshader wird von jedem Fragment einzeln durchlaufen. Dieser<br />

bekommt das Fragment, mit den interpolierten Vertex-Attributen, und<br />

weitere beliebige Werte durch die Varyings. Durch diese Informationen wird<br />

der endgültige Farbwert jedes Fragmentes berechnet. Dafür können beliebige<br />

Berechnungen angewendet oder Texturen ausgelesen und weiterverarbeitet<br />

werden. Auch Post-Processing-Effects wie Bloom, Blur oder Beleuchtung<br />

werden hier errechnet. Auch ist es möglich, gezielt Fragments zu verwerfen<br />

um Löcher in Objekten zu realisieren. Im Verlauf des Shaders muss dabei<br />

gl FragColor die endgültige Fragmentfarbe zugewiesen werden.<br />

Ein simples Beispiel für einen Fragment Shader ist in Listing 2.2 aufgeführt.<br />

varying vec2 textCoord ;<br />

uniform sampler2D t e x t u r e ;<br />

void main ( void )<br />

{<br />

// Lesen der Fragmentfarbe an der P o s i t i o n der Texturkoordinate<br />

g l F r a g C o l o r = texture2D ( texture , textCoord ) ;<br />

}<br />

Listing 2.2: Minimum Fragment Shader in GLSL<br />

Die OpenGL ES Pipeline ist auf den Vertex- und Fragmentshader beschränkt<br />

und diese Shadertypen sind auch zwingend erforderlich. Wenn diese nicht<br />

implementiert werden, ist es nicht möglich das Shaderprogramm zu kompilieren<br />

und auf die Grafikkarte zu übertragen. So etwas wie Standardimplementationen,<br />

welche bei nicht vorhanden sein verwendet werden, sind nicht<br />

vorhanden. Auf der Standard OpenGL Pipeline stehen noch zwei weitere<br />

Shader zu Verfügung. Dabei handelt es sich um den Tesselationshader und<br />

den Geometryshader und diese dienen dazu, die geometrischen Primitiven<br />

zu manipulieren und zu verfeinern. Diese Shadertypen sind dort optional<br />

und können weggelassen werden. Detailliertere Informationen zum Thema<br />

OpenGL ES können dem Buch OpenGL ES 2.0 Programming Guide[8] entnommen<br />

werden.


Kapitel 3<br />

Konzeption<br />

3.1 Auswahl des Rendering Verfahrens<br />

Wie in Abschnitt 2.1.3 Volume Rendering Verfahren dargestellt, gibt es unterschiedliche<br />

Verfahren eine Volumendarstellung zu erzeugen.<br />

Da bei der Umsetzung wegen möglichst großer Portierbarkeit auf OpenGL<br />

ES 2.0 zurückgegriffen wird, ergeben sich einige technische Einschränkungen.<br />

Die Ausschlaggebendste ist, dass dort keine 3D-Texturen zur Verfügung<br />

stehen. Des Weiteren ist ein gewichtiger Grund, dass die Verwendung von<br />

3D-Texturen auch sehr grafikspeicherfüllend ist. Gerade bei mobilen oder<br />

eingebetteten Systemen kann nicht davon ausgegangen werden, dass ein Grafikspeicher<br />

in solch einer Größe vorhanden ist. Mehr zu Datengrößen können<br />

dem Abschnitt 5.1 Datenqualität und Größe entnommen werden. Um Raytracing<br />

mit einer akzeptabler Leistung durchzuführen, wird eine Hardware<br />

der aktuellen Grafikkartengeneration benötigt. Denn erste diese schaffen es,<br />

Schleifen im Shadern mit einer annehmbaren Laufzeit zu verarbeiten. Es<br />

kann davon ausgegangen werden, das die meisten Systeme nicht über eine<br />

solche Hardware verfügen, besonders im Hinblick auf eingebettete Systeme.<br />

Daher würde diese Variante, welche mehrere Schleifen benötigt, eine<br />

sehr schlechte Laufzeit erreichen. Das Shear-Warp und Splatting sind für<br />

die CPU-Umsetzungen erstellt worden, und daher nicht sonderlich geeignet<br />

für die Umsetzung mittels Hardwarebeschleunigung (GPU) und damit wegen<br />

der Aufgabenstellung nicht verwendbar. Aus diesen Gründen wurde für<br />

das weitere Vorgehen die Texture slicing Variante gewählt. Diese eignet sich<br />

hervorragend um mittels Hardwarebeschleunigung auf der GPU umgesetzt<br />

zu werden. Dabei wird sehr speicherschonend gearbeitet, da es mit wenigen<br />

2D-Texturen auskommt.<br />

Die Grundidee des Texture slicing ist in Abbildung 3.1 abgebildet. Dafür<br />

werden mehrere Ebenen hintereinander aufgereiht, sodass diese frontal<br />

vom Benutzer betrachtet werden können. Auf diese werden anschließend die


3.2. PROTOTYPISCHER SOFTWARE-ENTWURF 20<br />

einzelnen Schnitte des Datensatze abgebildet. Durch die Verwendung von<br />

Transparenzen und das Aufaddieren der einzelnen Schnitte entsteht für den<br />

Betrachter eine Volumendarstellung.<br />

Abbildung 3.1: Prinzip der Texture slicing Volumendarstellung: a) Textur<br />

Anordnung b) 2D-Texturen c) Optische Interpretation; Entnommen Real-<br />

Time Volume Graphics [3] Seite 49, Figure 3.1.<br />

3.2 Prototypischer Software-Entwurf<br />

Neben der schriftlichen Ausarbeitung soll ein praktisch verwendbarer Prototyp<br />

erstellt werden. Dieser soll Datensätze, welche aus üblichen Bilddaten<br />

bestehen, laden und diese als Volumengrafik darstellen können. Diese Darstellung<br />

soll vom Benutzer durch eine einfache Oberfläche manipuliert werden<br />

können. Durch das Laden einer Transferfunktion soll der Benutzer die<br />

Möglichkeit haben, die von ihm gewünschten Informationen hervorzuheben<br />

und unerwünschte auszublenden.<br />

Für das Volumen Rendering müssen die Daten vorverarbeitet werden. Dieses<br />

umfasst die Berechnung der Gradienten, welche für die Beleuchtung benötigt<br />

werden, und die Erstellung der verschiedenen Textur-Datensätze für die<br />

verschiedenen Ansichten auf das Volumen. Nachdem diese Vorbereitungen<br />

getroffen wurden, kann eine Darstellung erstellt werden. Dafür müssen zuerst<br />

die Shader in die Grafikkarte übertragen werden, wobei auch die Darstellungsoptionen<br />

des Benutzers auf die Grafikkarte transferiert werden. Daraufhin<br />

werden die einzelnen Ebenen, welche nach dem Texture slicing Verfahren<br />

verwendet werden um das Volumen aufzubauen, einzeln der Grafikkarte<br />

übergeben. Die liest in ihrem Shader die Dichtewerte aus den übergebenen<br />

Texturen aus und klassifiziert diese durch die Transferfunktion, wobei der<br />

endgültige Farbwert ermittelt wird. Abhängig von der folgenden Beleuchtungsberechnung<br />

wird dieser Farbwert schwächer oder intensiver dargestellt.


3.2. PROTOTYPISCHER SOFTWARE-ENTWURF 21<br />

Die Farbwerte alle Ebenen werden miteinander überlagert, um das endgültige<br />

Bild zu erhalten, das auf dem Bildschirm angezeigt wird. Eine Darstellung<br />

der Interaktion des Benutzers und des Volumen Renderings ist in Abbildung<br />

3.2 dargestellt.<br />

Abbildung 3.2: Programmablauf mit Benutzerinteraktion und Rendering<br />

Schritten<br />

Um dieses Konzept zu realisieren, sind nur wenige Komponenten erforderlich.<br />

Die Benutzeroberfläche sollte verständlich gestaltet werden, um dem<br />

Benutzer eine einfache Manipulation der Darstellung zu ermöglichen. Alle<br />

Einstellungen vom Benutzer und die geladenen Daten, sollten performant in<br />

einem DataStore gespeichert werden. Dieses ist soweit wichtig, weil auf die<br />

Daten oft zugegriffen wird. Für die Darstellung wird ein OpenGL fähiges<br />

Widget benötigt, welches die Shader in die Grafikkarte lädt und die benötigten<br />

Daten überspielt. Auch die Anzeige läuft über diese Komponente.<br />

Dieser Grundaufbau ist in Abbildung 3.3 verdeutliche, und stellt die Interaktion<br />

zwischen den Komponenten dar.


3.2. PROTOTYPISCHER SOFTWARE-ENTWURF 22<br />

Abbildung 3.3: Komponenten und ihre angedachte Interaktion untereinander<br />

Bei der Umsetzung ist darauf geachtet worden, strukturiert an die einzelnen<br />

Funktionen heranzugehen. Um die Funktionen übersichtlich zu implementieren,<br />

wurden diese in einzelne Schritte unterteilt und objektorientiert in C++<br />

in Klassen und Methoden umgesetzt. Am Ende jeder einzelnen Funktion war<br />

dadurch eine komplett lauffähige Version verfügbar. So wurden nacheinander<br />

die verschiedenen Funktionen umgesetzt. Im nachfolgenden Abschnitt<br />

4 Umsetzung werden diese Funktionen und Schritte hintereinander präsentiert.


Kapitel 4<br />

Umsetzung<br />

4.1 2D Texture-Sliced Volumen Rendering<br />

4.1.1 Texturen Anordnung<br />

Mit der Darstellung durch mehrere Ebenen ist ein höherer Aufwand verbunden,<br />

als wenn man Volumen mit 3D-Texturen darstellt. Als Bedingung,<br />

damit bei dem Betrachter ein Eindruck eines Volumens entsteht, ist es nötig,<br />

dass die Blickrichtung dessen frontal auf die Ebenen gerichtet ist. Wenn die<br />

Bedingung nicht erfüllt ist, entsteht die Problematik, dass der untersuchte<br />

Strahl auf keine Ebene mehr trifft und der Betrachter nichts mehr vom darzustellenden<br />

Objekt sehen kann. Dieses ist das selbe Phänomen, als wenn<br />

ein Betrachter seitlich auf ein perfekt gerades Stück Papier blickt. So würde<br />

er nur die Stirnseite wahrnehmen, da das Papier eine Dicke besitzt, dennoch<br />

aber nichts von der Vorder- oder Rückseite sehen. Da in der Computergrafik<br />

Ebenen keine Dicke besitzen, wäre für den Betrachter nicht einmal die<br />

Stirnseiten zu erkennen. Dies ist im a) Zweig der Abbildung 4.1 dargestellt.<br />

Abbildung 4.1: Darstellung des Rotationsproblems von Ebenen a) ohne Ebenenwechsel<br />

b) mit Ebenenwechsel


4.1. 2D TEXTURE-SLICED VOLUMEN RENDERING 24<br />

Damit dies nicht passiert, müssen die Ebenen abhängig der Blickrichtung<br />

des Benutzers angepasst werden. Ab einer Blickrichtungsänderung von 45 ◦<br />

muss die Ebenenausrichtung so verändert werden, dass diese wieder frontal<br />

zum Betrachter steht. Wird das auf allen Achsen beachtet, wird bei einer<br />

beliebigen Rotation immer eine konsistente Darstellung entstehen. Dieses ist<br />

im b) Zweig der Abbildung 4.1 dargestellt.<br />

Da abhängig von der Blickrichtung nun verschiedene Ansichten der Daten<br />

benötigt werden, ist es notwendig, diese anfänglich zur Verfügung zu stellen.<br />

Dafür wird für jede der drei Achsen ein eigener Datensatz benötigt, zwischen<br />

welchen bei Bedarf gewechselt werden kann.<br />

4.1.2 Einzelne Textur<br />

Um das Volumen Rendering Integral 2.3 hardwarebeschleunigt nachzubilden,<br />

wird auf die Arbeitsweise des Framebuffers der Grafikkarten-Pipeline<br />

zurückgegriffen. Damit dieses aber funktioniert, ist es notwendig, die Ebenen<br />

von Hinten nach Vorne (back-to-front) zu zeichnen, damit diese aufsummiert<br />

werden können. Die nähere Beschreibung dieses Verfahrens wird unter 4.1.4<br />

Zusammensetzung im Framebuffer erläutert.<br />

Im nachfolgenden Listing 4.1, wird eine Möglichkeit dargestellt, dieses umzusetzen.<br />

Dabei werden die Daten in einem Einheitswürfel [-1,1] und mit<br />

ZCOUNT Ebenen dargestellt.<br />

void GLFrame : : drawSlices NegativZ ( )<br />

{<br />

// Rahmenwerte f ü r d i e Ebenen im Einheitsw ü r f e l<br />

double dZPos = −1.0;<br />

double dZStep = 2 . 0 / f l o a t (ZCOUNT−1) ;<br />

f o r ( i n t s l i c e = 0 ; s l i c e < ZCOUNT; s l i c e ++){<br />

// P o s i t i o n e n der V e r t i c e s<br />

QVector3D v e r t i c e s [ 6 ] = {<br />

. . .<br />

} ;<br />

// Texturekoordinaten der V e r t i c e s<br />

QVector2D textCoord [ 6 ] = {<br />

. . .<br />

} ;<br />

}<br />

}<br />

// Zeichnet d i e V e r t i c e s mit den angegebenen<br />

Texturkoordinaten<br />

drawTexturedQuad ( v e r t i c e s , textCoord ,<br />

bindTexture ( textureStackZ [ s l i c e ] )<br />

) ;<br />

dZPos += dZStep ;<br />

Listing 4.1: Berechnung der Ebenenrichtung der negativen Z-Achse


4.1. 2D TEXTURE-SLICED VOLUMEN RENDERING 25<br />

Um alle Blickrichtungen abzudecken, müssen analog dazu weitere Methoden<br />

für jede der Achsen in positiver und negativer Richtung erstellt werden. Um<br />

die Ebenen in umgekehrter Reihenfolge zu zeichnen, muss nur dZPos = 1.0<br />

initialisiert werden und dann in den negativen Bereich laufen. Der Texturzugriff<br />

erfolgt an der Stelle textureStackZ[ slice −1]. Diese Umsetzung erlaubt<br />

es, abhängig von der Betrachtungsrichtung, die passende Zeichenmethode<br />

aufzurufen.<br />

varying vec2 textCoord ;<br />

uniform f l o a t textAlpha ;<br />

uniform sampler2D t e x t u r e ;<br />

void main ( void )<br />

{<br />

// Konstante Transparenz<br />

f l o a t alpha = 0 . 0 1 ;<br />

g l F r a g C o l o r = vec4 ( texture2D ( texture , textCoord . xy ) . rgb , alpha ) ;<br />

}<br />

Listing 4.2: Fragmentshader für eine Textur mit konstanter Transparenz<br />

Mittels des Fragmentshaders wird dann, wie im Volumen Rendering Integral<br />

2.4 beschrieben, der Farbwert für die aktuelle Ebene berechnet.Dabei<br />

ist die Darstellung der Emission und Absorption direkt auf vorhandene Mittel<br />

übertragbar. Die Emission eines Fragments entspricht dabei genau dem<br />

Farbwert der Textur und die Absorption kann durch den Alpha-Wert der<br />

Farbe umgesetzt werden. Um eine einfache Volumendarstellung zu erzeugen,<br />

wird im Fragmentshader für die Transparenz eine konstanter Wert verwendet.<br />

Im Listing 4.2 wird dieser konstante Wert verwendet.<br />

Abbildung 4.2: Einfache Volumendarstellung mit konstanter Transparenz


4.1. 2D TEXTURE-SLICED VOLUMEN RENDERING 26<br />

Die daraus resultierende Volumendarstellung ist sehr von der Anzahl der<br />

Ebenen abhängig, denn der Farbwert jede Ebene geht mit 1% seines Farbwertes<br />

in das endgültige Bild ein. Dadurch kann es passieren, dass bestimmte<br />

Informationen nicht mehr vom Betrachter erkennbar sind, weil diese von vielen<br />

Ebenen verdeckt werden, die höheren Dichtwerte besitzen.<br />

4.1.3 Mehrfach Texturen<br />

Durch die einfache Texturierung kann zwar ein Volumen erstellt werden,<br />

dennoch ist die Bildqualität sehr von der Anzahl der Ebenen aus dem Datenensatz<br />

abhängig. Durch die 2D-Textur Umsetzung wird die Darstellung<br />

void GLFrame : : drawSlices NegativZ ( i n t r e n d S l i c e s )<br />

{<br />

f l o a t dZPos = −1.0;<br />

f l o a t dZStep = 2 . 0 / f l o a t ( r e n d S l i c e s −1) ;<br />

f o r ( i n t s l i c e = 0 ; s l i c e < r e n d S l i c e s − 1 ; s l i c e ++){<br />

}<br />

}<br />

// Berechnet den Index der benö t i g t e n Texturen<br />

f l o a t dZPosTex = (ZCOUNT*( dZPos +1.0) / 2 . 0 ) ;<br />

i n t texIndex = ( i n t ) dZPosTex ;<br />

// I n t e r p o l a t i o n s w e r t f ü r d i e P o s i t i o n der Ebene zwischen<br />

den Texturen<br />

f l o a t alpha = dZPosTex−f l o a t ( texIndex ) ;<br />

// P o s i t i o n e n der V e r t i c e s mit Verschiebung e ntlang der Z−<br />

Achse<br />

QVector3D v e r t i c e s [ 6 ] = {<br />

QVector3D ( −1.0 , 1 . 0 , dZPos ) ,<br />

. . .<br />

} ;<br />

// Texturekoordinaten der V e r t i c e s mit O f f s e t f ü r P o s i t i o n<br />

zwischen den Ebenen<br />

QVector3D textCoord [ 6 ] = {<br />

QVector3D ( 0 . 0 , 1 . 0 , alpha ) ,<br />

. . .<br />

} ;<br />

// Zeichnet d i e V e r t i c e s mit den angegebenen<br />

Texturkoordinaten<br />

drawTexturedQuad ( v e r t i c e s , textCoord ,<br />

bindTexture ( textureStackZ [ texIndex ] ) ,<br />

bindTexture ( textureStackZ [ min ( texIndex +1,ZCOUNT) ] )<br />

) ;<br />

dZPos += dZStep ;<br />

Listing 4.3: Berechnung einer Ebene aus den Texturen auf der Z-Achse


4.1. 2D TEXTURE-SLICED VOLUMEN RENDERING 27<br />

nur bilinear interpoliert, also entlang der Achsen zwischen denen die Ebene<br />

aufgespannt wird. Optisch wirkt sich dies in der Art aus, dass beim Volumen<br />

Stufenartefakte sichtbar sind. Um diese Beschränkung aufzuheben, kann auf<br />

zwei Texturen zurückgegriffen und aus diesen dann eine dazwischenliegende<br />

Ebene interpoliert werden.<br />

Die Problematik bei der Umsetzung besteht darin, die benachbarten Texturen<br />

zu finden, zwischen welchen die neue Ebene erstellt werden soll. Dafür<br />

muss die Position innerhalb des Einheitswürfels dZPos auf die Texture-Indizes<br />

abgebildet werden, was mittels float dZPosTex = (ZCOUNT * (dZPos + 1.0)/ 2.0)<br />

; geschieht. Dabei bildet (dZPos + 1.0)/ 2.0) die Position auf den Bereich [0.0,<br />

1.0] ab und durch Multiplikation mit der Gesamtanzahl der vorhandenen<br />

Ebenen erhält man eine Fließkommazahl, welche die Position der zu errechnenden<br />

Ebene entspricht. Um den unteren Textur-Index zu erhalten, wird<br />

davon der Ganzzahlteil verwendet. Die zweite Textur ist der nächst höhere<br />

Index bzw. der nächst tiefere darauf. Die Nachkommastellen der Zahl<br />

symbolisieren die Position zwischen den beiden Texturen.<br />

Da die Interpolation erst im Fragmendshader erfolgt, muss der Offset zwischen<br />

den Texturen dem Shader übergeben werden. Um keine zusätzlichen<br />

uniform Variablen verwenden zu müssen, bietet es sich an, die Texturkoordinaten<br />

um eine Dimension zu erweitern und dort den Offset zu speichern.<br />

Bei der Verwendung von Texturen im Zusammenhang mit dem Errechnen<br />

von Texturkoordinaten, kann es durch Rechenungenauigkeiten zu Darstellungsfehler<br />

kommen. Dadurch das die Rechengenauigkeit limitiert ist, kann<br />

es vorkommen, dass die Koordinaten, welche für den Texturzugriff verwendet<br />

werden, den Wertebereich von [0.0,1.0] weit in den Nachkommastellen<br />

verlassen. Dies kann damit behoben werden, dass beim Laden der Textur in<br />

die Grafikkarte (glBindTexture) das Verhalten für das Verlassen des Wertebereiches<br />

angegeben wird. In Listing 4.4 wird aufgeführt, wie die Texturparameter<br />

s und t beim Überschreiten auf den Grenzwert beschränkt werden.<br />

Diese Parameter beschreiben einen Punkt auf der Textur mit s als Breite<br />

und t als Höhe. Weitere Beschreibungen von Texturparametern können in<br />

der OpenGL SuperBible[9] nachgeschlagen werden.<br />

glTexParameteri (GL TEXTURE 2D,GL TEXTURE WRAP S,GL CLAMP TO EDGE<br />

) ;<br />

glTexParameteri (GL TEXTURE 2D,GL TEXTURE WRAP T,GL CLAMP TO EDGE<br />

) ;<br />

Listing 4.4: Textureinstellung zum begrenzen des Wertebereichen<br />

Durch ein paar Änderungen im Fragmentshader kann eine Interpolation<br />

zwischen die beiden Texturen vorgenommen werden. Dafür werden die beiden<br />

Texturen einzeln ausgewertet und zwischen den Ergebnissen, durch den<br />

Textur-Offset, welcher sich nun in den Texturkoordinaten befindet, linear<br />

interpoliert.


4.1. 2D TEXTURE-SLICED VOLUMEN RENDERING 28<br />

varying vec3 textCoord ;<br />

uniform sampler2D t e x t u r e 0 ;<br />

uniform sampler2D t e x t u r e 1 ;<br />

void main ( void )<br />

{<br />

f l o a t mixColor , c o l o r 0 , c o l o r 1 , alpha ;<br />

// Konstante Transparenz<br />

alpha = 0 . 0 1 ;<br />

c o l o r 0 = texture2D ( texture0 , textCoord . xy ) . r ;<br />

c o l o r 1 = texture2D ( texture1 , textCoord . xy ) . r ;<br />

// Lineare I n t e r p o l a t i o n zwischen den Texturen<br />

mixColor = mix ( c o l o r 0 , c o l o r 1 , textCoord . z ) ;<br />

g l F r a g C o l o r = vec4 ( mixColor , mixColor , mixColor , alpha ) ;<br />

}<br />

Listing 4.5: Fragmentshader für mehrere Texturen mit konstanter<br />

Transparenz<br />

Nachdem nun auch in Richtung der letzten Achse interpoliert wurde, ist<br />

die gesamte Volumengrafik nun trilinear interpoliert. Dieser Effekt muss,<br />

wenn im Shader mit 2D-Texturen gearbeitet wird, selbstständig umgesetzt<br />

werden. Bei 3D-Texturen wäre dieses automatisch enthalten, denn bei den<br />

Texturzugriffen würde gleich trilinear interpoliert werden. Bei diese Umsetzung<br />

ist durch etwas mehr Aufwand, die selbe Funktionalität entstanden,<br />

welche bei 3D-Texturen vorhanden wäre. Nur das diese Variante mit weniger<br />

Grafikkartenspeicher auskommt.<br />

Da die Anzahl der Ebenen nun gezielt geändert werden kann, ist es zB. möglich,<br />

bei schlechterer Hardware eine geringere Ebenenanzahl zu verwenden,<br />

um die Anzeige zu beschleunigen. Darunter leidet jedoch die Bildqualität.<br />

Analog kann durch zusätzlichen Ebenen eine bessere Approximation des Volumens<br />

erhalten werden, um die Bildqualität zu erhöhen. Durch die Verwendung<br />

einer Transferfunktion, beschrieben in 4.2.1 Transferfunktion, werden<br />

die auftretenden Stufenartefakte der Volumendarstellung sichtbar reduziert.<br />

Diese Auswirkung ist auf der Abbildung 4.3 gut zu erkennen.<br />

Diese Umsetzung hat jedoch einen großen Nachteil. Für jede Ebene müssen<br />

individuell die passenden Texturen in den Texturspeicher der Grafikkarte<br />

übertragen werden, was einiges an Zeit beansprucht. Alle Ebenentexturen<br />

in den Grafikkartenspeicher zu übertragen, wäre bei kleineren Datensätzen<br />

möglich. In der Realität aber unrealistisch, da gängige Datensätze meistens<br />

den Grafikkartenspeicher übersteigen.


4.1. 2D TEXTURE-SLICED VOLUMEN RENDERING 29<br />

Abbildung 4.3: Mittels Transferfunktion dargestellte Ebenen Links: Einzelne<br />

Texturen (20 Ebenen) Rechts: Mehrfach Texturen (200 Ebenen)<br />

4.1.4 Zusammensetzung im Framebuffer<br />

Damit das Volumen Rendering Integral 2.4 vollständig umgesetzt wird, fehlt<br />

noch die Aufsummierung der einzelnen Farbwerte der Ebenen. Diese Aufgabe<br />

wird hardwarebeschleunigt vom Framebuffer ausgeführt. Damit diese so<br />

ausgeführt werden kann, müssen die Farbwerte der Ebenen, vom Betrachter<br />

aus gesehen, von Hinten nach Vorne verarbeitet werden. Dafür kann das<br />

unter Compositing beschriebene 2.4.3 Alpha-Blending verwendet werden.<br />

Damit das Alpha-Blending die Werte einfach aufsummiert, kann folgende<br />

Funktion verwendet werden.<br />

glAlphaFunc (GL SRC ALPHA, GL ONE MINUS SRC ALPHA)<br />

Dieses bedeutet, dass von dem neuen Fragment, nur in Höhe seines Alpha-<br />

Wertes eingeht und das im Framebuffer schon vorhandene mit der Differenz<br />

dazu. Wenn C ∈ R, G, B die Farbe darstellt und A für die Alphawert steht,<br />

dann entspricht das Mathematisch:<br />

C dest = C src A src + C fb (1 − A src )<br />

Die Aufsummierung von Hinten nach Vorne hat aber auch einen Nachteil.<br />

Ausgegangen vom Raytracingansatz wird hierbei der Strahl rückwärts ausgewertet.<br />

Daher ist es bei diesem Ansatz nicht möglich, die Berechnung<br />

vorzeitig abzubrechen, wenn der Farbwert sich optisch nicht mehr ändern<br />

würde (early ray termination).


4.2. KLASSIFIZIERUNG 30<br />

4.2 Klassifizierung<br />

4.2.1 Transferfunktion<br />

Die simple Volumendarstellung, durch eine Standardtransparenz, ist nicht<br />

sonderlich ansehnlich und zu Auswertungszwecken nicht geeignet. Durch die<br />

Technik der Klassifizierung kann die Darstellung angepasst werden. Dabei<br />

geht es um das Separieren von unterschiedlichen Strukturen, innerhalb des<br />

Volumens, anhand ihrer Dichtewerte. Dafür wird jedem Dichtewert durch<br />

die Transferfunktion ein Farbwert zugewiesen. Durch dieses Abbilden ist es<br />

möglich, einem Dichtewert gezielt eine Absorption und Emission zuzuweisen.<br />

Abhängig von der Transferfunktion ist es nun möglich, bestimmte Informationen<br />

einzufärben oder auszublenden, was die praktische Nutzbarkeit<br />

fördert. Die Herangehensweise zur Erstellung von Transferfunktionen wird<br />

im späteren Abschnitt 5.2 Erstellung einer Transferfunktion beschrieben. In<br />

der dort zu findenden Abbildung 5.3 können Beispiele und ihre Auswirkungen<br />

betrachtet werden.<br />

Es gibt unterschiedliche Verfahren Daten zu klassifizieren, am Häufigsten<br />

findet Pre- bzw. Post-Classification Anwendung. Weiterführende Informationen<br />

können einem Text von Duda, Hart und Stork [10] entnommen werden.<br />

4.2.2 Pre- vs. Post-Klassifizierung<br />

Die beiden Varianten unterscheiden sich darin, zu welchem Zeitpunkt die<br />

Transferfunktionen angewendet wird. Geschieht dieses als Vorverarbeitungsschritt<br />

vor der Interpolation der Dichtewerte, so wird dieses als pre-classification<br />

bezeichnet, geschieht es danach als post-classification. Historisch<br />

gesehen sind die beiden Varianten nacheinander eingeführt worden, denn bei<br />

Abbildung 4.4: Schematische Darstellung von a) Pre-Klassifizierung: zuerst<br />

Klassifizierung dann Interpolation b) Post-Klassifizierung: zuerst Interpolation<br />

dann Klassifizierung


4.2. KLASSIFIZIERUNG 31<br />

der Fixed-Function-Pipeline war es nicht möglich Post-Klassifizierung vorzunehmen,<br />

aus diesem Grund musste dieses vorher geschehen. Erst durch<br />

eine Erweiterung war es möglich weitere Texturzugriffe umzusetzen, sodass<br />

Post-Klassifizierung realisiert werden konnte. Durch die Einführung der programmierbaren<br />

Pipeline wurde die Realisierung noch weiter vereinfacht, da<br />

dieser Standard überall verfügbar war.<br />

Die optische Qualität der beiden Klassifizierungsvarianten ist sehr unterschiedlich.<br />

Da bei der Pre-Klassifizierung die Daten zuerst klassifiziert werden<br />

und anschließend auf den Farbwerten interpoliert wird, treten starke<br />

Blockartefakte auf, sodass das Ergebnis recht grob und körnig wirkt. Bei<br />

der Post-Klassifizierung werden die Dichtewerte zuerst interpoliert, was eine<br />

höhere Auflösung der Werte erreicht. Anschließend können diese Werte gezielt<br />

klassifiziert werden, was zu einem feineren und detaillierteren Resultat<br />

führt. Der Unterschied ist schematische in Abbildung 4.4 dargestellt. Eine<br />

Gegenüberstellung beider Varianten ist im Abbildung 4.5 zu finden.<br />

Abbildung 4.5: Vergleich von Pre-(Links) und Post-Klassifizierung(Rechts)<br />

eines CT-Datensatzes mit der selben Transferfunktion. Entnommen Real-<br />

Time Volume Graphics [3] Seite 90, Figure 4.2.<br />

4.2.3 Post-Klassifizierung<br />

Um die Post-Klassifizierung in die Implementierung zu integrieren, sind nur<br />

ein paar zusätzlicher Codezeilen erforderlich. Dennoch ist dieser Schritt nicht<br />

zu unterschätzen, da er eine große optische Wirkung erzielt.<br />

Es gibt zwei Möglichkeiten Transferfunktionen umzusetzen. Entweder können<br />

diese als Funktion umgesetzt werden oder als Textur. Dabei hat die<br />

Textur-Variante einen Vorteil, diese ist im Gegensatz zu der Funktion aus-


4.2. KLASSIFIZIERUNG 32<br />

tauschbar und ist nicht fest im Code verankert. Dieses ist soweit relevant,<br />

dass die Interaktion mit der Darstellung ermöglicht wird, abhängig von<br />

den gewünschten Informationen können unterschiedliche Transferfunktionen<br />

(Texturen) geladen werden, um die Visualisierung anzupassen.<br />

Um eine Transferfunktion im Fragmentshader zu verwenden, wird diese einfach<br />

als zusätzliche Textur übergeben. Für die sinnvolle Nutzung dieser, ist<br />

noch ein Anwendungsbereich (fromIndex, toIndex) zu verwenden. Auf diesen<br />

Bereich wird die Transferfunktion skaliert und angewendet. Ist ein Dichtewert<br />

unter- oder oberhalb des Bereiches, wird der Minimal- bzw. Maximalwert<br />

verwendet.<br />

In Wirklichkeit ist die Funktion nur eine 1D-Funktion, aber technisch muss<br />

diese in unserem Fall als 2D-Textur umgesetzt werden, da OpenGL ES nur<br />

2D-Texturen unterstützt. Aus diesem Grund wird die Transferfunktionstextur<br />

mit der Höhe 1 umgesetzt und im Fragment Shader dann auf dem<br />

Höhenwert 0.5 ausgelesen, um sicher einen gültigen Farbwert zu erhalten.<br />

varying vec3 textCoord ;<br />

uniform sampler2D t e x t u r e 0 ;<br />

uniform sampler2D t e x t u r e 1 ;<br />

// K l a s s i f i z i e r u n g<br />

uniform sampler2D t r a n s f e r F u n c t i o n ;<br />

uniform f l o a t fromIndex ;<br />

uniform f l o a t toIndex ;<br />

void main ( void )<br />

{<br />

f l o a t mixSample , sample0 , sample1 ;<br />

sample0 = texture2D ( texture0 , textCoord . xy ) . r ;<br />

sample1 = texture2D ( texture1 , textCoord . xy ) . r ;<br />

mixSample = mix ( sample0 , sample1 , textCoord . z ) ;<br />

// Ab den Randwerten bekommt a l l e s den oberen oder unteren Wert<br />

zugewiesen , dazwischen wird d i e Funktion auf den Bereich<br />

s k a l i e r t<br />

i f ( mixSample < fromIndex ) {<br />

mixSample = 0 . 0 ;<br />

} e l s e i f ( mixSample > toIndex ) {<br />

mixSample = 1 . 0 ;<br />

} e l s e {<br />

mixSample = ( mixSample−fromIndex ) /<br />

( toIndex−fromIndex ) ;<br />

}<br />

g l F r a g C o l o r = texture2D ( t r a n s f e r F u n c t i o n , vec2 ( mixSample , 0 . 5 ) ) ;<br />

}<br />

Listing 4.6: Fragment Shader mit Transferfunktion


4.3. BELEUCHTUNG 33<br />

Post-Klassifizierung hat einen gewichtigen Nachteil. Diese kann nur auf neuerer<br />

Hardware umgesetzt werden, denn es wird ein Fragmentshader benötigt.<br />

Dafür ist eine Grafikkarte mit programmierbarer Pipeline notwendig.<br />

4.3 Beleuchtung<br />

4.3.1 Blinn-Phong<br />

Um die Volumendarstellung mit Beleuchtung zu erweitern, wird ein Beleuchtungsmodell<br />

benötigt, dass möglichst unaufwändig zu berechnen ist, sodass<br />

die Laufzeit nicht unnötig belastet wird.<br />

Eines der bekanntesten Beleuchtungsmodelle ist das Phong Shading, welches<br />

nach seinem Entwickler Bui Tuong Phong benannt wurde. In diesem Modell<br />

wird die Beleuchtung aus einem ambient, diffuse und specular Anteil errechnet.<br />

Dieses Modell ist in Formel 4.1 aufgeführt. Eine nähere Beschreibung<br />

der Terme kann dem Artikel [11] von Bui Tuong Phong entnommen werden.<br />

I(p, v) = k a L A<br />

} {{ }<br />

ambient<br />

∑<br />

∑<br />

+ k d L j (n · s j ) + k s L j (v · r j ) a<br />

j<br />

} {{ }<br />

diffuse<br />

j<br />

} {{ }<br />

specular<br />

(4.1)<br />

Ausgehend vom Phong Shading wurde von James F. Blinn 1977[12] ein<br />

erweitertes Modell (Blinn-Phong) vorgestellt, welches die Berechnung des<br />

spekularen Anteils modifiziert. Hier wird davon ausgegangen, dass ein Körper<br />

keine einheitliche gerade und perfekt spiegelnde Oberfläche besitzt, wie<br />

es im Phong-Modell angenommen wird. Diese von Blinn als makroskopische<br />

Normale bezeichnete Vereinfachung entspricht keiner realistischen Oberfläche.<br />

Im Blinn-Phong-Modell besteht eine Oberfläche aus sehr vielen kleinen<br />

spiegelnden Facetten, welche unterschiedlich orientiert sind. Jede dieser Facetten<br />

besitzt eine eigene mikroskopische Normale, dies wird in Abbildung<br />

4.6 schematisiert.<br />

Abbildung 4.6: Visualisierung von mikro- und makroskopischer Normalen


4.3. BELEUCHTUNG 34<br />

Damit nicht jede Facette einzeln Berechnet werden muss, kann eine Vereinfachung<br />

verwendet werden. Dieser Halfway Vectors beschreibt die Normale,<br />

welche eine perfekt spiegelnde Oberfläche haben müsste, um vom Betrachter<br />

v zur Lichtquelle s zu reflektieren. Dabei weicht der Halfway-Vektor h um<br />

den Winkel α h von der makroskopischen Normalen ab. Dies bedeutet, dass<br />

die mikroskopischen Normalen um cos α (α h ) um die makroskopische Normale<br />

verteilt sind.<br />

Abbildung 4.7: Darstellung der Winkel des Blinn-Phong Modells<br />

Durch die Auswertung des Winkels α h wird der spekulare Anteil berechnet.<br />

Der Halfway-Vektor wird wie in Formel 4.2 beschreiben errechnet.<br />

h = v + s<br />

|v + s|<br />

(4.2)<br />

Der spekulare Anteil wird beim Blinn-Phong-Modell mit h ∗ n berechnet.<br />

Wird diese Variante für den spekularen Anteil in der Phong Shading Formel<br />

verwendet, so erhält man die in 4.3 aufgelistete Formel.<br />

I(p, v) = k a L A<br />

} {{ }<br />

ambient<br />

∑<br />

∑<br />

+ k d L j (n · s j ) + k s L j (n · h j ) a<br />

j<br />

} {{ }<br />

diffuse<br />

j<br />

} {{ }<br />

specular(Phong)<br />

(4.3)<br />

Um Blinn-Phong im Fragmentshader umzusetzen, bietet es sich an, dieses in<br />

einer eigene Methode zu strukturieren. In Listing 4.7 ist eine mögliche Umsetzung<br />

dargestellt. Diese Methode bekommt als Parameter die Normale der<br />

Oberfläche n, die Betrachtungsrichtung v und die Richtung zur Lichtquelle<br />

l.


4.3. BELEUCHTUNG 35<br />

vec3 blinnPhong ( vec3 n , vec3 v , vec3 l )<br />

{<br />

// M a t e r i a l i e n<br />

vec3 kAmbient = vec3 ( 0 . 1 ) ; // Ambient<br />

vec3 k D i f f u s e = vec3 ( 0 . 6 ) ; // Defuse<br />

vec3 kSpecular = vec3 ( 0 . 2 ) ; // Spekular<br />

f l o a t s h i n i n e s s = 1 0 0 . 0 ; // R e f l e x i o n s f a k t o r<br />

// Licht<br />

vec3 ambientLight = vec3 ( 0 . 3 ) ;<br />

vec3 l i g h t C o l o r = vec3 ( 1 . 0 ) ;<br />

// Ambienter T e i l<br />

vec3 ambient = kAmbient* ambientLight ;<br />

// Halfway−Vektor zwischen Licht− und Betrachtungsrichtung<br />

vec3 h = normalize ( l+v ) ;<br />

// Defuser T e i l<br />

f l o a t d i f f u s e L i g h t = max( dot ( l , n ) , 0 ) ;<br />

vec3 d i f f u s e = k D i f f u s e * l i g h t C o l o r * d i f f u s e L i g h t ;<br />

// S p e k u l a r e r T e i l<br />

f l o a t s p e c u l a r L i g h t = pow(max( dot (h , n ) , 0 ) , s h i n i n e s s ) ;<br />

vec3 s p e c u l a r = kSpecular * l i g h t C o l o r * vec3 ( s p e c u l a r L i g h t ) ;<br />

// Aufsummieren der E i n z e l t e i l e<br />

r e t u r n ambient+d i f f u s e+s p e c u l a r ;<br />

}<br />

Listing 4.7: Blinn-Phong Methode im Fragmentshader<br />

4.3.2 Gradient Berechnung<br />

Bei der Beleuchtung von Volumengrafiken kommt ein großes Problem auf.<br />

Anders als bei oberflächenbasierten Objekten ist es bei Volumendaten nicht<br />

einfach möglich eine Normale anzugeben. Da keine Flächen oder Abhängigkeiten<br />

zu den benachbarten Daten vorhanden sind, kann ausgehend vom<br />

betrachteten Punkt keine eindeutige Orientierung angegeben werden. Für<br />

die Beleuchtungsberechnung ist eine Oberflächenorientierung aber unerlässlich,<br />

da an dieser der zusätzliche Lichtanteil der Lichtquelle bemessen wird.<br />

Um so etwas wie eine Orientierung zu erhalten, kann die Steigung (Gradient)<br />

der Dichtewerte an der betroffenen Position auswerten werden. Damit<br />

der Gradient dem Wert an der gesuchten Position entspricht, ist es nötig,<br />

diesen aus den umliegenden Daten zu bilden. Dafür wird in jeder Dimension<br />

einfach die Differenz zwischen den umliegenden Daten gebildet. Wenn der<br />

Wert an der Position x i + 1 mit dem Wert bei x i − 1 subtrahiert wird, ergibt<br />

sich daraufhin eine diskrete Approximation für den gesuchten Gradienten


4.3. BELEUCHTUNG 36<br />

an der Position x i . Dieses ist in Abbildung 4.8 symbolisch dargestellt, die<br />

grüne Linie bezeichnet die errechnete Steigung, die rote Linie die Tatsächliche.<br />

Abhängig von der Schrittweite kann eine unterschiedliche Differenz zur<br />

tatsächlichen Steigung auftreten.<br />

Abbildung 4.8: Berechnung des Gradienten (Steigung) an der Stelle x i<br />

Bedingt durch die Berechnung ist der Gradienten-Vektor in Richtung des zunehmenden<br />

Dichtewertes orientiert, also in Richtung der gedachten Fläche.<br />

Für das Beleuchtungsmodell wird aber ein fortgerichteter Vektor benötigt.<br />

Dieser kann durch die einfache Umkehr der einzelnen Komponenten erreicht<br />

werden. Diese Korrektur der Orientierung ist unbedingt notwendig, da sonst<br />

die falsche Seite für die Berechnung der Beleuchtung verwendet werden würde.<br />

In der Abbildung 4.9 ist eine Gegenüberstellung der beiden Ergebnisse,<br />

die Gradienten sind durch Pfeile dargestellt worden.<br />

Abbildung 4.9: Links: Berechnete Gradienten (Steigung), Rechts: Berichtigte<br />

Orientierung


4.3. BELEUCHTUNG 37<br />

Anhand der Länge des Gradienten-Vektors kann die Dichteänderung an der<br />

betrachteten Stelle bewertet werden. Dies kann dafür verwendet werden,<br />

dass nur Gradienten ab einer bestimmten Länge weiter verwendet werden.<br />

Dadurch kann die Wirkung der Beleuchtungsberechnung angepasst werden.<br />

Wenn zB. nur größere Änderungen berücksichtigt werden sollen, führt es<br />

dazu, dass die Konturen der Darstellung mehr betont würden.<br />

Es gibt zwei grundlegende Verfahren den Gradienten zu berechnen. Die erste<br />

Variante besteht darin, die Gradienten vorauszuberechnen (precomputed),<br />

die Zweite wäre es, den Gradienten individuell zu berechnen, wenn dieser<br />

benötigt wird(on the fly). Die zweite Variante würde direkt im Fragmentshader<br />

durchgeführt werden, und da damit auf Nachbarwerte zugegriffen werden<br />

müsste, würden 3D-Texturen benötigt werden. Diese sind in OpenGL<br />

ES nicht verfügbar, daher wurde für die Umsetzung die erste Variante gewählt.<br />

Dies hat noch einen weiteren Vorteil. Da die Daten schon vorhanden<br />

sind, fällt die benötigte Berechnungszeit während der Laufzeit weg. Diese<br />

Berechnungen müssen dennoch der Darstellung vorangehen, dies bietet sich<br />

beim Laden des Datensatzes an.<br />

Die in Listing 4.8 dargestellte Methode berechnet die Gradienten und speichert<br />

diese wieder in einer Textur. Bis jetzt wurde von den Texturwerten<br />

nur eine Komponente verwendet, da alle drei Farbwerte gleich sind (Graufarbenbild).<br />

Wenn der errechnete Gradienten-Vektor in den RGB-Werten<br />

der Textur und der alte Dichtewert in dem Alpha-Wert gespeichert wird,<br />

können alle Informationen in einer Textur untergebracht werden. Das hat<br />

den Vorteil, dass alle Informationen später mit einem Texturzugriff ausgelesen<br />

werden. Dabei ersetzt die Methode die ursprünglichen Daten mit den<br />

Neuen. Wenn die Datensätze für die anderen Ansichten aus dem Original<br />

zusammengesetzt werden, sind dadurch alle Datensätze mit den Gradienten<br />

versehen.<br />

Da sich Volumendaten in alle drei Dimensionen erstrecken, ist es auch nötig<br />

den Gradienten über diese zu bilden. Um dies zu tun, wird ein min-/<br />

und maxSample erstellt und die Differenz dieser ergibt den Gradienten. Die<br />

Sample werden jeweils um eine Einheit davor und danach gewählt.<br />

Da es auch möglich ist, dass der Gradient negative Werte annimmt, muss<br />

dieser zuerst in den positiven Wertebereich skaliert werden, damit er in einer<br />

Textur gespeichert werden kann. Dies geschieht mittels (gradientVec.x()+1)*125<br />

. Dadurch wird der normierte Vektor auf den Wertebereich [0,250] skaliert.<br />

Beim Auslesen der Werte in der Textur ist zu beachten, dass dieser auf<br />

den Wertebereich [-1,1] zurückskaliert wird. Der Wertebereich von 255 wird<br />

gezielt nicht komplett verwendet, da es bei Rechenungenauigkeiten dazu<br />

kommen könnte, dass der Grenzwert von 255 überschritten wird. Dadurch<br />

ist es sichergestellt, dass der Gradient in die Textur gespeichert werden kann.<br />

Um ein besseres optisches Resultat zu erhalten, ist zu empfehlen, nur größere<br />

Dichteänderungen in der Beleuchtung zu berücksichtigen. Das führt<br />

dazu, dass nur die Übergänge betont werden und sich die verschiedenen


4.3. BELEUCHTUNG 38<br />

Materialien stärker voneinander abgrenzen. Dieses kann einfach durch die<br />

Überprüfung der Länge eines Vektors geschehen.<br />

void DataStore : : b u i l d G r a d i e n t S l i c e s ( void )<br />

{<br />

QImageList g r a d i e n t L i s t ;<br />

QVector3D sampleMin = QVector3D ( ) ;<br />

QVector3D sampleMax = QVector3D ( ) ;<br />

const QImage* b e f o r e S l i c e , * c u r r S l i c e , * a f t e r S l i c e ;<br />

// Z u s a t z b i l d f ü r d i e Randuntersuchungen ( Dichtewert 0)<br />

QImage blackImage = QImage ( t e x t u r e S t a c k [ 0 ] . width ( ) ,<br />

t e x t u r e S t a c k O r g i n a l [ 0 ] . h e i g h t ( ) ,<br />

QImage : : Format ARGB32<br />

) ;<br />

blackImage . f i l l ( QColor ( 0 , 0 , 0 ) ) ;<br />

f o r ( i n t sliceNumber = 0 ; sliceNumberwidth ( ) ; x++){<br />

f o r ( i n t y = 0 ; y < c u r r S l i c e −>h e i g h t ( ) ; y++){<br />

// Setzen der min−/max Samples , f ü r Rä nder wird 0<br />

bzw . blackImage verwendet<br />

i f ( x == 0) {<br />

sampleMin . setX ( 0 ) ;<br />

sampleMax . setX ( qRed ( c u r r S l i c e −>p i x e l ( x+1,y ) ) ) ;<br />

} e l s e i f ( x == c u r r S l i c e −>width ( ) −1){<br />

sampleMin . setX ( qRed ( c u r r S l i c e −>p i x e l ( x−1,y ) ) ) ;<br />

sampleMax . setX ( 0 ) ;<br />

} e l s e {<br />

sampleMin . setX ( qRed ( c u r r S l i c e −>p i x e l ( x−1,y ) ) ) ;<br />

sampleMax . setX ( qRed ( c u r r S l i c e −>p i x e l ( x+1,y ) ) ) ;<br />

}<br />

i f ( y == 0) {<br />

sampleMin . setZ ( 0 ) ;<br />

sampleMax . setZ ( qRed ( c u r r S l i c e −>p i x e l ( x , y+1) ) ) ;<br />

} e l s e i f ( y == c u r r S l i c e −>h e i g h t ( ) −1){<br />

sampleMin . setZ ( qRed ( c u r r S l i c e −>p i x e l ( x , y−1) ) ) ;


4.3. BELEUCHTUNG 39<br />

sampleMax . setZ ( 0 ) ;<br />

} e l s e {<br />

sampleMin . setZ ( qRed ( c u r r S l i c e −>p i x e l ( x , y−1) ) ) ;<br />

sampleMax . setZ ( qRed ( c u r r S l i c e −>p i x e l ( x , y+1) ) ) ;<br />

}<br />

sampleMin . setY ( qRed ( b e f o r e S l i c e −>p i x e l ( x , y ) ) ) ;<br />

sampleMax . setY ( qRed ( a f t e r S l i c e −>p i x e l ( x , y ) ) ) ;<br />

QVector3D gradientVec = sampleMax−sampleMin ;<br />

// I n v e r t i e r e n der O r i e n t i e r u n g<br />

gradientVec *= −1.0;<br />

gradientVec . normalize ( ) ;<br />

}<br />

}<br />

// Speichern des Gradienten und Dichtewertes in e i n<br />

neues Image und s k a l i e r t den Wert in den<br />

p o s i t i v e n Bereich<br />

gradientImage . s e t P i x e l ( x , y ,<br />

qRgba ( ( gradientVec . x ( ) +1) *125 ,<br />

( gradientVec . y ( ) +1) *125 ,<br />

( gradientVec . z ( ) +1) *125 ,<br />

qRed ( c u r r S l i c e −>p i x e l ( x , y ) ) )<br />

) ;<br />

}<br />

}<br />

g r a d i e n t L i s t . append ( gradientImage ) ;<br />

// Ersetzen der O r i g i n a l d a t e n mit der Gradient−Variante<br />

t e x t u r e S t a c k . c l e a r ( ) ;<br />

t e x t u r e S t a c k . append ( g r a d i e n t L i s t ) ;<br />

Listing 4.8: Vorbrauserechnung des Gradienten auf dem Datensatz<br />

4.3.3 Integration<br />

Um die Beleuchtung im Fragmentshader zu verwenden, wird nach der Klassifizierung<br />

der Gradient aus der Textur gelesen. Dabei ist zu beachten, dass<br />

die Werte immer noch in ihren alten Wertebereich zurück gerechnet werden<br />

müssen. Dies geschieht mit 2.0*mixSample.rgb−vec3(1.0). Da die Orientierung<br />

des Gradienten beim Speichern schon korrigiert wurde, kann dieser als die<br />

angenommene Normale und die benötigten Betrachtungsrichtung und Lichtrichtung<br />

zur Berechnung der Beleuchtung verwendet werden. Das einfallende<br />

Licht der Beleuchtung wird anschließend auf den Emissionswert aufgerechnet.<br />

In Listing 4.9 ist eine einfache Umsetzung mit fester Lichtposition aufgeführt.


4.3. BELEUCHTUNG 40<br />

varying vec3 textCoord ;<br />

uniform sampler2D t e x t u r e 0 ;<br />

uniform sampler2D t e x t u r e 1 ;<br />

uniform mat3 normalMatrix ;<br />

uniform sampler2D t r a n s f e r F u n c t i o n ;<br />

uniform f l o a t fromIndex ;<br />

uniform f l o a t toIndex ;<br />

void main ( void )<br />

{<br />

f l o a t mixSample , sample0 , sample1 ;<br />

vec4 r e s u l t ;<br />

sample0 = texture2D ( texture0 , textCoord . xy ) . r ;<br />

sample1 = texture2D ( texture1 , textCoord . xy ) . r ;<br />

mixSample = mix ( sample0 , sample1 , textCoord . z ) ;<br />

i f ( mixSample . a < fromIndex ) {<br />

mixSample . a = 0 . 0 ;<br />

} e l s e i f ( mixSample . a > toIndex ) {<br />

mixSample . a = 1 . 0 ;<br />

} e l s e {<br />

mixSample . a −= ( mixSample . a−fromIndex ) /<br />

( toIndex−fromIndex ) ;<br />

}<br />

r e s u l t = texture2D ( t r a n s f e r F u n c t i o n , vec2 ( mixSample , 0 . 5 ) ) ;<br />

// Feste Licht− und Kameraposition<br />

vec3 l i g h t P o s = vec3 ( 0 . 0 , 1 0 . 0 , 0 . 0 ) ;<br />

vec3 eyePos = vec3 ( 0 . 0 , 0 . 0 , 0 . 0 ) ;<br />

// Erweitern und n o r m a l i s i e r e n des Normalen−Vektors<br />

vec3 normal = 2 . 0 * mixSample . rgb−vec3 ( 1 . 0 ) ;<br />

// Korrektur der O r t hogonalit ä t<br />

normal = normalize ( normalMatrix * normal ) ;<br />

// Berechnung der Licht− und B l i c k r i c h t u n g<br />

vec3 viewDir = normalize ( eyePos−p o s i t i o n ) ;<br />

vec3 l i g h t D i r = normalize ( l ightPos −p o s i t i o n ) ;<br />

// Addieren des Beleuchtungswertes zur Emision<br />

r e s u l t . rgb += blinnPhong ( normal , viewDir , l i g h t D i r ) ;<br />

g l F r a g C o l o r = r e s u l t ;<br />

}<br />

\ c l e a r p a g e<br />

Listing 4.9: Fragmentshader mit Transferfunktion und Blinn-Phong<br />

Beleuchtung


4.3. BELEUCHTUNG 41<br />

Diese Beleuchtung führt dazu, das Stellen, welche von der Lichtquelle erreicht<br />

werden, einfach heller dargestellt werden. Eine realistischere Darstellung<br />

kann erreicht werden, wenn als Material für den ambienten und diffusen<br />

Teil die Emission der Klassifizierung verwendet wird. Das führt dazu, dass<br />

die originale Darstellung weniger verfälscht wird und nur stärker oder schwächer<br />

in das Gesamtresultat eingeht.


Kapitel 5<br />

Ergebnisse & Auswertung<br />

Im Rahmen dieser Arbeit wurde ein Softwareprototyp entwickelt, der es ermöglicht,<br />

beliebige 3D-Bildstapel mittels direktem Volumen Rendering und<br />

beliebiger Transferfunktionen interaktiv darzustellen. Abbildung 5.1 zeigt<br />

einen Screenshot der Benutzeroberfläche. Es sind zwei unterschiedliche Darstellungen<br />

nutzbar. Zum Einen die Volumendarstellung, zum Anderen die<br />

Slices des Datensatzes. Diese Darstellungen können zusammen verwendet<br />

werden. Die Volumendarstellung kann rotiert werden, um diese von beliebigen<br />

Ansichten zu betrachten. Durch das Interface ist es möglich, den Anwendungsbereich<br />

der Transferfunktion einzustellen und die Lichtquelle für<br />

die Beleuchtung zu positionieren.<br />

Abbildung 5.1: Ansicht der resultierenden Umsetzung


5.1. DATENQUALITÄT UND DATENGRÖSSE 43<br />

5.1 Datenqualität und Datengröße<br />

Die Eigenschaften des Ausgangsdatensatzes sind maßgeblich am optischen<br />

Resultat der Volumendarstellung beteiligt. Ein Schnitt eines bildgebenden<br />

Verfahrens hat üblicherweise eine Bildgröße von 512 x 512 Pixel, aber auch<br />

kleinere Bildgrößen sind möglich. Bedingt durch die Art des bildgebenden<br />

Verfahrens besitzen die Datensätze eine Farbtiefe von 8, 12 bzw. 16 Bit. Die<br />

Farbtiefe beeinflusst die mögliche Menge der darstellbaren Farbabstufungen.<br />

Diese Abstufungen sind insoweit wichtig, da diese die möglichen Dichtewerte<br />

darstellen. Bei einer Farbtiefe von 8 Bit sind 2 8 = 256 Abstufungen möglich,<br />

es können daher 256 Dichtewerte unterscheiden werden. Dagegen können<br />

bei 16 Bit Farbtiefe bereits 2 16 = 65536 Abstufungen unterschieden werden.<br />

Durch die Verwendung von mehr Abstufungen kann das original Objekt<br />

besser abgebildet werden. Für die Bildqualität bedeuten mehr Abstufungen<br />

weichere Übergänge und die Möglichkeit die Klassifizierung genauer vornehmen<br />

zu können.<br />

Um so höher die Farbtiefe eines Bildes ist, um so mehr Speicherplatz wird benötigt.<br />

Bei der aktuellen Entwicklungsgeschwindigkeit von Hardware wird<br />

dies oft als nicht relevant angesehen, da Festplatten- und Arbeitsspeicher<br />

immer größer werden. Doch für die Nutzung von Bildern als Texturen in<br />

der GPU ist die Speicherkapazität eingeschränkter. Ein Standarddatensatz<br />

mit 512 x 512 Pixel und einer Farbtiefe von 8 Bit, welcher aus 600 Ebenen<br />

besteht, würde wie in Rechnung 5.1 beschrieben 150 MB Speicherplatz benötigen.<br />

Da bei dieser Volumendarstellung für jede Achse ein eigener Datensatz<br />

vorrätig gehalten werden muss, werden somit schon 450 MB nötig. Bei einer<br />

16 Bit Farbtiefe würde der selbe Datensatz schon 900 MB benötigen.<br />

(512 ∗ 512)pix ∗ 8 bit = 2097152 bit pro pix<br />

2097152 bit /8 bit = 262144 Bytes<br />

262144 Bytes /1024 = 256 kB<br />

(5.1)<br />

600 Ebenen ∗ 256 kB = 153600 kB /1024 = 150 MB<br />

150 MB ∗ 3 Texturstacks = 450 MB<br />

Für die Umsetzung wurde zusätzlich noch ein Alpha-Kanal verwendet, da<br />

für die Beleuchtung in den Farbkanälen der Gradient mit gespeichert wurde.<br />

Daher wurde ein Bildformat mit 32 Bit Farbtiefe verwendet. 24 Bit für die<br />

Farbkanäle + 8 Bit für den Alpha-Kanal, mit solch einem Format kommt<br />

der selbe Datensatz auf 1,76 GB Speicherbedarf. Eine hohe Anzahl von Abstufungen<br />

verbessert die Volumendarstellung, benötigt aber einen höheren<br />

Aufwand bei der Speicherverwaltung.


5.2. ERSTELLUNG EINER TRANSFERFUNKTION 44<br />

5.2 Erstellung einer Transferfunktion<br />

Die Erstellung von Transferfunktionen ist eine Wissenschaft für sich. Es existieren<br />

eigene Publikationen zu dieser Thematik, wie Optimal generation of<br />

transfer functions for direct volume rendering von G.M. Nicoletti [13]. Es<br />

gibt keine allgemeingültige gute Transferfunktion, diese müssen speziell für<br />

die zu betrachtende Information erstellt werden. Dies ist eine Arbeit die manuell<br />

erfolgen muss, da jeder Datensatz leicht unterschiedlich ist. Eine gute<br />

Transferfunktion erreicht eine scharfe Abgrenzung zwischen den verschiedenen<br />

Strukturen und besitzt dennoch weiche Übergänge innerhalb der selben<br />

Struktur.<br />

Eine Transferfunktion wird meistens auf einen Bereich angewendet, welcher<br />

sich innerhalb des Wertebereiches von vorhandenen Dichtewerten befinden.<br />

Die Transferfunktion wird dann auf diesen Bereich skaliert, sodass das Verhältnis<br />

der erstellten Funktion gleich bleibt. Für Dichtewerte, welche sich<br />

außerhalb dieses Bereiches befinden, werden die nächstliegenden definierten<br />

Werte verwendet. Also für darunter liegende Werte der Niedrigste und für<br />

darüber liegende der Höchste. Durch die Verwendung von Transparenzen<br />

können gezielt Objekte ausgeblendet werden.<br />

Die nachfolgende Betrachtung wurde mit der Software Visage7 1 der in Berlin<br />

ansässigen Firma Visage Imaging durchgeführt. Da dieses Programm einen<br />

ausgereiften und intuitiven Editor für Transferfunktionen beinhaltet, eignet<br />

es sich sehr gut um die Funktionsweise darzustellen.<br />

Um eine Transferfunktion zu erstellen, ist es vorteilhaft, zuerst ein Histogramm<br />

des Datensatzes zu betrachten. In diesem ist die Verteilung der Dichtewerte<br />

aufgezeigt und lässt es zu, grob die verschiedenen Strukturen zu unterscheiden.<br />

In Abbildung 5.2 ist dieses verdeutlicht: Im mit a) bezeichneten<br />

Bereich befinden sich Luft und andere gasartige Stoffe. In b) befindet sich<br />

Haut und c) schließt Muskeln, Sehnen und Adern ein. Im Bereich d) sind<br />

Knochen und härteren Materialien wie Zahnschmelz anzusiedeln. Um nun<br />

Abbildung 5.2: Histogramm eines Datensatzes a) Luft b) Haut c) Muskeln<br />

d) Knochen<br />

1 http://www.visageimaging.com/visage7-overview.html [24.01.13]


5.2. ERSTELLUNG EINER TRANSFERFUNKTION 45<br />

Knochen darzustellen, muss am Anfang des Dichtebereiches, wo die Knochen<br />

beginnen, zB. die Farbe Weiß zugewiesen werden. Um sanfte Übergänge in<br />

den Knochen zu erhalten, wird die Farbe mit fallender Transparenz umgesetzt.<br />

Auf die selbe Art können dann beliebige Bereiche hervorgehoben<br />

werden, zB. Muskelansätze oder die Haut. In Abbildung 5.3 ist das Resultat<br />

zusammen mit den Transferfunktionen dargestellt. Bei der Abbildung der<br />

Transferfunktion, ist an dem Slider-Element die Länge der Funktion zu erkennen.<br />

Abbildung 5.3: Beispiele für Transferfunktion mit Resultat<br />

Um ein optisch ansprechendes Resultat zu erhalten, ist darauf zu achten,<br />

dass durch passende Transparenzen weiche Übergänge entstehen. Das sorgt<br />

dafür, dass zB. ein Knochen eine konsistente Darstellung über seine Dichtewerte<br />

bekommt. Dabei ist zu beachten, nicht unnötig großzügig im Dichtebereich<br />

zu sein, denn nur wenn diese Bereiche den einzelnen Strukturen<br />

passend zugeordnet sind, wird zwischen ihnen eine scharfe Abgrenzung erreicht.<br />

Ist erst einmal eine Transferfunktion erstellt worden, ist diese, zumindest für<br />

CT-Daten, für ähnliche Körperteile wiederverwendbar. Dieses kommt daher,<br />

dass dank der Hounsfield-Skala 2 die Dichtewerte normiert sind. Bei dieser<br />

hat Luft einen Wert von -1000, Gewebe ca. -100, Wasser 0 und Knochen<br />

Werte von 500 bis 1500. Bei anderen bildgebenden Verfahren, wie MRT, ist<br />

solch ein Standard nicht vorhanden.<br />

Der Aufwand zur Erstellung einer Transferfunktion ist abhängig von der darzustellenden<br />

Information. Besonders die Feineinstellung, um die passenden<br />

Abgrenzungen zu erhalten, kann einiges an Zeit beanspruchen. Wenn in der<br />

2 http://www.mta-r.de/allgemein/2011/10/die-hounsfield-skala-im-ct/ [15.02.13]


5.3. VERGLEICH DER EIGENEN UMSETZUNG MIT VISAGE7 46<br />

Darstellung Beleuchtung verwendet werden soll, ist es nötig, eine Funktion<br />

auch darauf anzupassen. Eine nicht darauf angepasste Funktion wird in den<br />

meisten Fällen ein schlechteres Resultat erbringen.<br />

5.3 Vergleich der eigenen Umsetzung mit Visage7<br />

Auch wenn die eigene Umsetzung im Rahmen einer Bachelorarbeit nicht<br />

mit einer hochspezialisierten kommerziellen Lösung vergleichbar ist, muss<br />

sich eine Anwendung immer mit den existierenden Umsetzungen messen.<br />

Zum Vergleich wurde die kommerzielle Anwendung Visage7 verwendet. Verglichen<br />

wurde anhand der Bildqualität der resultierenden Volumendarstellung,<br />

die Bedienung und Funktionen wurden nicht mit berücksichtigt. Für<br />

die Darstellung wurden die selben Volumendaten verwendet und durch eine<br />

identische Transferfunktion interpretiert, bei beiden Anwendungen wird<br />

Beleuchtung verwendet.<br />

Um eine vergleichbare Qualität zu erhalten, wurde eine in Visage eingebaute<br />

Funktion für die eigene Umsetzung außerhalb der Anwendung vorbereitet.<br />

Visage filtert die verwendeten Datensätze automatisch beim Importieren,<br />

was dazu führt, dass die Oberflächen weicher und Gleichmäßiger dargestellt<br />

werden. Für die eigene Umsetzung wurde der Datensatz vorher mittels eines<br />

Bildverarbeitungsprogrammes mit einem Filter vorbearbeitet. Das Ausgangsmaterial<br />

ist für beide Anwendungen jedoch identisch. Die Gegenüberstellung<br />

ist in Abbildung 5.4 zu finden.<br />

Beobachtung<br />

ˆ Oben:<br />

Bei beiden Bildern sind die verschiedenen Bereiche gut zu unterscheiden,<br />

zB. erkennt man auf beiden Bildern gut den Kehlkopf und die<br />

Muskeln am Hals. Durch die Klassifizierung heben sich auch Haut und<br />

Knochen von einander ab. Die Schlüsselbeine sind sehr deutlich zu erkennen<br />

und auch das Stirnbein ist zu sehen, bei dem eigenen Bild um<br />

die Augen aber nur noch schwach angedeutet. Durch die Klassifizierung<br />

wirkt bei dem Bild von Visage die Oberfläche wie Haut, bei dem<br />

eigenen Bild wie Fleisch.<br />

ˆ Mitte:<br />

Beide Bilder haben eine Andeutung des Fleisches (rot), da diese nicht<br />

komplett transparent ist. Bei dem eigenen Bild wirkt dies zusammen<br />

mit der Farbe des Schädels wie eine Maske. Der Schädel und auch die<br />

Zähne sind bei dem Bild von Visage deutlicher erkennbar.


5.3. VERGLEICH DER EIGENEN UMSETZUNG MIT VISAGE7 47<br />

ˆ Unten:<br />

Die Zähne sind auf dem eigenen Bild so gut wie gar nicht mehr zu<br />

erkennen. Wieder verfälscht die Andeutung des Fleisches(rot) das Bild<br />

etwas. Im Vergleich zu dem Bild von Visage, bei dem noch Gewebeähnliches<br />

im Schädel angedeutet wird, ist bei dem eigenen Bild direkt die<br />

Schädelrückwand zu sehen. Auch treten bei dem eigenen Bild Fehler<br />

in Form von Ringen auf der Oberfläche auf.<br />

Analyse<br />

Der auffälligste Unterschied liegt darin, dass die eigene Umsetzung vertikal<br />

gestaucht ist. Dies kommt daher, dass in der Umsetzung die Proportionen<br />

nicht mit berücksichtigt werden, da der Datensatz immer in einem Einheitswürfel<br />

[-1,1] erstellt wird. Bei Visage wird der Schichtabstand der Datensätze<br />

mitverwendet, um die Proportionen zu erhalten. Die Farben bei Visage werden<br />

viel feiner abgestuft als bei der eigenen Umsetzung, daher können bei<br />

Visage die einzelnen Strukturen besser dargestellt werden. Dies kommt daher,<br />

dass Visage wahrscheinlich eine viel höhere Abstufungsanzahl, 12 bzw.<br />

16 Bit für jeden Farbkanal, verwendet, als die Eigene Umsetzung, die nur<br />

8 Bit für jeden Farbkanal verwendet und daher die Transferfunktion frei<br />

darauf verteilen kann. Daher sind die Übergänge in und zwischen den unterschiedlichen<br />

Strukturen viel weicher.<br />

Die Beleuchtung erfüllt bei beiden ihren Zweck, die Abgrenzung zwischen<br />

den Strukturen hervorzuheben. Nur treten bei der eigenen Umsetzung gelegentlich<br />

Beleuchtungsartefakte auf. Diese sind dann als helle Streifen sichtbar,<br />

ähnlich der Stufenartefakte nur nicht so weitläufig, und treten im diffusen<br />

Anteil der Beleuchtung auf.<br />

Da Visage serverbasiert arbeitet und die fertige Darstellung zu den Clienten<br />

überträgt, ist davon auszugehen, dass dieser Server mit einer sehr leistungsfähigen<br />

Grafikkarte mit hohem Grafikspeicher ausgestattet ist. Dort werden<br />

für die Berechnung der Darstellung vermutlich 3D-Texturen und eine weitaus<br />

größere Anzahl an Ebenen verwendet. Auch sind weitere Filterungstechniken<br />

denkbar, welche unterschiedlichste Artefakte kaschieren.


5.3. VERGLEICH DER EIGENEN UMSETZUNG MIT VISAGE7 48<br />

Abbildung 5.4: Gegenüberstellung von Visage7 (links) und der eigenen Umsetzung<br />

(rechts). Dabei wurden die selben Volumendaten und Transferfunktion<br />

verwendet.


Kapitel 6<br />

Zusammenfassung &<br />

Ausblick<br />

6.1 Zusammenfassung<br />

Das Ziel dieser Arbeit war, die Konzeption und Umsetzung eines Softwareprototypen,<br />

der die 3D-Visualisierung medizinischer Bilddaten mittels direktem<br />

Volumen Rendering ermöglicht. Dazu wurden alle grundlegenden<br />

Techniken betrachtet und umgesetzt. Die Anwendung ist in der Lage, beliebige<br />

Datensätze, welche aus Bilddateien bestehen, zu laden und anzuzeigen.<br />

Diese Volumendarstellung kann anschließend mittels einer Transferfunktion<br />

klassifiziert werden, um die Darstellung für den Betrachter anzupassen. Um<br />

die Klassifizierung noch weiter hervorzuheben, kann durch die Beleuchtung<br />

der Kontrast zwischen den einzelnen Strukturen verstärkt werden.<br />

Bei der Realisierung, wurde auf die Portabilität der Anwendung geachtet,<br />

sodass diese auch auf Geräten zu nutzen ist, welchen nur eingeschränkte grafische<br />

Ressourcen zur Verfügung stehen. Um das zu erreichen, wurden daher<br />

nur 2D-Texturen verwendet, da diese nur wenig Grafikspeicher benötigen.<br />

Um dennoch die relativ großen Volumendaten anzuzeigen, werden nur die<br />

aktuell benötigten Daten in den Grafikspeicher geladen. Dies führt dazu,<br />

dass oft neue Texturdaten zur Grafikkarte übertragen werden müssen, der<br />

Speicherverbrauch aber gering bleibt. Durch die Klassifizierung mittels einer<br />

Transferfunktion werden die Dichtewerte in beliebige Strukturen eingeteilt<br />

und dargestellt, dabei handelt es sich um ein Zuordnung von Farbwerten.<br />

Für die Beleuchtung wird das Blinn-Phong Modell verwendet. Damit dieses<br />

anwendbar ist, werden aus den Dichtewerten Gradienten errechnet, welche<br />

die Approximation der Normale für die Oberfläche darstellen.<br />

Besonders zu erwähnen ist, dass die Erarbeitung und Umsetzung der Beleuchtung<br />

nicht trivial ist. Die Problematik der nicht vorhandenen Normalen<br />

in Volumengrafiken, die durch Gradienten ersetzt werden, macht die Erstellung<br />

weitaus komplexer. Da für den Datentransfer in die GPU hauptsächlich


6.2. AUSBLICK 50<br />

auf Texturen zurückgegriffen wurde, ergaben sich öfters Rechenungenauigkeiten,<br />

welche zu störenden Darstellungsfehlern führten. Diese treten meistens<br />

auf, wenn der Wertebereich nicht passend gewählt oder überschritten<br />

wurde. Daher muss bei solch einer Umsetzung sehr genau auf Grenzwerte<br />

geachtet werden.<br />

Auch wenn die Herangehensweise über 2D-Texturen mehr Aufwand bedeutet,<br />

wird diese mit einer nützlichen Eigenschaft honoriert. Es muss zwar die<br />

dreifache Menge an Texturen an die Grafikkarte übergeben werden, ermöglicht<br />

aber, nur die momentan benötigten Texturen nacheinander hochzuladen.<br />

Dies hat den Vorteil, dass nicht das gesamte Volumen im Grafikspeicher<br />

vorhanden sein muss und dadurch wenig Speicher benötigt wird. Diese Eigenschaft<br />

geht bedauerlicherweise auf die Laufzeit des Programmes, da oft<br />

neue Texturen in den Grafikspeicher geladen werden müssen.<br />

Zusammenfassend kann das gesetzte Ziel als gelöst angesehen werden, trotz<br />

des sehr komplexen Themas wurde ein durchaus verwendbarer Prototyp<br />

entwickelt. Dieser läuft auf allen OpenGL ES 2.0-fähigen Plattformen und<br />

bietet alle Basisfunktionen zur Volumendarstellung.<br />

6.2 Ausblick<br />

Obwohl im Rahmen der vorliegenden Arbeit ein funktionierender Prototyp<br />

erstellt wurde, bietet sich eine Vielzahl von Möglichkeiten, um die Performanz<br />

und die Darstellungsqualität der 3D-Visualisierung zu verbessern. Im<br />

Nachfolgenden werden einige weiterführende Thematiken erörtert.<br />

Die momentane Ausarbeitung ist nur auf Funktionalität bedacht und beinhaltet<br />

keine Optimierungen. Besonders im Bereich der Performance ist dies<br />

dringend zu empfehlen, da diese vom Benutzer sehr dominant wahrgenommen<br />

wird. Eine Anwendung die ruckelt oder unnötig hohe Ladezeiten aufweist,<br />

wird es sehr schwer haben sich zu etablieren. Im Normalfall wird<br />

bei der Betrachtung einer Information der größte Teil des Datensatzes ausgeblendet,<br />

wobei sehr viele transparente Daten verrechnet werden. Durch<br />

Empty-Space Leaping [14] könnten transparente Bereiche im Vorfeld identifiziert<br />

und anschließend bei der Berechnung ausgelassen werden. Die Grundidee<br />

dabei ist es, den Datensatz in Blöcke zu zerlegen und für diese den<br />

minimalen und maximalen Dichtewert zu speichern. An diesen kann dann<br />

eine Bewertung stattfinden, welche entscheidet, ob der Block überhaupt berechnet<br />

werden muss. Eine andere Möglichkeit wäre das Bricking. Dieses<br />

Verfahren wird meist im Zusammenhang mit 3D-Texturen erwähnt, ist aber<br />

auch bei 2D-Texturen denkbar. Dabei geht es darum, das Volumen in mehrere<br />

Blöcke zu unterteilen und dann möglichst viele Blöcke im Grafikspeicher<br />

zu lassen, sodass so wenig Texturen wie möglich neu in den Grafikspeicher<br />

geladen werden müssen. Dadurch soll der Flaschenhals beim Übertragen der<br />

Daten in den Grafikspeicher reduziert werden. Eine weitere Optimierung


6.2. AUSBLICK 51<br />

wäre die Early Ray Termination, welche das Aufsummieren der Ebenen beschleunigt<br />

bzw. entlastet. Bei diesem Verfahren geht es darum, nur solange<br />

Ebenen zu berechnen, bis neue Ebenen keinen erkennbaren optischen Unterschied<br />

mehr ausmachen, also das Alpha-Blending im Framebuffer frühzeitig<br />

abzubrechen. Da die vorderen Ebenen dadurch aber immer eingehen werden,<br />

ist es nötig die Ebenen front-to-back durchzugehen und dann ggf. frühzeitig<br />

zu enden. Dieses Verfahren benötigt aber einiges an Aufwand, da die aktuelle<br />

Umsetzung mittelt dem back-to-front Verfahren arbeitet, um mittels<br />

Framebuffer die Fragments aufzusummieren.<br />

Ein anderer Optimierungsbereich wäre die Darstellungsqualität. Um ein einheitlicheres<br />

Bild zu erhalten, sollten die Originalbilder des Datensatzes vor<br />

der Verwendung noch gefiltert werden. Dies würde dafür sorgen, dass die<br />

Oberflächen und Übergänge weicher werden. Besonders für die Beleuchtung<br />

ist es von Vorteil, da die Fluktuation der Gradienten geringer wird. Da dies<br />

beim Laden des Datensatzes erfolgen müsste, hat es nur längere Ladezeiten<br />

der Datensätze zufolge. Die Berechnung der Volumendarstellung wird<br />

dabei nicht weiter beeinträchtigt. Abhängig davon, was für ein Datensatz<br />

verwendet wird, können Stufenartefakte auftreten. Es gibt verschiedenste<br />

Möglichkeiten wodurch diese hervorkommen zB. blending artifacts, shading<br />

artifacts, den größten Einfluss haben aber die sampling artifacts. Diese treten<br />

auf, wenn die Anzahl der Ebenen zu gering ist, also das sampling Theorem<br />

verletzt wurde. Dieses Theorem sag aus, dass die Abtastung > 2 * maximale<br />

Frequenz sein muss. Ein Weg dieses Problem zu behandeln läge darin, die<br />

Volumendarstellung in homogene und inhomogene Bereiche einzuteilen und<br />

die inhomogenen mit einer höheren Anzahl von Ebenen abzudecken.<br />

Um die praktische Nutzbarkeit zu steigern, wäre es von Vorteil gewisse neue<br />

Funktionen umzusetzen. Besonders die Möglichkeit Bereiche aus der Darstellung<br />

zu entfernen sollte umgesetzt werden. Ohne diese Funktion ist es<br />

nur möglich Dichtewerte gänzlich zu entfernen. Dennoch haben verschiedene<br />

Strukturen den selben Dichtewert und verdecken ggf. die gewünschte<br />

Information. Eine einfache Lösung dieses Problems sind virtuelle Schnittebenen(clipping<br />

planes). Diese blenden ab ihrer Position alles Weitere des<br />

Volumens aus, sodass es möglich ist an einer Stelle einen Querschnitt zu<br />

betrachten. Eine andere Realisierung, die mehr Aufwand erfordert, wäre die<br />

Möglichkeit, ein virtuelles Skalpell zu implementieren, sodass der Benutzer<br />

Elemente im Volumen herausschneidet und dadurch Bereich von Interesse<br />

freigelegt werden können. Da dies nur partiell geschieht, würde der Zusammenhang<br />

im Gesamtvolumen erhalten bleibt.<br />

Mit diesen Thematiken würde eine erhebliche Steigerung der praktischen<br />

Nutzbarkeit einhergehen. Dennoch sollte für weiteres Vorgehen der Aufwand<br />

der Erarbeitung dem Nutzen des Resultates entgegengestellt werden. Der<br />

größte Nutzen würde durch die Optimierung der Bildqualität einhergehen,<br />

denn dies wird vom Benutzer aktiv wahrgenommen und eine hochwertige<br />

Darstellung ist die Hauptaufgabe einer Volumendarstellungs-Anwendung.


6.2. AUSBLICK 52<br />

Anschließend sollte die Performance betrachtet werden. Dabei ist zu beachten,<br />

dass Performance-Verbesserungen aufwändig sind, vom Nutzer im<br />

sichtbaren Bereich aber direkt wahrgenommen werden. Neue Funktionen zu<br />

behandeln hat in der Weiterführung nicht unbedingt hohe Priorität. Besser<br />

wenige Funktionen, welche gut umgesetzt sind, als viele Möglichkeiten ohne<br />

passende Qualität.<br />

Während der Erarbeitung dieses Themas haben sich einige technische Neuerungen<br />

ergeben. Am 10. Januar 2013 ist die neue OpenGL ES 3.0 Spezifikation<br />

1 erschienen. Durch diese neue Spezifikation sind einige in dieser<br />

Ausarbeitung getroffene Entscheidungen neu zu bewerten, da mit ES 3.0 die<br />

Verwendung von 3D-Texturen möglich ist. Dennoch wird es noch weiter Zeit<br />

benötigen, bis die Grafikkarten die neue Version unterstützen. Auch wenn<br />

dadurch die technische Möglichkeit gegeben ist, wird von den meisten Geräten<br />

der Grafikspeicher nicht ausreichen, um ausreichend große 3D-Texturen<br />

bereitzustellen.<br />

1 http://www.khronos.org/registry/gles/


Danksagung<br />

An dieser Stelle möchte ich mich bei allen Personen bedanken, die mich bei<br />

der Erstellung dieser Arbeit unterstützt haben.<br />

Ein besonderer Dank gilt meinem Betreuer Herrn Prof. Dr.-Ing. Hartmut<br />

Schirmacher, der mit sehr viel Engagement, guten Anregungen, kritisches<br />

Hinterfragen und unermüdlichem Einsatz meine Bachelorarbeit betreut hat.<br />

Besonders zu den Zeiten, an denen mich die Motivation und der Wille weiterzumachen<br />

fehlte, hat er die Nerven behalten und an mich geglaubt. Vielen<br />

Dank für Zeit und Mühen, die Sie in meine Arbeit investiert haben.<br />

Daneben gilt mein Dank meiner Freundin Sandra Krohn, die in zahlreichen<br />

Stunden Korrektur gelesen hat. Und eine große Hilfe dabei war Schwächen<br />

aufzuweisen, wo noch Erklärungsbedarf bestand.<br />

Vielen Dank an die Berliner Firma Visage Imaging, welche mir Zugang zu einem<br />

Demo Account ihrer kommerziellen Anwendung Visage7 ermöglichten.<br />

Die Erfahrungen und Beobachtungen mit diesem Programm hat maßgeblich<br />

dazu beigetragen, dass diese Arbeit nun in der Form vorliegt.<br />

Nicht zuletzt gebührt meinen Eltern Dank, da Sie während des Studiums<br />

nicht nur finanziell, sondern vor allem auch emotional immer für mich da<br />

waren. Und als Fachfremde die Verständlichkeit der Arbeit begutachteten<br />

und Korrektur lasen.


Anhang A<br />

CD-Inhalt<br />

Datei<br />

CT-Data (Ordner)<br />

references (Ordner)<br />

voxelViewerSrc (Ordner)<br />

pre-compiled (Ordner)<br />

index.htm<br />

Readme.txt<br />

Bachelorarbeit.pdf<br />

Inhalt<br />

Datensätze und Transferfunktionen<br />

Offline Webreferenzen und Artikel<br />

Sourcecode der Umsetzung, VoxelViewer<br />

Vorkompilierte Versionen der Umsetzung<br />

Index der Webreferenzen<br />

Installations-/Verwendungsanleitung<br />

Digitale Version der Bachelorarbeit


Literaturverzeichnis<br />

[1] Andrew S. Glassner. Principles of Digital Image Synthesis. Morgan<br />

Kaufmann, 2nd edition, Mar. 1995.<br />

[2] H. C. Hege, T. Hollerer, and D. Stalling. Volume rendering<br />

mathematical models and algorithmic aspects.<br />

http://www.cs.ucsb.edu/∼holl/pubs/hege-1993-vrm.pdf, 1993. Report<br />

TR 93-7, Last checked 2013.01.10 19:34.<br />

[3] Klaus Engel, Markus Hadwiger, Joe Kniss, Christof Rezk-Salama, and<br />

Daniel Weiskopf. Real-Time Volume Graphics. A K Peters, Ltd., 1st<br />

edition, Aug. 2006.<br />

[4] Philippe Lacroute and Marc Levoy. Fast Volume Rendering Using a<br />

Shear-Warp Factorization of the Viewing Transformation. Proc. SIG-<br />

GRAPH ’94, pages 451–458, 1994.<br />

[5] Charles D. Hansen and Chris R. Johnson. Visualization Handbook.<br />

Academic Press, 1st edition, Dez. 2004.<br />

[6] Randima Fernando (Series Editor). GPU Gems: Programming Techniques,<br />

Tips and Tricks for Real-Time Graphics. Addison-Wesley Professional,<br />

Apr. 2004.<br />

[7] Khronos Group. OpenGL ES 2.0 Dokumentation.<br />

http://www.khronos.org/opengles/sdk/docs/man/. Last checked<br />

2013.01.13 15:10.<br />

[8] Aaftab Munshi, Dan Ginsburg, and Dave Shreiner. OpenGL ES 2.0 Programming<br />

Guide. Addison-Wesley Professional, 1 edition, Aug. 2008.<br />

[9] Richard S. Wright, Nicholas Haemel, Graham Sellers, and <strong>Benjamin</strong><br />

Lipchak. OpenGL SuperBible: Comprehensive Tutorial and Reference<br />

(5th Edition). Addison-Wesley Professional, 5th edition, Aug. 2010.<br />

[10] Richard O. Duda. Pattern Classification 2nd Edition with Computer<br />

Manual 2nd Edition Set. Wiley-Interscience, 2nd edition, June 2004.


LITERATURVERZEICHNIS 56<br />

[11] Bui Tuong Phong. Illumination for computer generated pictures. Commun.<br />

ACM, 18:311–317, June 1975.<br />

[12] James F. Blinn. Models of light reflection for computer synthesized<br />

pictures. SIGGRAPH Comput. Graph., 11(2):192–198, July 1977.<br />

[13] G.M. Nicoletti. Optimal generation of transfer functions for direct volume<br />

rendering. System Theory, 2003. Proceedings of the 35th Southeastern<br />

Symposium on, pages 367–371, March 2003.<br />

[14] Wei Li, Klaus Mueller, and Arie Kaufman. Empty space skipping and<br />

occlusion clipping for texture-based volume rendering. IEEE Visualization<br />

2003, pages 317–324, Dec. 2003.<br />

[15] Jason Gregory. Game Engine Architecture. A K Peters, Ltd., July 2009.<br />

[16] Randi J. Rost, Bill M. Licea-Kane, Dan Ginsburg, John M. Kessenich,<br />

Barthold Lichtenbelt, Hugh Malan, and Mike Weiblen. OpenGL Shading<br />

Language. Addison-Wesley Professional, 3rd edition, July 2009.<br />

[17] Willi A. Kalender. Computed Tomography. Publicis, 3rd edition, Aug.<br />

2011.<br />

[18] Joe M. Kniss, Klaus Engel, and Christof Rezk-Salama. High-quality volume<br />

graphics on consumer pc hardware. Course Note 42, SIGGRAPH,<br />

page 122, 2002.<br />

[19] Qt Project. Qt 4.7 Dokumentation.<br />

http://doc.qt.digia.com/4.7/index.html. Last checked 2013.01.13<br />

16:00.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!