Download - Benjamin Granzow Portfolio
Download - Benjamin Granzow Portfolio
Download - Benjamin Granzow Portfolio
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.