Arbeit als PDF - Fraunhofer-Institut für Techno

Transcription

Arbeit als PDF - Fraunhofer-Institut für Techno
Diplomarbeit
Verbesserung der
Darstellungsqualität einer
texturbasierten
Volumenvisualisierung unter
Verwendung moderner
Shadertechnologie
Oliver Nacke
Mat.Nr.:4200148
Rudolf-Breitscheidt Straße 83
67655 Kaiserslauten
<[email protected]>
23. April 2006
I
E R K L Ä R U N G
• Die Diplomarbeit entstand in Zusammenarbeit mit einer Institution
außerhalb der Fachhochschule Oldenburg / Ostfriesland / Wilhelmshaven.
• Soweit meine Rechte berührt sind, erkläre ich mich einverstanden, dass
die Diplomarbeit Angehörigen der Fachhochschule Oldenburg / Ostfriesland / Wilhelmshaven für Studium / Lehre / Forschung uneingeschränkt zugänglich gemacht werden kann.
EIDESSTATTLICHE
E R K L Ä R U N G
Hiermit erkläre ich an Eides statt, dass ich die vorliegende Diplomarbeit
bis auf die offizielle Betreuung selbst und ohne fremde Hilfe angefertigt habe
und die benutzten Quellen und Hilfsmittel vollständig angegeben sind.
Datum, Unterschrift
II
Diplomarbeit
im Studiengang Informatik
der Fachhochschule
Oldenburg Ostfriesland Wilhelmshaven
von:
Oliver Nacke
Rudolf-Breitscheidt Straße 83
67655 Kaiserslautern
Telefon: 0177/8296349
e-Mail: [email protected]
erstellt am:
Fraunhofer Institut Technound Wirtschaftsmathematik
Fraunhofer-Platz 1
67663 Kaiserslautern
Betreuung Fachhochschule:
Prof. Dr.-Ing. Dietrich Ertelt
Betreuung Diplomarbeitsstelle:
Dipl.-Inform. (FH) Falco Hirschenberger
Dr. Katja Schladitz
Inhaltsverzeichnis
Erklärung
I
1 Einleitung
1.1 Ziele der Volumenvisualisierung . . . . . . . . . . . . . . . . .
1.2 Flexibilität durch programmierbare Grafikhardware . . . . . .
1
1
3
2 Grundlagen
2.1 Beleuchtung und Schatten . . . . . . . . . . . . . . . . . .
2.1.1 Licht und Materie . . . . . . . . . . . . . . . . . . .
2.1.2 Die BRDF . . . . . . . . . . . . . . . . . . . . . . .
2.1.3 Der differentielle Raumwinkel . . . . . . . . . . . .
2.1.4 Definition einer BRDF . . . . . . . . . . . . . . . .
2.1.5 Kategorien und Eigenschaften von BRDFs . . . . .
2.1.6 Die BRDF Beleuchtungsgleichung . . . . . . . . . .
2.2 Reflexionsmodelle und Schattierung in der Computergrafik
2.2.1 Die ambiente Komponente . . . . . . . . . . . . . .
2.2.2 Die diffuse Komponente . . . . . . . . . . . . . . .
2.2.3 Die spiegelnde Komponente . . . . . . . . . . . . .
2.2.4 Die Schattierung . . . . . . . . . . . . . . . . . . .
2.3 Globale und lokale Beleuchtungsmodelle . . . . . . . . . .
2.3.1 Das Radiosity Verfahren . . . . . . . . . . . . . . .
2.3.2 Raytracing . . . . . . . . . . . . . . . . . . . . . . .
2.3.3 Das Phong Modell . . . . . . . . . . . . . . . . . .
2.4 Schatten . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Schatten und Lichtquelle . . . . . . . . . . . . . . .
2.4.2 Schattenberechnung . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
6
6
7
8
9
10
11
12
13
13
14
16
18
19
20
20
21
22
22
3 Techniken
3.1 Shader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.1 Der Vertexprozessor . . . . . . . . . . . . . . . . . . .
3.1.2 Der Fragmentprozessor . . . . . . . . . . . . . . . . . .
25
25
26
30
III
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
INHALTSVERZEICHNIS
3.2
3.3
3.4
3.5
IV
3.1.3 Shadersprachen . . . . . . . . . . . . . . . . . . . . .
Anwendung des Beleuchtungsmodells auf das Volumen . . .
3.2.1 Polygonbasiertes Rendering und texturbasiertes Volumenrendering . . . . . . . . . . . . . . . . . . . . . .
3.2.2 Per Pixel lighting einer texturbasierten Volumenvisualisierung mit dem Beleuchtungsmodell von Phong . .
Schattengenerierung . . . . . . . . . . . . . . . . . . . . . .
3.3.1 Strahlenverfolgung . . . . . . . . . . . . . . . . . . .
3.3.2 Shadow Volumes . . . . . . . . . . . . . . . . . . . .
3.3.3 Shadow Maps . . . . . . . . . . . . . . . . . . . . . .
Isoflächen Rendering . . . . . . . . . . . . . . . . . . . . . .
Progressives Rendering . . . . . . . . . . . . . . . . . . . . .
. 32
. 33
. 34
.
.
.
.
.
.
.
35
38
38
39
40
44
47
4 Implementierung
4.1 MAVI und das Voxel-Sculpture Modul
4.2 Die Klassenstruktur . . . . . . . . . . .
4.2.1 Die Shaderklassen . . . . . . . .
4.2.2 Die Renderklassen . . . . . . .
4.2.3 Progressives Rendering . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
50
50
51
52
55
69
5 Ergebnisse
5.1 Messungen . . . . . . . . . . .
5.1.1 Die Beleuchtungsklasse
5.1.2 Die Isoflächenklasse . .
5.1.3 Auswertung . . . . . .
5.1.4 Ausblick . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
77
77
79
80
80
85
.
.
.
.
.
.
87
87
89
91
93
95
97
A Gallerie
A.1 Sinterkupfer . . . .
A.2 Schädel . . . . . .
A.3 Feuerbeton . . . .
A.4 Motorblock . . . .
A.5 Menschlicher Kopf
A.6 Aluminiumschaum
Abbildungsverzeichnis
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
99
Quelltextverzeichnis
101
Literaturverzeichnis
102
Kapitel 1
Einleitung
1.1
Ziele der Volumenvisualisierung
Der Bereich Volumenvisualisierung befasst sich mit der grafischen Darstellung dreidimensionaler Datensätze. Solche Datensätze werden meist mit bildgebenden Verfahren wie der Kernspintomographie (MRI, Magnetic Resonance Imaging) oder der Computertomographie (CT, Computed Tomography) erzeugt. Die Hauptanwendungsgebiete liegen in der medizinischen Diagnostik und der Qualitätskontrolle. Damit der Mensch in der Lage ist diese
Daten zu erfassen, müssen sie ihm in geeigneter Weise präsentiert werden.
”
Ein Bild sagt mehr als tausend Worte.“
Wie schon dem obigen Sprichwort zu entnehmen, ist der Mensch visuell
orientiert. Anstatt die Daten auf komplizierte Weise zu beschreiben, wird
einfach ein Bild des Objekts geliefert, was durch die Daten beschrieben wird.
Dadurch ist der Mensch in der Lage auch riesige Datensätze schnell und intuitiv zu erfassen. Je detaillierter und hochwertiger die Darstellung ist, desto
mehr Informationen können aus ihr gewonnen werden. Seit der Antike (ca.
1200v.Chr. - 600n.Chr.) ist beispielsweise bekannt, dass die Verteilung von
Licht und Schatten helfen kann, Tiefeninformationen zu vermitteln. In der
Malerei werden Licht und Schatten ganz bewusst eingesetzt um dem flachen
Bild eine gewisse Plastizität und Tiefenwirkung zu verleihen. Leonardo da
Vinci (1452-1519) beschrieb als erster die Prinzipien, nach denen Licht und
Schatten in der Malerei einzusetzen sind.
Die Darstellung einer Szene durch den Künstler auf einer Leinwand, unterscheidet sich nur im Bezug auf die verwendeten Mittel von der Darstellung ei-
1
KAPITEL 1. EINLEITUNG
2
ner Szene auf dem Computerbildschirm. Die Darstellungsqualität einer, vom
Computer berechneten, Volumenvisualisierung profitiert genauso von dem
Einsatz von Licht und Schatten wie ein Gemälde.
Das Hauptproblem bei der Darstellung liegt in der Grösse der Daten. Eine
Grösse von 5123 Werten pro Datensatz ist keine Seltenheit, woraus ein enormer Rechenaufwand bei der Darstellung resultiert. Aus diesem Grund war
es bis vor wenigen Jahren nicht möglich, solche Datensätze auf herkömmlicher PC-Hardware mit akzeptabler Geschwindigkeit, auch ohne Beleuchtung,
zu realisieren. Durch die rasche Entwicklung der Grafikkarten in den letzten
Jahren, wurden diese so leistungsfähig, dass sie für die Volumenvisualisierung eingesetzt werden können. Das stellt einen grossen Vorteil dar, da keine
teuren Spezialsysteme angeschafft werden müssen. Die Entwicklung der Grafikprozessoren lässt sich am eindrucksvollsten anhand einer vor 40 Jahren
von Gordon E. Moore aufgestellten These zeigen, welche besagt dass sich
die Anzahl der Transistoren auf einem Chip alle 24 Monate verdoppelt. Diese These ist durch die Entwicklung der CPUs1 längst bewiesen, wurde aber
durch die modernen GPUs2 heutiger Grafikkarten sogar überboten. Aktuelle
Grafikchips besitzen wesentlich mehr Transistoren als aktuelle CPUs3 .
Die Basis für diese Arbeit bildet eine bereits vorhandene texturbasierte Volumenvisualisierung. Die Möglichkeiten dieser Visualisierung sind sehr begrenzt
und die Qualität der Darstellungen somit vergleichsweise eher mässig.
Hauptziel dieser Arbeit ist es, den visuellen Eindruck zu verbessern und weiterhin eine akzeptable Geschwindigkeit zu erzielen. Zur Verbesserung der
Darstellung wird die Visualisierung um Beleuchtung erweitert. Durch die
Beleuchtung wird die Darstellung der Oberfläche eines Objekts maßgeblich
verbessert. Ohne Beleuchtung wirkt diese häufig verwaschen, so dass feine
Strukturen nicht erkennbar sind. Aus der Beleuchtung ergeben sich auch
Schatten, welche das Objekt auf sich selbst wirft. Durch Schatten wird die
räumliche Darstellung verbessert. Schatten helfen dabei, die räumlichen Beziehungen der Strukturen eines Objekts zueinander herzustellen.
Eine weitere Möglichkeit der Darstellung besteht darin, Objekte halbtransparent zu visualisieren. Dadurch können eingeschlossene Teile eines Objekts
bequem innerhalb ihres Gesamtkontexts gezeigt werden.
1
Central Processing Unit
Graphics Processing Unit
3
Zum Vergleich: Der NV40 Grafikchip einer GeForce 6800 Ultra besteht aus 222 Millionen
Transistoren, ein Intel Pentium 4 Prescott“ dagegen nur aus 125 Millionen
”
2
KAPITEL 1. EINLEITUNG
1.2
3
Flexibilität durch programmierbare Grafikhardware
Die ständige Weiterentwicklung der Grafikkarten erhöhte vor allem deren Geschwindigkeit. Dadurch stieg die Datenmenge, die verarbeitet werden kann.
Aufgrund der steigenden Leistung, konnten theoretisch auch rechenintensivere Algorithmen, zum Beispiel für die Beleuchtungsberechnung, eingesetzt
werden. Praktisch funktionierte das aber nur bedingt, da die Grafikkarten
nicht programmierbar waren.
Im Jahre 2001 führte NVidia[NV] mit der GeForce3 und ATI[ATI] mit der
Radeon 8500 die ersten programmierbaren Grafikprozessoren ein. Man war
fortan in der Lage, Grafikkarten flexibel über kleine Programme, den sogenannten Shadern zu programmieren. Dadurch entstand die Möglichkeit, eigene Algorithmen zu implementieren und von der Grafikhardware ausführen
zu lassen. Die Möglichkeiten sind hierbei sehr vielfältig. Einige Beispiele, um
einen Eindruck der bestehenden Möglichkeiten zu erhalten, zeigt Abbildung
1.1.
KAPITEL 1. EINLEITUNG
4
Abbildung 1.1: Die Bilder zeigen einige mögliche Verwendungszwecke für Shader. Bump
Mapping und Reflexion (o.li.), Cell Shader (Toon Shader) (o.re.), Realistische Wassereffekte (u.li.), Lichtbrechung und Reflexion (u.re.) Quelle: http://www.nvidia.com, Cell
Shader: http://xiii.ubisoft.de
KAPITEL 1. EINLEITUNG
5
Die Idee der Shader ist dabei keineswegs neu, sie stammt aus den Studios
der Animationsfilme. Die Firma Pixar schuf in den späten 80er Jahren für
ihr Rendering-Interface Renderman[Pix] eine eigene Shadersprache, die Renderman Shading Language. Diese Anwendung war jedoch für das Berechnen
einzelner Bilder eines Films gedacht. Dabei spielt die Rechenzeit nur eine
untergeordnete Rolle. Einige bekannte Filmtitel sind etwa Toy Story oder
Finding Nemo.
Bei Grafikkarten besteht das Ziel darin, diese Shader möglichst schnell auszuführen um flüssige Animationen zu erhalten.
Diese Arbeit beschäftigt sich mit der Verbesserung der Darstellungsqualität
einer texturbasierten Volumenvisualisierung. Dazu werden verschiedene Methoden zur Realisierung von Beleuchtung und Schatten für klassische, polygonbasierte, Darstellungen vorgestellt. Dabei wird untersucht, ob sich diese
Verfahren auch für die Volumenvisualisierung eignen und welche Anpassungen vorgenommen werden müssen. Ferner wird eine Methode zur direkten
Darstellung sogenannter Isoflächen vorgestellt, mit der sich Volumen auch
halbtransparent visualisieren lassen. Programmierbare Grafikkarten stellen
dabei ein nützliches Werkzeug für die Realisierung dar. Um weiterhin eine Visualisierung mit akzeptabler Geschwindigkeit zu ermöglichen, wird ein
Verfahren vorgestellt, mit dem die Geschwindigkeit temporär, zu lasten der
Bildqualität, erhöht werden kann.
Kapitel 2
Grundlagen
2.1
Beleuchtung und Schatten
Ziel der Volumenvisualisierung ist es, dreidimensionale Daten so darzustellen,
dass der Anwender die gewünschten Informationen direkt erfassen kann. Dies
muss nicht immer auf eine besonders realitätsnahe Visualisierung herauslaufen, sondern auch Methoden wie zum Beispiel die Darstellung in Falschfarben können den Informationsgehalt erhöhen. Wird aber eine möglichst
realistische Visualisierung gewünscht, dann spielt die Beleuchtung dabei eine entscheidende Rolle. Insbesondere die Oberflächenstruktur eines Objektes
kommt erst durch eine geeignete Beleuchtung zur Geltung. Auch die dabei
entstehenden Schatten können helfen, die Struktur eines Objektes besser zu
erfassen.
2.1.1
Licht und Materie
Die Beleuchtung eines Objekts ist eine Licht-Materie Interaktion. Diese Interaktion ist im allgemeinen sehr komplex und hängt von vielen physikalischen Parametern, sowohl des Lichts, als auch der Materie ab. Ein Spiegel
reflektiert das Licht völlig anders als Schmirgelpapier. Eine allgemeine LichtMaterie Interaktion ist in Abbildung 2.1 skizziert.
Wenn Licht von einem Medium in ein anderes wechselt, können drei verschiedene Effekte auftreten: Reflexion, Absorbtion und Transmission. Reflektiertes
Licht kann dabei, abhängig von den Eigenschaften der Materie, in bestimmte Richtungen unterschiedlich stark gestreut werden. Absorbiertes Licht wird
von der Materie in Form von Energie aufgenommen. Transmittiertes Licht
ist der Anteil des einfallenden Lichts, der die die Materie durchdringt. Beim
6
KAPITEL 2. GRUNDLAGEN
7
Reflexion
Absorption
Transmission
Abbildung 2.1: Licht-Materie Interaktion allgemein
Austritt aus der Materie treten obige Effekte wieder auf, da das Licht wieder
das Medium wechselt.
Da Licht eine Form von Energie ist, gilt der Energieerhaltungssatz.
L i = L r + La + L t
(2.1)
Li ist das gesamte Licht das auf die Oberfläche trifft, Lr ist der reflektierte Anteil, La der absorbierte Anteil und Lt der transmittierte Anteil. Für
opake1 Materialien wird der grösste Teil des einfallenden Lichts reflektiert
und absorbiert. Was ein Betrachter sieht ist das reflektierte Licht. Der Anteil
des reflektierten Lichts wird durch die sogenannte Bidirectional Reflectance
Distribution Function (im Folgenden BRDF abgekürzt) bestimmt [Wyn].
2.1.2
Die BRDF
Der Anteil des reflektierten Lichts hängt maßgeblich von der Position der
Lichtquelle und des Betrachters, relativ zur Normalen der beleuchteten Fläche,
ab. Daraus resultiert, dass die BRDF eine Funktion der einfallenden2 und
ausfallenden3 Richtung, relativ zum Interaktionspunkt ist. Ausserdem werden verschiedene Wellenlängen (Farben) des Lichts unterschiedlich stark reflektiert, absorbiert und transmittiert. Zwangsläufig muss die BRDF also
auch eine Funktion der Wellenlänge sein. Eine letzte wichtige Eigenschaft ist
die unterschiedliche Beschaffenheit des Materials an verschiedenen Punkten,
auch bezeichnet als positional variance. Aufgrund der unterschiedlichen Zusammensetzung des Materials an verschiedenen Stellen wird Licht dort auch
unterschiedlich reflektiert. Als Beispiel kann man sich Holz vorstellen. Die
1
Opazität ist das Gegenteil der Transparenz
Von der Lichtquelle kommend
3
Zum Betrachter hin
2
KAPITEL 2. GRUNDLAGEN
8
Maserung variiert häufig sehr stark. Allgemein kann die BRDF folgendermaßen beschrieben werden:
BRDFλ (Θi , φi , Θr , φr , u, v)
(2.2)
Der Index λ kennzeichnet die Abhängigkeit von der Wellenlänge. Θi , φi bestimmen die Einfallsrichtung und Θr , φr die Reflexionsrichtung in sphärischen
Koordinaten. u und v bestimmen die aktuelle Position in Texturkoordinaten.
Dadurch kann die Heterogenität des Materials nachgebildet werden, indem
eine Textur benutzt wird, die die strukturelle Beschaffenheit des Objektes
beschreibt. Häufig werden BRDFs aber auch ohne diese beiden Parameter
beschrieben. Solche BRDFs werden als position-invariant oder shift-invariant
bezeichnet:
BRDFλ (Θi , φi , Θr , φr )
(2.3)
2.1.3
Der differentielle Raumwinkel
Bisher wurde angenommen das das einfallende Licht aus genau einer RichW
tung kommt. Licht wird aber gemessen als Energie pro Fläche ( m
2 ). Es ist
daher zweckmäßiger anzunehmen, dass Licht aus einer kleinen Region von
Richtungen kommt.
~n
ωi
dω
x
Abbildung 2.2: Licht trifft aus Richtung ωi auf den Interaktionspunkt x
Der Raumwinkel dω beschreibt die Menge Licht, die auf den Punkt x trifft.
Ein Raumwinkel ist die dreidimensionale Erweiterung eines zweidimensionalen Winkels. Der differentielle Raumwinkel kann als eine kleine rechteckige
KAPITEL 2. GRUNDLAGEN
9
sinΘ
dΘ
dω
Θ
Φ
dΦ
Abbildung 2.3: Der differentielle Raumwinkel dω als Fläche auf der Einheitskugel
Fläche auf der Einheitskugel verstanden werden:
Abbildung 2.3 veranschaulicht die Zusammenhänge. dΘ und dφ sind kleine
differentielle Änderungen des Winkels. Dadurch entsteht ein pyramidenförmiges Volumen das die Lichtmenge beinhaltet. Die Fläche die durch den Schnitt
dieses Volumens mit der Einheitskugel entsteht ist der differentielle Raumwinkel dω:
dω = sin Θ · dΘ · dφ
(2.4)
Die Einheit des Raumwinkels ist steradians (sr ).
2.1.4
Definition einer BRDF
Betrachtet man eine Einfallsrichtung wi = (Θi , φi ) und eine Ausfallsrichtung
wr = (Θr , φr ), relativ zu einem kleinen Flächenstück, dann ist die BRDF definiert als das Verhältnis von der reflektierten Strahlungsdichte Lr in Richtung
wr zur Beleuchtungsstärke Ei aus Richtung wi :
BRDFλ (wi , wr ) =
Lr
Ei
(2.5)
Da der differentielle Raumwinkel relativ klein ist, kann er als eine flache
Fläche betrachtet werden. Diese wird gleichmäßig stark beleuchtet. Die Beleuchtungsstärke beträgt Li · dωi . Die Beleuchtungsstärke bezieht sich aber
KAPITEL 2. GRUNDLAGEN
10
auf den differentiellen Raumwinkel und nicht auf die eigentlich zu beleuchtende Fläche. Um die Beleuchtungsstärke für die betrachtete Fläche zu erhalten,
muss das einfallende Licht noch auf selbige projeziert werden. Dazu wird der
Ausdruck mit cos Θi = ~n · w
~ i multipliziert (Abbildung 2.4). Für die BRDF
aus Gleichung 2.5 ergibt sich:
BRDFλ (wi , wr ) =
Lr
Li · cos Θi · dωi
(2.6)
~n
ω~i
dω
Θi
da
Abbildung 2.4: Projektion des Raumwinkels dω auf die relevante Fläche da
2.1.5
Kategorien und Eigenschaften von BRDFs
Grundsätzlich unterscheidet man zwei Klassen von BRDFs, anisotropische
und isotropische. Isotropische BRDFs beschreiben Reflexionseigenschaften
die invariant bezüglich einer Rotation der Fläche um ihre Normale sind.
Plastik hat häufig isotropische Reflexionseigenschaften. Im Gegensatz dazu
beschreiben anisotropische BRDFs Reflexionseigenschaften die sich bzgl. einer Rotation der Fläche um ihre Normale ändern. Ein Beispiel hierzu wäre
gebürstetes Metall.
BRDFs die auf physikalischen Gesetzen beruhen besitzen darüber hinaus zwei
weitere Eigenschaften: Umkehrbarkeit und Energieerhaltung. Umkehrbarkeit
meint das der Wert der BRDF sich nicht ändert, wenn man die Einfalls- und
Ausfallsrichtung vertauscht. Mathematisch kann diese Eigenschaft folgendermaßen ausgedrückt werden:
BRDFλ (Θi , φi , Θr , φr ) = BRDFλ (Θr , φr , Θi , φi )
(2.7)
Mit Energieerhaltung ist gemeint das die Menge des reflektierten Lichts die
Menge des einfallenden Lichts nicht übersteigen darf. Allgemein ist es so,
KAPITEL 2. GRUNDLAGEN
11
dass Licht das aus einer bestimmten Richtung auf eine Fläche trifft in verschiedene Richtungen gestreut wird. Die Summe des gestreuten Lichts darf
also die Menge des einfallenden Lichts nicht überschreiten. Für die BRDF
bedeutet das, dass die Summe aller Ausfallsrichtungen multipliziert mit dem
projezierten Raumwinkel maximal 1 werden darf. Mathematisch ausgedrückt:
Z
BRDFλ (Θi , φi , Θr , φr ) · cos Θr dωr ≤ 1
(2.8)
Ω
R
Ω
bedeutet eine Integration über die gesamte Hemisphäre.
2.1.6
Die BRDF Beleuchtungsgleichung
In der realen Welt ist es so, dass die gesamte Szene an der Beleuchtung eines
Punktes auf einer Fläche beteiligt ist:
Betrachter
ωr
Abbildung 2.5: Das gesamte einfallende Licht bestimmt die Intensität in Reflexionsrichtung
ωr
Abbildung 2.5 zeigt einen Punkt auf einer Fläche der von der gesamten Szene
beleuchtet wird. Ein Beobachter ist in Richtung wr positioniert. Die Menge
des Lichts das in Richtung wr reflektiert wird ist eine Funktion über sämtliche
Einfallsrichtungen wi und der BRDF des Oberflächenpunkts. Für die Menge
Lr des ausgehenden Lichts gilt also:
Z
Lr =
Lri (wi , wr )dwi
(2.9)
Ω
Lri (wi , wr ) ist dabei der Anteil des Lichts aus Richtung wi , das in Richtung wr
reflektiert wird. Ω beschreibt wieder die gesamte Hemisphäre der einfallenden
Lichtstrahlen. Für den diskreten Fall ergibt sich aus Gleichung 2.9:
X
Lr =
Lri (wi , wr )
(2.10)
in
KAPITEL 2. GRUNDLAGEN
12
Der reflektierte Teil des Lichts aus einer Richtung wi ergibt sich genau aus
der entsprechenden BRDF. Daher gilt für Lri :
Lri = BRDF (Θi , φi , Θr , φr ) · Ei
(2.11)
Ei ist die Beleuchtungsstärke. Allerdings bezieht sie sich wieder auf den differentiellen Raumwinkel und nicht auf das beleuchtete Flächenstück. Um die
korrekte Beleuchtungsstärke für das Flächenstück zu erhalten, muss wieder
mit cos Θi = ~n · w
~ i projeziert werden (Siehe auch Abbildung 2.4):
Ei = Li · cos Θi · dwi
(2.12)
beziehungsweise für den diskreten Fall:
Ei = Li · cos Θi
(2.13)
Für die reflektierte Lichtmenge in Richtung des Betrachters wr aus Einfallsrichtung wi ergibt sich:
Lri = BRDF (Θi , φi , Θr , φr ) · Li · cos Θi
(2.14)
Das gesamte reflektierte Licht, als Summe der reflektierten Lichtmengen aller
Einfallsrichtungen wi , ist somit definiert als:
X
Lr =
BRDF (Θi , φi , Θr , φr ) · Li · cos Θi
(2.15)
in
Man kann sich Gleichung 2.15 als die Summe vieler Punktlichter vorstellen,
wobei jede Punktlichtquelle genau eine Einfallsrichtung repräsentiert. Statt
über die gesamte Hemisphäre zu integrieren, was sehr rechenaufwändig wäre,
könnnen so einige wenige Punktlichtquellen benutzt, um die gesamte reflektierte Lichtmenge näherungsweise zu berechnen.
Für eine einzige Punktlichtquelle ergibt sich das reflektierte Licht in Richtung
des Betrachters aus:
Lr = BRDF (Θi , φi , Θr , φr ) · Li · cos Θi
(2.16)
Gleichung 2.16 ist die allgemeine BRDF Beleuchtungsgleichung für eine einzelne Punktlichtquelle.
2.2
Reflexionsmodelle und Schattierung in der
Computergrafik
Um eine Visualisierung am Computer in Echtzeit zu ermöglichen, muss eine Möglichkeit gefunden werden die BRDF möglichst effizient zu berechnen.
KAPITEL 2. GRUNDLAGEN
13
Häufig gilt: Je näher die BRDF an der physikalischen Realität liegt, desto
aufwändiger ist es, sie zu berechnen. Eine Möglichkeit um eine hohe Realitätsnähe zu wahren und dennoch akzeptable Laufzeiten zu erzielen, liegt
darin, die BRDF gar nicht erst als analytische Funktion zu modellieren, sondern eine Tabelle mit real gemessenen Daten als Grundlage für einen Lookup
zu nehmen. Die andere Möglichkeit besteht darin, einfachere Modelle zu verwenden. Einfache Modelle sind häufig empirischer1 Natur und spiegeln die
physikalische Realität somit nur bedingt wieder. Man geht also immer einen
Kompromiss zwischen Realitätsnähe auf der einen und verfügbarer Rechenleistung auf der anderen Seite ein. Durch die stetig steigende Rechenleistung
heutiger CPUs und insbesondere GPUs verschiebt sich dieser Kompromiss
aber zusehends in Richtung Realitätsnähe.
Gerade in einfacheren Modellen wird die Reflexion häufig getrennt in drei unabhängigen Komponenten berechnet: Der ambienten Komponente, der diffusen Komponente und der spiegelnden Komponente.
2.2.1
Die ambiente Komponente
Die ambiente Komponente bestimmt den Teil des reflektierten Lichts der indirekt auf das Objekt trifft. In der Realität ist es so, dass ein Objekt nicht
nur direkt von einer Lichtquelle beleuchtet wird, sondern auch vom emittierten/gespiegelten Licht anderer Objekte. Man stelle sich dazu einen Raum
vor, der nur ein Fenster hat. Licht das durch dieses Fenster auf den Boden
des Raums trifft erhellt zunächst den Boden direkt. Das Licht wird aber
vom Boden reflektiert und beleuchtet so auch die Decke und die Wände mit
abgeschwächter Intensität. Die ambiente Komponente bildet die Grundintensität. Ohne sie wären Objekte, die nicht direkt von einer Lichtquelle bestrahlt
werden, schlicht schwarz.
2.2.2
Die diffuse Komponente
Bei einer diffusen Reflexion wird Licht, abhängig vom Einfallswinkel, gleichverteilt in alle Richtungen reflektiert. Die Intensität der Reflexion ist somit
abhängig vom Einfallswinkel des Lichts, aber unabhängig vom Betrachtungswinkel, da gleichverteilt in sämtliche Richtungen. Die diffuse Reflexion kann
mit dem Lambertschen Kosinusgesetz beschrieben werden:
Ird = Ir0 · cos φ = Ir0 · (n~0 · l~0 )
(2.17)
Die diffus reflektierte Intensität Ird ist abhängig vom Winkel φ zwischen
der normierten Flächennormalen n~0 und dem normierten Lichtvektor l~0 des
1
Empirische Modelle basieren auf Beobachtungen, nicht auf physikalischen Grundlagen
KAPITEL 2. GRUNDLAGEN
14
einfallenden Lichts. Ir0 beschreibt die maximale reflektierte Intensität (für
φ = 0).
Da die Reflexion nur für Einfallwinkel 0 ≤ φ ≤ π2 definiert ist ergibt sich
aus Gleichung 2.17:
Ird = Ir0 · max((n~0 · l~0 ), 0)
(2.18)
~n
l~2
Betrachter
l~1
φ2
φ1
v~2
v~1
Abbildung 2.6: Diffuse Reflexion
Abbildung 2.6 veranschaulicht zwei diffuse Reflexionen mit jeweils unterschiedlichen Einfallswinkeln φ. Man erkennt das die reflektierte Intensität
für zunehmende Einfallswinkel φ gemäß des Kosinus abnimmt. Die Halbkreise veranschaulichen die reflektierte Intensität für beide Einfallswinkel φ1 und
φ2 . Beispielhaft sind die Vektoren v~1 und v~2 in Richtung des Betrachters skizziert.
Abhängig vom Material des Objektes werden unterschiedliche Wellenlängen
auch unterschiedlich stark reflektiert. Der diffuse Reflexionskoeffizient kd (λ)
bestimmt wie stark eine bestimmte Wellenlänge reflektiert wird und ist somit
eine Funktion der Wellenlänge, definiert als
f : A → B,
mit A ∈ IR und B ∈ [0, 1]
(2.19)
Somit erweitert sich Gleichung 2.18 zu
Ird (λ) = Ir0 (λ) · kd (λ) · max((n~0 · l~0 ), 0)
2.2.3
(2.20)
Die spiegelnde Komponente
Die spiegelnde Komponente erzeugt sogenannte Glanzlichter (Highlights)
auf der Oberfläche. Dabei gilt: Je glatter die Oberfläche des Objekts, desto
stärker die Intensität des Glanzlichts. Bei dem Glanzlicht handelt es sich um
KAPITEL 2. GRUNDLAGEN
15
eine Spiegelung der Lichtquelle auf der Oberfläche des Objekts. Im Gegensatz zur diffusen Reflexion ist die spiegelnde Reflexion blickwinkelabhängig,
da eine Spiegelung immer richtungsgebunden ist.
Bekannte Modelle wie das von Phong[Pho75] verwenden folgende Gleichung
zur Berechnung des Glanzlichts:
Irs = coss ρ = (~
r0 · v~0 )s
(2.21)
r~0 ist der normierte Reflexionsvektor und v~0 der normierte Vektor in Richtung des Betrachters. ρ ist der aufgespannte Winkel beider Vektoren. Der
Exponent s wird häufig als shininess bezeichnet. Er bestimmt die Grösse des
Glanzlichts. Der Begriff shininess ist eigentlich irreführend, da der Faktor
nicht die Materialeigenschaft der Fläche beschreibt, sondern die Grösse des
Glanzlichts betimmt. Objekte mit kleinem Glanzlicht wirken aber spiegelnder als Objekte mit grossem Glanzlicht, daher hat der Begriff shininess eine
breite Akzeptanz in der Literatur gefunden.
Die spiegelnde Reflexion ist, analog zur diffusen Reflexion, ebenfalls nur für
Winkel 0 ≤ ρ ≤ π2 definiert, daher ergibt sich aus Gleichung 2.21:
Irs = (max(~
r0 · v~0 ), 0)s
~n
(2.22)
~r
~l
φ φρ
Betrachter
~v
Abbildung 2.7: Spiegelnde Reflexion für s = 1, 2, 10, 20, 40, 80, 160
In Abbildung 2.7 werden die Intensitäten der spiegelnden Reflexion für verschiedene Werte für s veranschaulicht. Je kleiner der Wert s, desto stärker
nimmt die Intensität mit steigendem Winkel ρ ab. Der Vektor ~v ist beispielhaft für eine Reflexion mit s = 2 eingezeichnet. Ebenfalls wird deutlich das
die Intensität am stärksten ist, wenn die Blickrichtung gleich dem Vektor ~r
ist, also ρ = 0.
Für die spiegelnde Reflexion existiert ebenfalls ein Reflexionskoeffizient ks ,
KAPITEL 2. GRUNDLAGEN
16
welcher die Menge des gespiegelten Lichts angibt. Er ist definiert als
ks ∈ [0, 1]
(2.23)
Im Gegensatz zum diffusen Reflexsionskoeffizient, ist er unabhängig von der
Wellenlänge, damit das Glanzlicht immer in der Farbe der Lichtquelle erscheint. Folglich ergibt sich aus Gleichung 2.22:
Irs = ks · (max(~
r0 · v~0 ), 0)s
2.2.4
(2.24)
Die Schattierung
Bisher wurde nur erläutert wie der Farbwert (das reflektierte Licht) eines
Oberflächenpunktes berechnet werden kann. Allerdings wurde noch nicht genauer spezifiziert, wie das Beleuchtungsmodell auf die Polygone eines Objekts
angewendet wird. Dieser Vorgang wird als Schattierung (shading) bezeichnet und bestimmt letztlich die Farbe eines jeden Pixels. Es existieren drei
gängige Schattierungsverfahren: Flat shading, Gouraud shading und Phong
shading.
Flat Shading
Komplexes dreidimensionale Modelle setzen sich immer aus einzelnen Dreicken zusammen, welche die Grafikhardware weiterverarbeitet. Dieses Vorgehen hat viele Vorteile, insbesondere da drei Punkte immer eine Fläche im
dreidimensionalen Raum beschreiben. Dadurch wird es ermöglicht, Werte,
die pro Vertex1 angegeben werden, wie Flächennormalen, linear über eine
Dreiecksfläche zu interpolieren. Beim Flat Shading wird das Reflexionsmodell immer pro Dreieck angewendet, das heisst es wird die Normale der Dreiecksfläche bestimmt und mit dem Reflexionsmodell verrechnet. Der daraus
resultierende Farbwert wird dann für das gesamte Dreieck verwendet.
Das Flat Shading zeichnet sich durch seine hohe Effizienz aus. Für jedes Dreieck muss nur einmal die Flächennormale bestimmt und das Reflexionsmodell
angewendet werden. Die beiden großen Nachteile des Verfahrens sind das,
insbesondere bei gekrümmten Oberflächen, facettenhafte Aussehen und die
sehr schlechte Darstellung von Glanzpunkten.
1
Eckpunkt eines Polygons
KAPITEL 2. GRUNDLAGEN
17
~n
~v1
4
~v2
Abbildung 2.8: Flat Shading: Das Linke Bild schematisiert das Prinzip für ein einzelnes
Polygon ∆. Das rechte Bild zeigt das Resultat am Beispiel.
Gouraud Shading
Beim Gouraud Shading wird das Reflexionsmodell für jedes Vertex angewendet. Die resultierenden Farbwerte werden dann über die gesamte Dreiecksfläche linear interpoliert. Gouraud Shading hat eine ähnliche Effizienz
wie das Flat Shading, liefert aber wesentlich ansprechendere Resultate. Das
facettenhafte Aussehen, des Flat Shading, entfällt durch die lineare Interpolation vollständig. Insgesamt wird die Reflexionsgleichung für jedes Dreieck
dreimal (an jedem Vertex) angewendet. Die lineare Interpolation der Farbwerte über die Dreiecksfläche wird bei heutigen Grafikkarten vollständig in
der Hardware ausgeführt.
~n1
~n2
~i
~v1
4
~v2
Abbildung 2.9: Gouraud Shading: Das Linke Bild schematisiert das Prinzip für ein einzelnes
Polygon ∆. Das rechte Bild zeigt das Resultat am Beispiel.
Beim Gouraud Shading entfällt die facettenhafte Erscheinung, allerdings werden Glanzpunkte nicht sauber dargestellt, da sie in die Farbinterpolation
benachbarter Dreiecksflächen mit einbezogen werden. Ebenfalls kann es passieren das Glanzlichter gar nicht dargestellt werden, falls diese genau mittig
in ein Dreieck fallen und kein Vertex berühren.
KAPITEL 2. GRUNDLAGEN
18
Phong Shading
Das qualitativ hochwertigste Schattierungsmodell ist das Phong Shading,
nicht zu verwechseln mit dem Phong Beleuchtungsmodell. Ein Schattierungsmodell gibt immer an wie ein Beleuchtungsmodell auf ein Objekt angewendet
wird1 . Beim Phong Shading werden zunächst die Normalen der Vertices einer
Dreiecksfläche bestimmt. Diese werden dann über die Dreiecksfläche linear
interpoliert. Das Reflexionsmodell wird dann für jedes Pixel anhand der interpolierten Normalen angewendet. Man spricht auch von per pixel lighting.
~n1
~n2
~n
~v1
4
~v2
Abbildung 2.10: Phong Shading: Das Linke Bild schematisiert das Prinzip für ein einzelnes
Polygon ∆. Das rechte Bild zeigt das Resultat am Beispiel.
Das Phong Shading bietet die höchste Qualität, hat aber auch eine vielfach
schlechtere Laufzeit gegenüber den anderen beiden Modellen. Glanzlichter
werden akkurat dargestellt, da die Beleuchtung pro Pixel berechnet wird.
Es ist prinzipiell möglich, gleichwertige Bilder mittels Gouraud Shading zu
erzielen, indem man das Modell so fein macht das die Grösse eines einzelnen
Dreiecks kleiner ist als die eines Pixels. Das ist aber nicht praktikabel, da der
Aufwand dreimal so hoch wäre wie beim echten Phong Shading, da ja für
jedes Dreieck (welches nun die Größe eines Pixels hat) drei Normalen berechnet werden (eine pro Ecke) auf die dann das Reflexionsmodell angewendet
wird. Beim Phong Shading wird das Reflexionsmodell nur einmal pro Pixel
angewendet.
2.3
Globale und lokale Beleuchtungsmodelle
Grundsätzlich unterscheidet man in der Computergrafik zwei Arten von Beleuchtungsmodellen, den globalen und den lokalen Modellen.
Lokale Beleuchtungsmodelle lassen die Interaktion zwischen Objekten ausser
1
Man kann z.B. das Phong Beleuchtungsmodell zusammen mit Gouraud Shading verwenden. Das Standarbeleuchtungsmodell von OpenGL verwendet das Phong Beleuchtungsmodell wahlweise mit Flat- oder Gouraud Shading
KAPITEL 2. GRUNDLAGEN
19
Acht. Sie werden auch als first-order Modelle bezeichnet, da sie nur die direkte, also erste Reflektion des Lichtstrahls, ausgehend von einer Lichtquelle,
betrachten. Licht das von einem anderen Objekt reflektiert wurde, wird nicht
weiter berücksichtigt. Die ambiente Komponente wird in lokalen Modellen
also nicht berücksichtigt. Damit unbeleuchtete Objekte nicht völlig schwarz
erscheinen wird die ambiente Komponente häufig durch eine Konstante, die
Grundhelligkeit, ausgedrückt. Durch die Mißachtung der Objektinteraktionen sind Spiegelungen oder Schatten mit lokalen Beleuchtungsmodellen nicht
direkt realisierbar. Insbesondere in Echtzeitanwendungen werden häufig lokale Beleuchtungsmodelle aufgrund ihres besseren Laufzeitverhaltens eingesetzt.
Globale Beleuchtungsmodelle unterscheiden sich von den lokalen Modellen
dadurch, dass die Interaktion von verschiedenen Objekten untereinander mit
berücksichtigt wird. Objekte interagieren durch Reflexion, Transparenz oder
Schatten miteinander. Globale Beleuchtungsmodelle sind wesentlich rechenaufwändiger, da sie diese Interaktionen berücksichtigen müssen.
Im Folgenden werden einige bekannte globale und lokale Beleuchtungsverfahren vorgestellt.
2.3.1
Das Radiosity Verfahren
Das Radiosity Verfahren beruht auf dem Energieerhaltungssatz und stammt
ursprünglich aus der Thermodynamik. Es ist neben dem Raytracing eines der
bekanntesten globalen Beleuchtungsmodelle. Dabei wird die gesamte Szene
in Flächen (Patches) aufgeteilt. Die Lichtmenge die eine Fläche emittiert, ist
die Differenz zwischen der empfangenen und absorbierten Menge Licht. Insbesondere kann eine Fläche auch selbstleuchtend sein, also eine Lichtquelle
repräsentieren. Es wird davon ausgegangen das eine Fläche, Licht immer ideal
diffus, also gleichverteilt in alle Richtungen, emittiert (Lambertsche Fläche).
Ziel ist es ein Gleichungssystem zu lösen, dessen Gleichungen die emittierte Lichtmenge, und somit die Helligkeit, einer jeden Fläche beschreibt. Der
aufwändigste Teil ist die Bestimmung sogenannter Formfaktoren. Ein Formfaktor beschreibt die ausgetauschte Strahlung zwischen zwei Flächen. Dazu
muss die Sichtbarkeit und Ausrichtung sämtlicher Flächen zueinander bestimmt werden[WW92]. Ein Vorteil des Verfahrens ist seine Blickwinkelunabhängigkeit. Das Gleichungssystem muss nur einmal berechnet werden,
danach lässt sich die Szene in Echtzeit rendern. Nachteile sind vor allem die
hohe Vorverarbeitungszeit sowie die mangelnde Unterstützung für spiegelnde und transparente Objekte. Das Radiosity Verfahren wurde weitestgehend
von modernen Raytracingverfahren verdrängt, findet aber noch Anwendung
insbesondere im Echtzeitrendering von Architekturmodellen.
KAPITEL 2. GRUNDLAGEN
2.3.2
20
Raytracing
Raytracing ist das wohl bekannteste globale Beleuchtungsverfahren. Beim
Raytracing werden Strahlen genutzt um die Szene abzutasten. Für jedes darzustellende Pixel wird ein Strahl, ausgehend vom Betrachter, durch das entsprechende Pixel der Projektionsebene, in die Szene geschickt und verfolgt.
Trifft der Strahl auf ein Objekt, so kann er, abhängig von den Eigenschaften
des Objektes, gebrochen, reflektiert oder absorbiert werden. Für diese Berechnungen wird wieder auf lokale Verfahren zurückgegriffen. Diese Strahlen
können rekursiv weiter verfolgt werden (rekursives Raytracing). Die Vorteile liegen beim Raytracing vor allem in seiner leichten Erweiterbarkeit und
prinzipiellen Parallelisierbarkeit.
2.3.3
Das Phong Modell
Das Phong Modell zählt zu den bekanntesten lokalen Beleuchtungsmodellen.
Obwohl es an die Wellentheorie des Lichts angelehnt ist handelt es sich bei
dem Phong Modell um ein empirisches Modell. Die reflektierte Intensität
einer Fläche entspricht der Summe der ambienten, diffusen und spiegelnden
Komponente, die Grundformel lautet:
~ ·N
~ ) + Iin · ks · ((R
~ · V~ )s )
Ir = Ia · ka + Iin · kd · (L
(2.25)
Die Summanden repräsentieren die ambiente, die diffuse und die spiegelnde Komponente. Die ambiente Komponente besteht aus der Intensität des
ambienten Lichts, Ia , sowie dem ambienten Reflexionskoeffizienten ka . Da es
sich beim Phong Modell um ein lokales Beleuchtungsmodell handelt, wird
für die ambiente Komponente eine Konstante verwendet.
Die diffuse Komponente ist ein Produkt aus der Intensität des einfallenden
Lichts Iin , dem diffusen Reflexionskoeffzienten kd und dem Winkel zwischen
~ und Flächennormale N
~ , ausgedrückt durch das Skalarprodukt
Lichtvektor L
beider Vektoren.
Die spiegelnde Komponente ist das Produkt aus der Intensität des einfallenden Lichts Iin , dem Reflexionskoeffzienten ks für spiegelnde Reflexion und
~ und der Richtung
dem Winkel zwischen der Reflexionsrichtung des Lichts R
~
~
~
des Betrachters V . Das Skalarprodukt R · V wird noch mit s, der shininess
potenziert. Dadurch wird die Grösse des Glanzlichts kontrolliert (siehe auch
Kapitel 2.2).
KAPITEL 2. GRUNDLAGEN
21
Die Formel kann auch grafisch veranschaulicht werden:
Abbildung 2.11: Phong Komponenten (Quelle: http://en.wikipedia.org/wiki/Phong%5Fshading)
2.4
Schatten
Schatten sind ein weiterer wichtiger Bestandteil der Beleuchtung. Die Bedeutung von Schatten für die Wahrnehmung einer Szene wird einem oft erst
bewusst, wenn diese nicht vorhanden sind. Schatten ermöglichen es erst, die
räumliche Beziehung von Objekten untereinander zu erkennen. Auch lassen
Schatten Rückschlüsse auf die Richtung vorhandener Lichtquellen zu. Wie
wichtig Schatten für die Wahrnehmung sind verdeutlicht Abbildung 2.12.
a)
b)
c)
Abbildung 2.12: Dieselbe Szene mit verschiedenen Schatten: a) Die Szene ohne Schatten.
b) Die Szene mit Schatten direkt unterhalb des Objekts. c) Die Szene mit Schatten weiter
entfernt vom Objekt.
In Bild a) ist es unmöglich eine Aussage bezüglich der räumlichen Anordnung des Torus zur Ebene zu machen. In Bild b) ist deutlich zu erkennen,
dass der Torus auf der Ebene liegt und in Bild c) schwebt der Torus weit
über der Ebene. Die Bilder unterscheiden sich lediglich durch die Position
KAPITEL 2. GRUNDLAGEN
22
des Schattens.
Ferner tritt bei konkaven Objekten noch die sogenannte Selbstschattierung
auf, das heisst das Objekt wirft Schatten auf sich selbst. Während das Fehlen
von Objektschatten unrealistisch wirkt (die Objekte scheinen ohne konkreten
Bezugspunkt im Raum zu schweben), bedeutet das Fehlen von Selbstschattierung eine falsche Darstellung der konvexen Oberfläche aufgrund der falschen
Beleuchtung. Die Darstellung von Schatten im Zusammenhang mit Beleuchtung ist somit ein wichtiger Bestandteil, um eine intuitivere und korrekte
Visualisierung zu ermöglichen.
2.4.1
Schatten und Lichtquelle
Mit Schatten ist eine Fläche gemeint die nicht beleuchtet wird, weil sie von
einem anderen Objekt verdeckt wird. Theoretisch muss diese Fläche dann
absolut schwarz sein. Praktisch ist das aber fast nie der Fall. Das liegt daran,
dass in der Realität Licht praktisch aus allen Richtungen kommt, entweder direkt von einer Lichtquelle, oder indirekt über Reflexion. Daher werden
Objekte in der Realität meist nur teilweise schattiert. Man spricht von Kernschatten und Halbschatten (weiche Schatten). Kernschatten sind Gebiete die
vollständig schattiert sind und Halbschatten entsprechend die Gebiete die
nur teilweise schattiert sind. Selbst wenn nur eine einzige Lichtquelle existiert
ergeben sich Halbschatten sobald diese Lichtquelle eine räumliche Ausdehnung hat: Abbildung 2.13 skizziert die Zusammenhänge zwischen der Grösse
der Lichtquelle und der Schattierung. Je kleiner die Lichtquelle, desto kleiner wird auch das Verhältnis von Halbschatten zu Kernschatten. Wird eine
Punktlichtquelle verwendet, existieren gar keine Halbschatten. Punktlichter
werfen immer harte Schatten.
2.4.2
Schattenberechnung
Der Begriff Schattenberechnung macht, im Gegensatz zur Reflektionsberechnung, zunächst wenig Sinn, da Schatten ein natürlicher Bestandteil globaler
Beleuchtung sind. Sie ergeben sich direkt aus der Reflexionsberechnung, da
auf eine schattierte Fläche entsprechend wenig Licht einfällt und dementsprechend umso weniger Licht reflektiert wird. Daraus resultiert, dass die Fläche
wesentlich dunkler erscheint als eine gut beleuchtete Fläche.
In der Computergrafik finden aber überwiegend lokale Beleuchtungsmodelle Verwendung. Da lokale Beleuchtungsmodelle die Objektinteraktion unberücksichtigt lassen, ist es nicht möglich Schatten direkt darzustellen. Da
Szenen ohne Schatten unrealistisch wirken und im Falle der Selbstschattie-
KAPITEL 2. GRUNDLAGEN
23
a1)
a2)
a3)
b1)
b2)
b3)
Abbildung 2.13: Kernschatten und Halbschatten in Abhängigkeit zur Größe der Lichtquelle
rung sogar falsch dargestellt werden, wurden Algorithmen entwickelt, die eine
getrennte Schattenberechnung ermöglichen. Ein Algorithmus zur Schattenberechnung muss im wesentlichen zwei Aufgaben erfüllen:
• Bestimmung der Schattenregion
• Bestimmung der Intensität des Schattens
In einigen Sonderfällen ist die Bestimmung der Schattenregion recht einfach.
Ein solcher Fall wäre die Bestimmung einer Schattenregion auf einer planen
Fläche. Dabei werden die beteiligten Objekte auf eben diese Fläche projeziert. Im allgemeinen Fall ist es aber so, dass beliebige Objekte Schatten auf
beliebige andere Objekte und, im Falle der Selbstschattierung, auch auf sich
selbst werfen können. Dadurch erhöht sich die Komplexität enorm.
Der zweite Punkt, die Intensität des Schattens, hängt davon ab, wieviel Licht
trotz Schattenwurf noch auf das Gebiet trifft. Dies kann indirekt reflektiertes Licht sein. Aber auch die Grösse der Lichtquelle spielt eine Rolle, wie in
Kapitel 2.4.1 gezeigt.
Häufig wird die Intensitätsberechnung der Schattenregion auch vernachlässigt
und stattdessen nur eine konstante ambiente Intensität verwendet.
Damit die Schattenberechnung nicht zu komplex wird, werden häufig Einschränkungen gemacht. Oft wird eine Punktlichtquelle, also eine Lichtquelle
KAPITEL 2. GRUNDLAGEN
24
ohne räumliche Ausdehnung, als Berechnungsgrundlage verwendet. Die indirekte Beleuchtung, also die ambiente Komponente, wird auch häufig ausser
Acht gelassen und als konstant angenommen. Als Konsequenz ergeben sich
harte Schatten. Eine Übersicht über verschiedene Schattengenerierungsalgorithmen wird in Kapitel 3.3 gegeben.
Im folgenden Kapitel werde einige Verfahren vorgestellt, mit denen die behandelten Grundlagen umgesetzt werden können. Das resultierende Laufzeitverhalten stellt dabei das Hauptkriterium dar. Um die Interaktivität insgesamt
zu erhöhen wird das progressive Rendering eingeführt. Ferner wird noch ein
Verfahren zur direkten Visualisierung sogenannter Isoflächen vorgestellt.
Kapitel 3
Techniken
In diesem Kapitel werden verschiedene Techniken für die jeweiligen Aufgaben wie Beleuchtung, Schattengenerierung oder der Isoflächendarstellung
erläutert. Es werden jeweils die Vor- und Nachteile diskutiert und begründet
warum ein bestimmtes Verfahren für die Implementierung verwendet wurde.
Als Basis sämtlicher Verfahren dient die 3d-Grafikbibliothek OpenGL [GL].
Da die meisten Aufgaben über Shader realisiert sind, wird ebenfalls eine
Einleitung in diese Technik gegeben.
3.1
Shader
Als Shader werden Programme bezeichnet, die vollständig auf der GPU der
Grafikhardware ausgeführt werden. Shader können dazu dienen, Objekte zu
deformieren, oder die Farbe einzelner Fragmente zu bestimmen. Als Fragment wird dabei ein Pixel bezeichnet, dem noch kein Farbwert zugeordnet
ist. Dieser wird erst in einer weiteren weiteren Verarbeitungsstufe, dem Shading, anhand von Texturen oder Beleuchtungsgleichungen ermittelt.
Moderne Grafikkarten arbeiten wie eine Pipeline, das heisst es existieren verschiedene Verarbeitungsstufen die in einer festen Reihenfolge abgearbeitet
werden. Die Daten werden zwischen diesen einzelnen Stufen weitergereicht.
Die konkrete Realisierung dieser Pipeline in Hardware ist Sache der Hersteller und geheim. Der logische Ablauf ist in Abbildung 3.1 dargestellt. Der
Grafikkartentreiber stellt die Abstraktion zwischen Hardware und OpenGL
her. Die Eingabedaten bestehen aus Vertexdaten, den OpenGL-Primitiven.
Zusätzlich können jedem Vertex noch zusätzliche Informationen mitgegeben
werden, wie etwa die Farbe oder eine Normale. Diese Daten durchlaufen
die verschiedenen Verarbeitungsstufen, Vertexdaten können rotiert, skaliert
oder translatiert werden. In einem weiteren Schritt werden diese Daten dann
25
KAPITEL 3. TECHNIKEN
26
rasterisiert, also in Fragmente zerlegt, eventuell mit Texturen versehen und
im Framebuffer abgelegt. Dieser sehr grobe Ablauf ist in [Hir05] genauer
erläutert.
Die ersten 3d-Beschleunigerkarten waren nicht programmierbar. Man spricht
auch von der sogenannten fixed function pipeline, da alle Algorithmen fest
in der Hardware verankert waren. Der Nachteil dieser festen Pipeline ist die
fehlende Flexibilität. Mit der fortschreitenden Entwicklung der Grafikkarten
wurden diese auch immer flexibler. Der Grafikkartenhersteller NVidia führte mit der GeForce256 die Register-Combiner ein. Diese erlaubten es die
OpenGL-Textureinheit zu umgehen und stattdessen eine Reihe hintereinandergeschalteter Registeroperationen durchzuführen. Auf diese Weise waren
komplexe Verknüpfungen zwischen verschiedenen Texturen möglich. Allerdings kann man bei den Register-Combinern noch nicht von einer frei programmierbaren Einheit sprechen. Vielmehr bestehen sie aus einer Auswahl
verschiedener, fest programmierter, aber frei parametrisierbarer Operationen.
Aktuelle Grafikkarten besitzen zwei programmierbare Einheiten, eine programmierbare Geometrieeinheit (Vertexprozessor) und eine programmierbare Rasterisierungseinheit (Fragmentprozessor).
3.1.1
Der Vertexprozessor
Die Transformation und Beleuchtung der Vertices wurde ursprünglich auf der
CPU ausgeführt. Um die CPU zu entlasten wurde die T&L-Einheit (transform and lighting) eingeführt. Diese führt die entsprechenden Berechnungen in der Hardware auf der Grafikkarte aus. Das hat den Vorteil, dass die
Transformation und Beleuchtung nun sehr schnell berechnet werden kann,
bringt aber gleichzeitig den Nachteil das die Beleuchtungsgleichungen fest in
der Hardware verankert sind. Um zum Einen Flexibilität zu gewährleisten
und zum Anderen die die CPU zu entlasten, wurde der Vertexprozessor eingeführt.
Beim Vertexprozessor handelt es sich um eine frei programmierbare Geometrieeinheit auf der Grafikkarte. Er ersetzt die klasssische T&L-Einheit.
Der Vertexprozessor ist über sogenannte Vertexshader frei programmierbar.
Beim Vertexshader handelt es sich um ein kleines Programm, welches auf der
Grafikkarte vom Vertexprozessor ausgeführt wird. Der Vertexshader wird für
jedes Vertex genau einmal ausgeführt. Es können dabei weder neue Vertices
erzeugt, noch vorhandene Vertices gelöscht werden.
Es besteht nun die Möglichkeit entweder die klassische fest verdrahtete T&L-
KAPITEL 3. TECHNIKEN
27
Abbildung 3.1: Schematischer Aufbau der Grafikpipeline
Einheit zu verwenden oder einen Vertexshader. Beides in Kombination ist
nicht möglich. Wird ein Vertexshader verwendet, muss er daher neben der
eigentlichen Aufgabe zusätzlich die Funktion der T&L-Einheit erfüllen. Im
einfachsten Fall bedeutet das, er muss mindestens die Vertextransformation
durchführen. Die T&L-Einheit führt, je nach Bedarf, folgende Operationen
durch:
• Vertextransformation (Multiplikation mit der Modelview- und Projektionsmatrix)
• Normalentransformation und Normalisierung
• Generierung von Texturkoordinaten
• Texturkoordinatentransformation
• Beleuchtungsberechnungen für jedes Vertex
• Anwendung von Farbmaterialwerten (für die Beleuchtung)
KAPITEL 3. TECHNIKEN
28
Dabei hat der Vertexshader immer nur Kenntnis vom aktuellen Vertex. Geometrieoperationen die Kenntnis anderer Vertices voraussetzen, wie Frustumoder Userclipping werden nicht durch die Vertexeinheit ersetzt. Unter OpenGL
muss ein view-frustum definiert werden. Dabei handelt es sich um ein kegelförmiges Objekt1 , das den Sichtbereich der Kamera darstellt. Alle Objekte
innerhalb dieses Kegels werden gerendert. Liegt ein Objekt sowohl innerhalb,
als auch ausserhalb dieses Bereichs, wird es entsprechend abgeschnitten. Man
spricht dabei von clipping. Zusätzlich können weitere Flächen definiert werden, an denen ein Objekt abgeschnitten werden soll. Man spricht dann von
user clip planes.
Da die Vertexeinheit eine Verarbeitungsstufe in der Renderingpipeline ist,
erhält sie bestimmte Daten von der vorherigen Stufe und muss auch bestimmte Daten an die nächste Stufe weitergeben. Die Vertexeinheit erhält
ihre Daten direkt von der Applikation. Im einfachsten Fall bestehen die Daten nur aus der Position des aktuellen Vertex. Je nach verwendeten OpenGL
Funktionen können aber noch zusätzliche Parameter zugänglich gemacht werden wie Vertexnormalen, Texturkoordinaten oder die Farbe des diffusen und
Glanzlichts. Zusätzlich können dem Vertexshader noch benutzerdefinierte Parameter zugänglich gemacht werden. Vertexshader können dazu benutzt werden um Objekte zu deformieren, indem die Position des Vertex in bestimmter
Weise manipuliert wird. Die Ergebnisse der Berechnungen werden den weiteren Stufen der Pipeline (und somit auch dem Fragmentprozessor) zugänglich
gemacht.
Abbildung 3.2 zeigt schematisch den Aufbau eines DirectX 8.0 konformen
Vertexprozessors. Modernere Vertexprozessoren arbeiten nach demselben Schema, nur besitzen sie häufig mehr Register und einen erweiterten Befehlssatz.
Der Vertexprozessor erhält seine Daten über die Eingaberegister v0..v15.
Sämtliche Register haben eine Breite von 128 Bit. Diese werden in 4*32 Bit
float Werte unterteilt. Jedes Register kann somit einen Vektor, bestehend
aus vier 32 Bit float Komponenten, speichern. Dieses Datenformat ist sehr
nützlich, da Transformationen üblicherweise mit 4x4 Matrizen durchgeführt
werden. Auch bestehen Vertexkoordinaten tatsächlich aus 4 Komponenten.
Das hat den Grund, dass Transformationen wie Rotation und Skalierung
zwar mit einer zusammengefassten 3x3 Matrix durchgeführt werden können,
die Translation aber nicht. Eine 4x4 Matrix ermöglicht sämtliche Transformationen mit einer einzigen zusammengefassten Matrix[Len02]. Damit diese
1
Für die orthogonale Projektion ist das Objekt quaderförmig.
KAPITEL 3. TECHNIKEN
29
Abbildung 3.2: Schematische Darstellung eines Vertexprozessors
4x4 Matrix auf die dreidimensionalen Koordinaten angewendet werden kann,
werden diese künstlich erweitert. Die 4. Komponente nimmt dabei meist den
Wert 1 an.
Die Eingaberegister enthalten die Koordinaten des aktuellen Vertex, sowie
dessen Farbwert. Zusätzlich können sie, abhängig von der Applikation, weitere Werte wie etwa Vertexnormalen oder Farbwerte für diffuses Licht enthalten.
Der Vertexprozessor kann diese Register ausschliesslich lesen. Zusätzlich ist
es möglich weitere, beliebige, Parameter an den Vertexprozessor zu schicken.
Diese werden im Konstantenregister gehalten. Auch hier hat der Vertexprozessor ausschliesslich Leserechte, allerdings ist es möglich das Konstantenregister mit Hilfe des Adressregisters indirekt zu addressieren.
Die temporären Register dienen zur Speicherung von Zwischenergebnissen,
der Vertexprozessor kann sie sowohl lesen als auch schreiben.
Daten die an die nächste Verarbeitungsstufe geschickt werden müssen, werden im Ausgaberegister abgelegt. Dies sind in jedem Fall die transformierten
Vertexkoordinaten, aber es können auch andere Daten weitergeleitet werden.
Der Vertexshader kann beispielsweise dazu benutzt werden, Texturkoordinaten zu berechnen. Diese können weitergeleitet werden um sie einem Fragmentshader zugänglich zu machen. Der Fragmentprozessor erhält dann für
jedes Fragment automatisch die interpolierten Texturkoordinaten vom Vertexprozessor. In die Ausgaberegister kann ausschliesslich geschrieben werden.
Die Anzahl der Ausgaberegister variiert abhängig von der verwendeten Gra-
KAPITEL 3. TECHNIKEN
30
fikhardware.
3.1.2
Der Fragmentprozessor
Beim Fragmentprozessor handelt es sich um eine weitere frei programmierbare Einheit auf der Grafikkarte. Er kann anstelle der fest verdrahteten Multitextureinheit moderner Grafikkarten benutzt werden. Programme die auf
dem Fragmentprozessor ausgeführt werden, heissen Fragmentshader (auch
Pixelshader genannt).
Aufgabe des Fragmentshaders ist es, die Farbe und Transparenz eines Fragments zu bestimmen. Dies kann über Texturen geschehen, wobei auch mehrere Texturen verknüpft werden können, oder es können komplexe Beleuchtungsberechnungen pro Pixel durchgeführt werden. Analog zum Vertexshader
wird ein Fragmentshader genau einmal für jedes Fragment ausgeführt. Auch
besitzt der Fragmentshader keinerlei Informationen über andere Fragmente
ausser dem aktuell behandelten. Die Eingabedaten erhält der Fragmentshader von der vorherigen Verarbeitungsstufe der Grafikpipeline. Auf diese Weise
ist es auch möglich, dem Fragmentshader Ausgabedaten eines Vertexshaders
zugänglich zu machen. Zum Beispiel kann der Vertexprozessor Vertexnormalen linear über eine Dreiecksfläche interpolieren und in einem speziellen
Register speichern. Der Fragmentshader kann nun auf dieses Register zugreifen und erhält so automatisch die interpolierte Normale für den aktuellen
Pixel. Auf diese Weise lässt sich zum Beispiel eine Beleuchtung pro Pixel
realisieren, bei dem für jedes Pixel die entsprechende Normale bekannt sein
muss, da diese üblicherweise in die Beleuchtungsberechnung mit einfliesst
(Kapitel 2.2.4). Der Vorteil liegt darin, das die lineare Interpolation der Normalen im Vertexprozessor praktisch keine Rechenzeit kostet. In der Tat ist es
häufig so, dass man Vertex- und Fragmentshader als Einheit betrachtet um
die höchste Effizienz zu erzielen, da der Vertexshader wesentlich seltener ausgeführt wird (pro Vertex) als der Fragmenshader (pro Fragment). Bei Daten
die linear über eine Fläche interpoliert werden können, beispielsweise Texturkoordinaten oder Flächennormalen, macht es also durchaus Sinn, diese in
einem Vertexprogramm zu generieren. Diese werden dann automatisch über
die Dreiecksfläche interpoliert und den weiteren Verarbeitungsstufen der Pipeline zugänglich gemacht.
Zusätzlich können dem Fragmentshader benutzerdefinierte Parameter aus der
Applikation übergeben werden.
Abbildung 3.3 zeigt den schematischen Aufbau eines DirectX 8.0 konformen
Fragmentshaders. Modernere Fragmentshader besitzen eine höhere Anzahl
KAPITEL 3. TECHNIKEN
31
Register, einen erweiterten Befehlssatz und können bis zu 8 Texturen adressieren.
Abbildung 3.3: Schematische Darstellung eines Fragmentprozessors
Hauptaufgabe des Fragmentshaders ist es, die Farbe für ein Fragment zu
bestimmten. Dazu erhält er die Grundfarbe des aktuellen Fragments von der
vorherigen Verarbeitungsstufe über die nur lesbaren Farbregister v0 und v1.
Der Zugriff auf Texturen erfolgt über die Texturregister tn. Sie enthalten
eine Referenz auf die jeweilige Textur. Beim Zugriff auf eine Textur wird der
entsprechende Texel1 automatisch, mittels entsprechendem Filter, zwischen
den benachbarten Pixeln rekonstruiert. Als Filter stehen meist der Box-Filter
(nearest-neighbour Interpolation) und der Tent-Filter (lineare Interpolation)
zu Verfügung.
In den Konstantenregistern werden die benutzerspezifischen Parameter gespeichert.
Der berechnete Farbwert des Fragments wird in das Ausgaberegister geschrieben. Dieses dient gleichzeitig als temporäres Register, so dass sowohl Leseals auch Schreibzugriffe möglich sind.
1
Texture Element
KAPITEL 3. TECHNIKEN
3.1.3
32
Shadersprachen
Die ersten frei programmierbaren Vertex- und Fragmentprozessoren waren
noch sehr beschränkt in ihrem Funktionsumfang. Die entsprechenden Shader
wurden in Assembler programmiert. Das genügte zunächst, da die Shader nur
eine begrenzte (kurze) Anzahl an Instruktionen enthalten durften. Ein Vertexshader der ersten Generation unter DirectX 8.1 [MDX] durfte aus maximal
128 Befehlen bestehen und der Sprachumfang bestand aus 17 verschiedenen
Instruktionen [AMH02]. Ebenso kannten die ersten Shader keine Statements
zur Flußkontrolle, wie etwa if, for, while oder goto.
Mit dem Erscheinen immer neuerer Grafikkarten wuchs auch der Umfang
der entsprechenden Shaderfunktionalität immer weiter und die Nachteile der
in Assembler programmierten Shader wurden offensichtlich. Moderne Shader können mittlerweile eine beliebige Anzahl an Instruktionen enthalten.
Ebenso werden konditionale Statements wie if-Anweisungen und Schleifen
unterstützt. Auch können heutige Shader Unterprogramme aufrufen. In Assembler geschriebene Shader haben den Nachteil, dass sie eine längere Entwicklungszeit benötigen und schwerer zu lesen und zu warten sind. Ein weiterer entscheidender Nachteil ist, dass sie nicht portabel sind.
Mittlerweile existieren drei gängige Hochsprachen für die Shaderprogrammierung. Im Funktionsumfang sind sie sich recht ähnlich, auch die Syntax ist
bei allen drei Sprachen stark an C angelehnt, jedoch hat jede Sprache ihre
Vor- und Nachteile.
Die OpenGL Shading Language
Die OpenGL Shading Language (GLSL) [KBR] ist seit der Version 2.0 Bestandteil von OpenGL. GLSL kann somit nur in Verbindung mit OpenGL
benutzt werden. Eine Integration in eine andere Grafik-API wie DirectX ist
nicht möglich. Diesem Nachteil steht der Vorteil gegenüber, dass Shader die
in GLSL geschrieben wurden direkten Zugriff auf wichtige OpenGL Variablen
haben, wie den diversen Matrizen oder Beleuchtungsparametern.
Die High Level Shading Language
Die High Level Shading Language (HLSL) [HLS] wurde von Microsoft[MS]
entwickelt und ist Bestandteil von DirectX seit Version 9.0. HLSL kann ausschliesslich in Verbindung mit DirectX benutzt werden und ist somit auch
an das Betriebssystem Windows gebunden. Plattformunabhängige Shader,
wie sie mit GLSL möglich sind (sofern die Zielplattform OpenGL 2.0 unterstützt), sind mit HLSL nicht möglich.
KAPITEL 3. TECHNIKEN
33
C for Graphics
C for Graphics, oder kurz Cg [CG05] ist eine von NVidia entwickelte Hochsprache für Shader. Der Name deutet bereits an das die Sprache stark an die
Programmiersprache C angelehnt ist. Cg zeichnet sich vor allem durch die
hohe Universalität aus. Cg Shader sind nicht an eine bestimmte Grafikbibliothek gebunden. Derzeit können Cg Shader sowohl mit DirectX als auch mit
OpenGL benutzt werden, dabei wird der Shader für ein bestimmtes Profil
kompiliert. Es existieren Profile für sämtliche DirectX und OpenGL Shaderversionen. Der Entwickler muss natürlich sicherstellen, dass die Shader die
gegebenen Beschränkungen des jeweiligen Profils nicht überschreiten.
Um die Plattformunabhängigkeit zu realisieren benötigen Cg Shader eine
Laufzeitbibliothek. Die Laufzeitbibliothek besteht aus zwei Teilen, der Core
Cg Runtime und einer Anbindung an die entsprechende Grafikbibliothek, also derzeit entweder die Direct3D Cg Runtime (für DirectX) oder der OpenGL
Cg Runtime (für OpenGL). Auf diese Weise können die Shader auch direkt
von der Installation einer neueren (optimierten) Cg Runtime profitieren. Es
existiert die Möglichkeit, Shader erst zur Laufzeit zu kompilieren, dadurch
kann das beste vorhandene Profil der Zielplattform genutzt werden.
Sämtliche während dieser Arbeit entwickelten Shader sind mit Cg realisiert.
3.2
Anwendung des Beleuchtungsmodells auf
das Volumen
Die in Kapitel 2.1 vorgestellten Verfahren zur Beleuchtung und Schattierung
beziehen sich auf polygonbasierte dreidimensionale Objekte. In dieser Arbeit
müssen die Verfahren aber auf ein texturbasiertes Volumenrendering angewendet werden. Dazu ist es unerlässlich, die grundsätzlichen Unterschiede
zwischen beiden Renderingverfahren zu kennen. Dazu wird im nächsten Kapitel das Prinzip des texturbasierten Volumenrenderings kurz erläutert und
auf die Unterschiede zum polygonbasierten Rendering eingegangen. Im darauf folgenden Kapitel wird erläutert, wie die auftretenden Probleme gelöst
werden können.
KAPITEL 3. TECHNIKEN
3.2.1
34
Polygonbasiertes Rendering und texturbasiertes
Volumenrendering
Beim klassischen polygonbasierten Rendering werden dreidimensionale Objekte anhand von geometrischen Primitiven im Raum modelliert. Man spricht
auch vom sogenannten Drahtgittermodell (engl. Wireframe). Jedem Vertex
können zusätzliche Informationen mitgegeben werden, wie Farbwerte, Texturkoordinaten oder Normalenvektoren. Dadurch erhält das Objekt seine Farbe und kann bei Bedarf beleuchtet werden. Es ist wichtig festzuhalten, dass
die Objektgeometrie der tatsächlichen Form des Objektes entspricht. Abbildung 3.4 verdeutlicht den Zusammenhang zwischen der Modellgeometrie und
dem gerenderten Objekt am Beispiel einer Kugel.
a)
b)
Abbildung 3.4: Bild a) zeigt die Geometrie des Objekts und b) das fertig gerenderte Objekt.
Das texturbasierte Volumenrendering dagegen verfolgt einen anderen Ansatz. Das dreidimensionale Objekt wird in Scheiben (Slices) zerlegt. Diese
Scheiben werden als Texturen verwendet und hintereinander von hinten nach
vorne gerendert. Die einzelnen Texturscheiben werden mit einem geeigneten
Operator verknüpft (blending). Die Objektgeometrie stimmt dabei nicht mit
der Form des gerenderten Objekts überein. Die Objektgeometrie besteht vielmehr nur aus einzelnen Rechtecken, auf denen die Texturen gerendert werden.
Das genaue Verfahren des texturbasierten Volumenrenderings ist in [Hir05]
erläutert. Zum Vergleich zeigt Abbildung 3.5 die Objektgeometrie und das
gerenderte Objekt beim texturbasierten Volumenrenderung.
Die Objektgeometrie bildet den entscheidenden Unterschied zwischen beiden
Verfahren. Daraus resultiert, dass sämtliche angewandten klassischen Algorithmen, wie Beleuchtung oder Schattengenerierung, auf diesen Unterschied
KAPITEL 3. TECHNIKEN
a)
35
b)
Abbildung 3.5: Bild a) zeigt die Geometrie des Objekts1 und b) das fertig gerenderte
Objekt.
hin angepasst werden müssen.
3.2.2
Per Pixel lighting einer texturbasierten Volumenvisualisierung mit dem Beleuchtungsmodell von
Phong
Um das Reflexionsmodell von Phong (siehe Kapitel 2.3.3) auf jedes Pixel
anzuwenden bedarf es die Kenntnis einiger Parameter:
• Die Normale ~n des zu beleuchtenden Pixels.
• Die Position ~l der Lichtquelle.
• Die Position des Betrachters ~v .
• Weitere Parameter wie die Farbe der Lichtquelle, die Farbe des zu beleuchtenden Materials, etc.
Die meisten der obigen Parameter werden entweder vorgegeben (Farbe der
Lichtquelle, Position der Lichtquelle, Farbe des Materials, etc.) oder können
aus den Parametern der aktuellen Szene ermittelt werden (Blickrichtung des
Betrachters). Dei Hauptaufgabe besteht in der Bestimmung der Flächennormalen.
Beim klassischen polygonbasierten Rendering geht man so vor, dass die Normale pro Vertex in der Applikation mit angegeben wird. Die Geometrieeinheit
der Grafikkarte interpoliert diese Normalen linear zwischen den Vertices. Auf
1
Aus Gründen der Übersicht wurde nur jede 20. Scheibe gerendert.
KAPITEL 3. TECHNIKEN
36
diese Weise hat man im Fragmentshader Zugriff auf die (interpolierte) Normale eines jeden Fragments. Dieses Verfahren entspricht dem Phong Shading
aus Kapitel 2.2.4.
Beim texturbasierten Volumenrendering macht genau das keinen Sinn, weil
die Objektgeometrie, wie im vorherigen Kapitel beschrieben, keinerlei Bezug
zur tatsächlichen Objektform hat. Die Objektgeometrie besteht ja nur aus
einzelnen Scheiben auf denen die Texturen gerendert werden. Die Normalen
dieser Scheiben wären alle gleich und stünden in keinem Zusammenhang zu
den Normalen des tatsächlichen Objekts. Die Beleuchtung würde also völlig
falsch berechnet.
Es existieren verschiedene Verfahren für die Normalenbestimmung von Volumendaten. Eine Methode ist das grey-level shading [Win02]. Das grey-level
shading zeichnet sich durch eine hohe Präzision aus, bietet die Möglichkeit
Normalen auch für halbtransparente Medien zu berechnen und ist Blickwinkelunabhängig. Der Nachteil ist das es für Daten versagt für die kein Gradient
berechnet werden kann, beispielsweise Binärdaten[YCK].
Ein Normalenvektor für einen Punkt (x, y, z) eines diskreten Volumens V ist
die partielle Ableitung von V nach x, y und z:
∇V (x, y, z) = (
∂V ∂V ∂V
,
,
)
∂x ∂y ∂z
(3.1)
Bei einem dreidimensionalen Datensatz V (x, y, z) handelt es sich um diskrete Abtastwerte, den sogenannten Voxeln1 . Im Allgemeinen ist die Funktion
V (x, y, z) nicht bekannt, daher wird der Gradient aus den diskreten Voxeln
bestimmt. Wird der Gradient für eine Stelle benötigt, die nicht einem diskreten Abtastpunkt entspricht, muss ein Rekonstruktionsfilter verwendet werden
um die Ableitung für jede Richtung zu bestimmen. Mit Hilfe des Sinc Filters
lassen sich Signale ohne Informationsverlust rekonstruieren, solange bei der
Abtastung das Nyquist-Theorem eingehalten wurde, welches besagt, dass die
Abtastfrequenz eines Signals größer als die doppelte Bandbreite fg des Signals sein muss. Diskrete Voxeldaten entsprechen im Allgemeinen nicht dem
Nyquist-Theorem, da sie nicht bandbegrenzt sind. Kanten von Polygonen
oder Schattengrenzen erzeugen sehr hohe Frequenzen, die auch bei Erhöhung
der Abtastfrequenz nicht optimal abgetastet werden können. Trotzdem bildet der Sinc Filter den optimalen Rekonstruktionsfilter. Er liefert eine stetige
Funktion V 0 (x, y, z) die sich (leicht) von der Originalfunktion unterscheidet.
Da der Gradient die partielle Ableitung der Funktion V (x, y, z) ist, ist der
1
von Volume Element in Anlehnung an das Pixel für Picture Element
KAPITEL 3. TECHNIKEN
37
optimale Rekonstruktionsfilter ebenfalls die Ableitung des Sinc Filters, Cosc:
cos (πx)−Sinc(πx)
x 6= 0
x
Cosc(x) =
(3.2)
0
sonst
Praktisch kann der Cosc Filter aber nicht verwendet werden, da er unendliche
Ausdehnung besitzt. Eine andere Möglichkeit die Normalen zu bestimmen,
ist die Methode der zentralen Differenzen. Dabei wird ∇V über die Differenz
benachbarter Werte in der jeweiligen Dimension berechnet:
• Zentrale Differenz:
V (x + 1, y, z) − V (x − 1, y, z)
∂V
=
∂x
2Dx
(3.3)
• Vorwärtsdifferenz:
∂V
V (x + 1, y, z) − V (x, y, z)
=
∂x
Dx
(3.4)
• Rückwärtsdifferenz:
∂V
V (x, y, z) − V (x − 1, y, z)
=
∂x
Dx
(3.5)
Die Distanz zweier benachbarter Pixel in x-Richtung beträgt Dx . Die Vorwärtsund Rückwärtsdifferenz wird an den Kanten des Datensets angewendet, da
und ∂V
werden
dort keine entsprechenden Nachbarpixel existieren. Für ∂V
∂y
∂z
die Formeln analog verwendet.
Die Berechnung der Normalen mittels der zentralen Differenzen kann komplett im Fragmentshader realisiert werden. Der Volumendatensatz wird dem
Shader in Form einer dreidimensionalen Textur zugänglich gemacht. Anschliessend wird die Beleuchtungsgleichung auf das Fragment angewendet.
Um die Performance zu erhöhen kann man die Normalen auch in einem
Vorverarbeitungsschritt berechnen und in Form einer Lookup-Tabelle speichern. Die Performance kann dadurch spürbar verbessert werden, allerdings
benötigt eine solche Lookup-Tabelle auch sehr viel Speicher, da die Normale
eines jeden Pixels gespeichert werden muss. Ein 8-Bit Grauwertbild mit den
Dimensionen 5123 benötigt alleine schon 128MB Speicher. Würde man die
Normalen in Form einer RGB-Textur mit den Dimensionen 5123 speichern,
so würden zusätzliche 384MB Speicher benötigt. Diese Grössenordnungen
stellen selbst moderne Grafikkarten vor Probleme. Letztlich entscheidet die
Rechenleistung, die Größe des Volumendatensatzes und Speicherausstattung
der Grafikkarte darüber, ob eine Berechnung zur Laufzeit sinnvoll ist.
KAPITEL 3. TECHNIKEN
3.3
38
Schattengenerierung
Im vorherigen Kapitel wurde erläutert wie die Normale zu jedem Fragment
ermittelt werden kann um die Beleuchtung zu realisieren. Die gesamte Berechnung geschieht im Fragmentshader. Folglich wird die Beleuchtungsberechnung für jedes Fragment ausgeführt. Da es sich bei dem verwendeten
Modell um ein lokales Modell handelt sind Schatten, wie in Kapitel 2.4.2
bereits erwähnt, nicht direkt realisierbar. Jedes Pixel des Volumens würde
also beleuchtet werden, was zu einer falschen Darstellung, insbesondere bei
konkaven Objekten (Kapitel 2.4) führt.
Um eine Schattendarstellung zu realisieren muss eine Möglichkeit gefunden
werden, im Fragmentshader zu entscheiden ob das aktuelle Fragment verdeckt ist (also im Schatten liegt) oder nicht. Es existieren eine Vielzahl von
Schattengenerierungsalgorithmen, jeder mit spezifischen Vor- und Nachteilen. In der Praxis ist es so, dass auch tatsächlich eine Vielzahl dieser Algorithmen verwendet wird. Man kann also derzeit keinen klaren Favoriten
nennen. Die folgenden Kapitel stellen einige Verfahren vor und zeigen die
jeweiligen Vor- und Nachteile auf.
3.3.1
Strahlenverfolgung
Das Prinzip der Strahlenverfolgung ist trivial. Für jedes Pixel wird ein Strahl
zur Lichtquelle verfolgt. Wird dieser Strahl von einem anderen Objekt blockiert, dann liegt der betreffende Pixel im Schatten, andernfalls ist er beleuchtet. Um das Verfahren zu benutzen bedarf es keiner Vorverarbeitung
der Daten.
Das Hauptproblem dieses Algorithmus liegt in dem enormen Rechenaufwand.
Für jedes Pixel muss ein Strahl gegen sämtliche Polygone (beziehungsweise
Fragmente) der Szene getestet werden. Prinzipiell ist es möglich dieses Verfahren für die Schattengenerierung im Kontext einer texturbasierten Volumenvisualisierung zu verwenden. Aufgrund des hohen Aufwands ist dies aber
nicht interaktiv möglich. Für das Echtzeitrendering bietet sich das Verfahren
daher nicht an, allerdings existieren diverse Techniken um die Performance
zu steigern, beispielsweise können light buffer verwendet werden um die Performance des Schattentests auf Kosten eines Vorverarbeitungsschritts und
des zusätzlich benötigten Speichers zu senken [WW92].
KAPITEL 3. TECHNIKEN
3.3.2
39
Shadow Volumes
Ursprünglich vorgestellt von Franklin Crow[Cro77], basiert diese Methode
darauf, dass zu jedem Polygon ein sogenanntes Schattenvolumen (engl. shadow volume) erzeugt wird. Dazu wird für jede Kante eines Polygons jeweils
ein Strahl von der Lichtquelle aus, durch die Kantenpunkte, Richtung Unendlich geschickt. Das Schattenpolygon erhält man, indem man die Strahlen in
maximaler Entfernung abschneidet und durch eine Kante verbindet. Sämtliche Schattenpolygone eines Objektpolygons bilden das Schattenvolumen. Die
Schattenpolygone werden nicht gezeichnet, sie werden aber wie gewöhnliche
Polygone behandelt, das heisst sämtliche Transformationen werden auch auf
ihnen ausgeführt.
Die Entscheidung, ob ein Pixel im Schatten liegt, wird anhand eines Zählers
ermittelt. Dabei wird die Strecke, ausgehend vom Beobachter, zum fraglichen Pixel hin untersucht. Jedesmal wenn man dabei auf die Vorderseite
eines Schattenpolygons trifft wird der Zähler um 1 erhöht da ein Schattenvolumen betreten wird. Trifft man auf die Rückseite eines Schattenpolygons
wird der Zähler entsprechend um 1 erniedriegt, da ein Schattenvolumen verlassen wurde. Trifft man auf den betreffenden Pixel und der Zähler ist gleich
0, dann ist der Pixel beleuchtet, andernfalls liegt er im Schatten. Bei der
Initialisierung des Zählers ist darauf zu achten, dass er mit der Anzahl der
Schattenvolumina initialisiert wird in denen sich der Betrachter befindet.
Der Algorithmus in der obigen Grundform ist sehr rechenaufwändig, da sehr
viele zusätzliche Polygone erzeugt werden. Es existieren viele Ansätze zur
Verbesserung der Performance. Anstatt das Schattenvolumen für jedes einzelne Polygon zu berechnen, kann es für zusammenhängende Objekte berechnet
werden [WW92]. Dazu muss die Silhouette des Objekts aus der Richtung der
Lichtquelle bestimmt werden. Das Schattenvolumen wird dann für die Kanten der Silhouette berechnet. Dadurch kann die Zahl der Schattenpolygone
beträchtlich reduziert werden, allerdings ist die Bestimmung der Silhouette
nicht trivial. Aktuelle Implementationen verwenden auch häufig den stencil
buffer für die Realisierung des Schattenzählers. Dieses Verfahren ist auch bekannt als Stencil Shadows, basiert aber ebenfalls auf Schattenvolumen.
Mittels Schattenvolumen ist es möglich Schatten in Echtzeit zu realisieren.
Für die interaktive Volumenvisualisierung scheidet das Verfahren dennoch
prinzipiell aus, da die Schattenvolumen die zugrunde liegende Geometrie der
Objekte für die Berechnung verwenden, diese aber bei der texturbasierten
Volumenvisualisierung nicht vorhanden ist (siehe Kapitel 3.2.1).
KAPITEL 3. TECHNIKEN
3.3.3
40
Shadow Maps
Ein ebenfalls weit verbreitetes Verfahren ist die Verwendung von Shadow
Maps [Wil78]. Um Shadow Maps zu verwenden wird ein Z-Buffer (auch als
depth buffer bezeichnet) benötigt.
Der Z-Buffer wurde ursprünglich entwickelt um einen pixelgenauen Sichtbarkeitstest beim Rendern einer dreidimensionalen Szene durchzuführen. Dazu
verwendet man einen Buffer, der dieselben Dimensionen besitzt wie der Framebuffer. Der Z-Buffer wird zunächst mit dem maximalen Wert initialisiert.
Nach der Projektion der Szene auf die Betrachtungsebene entspricht die ZKoordinate eines Pixels der Entfernung zum Betrachter, beziehungsweise der
Projektionsebene. Die Z-Koordinate wird beim Rendern des Pixels mit dem
korrespondierenden Wert im Z-Buffer verglichen. Ist die Z-Koordinate des
Pixels kleiner als der Eintrag im Z-Buffer, dann bedeutet das, dass dieser
Pixel näher am Betrachter ist. Er wird also gerendert und der Eintrag im
Z-Buffer wird aktualisiert. Ist die Z-Koordinate grösser als der Eintrag im
Z-Buffer, dann bedeutet das, dass es bereits einen Pixel gibt der näher am
Betrachter ist und der aktuelle Pixel wird nicht gerendert. Nachdem die gesamte Szene gerendert wurde, enthält der Z-Buffer die Abstände sämtlicher
gerenderter Pixel zum Betrachter.
Der Ansatz der Shadow Map ist dabei folgender: Die Szene wird zunächst
aus Sicht der Lichtquelle gerendert. Alle Pixel die dabei gerendert werden
sind beleuchtet. Der Z-Buffer enthält nun die Entfernungen sämtlicher der
Lichtquelle nächsten Pixel. Der gesamte Inhalt des Z-Buffers bildet die Shadow Map. Diese Informationen werden gespeichert und die Szene wird ein
weiteres Mal, diesmal aus Sicht des Betrachters, gerendert. Dabei wird die ZKoordinate jedes zu rendernden Pixels mit dem entsprechenden Eintrag aus
der Shadow Map verglichen. Ist der Wert in der Shadow Map gleich, handelt
es sich um einen beleuchteten Pixel. Ist er kleiner, wird der Pixel von einem
anderen verdeckt und ist nicht beleuchtet. Abbildung 3.6 zeigt das Vorgehen
schematisch und die dabei erzeugten Resultate.
Da die Pixel der Shadow Map im Projektionsraum der Lichtquelle liegen und
die tatsächlich zu Rendernden im Projektionsraum des Betrachters, müssen
die Koordinaten vor dem Vergleich noch transformiert werden: Abbildung
3.7 schematisiert die beteiligten Transformationen beim Shadowmapping.
Der grüne Pfeil symbolisiert die Transformation der Koordinaten im Kameraraum zu dem der Lichtquelle, um den Vergleich mit der Shadow Map
durchzuführen.
In OpenGL besteht die Möglichkeit Texturkoordinaten automatisch zu ge-
KAPITEL 3. TECHNIKEN
41
Lichtquelle
Shadow Map
Betrachter
Lichtquelle
Shadow Map
a
b
va
a)
b)
c)
d)
vb
Abbildung 3.6: Bild a) zeigt wie die Shadow Map aus Sicht der Lichtquelle erzeugt wird.
Bild c) zeigt die Shadow Map, hellere Pixel sind weiter von der Lichtquelle entfernt. Bild
b) zeigt den Vergleich der Z-Werte. Das Objekt wird vom Betrachter in Punkt va getroffen.
Der korrespondierende Eintrag in der Shadow Map, an Stelle a, entspricht dem Abstand
des Punktes va . Der Punkt ist somit beleuchtet. Der Eintrag an Stelle b in der Shadow
Map, für den korrespondierenden Punkt vb ist wesentlich kleiner als die Distanz zwischen
vb und Lichtquelle, da dieser bereits von einem anderen Pixel verdeckt ist. Der Pixel
ist somit verdeckt und liegt im Schatten. Bild d) zeigt das fertig gerenderte Objekt mit
Schatten.
nerieren. Dazu wird eine Matrix benötigt die die entsprechenden Parameter
zur Texturkoordinatengenerierung enthält. Diese Matrix setzt sich wie folgt
zusammen:
T = Pl · Vl · Vv−1
(3.6)
Die Texturmatrix T ist das Produkt aus der Projektionsmatrix der Lichtquelle Pl , der Viewmatrix der Lichtquelle Vl und der inversen Viewmatrix
des Betrachters Vv−1 . Man kann die Transformation anhand von Abbildung
3.7 nachvollziehen.
Es gibt aber noch einen einfacheren Weg um die Transformation durchzuführen. Da die Transformationen innerhalb der Grafikpipeline in der Geometrieeinheit durchgeführt werden, bieten sich Vertexshader an, um die Transformation explizit durchzuführen. Der Vertexshader erhält die Vertexkoordi-
KAPITEL 3. TECHNIKEN
42
Modelview Matrix
des Betrachters
Modelview Matrix
der Lichtquelle
Objektkoordinaten
Model Matrix
Weltkoordinaten
View Matrix
des Betrachters
Koordinatenraum des
Betrachters
Projection Matrix
des Betrachters
Sichtfeld des
Betrachters
View Matrix
der Lichtquelle
Koordinatenraum der
Lichtquelle
Projection Matrix
der Lichtquelle
Sichtfeld der
Lichtquelle
Abbildung 3.7: Schematische Darstellung der benötigten Transformationen beim Shadowmapping mittels OpenGL. Der grüne Pfeil symbolisiert die Transformation, die durch die
Texturmatrix durchgeführt wird.
naten in Form von Objektkoordinaten. Diese müssen im Vertexshader mit
der kombinierten Modelview-Projektionsmatrix des Betrachters multipliziert
werden um Koordinaten im Projektionsraum des Betrachters zu erhalten.
Wie aus Abbildung 3.7 ersichtlich, kann der Vertexshader die Objektkoordinaten aber zusätzlich auch mit der kombinierten Modelview-Projektionsmatrix
der Lichtquelle multiplizieren. Auf diese Weise erhält man direkt die korrespondierenden Koordinaten im Projektionsraum der Lichtquelle. Diese kann
der Vertexshader als Texturkoordinaten in entsprechende Register schreiben
und so einem Fragmentshader zugänglich machen, der dann das entsprechende Fragment mit dem korrespondieren Eintrag aus der Shadow Map
vergleicht. Die Shadow Map wird dem Fragmentshader in Form einer Textur
übergeben.
Beim Shadowmapping handelt es sich um ein bildbasiertes Verfahren, das
heisst es werden keine Geometriedaten der Szene für den Schattentest benötigt,
sondern lediglich die Tiefeninformationen der Szene aus Sicht der Lichtquelle. Dadurch ergeben sich einige entscheidende Vorteile gegenüber anderen
Verfahren:
• Es können beliebig große Szenen verarbeitet werden.
• Der Algorithmus benötigt keinerlei Geometrieinformationen der Szene.
KAPITEL 3. TECHNIKEN
43
Er kann praktisch alle Szenen verarbeiten die sich rendern lassen.
• Der Algorithmus hat nur ein lineares Kostenwachstum bzgl. der Komplexität der Szene [Wil78].
• Die Entscheidung ob ein Pixel beleuchtet ist oder nicht benötigt nur
einen Lookup in der Shadow Map, ist somit konstant.
Aufgrund der oben genannten Vorteile bietet sich der Algorithmus für die
texturbasierte Volumenvisualisierung an. Er benötigt keine Geometrieinformationen, was eine Voraussetzung ist, da diese für das Volumen nicht vorhanden sind, und er ist vergleichsweise schnell. Allerdings wird für die Erstellung
der Shadow Map ein zusätzlicher Renderzyklus benötigt.
Die Tatsache, dass es sich um ein bildbasiertes Verfahren handelt, bringt
aber auch zwei Nachteile mit:
• Die Qualität der Schatten hängt direkt, sowohl von der Auflösung als
auch der Präzision des Z-Buffers ab (Abbildung 3.8a).
• Die Shadow Map wird beim Vergleich gesampelt. Dadurch ergeben sich
Alias-Effekte, besonders in Nähe der Schattenränder. Auch fangen Objekte aufgrund dieser Ungenauigkeit an, sich selbst zu schattieren (Abbildung 3.8b).
a)
b)
Abbildung 3.8: Bild a) verwendet eine sehr niedrig aufgelöste Shadow Map. Der Alias-Effekt
ist deutlich zu erkennen. Bild b) zeigt die fehlerhafte Selbstschattierung des Objekts.
KAPITEL 3. TECHNIKEN
44
Um obige Probleme zu reduzieren (ganz vermeiden lassen sie sich nicht), wird
ein Bias-Faktor, also ein kleiner Fehler mit eingeführt. Anstatt zu überprüfen
ob zwei Z-Werte gleich sind, was bei float-Werten generell schlecht ist, wird
überprüft ob die Differenz beider Werte kleiner als eine bestimmte Schranke
(der Bias-Faktor) ist. Es ist nur schwer möglich eine allgemeine Aussage zu
treffen, wie groß dieser Faktor genau sein soll. Allgemein gilt: Je höher die
Genauigkeit des Z-Buffers, desto niedriger kann der Bias ausfallen.
Die Genauigkeit des Z-Buffers spielt eine wichtige Rolle. Die Werte im ZBuffer werden immer zwischen 0 und 1 skaliert. In OpenGL muss ein sogenanntes viewing volume definiert werden. Alles was sich innerhalb diese
Volumens befindet kann der Betrachter sehen und wird gerendert. Zwei Begrenzungsebenen des viewing volume spielen dabei eine besondere Rolle, die
near clipping plane und die far clipping plane. Sie begrenzen das viewing
volume aus Sicht des Betrachters nach vorne und hinten. Der Abstand dieser
Ebenen zueinander entspricht dem Wertebereich den der Z-Buffer abdecken
muss. Man kann die effektive Genauigkeit des Z-Buffers erhöhen, indem den
Abstand zwischen near- und far clipping plane möglichst klein macht. Beim
Erzeugen der Shadow Map sollte die near plane immer möglichst weit von der
Lichtquelle entfernt und die far plane möglichst nahe zur Lichtquelle plaziert
werden.
3.4
Isoflächen Rendering
Eine Isofläche ist definiert als eine Menge von Punkten innerhalb eines Volumens, welche einen gemeinsamen Wert haben. Volumendaten, wie sie ein
Computertomograph liefert, repräsentieren Dichtewerte. Sämtliche Punkte
die denselben Dichtewert besitzen entsprechen somit einer Isofläche.
Isoflächen werden häufig durch Polygone angenähert. In einem Vorverarbeitungsschritt wird dabei die Isofläche aus dem Volumendatensatz extrahiert.
Bei dieser Vorgehensweise spricht man von indirektem Isoflächenrendering,
weil die Isofläche nicht direkt visualisiert wird, sondern zunächst in eine andere Form, ein Polygon, überführt wird. Ein bekanntes indirektes Verfahren
um Isoflächen zu erzeugen ist das Marching-Cubes-Verfahren[LC87]. Dabei
wird das zuvor binarisierte Volumen mit einem kubusförmigen Element abgetastet. Schneidet eine Isofläche den Kubus, wird diese durch ein Polygon
angenähert. Nachdem das gesamte Volumen abgetastet wurde erhält man so
die, durch Polygone angenäherte, Isofläche.
Isoflächen können aber auch direkt, also ohne vorherige Bestimmung von
Geometrieinformationen, visualisiert werden. Um eine Isofläche unter OpenGL
zu rendern, kann der Alpha-Test benutzt werden. Bevor ein Pixel endgültig in
KAPITEL 3. TECHNIKEN
45
den Framebuffer geschrieben wird, kann der Alphawert des Pixels mit Hilfe einer Vergleichsfunktion auf einen Referenzwert überprüft werden. Verläuft der
Test erfolgreich wird der Pixel gerendert, andernfalls wird er verworfen. Die
Vergleichsfunktion und der Referenzwert lassen sich mit Hilfe der OpenGL
Funktion glAlphaFunc() setzen. Folgende Vergleichsfunktionen stehen dabei zur Verfügung:
OpenGL Konstante
GL NEVER
GL LESS
GL EQUAL
GL LEQUAL
GL GREATER
GL NOTEQUAL
GL GEQUAL
GL ALWAYS
Bedeutung
Jeder Pixel wird verworfen.
<
=
≤
>
6=
≥
Jeder Pixel wird gezeichnet.
Um eine bestimmte Isofläche zu rendern kann die Vergleichsfunktion GL EQUAL
benutzt werden, so das nur Pixel in den Framebuffer geschrieben werden, deren Alphawert gleich dem Referenzwert ist.
Häufig sind die Ergebnisse mittels GL EQUAL unbefriedigend, da real gemessene Volumendaten häufig nicht völlig homogen sind und somit Dichteschwankungen enthalten. Die so gerenderte Isofläche enthält häufig Artefakte oder
ist nicht zusammenhängend. Eine bessere Möglichkeit zur direkten Darstelleung von Isoflächen besteht darin, ein Intervall von Isowerten anzugeben,
welche gerendert werden sollen. Diese Funktionalität wird nicht direkt von
OpenGL unterstützt, kann aber mit mehreren Renderzyklen, unter Benutzung des Stencil-Buffers, erreicht werden [Bau00].
Der Einsatz eines Fragmentshaders erlaubt ebenfalls eine effiziente Möglichkeit Isoflächen zu rendern. Dabei ist es sogar möglich, beliebig viele Isoflächen
in nur einem einzigen Renderzyklus zu visualisieren.
Der Fragmentshader erhält dabei ein Array von Intervallen, welche die Isowerte enthalten, die gerendert werden sollen. Ein Intervall wird dabei durch
einen float2-Datentyp repräsentiert, also einem zweidimensionalen Vektor.
Die x-Komponente des Vektors enthält die untere Grenze und die y-Komponente
die obere Grenze des Intervalls. Der Rückgabewert des Shaders, also das
RGBA-Tupel des Fragments, wird mit einem Alphawert 0.0 initialisiert. Mit
einer Schleife kann dann das Array mit den Intervallen iteriert werden. Bei
KAPITEL 3. TECHNIKEN
46
jedem Iterationsschritt wird geprüft, ob der Grauwert des aktuellen Fragments innerhalb des Intervalls liegt. Ist das der Fall, wird dem Fragment das
RGBA-Tupel der Transferfunktion zugewiesen.
Sollte das aktuelle Fragment nicht in einem der Intervalle liegen, so behält
es seinen initialen Wert 0.0. Es wird also völlig transparent gerendert. Der
Fragmentshader sorgt also dafür, dass sämtliche Fragmente die nicht gerendert werden sollen, den Alphawert 0.0 erhalten. Fragmente die gerendert
werden sollen, erhalten automatisch den entsprechenden Farbwert aus der
Transferfunktion.
Mit Hilfe des shaderbasierten Isoflächenrenderers lassen sich halbtransparente Darstellungen leicht realisieren. Zur Veranschaulichung ist in Abbildung
3.9 ein Motorblock halbtransparent dargestellt. Die inneren Strukturen (rot
eingefärbt) bestehen aus einem anderen Material als der umgebende Motorblock und besitzen eine andere Dichte. Sie wurden mit einer hohen Opazität
gerendert. Dem umgebenden Motorblock wurde nur eine geringe Opazizät
zugewiesen, so das er halbtransparent wirkt.
Abbildung 3.9: Semitransparente Darstellung eines Motorblocks
KAPITEL 3. TECHNIKEN
3.5
47
Progressives Rendering
Die benötigte Rechenzeit für die Visualisierung eines Datensatzes hängt direkt mit dessen Größe zusammen. Je größer der Datensatz desto mehr Rechenzeit wird benötigt. Effekte wie Beleuchtung wirken sich dabei zusätzlich
negativ auf die Performance aus. Sinkt die Framerate dabei auf nur sehr
wenige Frames pro Sekunde, leidet die Interaktivität spürbar. Das System
reagiert nur noch sehr träge auf Benutzereingaben.
Progressives Rendering hilft, die Interaktivität auf Kosten der Bildqualität
zu erhöhen. Dazu wird die darzustellende Datenmenge bei der Interaktion
reduziert, was die Performance erhöht. Wie die Datenmenge genau reduziert
wird hängt stark von der Visualisierungsmethode ab.
Beim Raycasting besteht die Möglichkeit, die Anzahl der ausgesandten Strahlen zu reduzieren, indem man ein groberes Bildraster wählt. Der Rendervorgang wird dadurch beschleunigt, das Bild ist durch das grobere Bildraster
entsprechend grober aufgelöst. Ist die Interaktion beendet, wird das Bild nach
und nach verfeinert (progressive refinement).
Die Performance der Visualisierung beim texturbasierten Volumenrendering
hängt insbesondere von zwei Faktoren ab:
• Anzahl der Texturscheiben
Je höher die Anzahl der verwendeten Texturscheiben bei der Visualisierung ist, desto besser ist auch die resultierende Bildqualität. Dabei
gilt: Je grösser der zugrunde liegende Datensatz ist, desto mehr Texturscheiben sollten verwendet werden. Um die Performance zu erhöhen,
wäre es möglich, die Anzahl der Texturscheiben beim Rendern während
einer Interaktion zu erniedrigen.
• Anzahl der zu rendernden Fragmente
Insbesondere unter Verwendung von Fragmentshadern beim Rendern,
kann ein grosser Performancegewinn erzielt werden, indem eine Möglichkeit gefunden wird, weniger Fragmente zu rendern. Ziel ist es also, die
Anzahl der Aufrufe eines verwendeten Fragmentshaders zu reduzieren,
da dieser für jedes Fragment einmal ausgeführt werden.
Die Grösse des Datensatzes ist dabei nicht notwendigerweise ein Kriterium für die Anzahl der tatsächlich gerenderten Pixel, da ein Objekt
gezoomt werden kann. Ein kleinerer Datensatz kann dabei durchaus,
bei einem entsprechend hohen Zoomfaktor, mehr Pixel erzeugen als ein
grösserer Datensatz mit entsprechend kleinerem Zoomfaktor.
KAPITEL 3. TECHNIKEN
48
Eine Reduzierung der Anzahl der Texturscheiben während der Interaktion ist
relativ einfach zu implementieren. Dazu wird einfach der Abstand der Scheiben beim Rendern entsprechend vergrößert. Die Anzahl der zu rendernden
Pixel wird dadurch ebenfalls reduziert, da die ausgelassenen Texturscheiben
nicht gerendert werden. Allerdings wirken die Objekte bei einem zu grossen
Abstand zwischen den Texturscheiben nicht mehr plastisch und es enstehen
grobe Artefakte, insbesondere an den Rändern des Objekts.
Eine optisch ansprechendere Methode, sowohl die Anzahl der Texturscheiben als auch die der Pixel senken, bestehet darin, durch Subsampling einen
weiteren Datensatz mit einer entsprechend niedrigeren Auflösung zu generieren. Während einer Interaktion wird dieser kleine Datensatz gerendert.
Das gerenderte Bild wird dann, in Form einer Textur, der Grafikpipeline erneut zugänglich gemacht und, entsprechend hochskaliert, in den Framebuffer
gerendert. Der eigentliche, rechenintensive, Rendervorgang wird anhand des
kleinen Datensatzes durchgeführt. Der kleine Datensatz benötigt einerseits
weniger Texturscheiben zur Darstellung und reduziert andererseits die Anzahl der zu berechnenden Pixel, bei gleichem Zoomfaktor, beträchtlich1 .
Trotz des zusätzlich benötigten Renderzyklus ist diese Variante bedeutend
schneller als das direkte Rendern des hochaufgelösten Datensatzes, da der
zweite Renderzyklus nur die zuvor erzeugte Textur in den Framebuffer rendern muss, was vergleichsweise sehr schnell geschieht.
In Abbildung 3.10 ist dieser Vorgang schematisch dargestellt. Durch das
16
12
Offscreen Buffer
32
24
32
24
Framebuffer
Framebuffer
Abbildung 3.10: Das linke Bild zeigt den Inhalt des Framebuffers nach dem Rendern des
hochaufgelösten Datensatzes. Im rechten Bild wird der Vorgang beim Rendern eines niedriger aufgelösten Datensatzes gezeigt. Der kleinere Datensatz wird zunächst in einen (kleineren) Offscreen-Buffer gerendert. Dieser wird in einem weiteren Renderzyklus als Textur
verwendet und, entsprechend hochskaliert, in den eigentlichen Framebuffer gerendert.
1
Ein Datensatz der Größe 1283 besteht aus 2097152 Werten, ein verkleinerter Datensatz
der Größe 643 besitzt nur noch 81 der Werte, also 262144
KAPITEL 3. TECHNIKEN
49
Hochskalieren der Textur wirkt das Volumen natürlich grobpixelig. Die Erhöhung
der Interaktivität geht immer zu Lasten der Qualität, allerdings bleibt bei
diesem Verfahren die Plastizität des Objekts erhalten.
Üblicherweise werden gleich mehrere solcher kleinen Datensätze, abgestuft
nach Grösse, erzeugt. Beim Rendern des Volumens werden die Datensätze
sukzessiv, beginnend mit dem kleinsten, gerendert. Nach jedem Renderdzyklus erhöht sich dabei die Bildqualität. Bei einer Interaktion des Anwenders
wird die Renderfolge abgebrochen und das Volumen, mit den neuen Parametern, wieder sukzessiv beginnend mit dem kleinsten Datensatz gerendert.
Man spricht dann von progressivem Rendering, da die Bildqualität kontinuierlich, mit jedem Renderzyklus, verbessert wird. Abbildung 3.11 zeigt die
Schrittweise Verbesserung in drei Stufen:
Abbildung 3.11: Drei Stufen des progressiven Renderings. Links: niedrige Auflösung. Mitte:
mittlere Auflösung. Rechts: Höchste Auflösung.
Das nächste Kapitel behandelt die Implementierung der hier vorgestellten
Techniken, sowie die Integration in das Modul Voxel-Sculpture welches bereits eine texturbasierte Volumenvisualisierung bereitstellt.
Kapitel 4
Implementierung
Dieses Kapitel erläutert die Implementierungsdetails der einzelnen Aufgaben.
Sämtliche entwickelten Klassen sind Bestandteil des Moduls Voxel-Sculpture.
Zunächst wird eine kurze Übersicht über Voxel-Sculpture gegeben. In den
weiteren Kapiteln werden die einzelnen Aufgaben näher erläutert und das
Modul schrittweise erweitert.
4.1
MAVI und das Voxel-Sculpture Modul
MAVI [MAV] ist ein 3d-Bildverarbeitungsframework. Es lädt über einen
Plugin-Mechanismus verschiedene Module, die Bildverarbeitungsalgorithmen
oder Visualisierungsverfahren enthalten. Diese können dann einfach in MAVI aufgerufen werden. Für die 3d-Visualisierung existiert das Modul VoxelSculpture (VS). Es ermöglicht die Visualisierung dreidimensionaler Datensätze
mit Hilfe eines texturbasierten Renderingverfahrens. VS wurde komplett in
C++ realisiert. Für die Anzeige wurde OpenGL verwendet und die Benutzerschnittstelle wurde mit Qt [Tro] realisiert.
MAVI, und damit auch VS, ist sowohl unter Linux als auch Windows lauffähig.
Aufgrund einiger verwendeter herstellerspezifischer OpenGL-Erweiterungen
funktionierte VS nur mit Grafikkarten des Herstellers NVidia, durch die Erweiterung des Moduls um shaderbasierte Renderklassen läuft das Modul nun
auch auf shaderfähigen Grafikkarten anderer Hersteller.
50
KAPITEL 4. IMPLEMENTIERUNG
4.2
51
Die Klassenstruktur
Da VS bei dieser Arbeit als Basis verwendet wurde, wird die Struktur kurz
erläutert. Der genaue Aufbau und die Funktionsweise von VS ist in [Hir05]
beschrieben. Einen Überblick über das Modul gewährt Abbildung 4.1. Die
1
VS::VoxelSculpture
QGLWidget
1
1
ITWM::CImage
1 VS::CRenderWidget
1
1
VS::CRenderEngine
VS::C2d3tsRenderEngine
VS::CTexture
VS::C3dtRenderEngine
1
1
n
VS::CTexture2d
VS::CTexture3d 1
ITWM::CBox
1
VS::CMultiTexture2d
n VS::CBrick
1
n
VS::StIntersectionPoint
Abbildung 4.1: Statisches UML-Klassendiagramm der Kernklassen des Moduls VoxelSculpture
oberste Klasse ist CVoxelSculpture. Sie hält eine Referenz auf ein CImageObjekt, welches das darzustellende Bild enthält. Die Klasse CImage ist Bestandteil einer externen Bibliothek und im Namensraum ITWM definiert. Für
die Darstellung erzeugt CVoxelSculpture ein Objekt der Klasse CRenderWidget,
welches von der Klasse QGLWidget abgeleitet ist. QGLWidget ist Bestandteil
der GUI-Bibliothek Qt und Zuständig für die Anzeige der mit OpenGL erzeugten Visualisierung.
Das CRenderWidget erzeugt, abhängig von der zur Verfügung stehenden
Hardware, eine Renderklasse. Die Renderklasse ist dabei von der gemeinsamen Basisklasse CRenderEngine abgeleitet.
KAPITEL 4. IMPLEMENTIERUNG
52
Ursprünglich existierten zwei verschiedene Renderklassen: C2d3tsRenderEngine
und C3dtRenderEngine, jeweils verantwortlich für die Visualisierung mittels zweidimensionaler- beziehungsweise dreidimensionaler Texturen. Dazu
aggregieren sie Texturklassen, welche die einzelnen Texturscheiben (Slices)
kapseln.
Dies ist der grundsätzliche Aufbau von VS. Es existieren noch eine Reihe
weiterer Hilfsklassen wie etwa verschiedene Dialoge, um das Rendering zu
konfigurieren.
4.2.1
Die Shaderklassen
Sämtliche im Rahmen dieser Arbeit entwickelten Shader, sind in der Shadersprache Cg geschrieben. Die Endung solcher Quellcodedateien ist üblicherweise .cg. Um die Shader zu verwenden, müssen folgende Schritte durchgeführt
werden:
1. Erzeugen eines Cg Contexts. Ein Cg Context ist eine Art Container der
mehrere Cg Shader (sowohl Vertex- als auch Fragmentshader) enthalten
kann.
2. Erzeugen eines Cg Programs. Das Cg Programm abstrahiert einen bestimmten Shader. Liegt der Shader als Quellcode vor wird er bei diesem
Schritt automatisch kompiliert. Das Cg Programm wird dabei einem
Cg Context zugeordnet.
3. Laden des Programms. Der Objektcode des Shaders wird so der jeweiligen 3d-API1 zugänglich gemacht.
4. Übergabe von Parametern an den Shader (soweit benötigt).
5. Aktivieren des entsprechenden Profils für das der Shader kompiliert
wurde und binden des Programs. Danach wird der Shader automatisch
für jedes Vertex (Vertexshader) oder Fragment (Fragmentshader) ausgeführt.
Die Cg-Laufzeitbibliothek enthält die nötigen Funktionen um die oben beschriebenen Schritte durchzuführen. Um die Arbeit mit Shadern zu vereinfachen, wurde das Modul VS um einige Hilfsklassen erweitert.
Es wurde eine Klasse CCgShaderManager eingeführt, die eine Reihe von
CCgShader-Objekten verwaltet. Bei der Klasse CCgShader handelt es sich
um eine allgemeine Basisklasse. Es existieren zurzeit zwei weitere Spezialisierungen: CCgVertexShader und CCgFragmentShader.
1
Application Programming Interface
KAPITEL 4. IMPLEMENTIERUNG
53
Der Aufbau der Klassen ist in Abbildung 4.2 dargestellt. Die oberste Klasse
VS::CCgShaderManager
1
n
VS::StShader
1
1
VS::CCgShader
VS::CCgVertexShader
VS::CCgFragmentShader
Abbildung 4.2: Statisches UML-Klassendiagramm der beteiligten Shaderklassen
CCgShaderManager enthält ein Array von StShader-Objekten. Bei StShader
handelt es sich lediglich um eine Struktur die ein CCgShader-Objekt beschreibt. Die Struktur beinhaltet, neben der Referenz auf das CCgShaderObjekt den Typ des Shaders (Vertex- oder Fragmentshader) sowie einen,
vom Aufrufer definierten, Namen in Form eines Strings, anhand dessen ein
Shader identifiziert werden kann.
Der Shadermanager besitzt vier Methoden um Shader hinzuzufügen:
AddVertexShader(), AddVertexShaderFromFile(), AddFragmentShader()
und AddFragmentShaderFromFile(). Die Shader können entweder in Form
einer Datei hinzugefügt werden oder in Form eines Strings, der den Shadercode enthält. Ausserdem kann der Shader bereits vorkompiliert als Objektcode
vorliegen oder als Quellcode. Liegt der Shader als Quellcode vor, wird er zur
Laufzeit kompiliert.
Der Shadermanager besitzt ausserdem je einen Zeiger auf ein CCgVertexShaderObjekt und auf ein CCgFragmentShader-Objekt. Diese beiden Shaderobjekte
sind jeweils der aktuell ausgewählte Vertex- beziehungsweise Fragmentshader. Diese Zeiger können abgefragt werden um Zugriff auf die aktuellen Shader zu erhalten. Diese können dann weiter konfiguriert und aktiviert werden.
KAPITEL 4. IMPLEMENTIERUNG
54
Es ist immer nur möglich einen Vertexshader und/oder einen Fragmentshader gleichzeitig zu benutzen.
Der Shadermanager übernimmt also Schritt 1, er erzeugt intern einen Cg
Context, welcher die weiteren Shader aufnimmt.
Beim Hinzufügen eines Shaders zum Manager wird automatisch dessen Init()Methode aufgerufen. Init() ist eine pur-virtuelle Methode der Basisklasse
CCgShader. Die entsprechende Implementierung in den abgeleiteten Klassen
(CCgVertexShader, CCgFragmentShader) sorgt dafür das der Shader kompiliert und geladen wird. Die benötigten Parameter bekommt das Shaderobjekt
vom Shadermanager. In diesem Schritt wird das Cg Program erzeugt. Anschliessend wird das Programm geladen, also der 3d-API übergeben.
Die Init()-Methode der Shader wurde bewusst mit dem Attribut protected
versehen, um zu verhindern das diese Methode direkt aufgerufen wird. Die
Shaderobjekte sollen nur in Zusammenhang mit dem Shadermanager verwendet werden. Dieser ist als friend-Klasse der Shaderklassen deklariert,
hat somit Zugriff auf deren privaten Methoden. Beim Initialisieren des Shaderobjekts werden Schritt 2 und 3 durchgeführt.
Um die Parameter eines Shaders zu setzen und ihn zu aktivieren, wird eine
Referenz auf das Shaderobjekt benötigt. Mit der Methode SelectShader()
des Shadermanagers wird der aktuelle Vertex- beziehungsweise Fragmentshader gesetzt. GetShader() liefert dann einen Zeiger auf den aktuellen Vertexoder Fragmentshader. Über diesen Zeiger kann direkt auf das Shaderobjekt
zugegriffen werden. Die Basisklasse CCgShader besitzt entsprechende Methoden um die verschiedenen Parametertypen zu setzen. Folgende Datentypen
werden sowohl vom Vertex- als auch vom Fragmentshader unterstützt:
• float, double - Ein einfacher float- beziehungsweise double-Parameter.
• float2, double2 - Ein zweidimensionaler float- beziehungsweise doubleVektor.
• float3, double3 - Ein dreidimensionaler float- beziehungsweise doubleVektor.
• float4, double4 - Ein vierdimensionaler float- beziehungsweise doubleVektor.
• floatAxB, doubleAxB - Eine float- beziehungsweise double-Matrix. A
und B dürfen maximal 4 sein. Ein Sonderfall bilden die sogenannten
KAPITEL 4. IMPLEMENTIERUNG
55
OpenGL State-Matrizen, also etwa die Modelview-Matrix. Dabei handelt es sich immer um float4x4 Matrizen, welche direkt gesetzt werden
können.
Mit Ausnahme der State-Matrizen dürfen alle oben aufgeführten Parametertypen auch als Array verwendet werden.
Die Klasse CCgVertexShader erlaubt es noch sogenannte varying Parameter
zu setzen. Dabei handelt es sich um ein Array von Werten, wobei jeder Wert
an ein bestimmtes Vertex gebunden ist. Der Vertexshader hat dann immer
nur Zugriff auf den entsprechenden Wert der varying Parameter für das aktuelle Vertex.
Die CCgFragmentShader-Klasse besitzt die Möglichkeit Texturparameter zu
setzen, um im Shader Zugriff auf entsprechende Texturen zu haben.
Eine Liste der Namen der verwendeten Parameter kann mit der Methode
GetParameterList() abgefragt werden.
Um den Shader zu aktivieren, wird die Methode Enable() der Shaderklasse
aufgerufen. Diese Methode aktiviert das entsprechende Profil und bindet den
Shader. Der Shader wird für alle folgenden OpenGL Zeichenfunktionen aufgerufen. Um ihn zu deaktivieren steht entsprechend die Methode Disable()
zur Verfügung.
4.2.2
Die Renderklassen
Ursprünglich enthielt VS zwei Implementierungen vom Interface CRenderEngine.
Die Klasse C2d3tsRenderEngine für die Visualisierung mittels 2D-Texturen
und die Klasse C3dtRenderEngine für die entsprechende Visualisierung mittels 3D-Texturen. Aufgrund des gemeinsamen Interfaces bleibt die Schnittstelle beider Renderklassen für andere Klassen transparent. Weitere Renderklassen, die im Laufe dieser Arbeit entstanden sind, implementieren ebenfalls
das Interface der Basisklasse. Insgesamt sind drei weitere Renderklassen hinzugekommen: Die Klasse C3dtCgRenderEngine, C3dtCgLightingRenderEngine
und C3dtCgIsoValueRenderEngine. Wie das Prefix Cg im Klassennamen bereits andeutet basieren alle drei Klassen auf Cg Shadern. Die erweiterte Klassenstruktur von VS zeigt Abbildung 4.3. Die Klasse C3dtCgLightingRenderEngine
implementiert die shaderbasierte Beleuchtung und Schattengenerierung und
die Klasse C3dtCgIsoValueRenderEngine das Isoflächenrendering.
Die Klasse C3dtCgRenderEngine implementiert die Volumenvisualisierung
mittels 3D-Textur. Die Funktionalität ist dabei dieselbe wie bei der bereits
vorhandenen Klasse C3dtRenderEngine. Der einzige Unterschied besteht in
der Anwendung der Transferfunktion. In der Klasse C3dtRenderEngine ge-
1
1
VS::VoxelSculpture
1
VS::CTexture2d
n
1
n VS::CBrick
ITWM::CBox
VS::StIntersectionPoint
1
1
VS::CCgVertexShader
VS::CCgFragmentShader
VS::CCgShader
1
1
VS::StShader
n
1
VS::CCgShaderManager
VS::C3dtCgLightingRenderEngine
VS::C3dtCgRenderEngine
VS::C3dtCgIsoValueRenderEngine
VS::C3dtRenderEngine
VS::CTexture3d 1
VS::CTexture
VS::CRenderEngine
1
1
1 VS::CRenderWidget
QGLWidget
VS::CMultiTexture2d
n
VS::C2d3tsRenderEngine
ITWM::CImage
1
KAPITEL 4. IMPLEMENTIERUNG
56
Abbildung 4.3: Statisches UML-Klassendiagramm der neuen Renderklassen. Die alten
Klassen grau dargestellt.
KAPITEL 4. IMPLEMENTIERUNG
57
schah dies über die OpenGL Erweiterung, GL EXT paletted texture. Dabei
wurden die Grauwerte der 3D-Textur des Volumens als Indizes für eine Farbtabelle, der Transferfunktion, interpretiert und entsprechend ersetzt. Aktuelle
Grafikkarten unterstützen diese OpenGL Erweiterung nicht mehr. Deshalb
benutzt die Renderklasse C3dtCgRenderEngine einen Fragmentshader um
die Transferfunktion anzuwenden. Die Transferfunktion wird dem Shader in
Form einer eindimensionalen Textur zugänglich gemacht. Der Shader besorgt
sich aus der 3D-Textur zunächst den Grauwert des Volumens für das aktuelle
Fragment und benutzt diesen als Index für einen weiteren Texturzugriff auf
die Transferfunktion. Der resultierende RGBA-Wert wird dem Fragment zugewiesen. Auf die Implementierung dieser Klasse wird hier nicht genauer eingegangen, da der einzige Unterschied zur C3dtRenderEngine in der Benutzung
eines Shaders besteht. Diese werden ausreichend anhand der Beleuchtungsund Isoflächenrenderklasse erläutert.
Ein wichtiger Unterschied zwischen beiden Renderklassen besteht dennoch:
Die palettenbasierte Renderklasse wendet die Transferfunktion vor der Filterung der Textur an (pre-classification), die shaderbasierte Renderklasse
hingegen erst danach (post-classification). Beide Verfahren führen zu unterschiedlichen Ergebnissen. Im allgemeinen liefert die post-classification bessere, kontrastreichere Ergebnisse. Die post-classification ist auch das kor”
rektere“ Verfahren, weil zunächst das Voxel des Volumens mittels trilinearer
Filterung rekonstruiert wird und dann erst die Transferfunktion auf das rekonstruierte Voxel angewendet wird. Bei der pre-classification dagegen wird
nicht der ursprüngliche Voxel, also das ursprüngliche Signal, rekonstruiert,
sondern ein Farbwert der benachbarten Voxel, auf die bereits die Transferfunktion angewendet wurde. Abbildung 4.4 verdeutlicht wie die unterschiedlichen Resultate zustande kommen. Die beiden Funktionen stellen die Transferfunktion dar. Der zu rekonstruierende Voxel liegt genau mittig zwischen
den beiden diskreten Abtastwerten v0 und v1 . Bei der pre-classification werden die beiden Abtastwerte zunächst durch den korrespondierenden Wert der
Transferfunktion, t0 , beziehungsweise t1 ersetzt. Anschliessend wird zwischen
diesen Werten linear interpoliert.
Bei der post-classification werden die Werte v0 und v1 direkt linear interpoliert. Daraus resultiert der Wert vi . Auf diesen rekonstruierten Wert des
Signals wird die Transferfunktion angewendet. Das Voxel erhält somit den
Wert ti .
Die Transferfunktionen sind zwischen beiden Verfahren somit nicht austauschbar. Die pre-classification produziert durch die Interpolation der Farbwerte
immer weiche Übergänge. Auch lassen sich sehr hohe Frequenzen mit der
pre-classification nicht darstellen.
KAPITEL 4. IMPLEMENTIERUNG
58
Post-Classification
Pre-Classification
Emission/Absorption
interpolation
Emission/Absorption
ti
t1
t0
interpolation
v0
v1
Grauwert
v0 vi v1
Grauwert
Abbildung 4.4: Im linken Bild wird die Transferfunktion zunächst auf die Voxelwerte angewendet und dann interpoliert (pre-classification). Im rechten Bild wird zunächst der
Voxelwert interpoliert und dieser dann als Index für die Transferfunktion benutzt (postclassification)
Prinzipbedingt zeigen sich bei der post-classification allerdings Artefkate an
Objektübergängen bei Binär- und Labelbildern, insbesondere wenn eine hohe Opazität eingestellt ist (Abbildung 4.5). Um die Effekte aus Abbildung
4.5 zu erklären, stelle man sich zunächst ein gespreiztes Binärbild vor (obere Reihe), das heisst Vordergrundpixel haben einen Grauwert von 255 und
Hintergrundpixel entsprechend 0. Ein mittels linearer Interpolation rekonstruierter Wert an einer Objektgrenze besitzt demnach einen entsprechend
gewichteten Grauwert, etwa 100.
Aufgrund der eingestellten Transferfunktion bekommen Pixel mit einem Grauwert von 255 einen RGBA-Wert von (255, 255, 255, 255), also weiß mit
maximaler Opazität. Sämtliche Objekte (oder Vordergrundpixel) werden also in weiß gerendert, erkennbar an den weißen Flächen an den Seiten des
Volumens. Ein Pixel an der Objektgrenze, mit dem beispielhaften Grauwert
100, erhält aufgrund der linearen Transferfunktion einen RGB-Wert von (100,
100, 100) mit einer ebenfalls sehr hohen Opazität. Der Randpixel ist also
deutlich dunkler als das Objekt selbst. Dadurch entstehen dunkle Artefakte
an den Objektgrenzen.
Schlimmer äussert sich das Problem bei Labelbildern, bei denen den einzelnen Labeln eine eigene Farbe zugewiesen wird. Labelbilder sind ähnlich den
Binärbildern, nur das durch die Segmentierung mehr als zwei Repräsentativwerte verwendet werden. Objekte können beispielsweise in Größenklassen unterteilt werden, wobei sämtliche Objekte einer Größenklasse denselben Grauwert (Label) erhalten. Die Transferfunktion in Abbildung 4.5, untere Reihe,
färbt Objekte mit verschiedenen Labeln unterschiedlich ein. Betrachtet man
KAPITEL 4. IMPLEMENTIERUNG
59
Abbildung 4.5: Die linke Spalte zeigt ein Binär- und ein Labelbild mittels pre-classification
visualisiert. Die mittlere Spalte zeigt dieselben Bilder mittels post-classification. Die Artefakte an den Objekträndern sind deutlich zu erkennen. Die rechte Spalte zeigt die verwendete Transferfunktion für die jeweiligen Bilder. Die gelbe Kurve bezeichnet die Opazität,
Die rote, grüne und blaue Kurve jeweils die entsprechende Farbe.
zunächst ein grün eingefärbtes Objekt (im Histogramm durch einen türkisfarbenen Balken markiert) so hat dieses ungefähr einen Grauwert (Label)
von 70. An der Objektgrenze herrschen wieder hohe Frequenzen (Grauwert
0). Bei der linearen Filterung könnte dann ein Grauwert mit dem Betrag 35
entstehen (im Histogramm durch einen magentafarbenen Balken markiert).
Dem Grauwert 35 wird aber über die Transferfunktion ein sattes Rot zugewiesen, da dieses Label einem anderen Objekt entspricht. Dadurch erscheinen
rote Artefakte an den Objektgrenzen eigentlich grüner Objekte. Andersfarbige Objekte vermischen die Farben entsprechend anders.
Bei der pre-classification entstehen diese Artefakte nicht, da die Transferfunktion auf die ungefilterten Pixel angewendet wird. Der Farbe des gefilterten
Pixel ist also eine lineare Gewichtung der Farbwerte der benachbarten Pixel.
Dadurch können keine Sprünge“ entstehen wie bei der post-classification.
”
Dieses Problem der post-classification ist nur sehr schwierig zu lösen. Eine
Möglichkeit wäre, pro zu rendernder Texturscheibe für jedes Label einen eigenen Renderzyklus zu benutzen, bei dem die Transferfunktion sämtlichen
Grauwerten ausser dem gerade zu renderndem Label volle Tranzparenz zuweist [Chr01]. Ein solches Verfahren ließe sich aber kaum mit interaktiven
Frameraten realisieren.
KAPITEL 4. IMPLEMENTIERUNG
60
Die Beleuchtungsklasse
Für die Beleuchtung wurde eine neue Renderklasse
C3dtCgLightingRenderEngine von der Klasse C3dtCgRenderEngine abgeleitet. Die C3dtCgLightingRenderEngine erzeugt zunächst eine Shadowmap
aus Sicht der Lichtquelle (siehe Kapitel 3.3.3). Dazu wird die Szene normal
gerendert. Der Framebuffer wird dabei allerdings aus Performancegründen
mittels glColorMask( GL FALSE, GL FALSE, GL FALSE, GL FALSE ) deaktiviert, da für die Shadowmap nur der Inhalt des Z-Buffers von Interesse ist.
Anschliessend wird der Inhalt des Z-Buffers ausgelesen und in Form einer
2D-Textur gespeichert. Im folgenden Rendervorgang, diesmal mit aktiviertem Framebuffer, wird die Shadowmap einem Fragmentshader zugänglich
gemacht, damit dieser entscheiden kann ob ein Pixel beleuchtet ist. Ist das
der Fall, wird die Normale des Pixels anhand der zentralen Differenzen (siehe
Kapitel 3.2.2) bestimmt und anschliessend das Beleuchtungsmodell angewendet. Liegt der Pixel im Schatten, wird stattdessen einfach der Farbwert der
Transferfunktion für diesen Pixels benutzt.
Aufgrund der Verwendung von einfachen Shadowmaps können keine omnidirektionalen Lichtquellen verwendet werden. Stattdessen muss ein Spotlight
verwendet werden. Das hat zur Folge das die Lichtquelle nicht innerhalb des
Volumens platziert werden kann. Da ein Spotlight mit konstantem Öffnungswinkel verwendet wird, spielt die Entfernung zum Objekt ebenfalls eine entscheidende Rolle um den zur Verfügung stehenden Platz der Shadowmap optimal auszunutzen wie Abbildung 4.6 verdeutlicht. Damit der Anwender sich
Far clipping plane
Far clipping plane
Objekt
Far clipping plane
Objekt
Near clipping plane
Objekt
Near clipping plane
Near clipping plane
Lichtquelle
Lichtquelle
a)
b)
c)
Lichtquelle
Abbildung 4.6: In Bild a) ist die Lichtquelle zu nah am Objekt platziert. Das Objekt kann
durch den festen Öffnungswinkel nur teilweise erfasst werden. In b) ist der Abstand der
Lichtquelle zum Objekt für den gegebenen Öffnungswinkel und der gegebenen Objektgrösse optimal. In c) ist die Lichtquelle zu weit vom Objekt entfernt. Das Objekt wird
komplett erfasst, allerdings wird die Shadowmap nicht optimal ausgenutzt.
KAPITEL 4. IMPLEMENTIERUNG
61
nicht um diese Details bei der Positionierung der Lichtquelle kümmern muss,
wurde das Interface so gewählt, dass der Anwender vielmehr nur die Richtung bestimmt aus der das Licht kommt. Die Renderklasse berechnet dann
anhand der Grösse des Volumens und des vorgegebenen Öffnungswinkels der
Lichtquelle automatisch die optimale Entfernung für die gegebene Lichtquelle. Die near- und far-clipping planes werden ebenfalls jeweils möglichst dicht
an das Objekt gelegt, um eine möglichst hohe relative Genauigkeit des ZBuffers zu erreichen (siehe auch Kapitel 3.3.3).
Bevor das Volumen gerendert wird, wird unterschieden ob die Beleuchtung
aktiviert ist, Erst dann wird eine Shadowmap generiert, andernfalls wird das
Objekt ohne Beleuchtung gerendert (siehe Quelltext 4.1).
Quelltext 4.1 Bestimmung ob mit Beleuchtung gerendert wird.
1
2
3
4
5
6
7
8
9
if( m_bLightingEnabled )
{
GenerateShadowMap();
DrawShadedVolume();
}
else
{
DrawVolume();
}
Die Methode GenerateShadowMap() erzeugt die Shadowmap für den aktuellen Frame (siehe Quelltext 4.2). Zunächst wird der Viewport auf die Grösse
der Shadowmap gesetzt und der Z-Buffer gelöscht. Nachdem der Framebuffer
deaktiviert wurde und der Betrachter auf die Position der Lichtquelle gesetzt
wurde, wird ein Fragmentshader initialisiert. Der Fragmentshader hat lediglich die Aufgabe, die Transferfunktion anzuwenden. Das Volumen wird zwar
nicht in den Framebuffer gerendert, aber der Z-Buffer enthält auf diese Weise nach dem Rendervorgang die korrekten Werte. Der Inhalt des Z-Buffers
wird nun, mit Hilfe der OpenGL-Funktion glCopyTexSubImage2D() in den
Speicherbereich einer Textur kopiert. Diese Textur, also die Shadowmap,
wird im folgenden Renderzyklus dazu benutzt, zu bestimmen, ob ein Pixel
beleuchtet ist.
Der eigentliche Rendervorgang mit Beleuchtung findet in der Methode
DrawShadedVolume() statt. Die Methode benutzt einen Vertex- und einen
Fragmentshader zum Rendern des Volumens. Der Vertexshader berechnet
KAPITEL 4. IMPLEMENTIERUNG
62
Quelltext 4.2 Erzeugen der Shadowmap
1
2
3
4
5
void C3dtCgLightingRenderEngine::GenerateShadowMap()
{
glViewport( 0, 0, m_aShadowMapWidth, m_aShadowMapHeight );
// Clear depth buffer
glClear( GL_DEPTH_BUFFER_BIT );
6
...
7
8
// Disable writing to framebuffer since we only need the depth buffer values
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
9
10
11
// Set lightview matrices
glMatrixMode( GL_PROJECTION );
glPushMatrix();
glLoadMatrixf( m_aLightProjMat );
glMatrixMode( GL_MODELVIEW );
glPushMatrix();
glLoadMatrixf( m_aLightViewMat );
12
13
14
15
16
17
18
19
...
20
21
// Setup Shader
m_cShaderManager.SelectShader( "FS_GenerateShadowMap" );
CCgFragmentShader* pFS =
(CCgFragmentShader*)m_cShaderManager.GetShader( SHADER_FRAGMENT );
pFS->SetTextureParameter( "transferFunc", m_ColorTableTexture );
pFS->EnableTextureParameter( "transferFunc" );
pFS->Enable();
22
23
24
25
26
27
28
29
...
30
31
// Render the volume
m_pTexture->Draw( aLightViewVec );
32
33
34
// Disable Shader
pFS->DisableTextureParameter( "transferFunc" );
pFS->Disable();
35
36
37
38
// Restore matrices
glPopMatrix();
glMatrixMode( GL_PROJECTION );
glPopMatrix();
glMatrixMode( GL_MODELVIEW );
39
40
41
42
43
44
// Copy Z-Buffer to texture (shadowmap)
glBindTexture( GL_TEXTURE_2D, m_ShadowMap );
glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0,
m_aShadowMapWidth, m_aShadowMapHeight );
45
46
47
48
49
// Enable framebuffer again
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
50
51
52
}
KAPITEL 4. IMPLEMENTIERUNG
63
die Texturkoordinaten im Lichtraum, welche für den Lookup der Shadowmap im Fragmentshader benötigt werden (siehe Quelltext 4.3). Der Vertexshader benötigt für seine Berechnungen die zusammengefasste ModelviewProjektionsmatrix des Betrachters, sowie der Lichtquelle.
Die Vertexkoordinaten werden zunächst mit der Modelview-Projektionsmatrix
des Betrachters mutlipliziert um Clip-Koordinaten im Raum des Betrachters
zu erhalten (vergleiche Abbildung 3.7). Clip-Koordinaten sind normiert auf
den Sichtraum des Betrachters. Diese Transformation wird generell für alle
Vertices durchgeführt.
Da die Shadowmap aus Sicht der Lichtquelle erstellt wurde, werden die Vertexkoordinaten ebenfalls mit der Modelview-Projektionsmatrix der Lichtquelle multipliziert um die Clip-Koordinaten des Vertex im Lichtraum zu
erhalten. Die Z-Werte der Clip-Koordinaten liegen im Intervall [-1,1]. Da
Texturkoordinaten üblicherweise im Intervall [0,1] liegen müssen die ClipKoordinaten noch transformiert werden. Anschliessend werden sie als Texturkoordinaten weitergereicht.
Quelltext 4.3 Vertexshader für das Shadowmapping
1
2
3
4
5
6
7
vertout VS_Lighting( appin IN,
in uniform float4x4 glLightViewProjMat,
in uniform float4x4 glModelViewProjMat )
{
vertout OUT;
// Pass the texture coordinate for the volume
OUT.Tex0 = IN.Tex0;
8
float4 tmp;
float4 pos;
pos.xyz = IN.VPos.xyz;
pos.w = 1.0;
9
10
11
12
13
// Calculate homogeneous coordinates
OUT.HPos = mul( glModelViewProjMat, IN.VPos );
14
15
16
// Transform current coordinates to light space
tmp = mul( glLightViewProjMat, pos );
// Map coordinates from [-1..1] to [0..1]
OUT.Tex1.xyz = 0.5 * tmp.xyz / tmp.w + float3( 0.5, 0.5, 0.5 );
17
18
19
20
21
return OUT;
22
23
}
An dieser Stelle knüpft der Fragmentshader an. Er vergleicht zunächst den
KAPITEL 4. IMPLEMENTIERUNG
64
Z-Wert des aktuellen Fragments (im Lichtraum) mit dem korrespondierenden
aus der Shadowmap. Sind die Werte (im Rahmen der Rechenungenauigkeit)
gleich, ist das Fragment beleuchtet, andernfalls nicht (siehe Quelltext 4.4).
Liegt das Fragment im Schatten, wird einfach der Grauwert des Fragments
aus der 3D-Textur gelesen und als Index für die Transferfunktion benutzt.
Der so erhaltene RGBA-Wert wird noch mit der ambienten Intensität multipliziert. Dadurch ist es möglich die ambiente Komponente abzuschwächen um
intensivere Schatten zu gewinnen.
Quelltext 4.4 Bestimmung ob das Fragment beleuchtet ist
1
2
// Shadowmap lookup
float4 depth = tex2D( shadowMap, IN.Tex1.xy );
3
4
5
6
7
8
9
10
11
12
// Compare depth values
if( ( IN.Tex1.z - depth.z ) <= fShadowMapBias )
{
// Fragment is illuminated
}
else
{
// Fragment is in shadow
}
Ist das Fragment beleuchtet, wird das Reflexionsmodell angewendet. Dazu
wird zunächst die Normale des Fragments mittels zentraler Differenzen bestimmt (vergleiche Kapitel 3.2.2 und Quelltext 4.5).
Dazu werden die benachbarten Texel des aktuellen Fragments jeweils in x,
y und z-Richtung ausgelesen und daraus die entsprechenden Komponenten
der Normalen bestimmt. Nun kann die Beleuchtungsgleichung angewendet
werden. Die zusätzlich benötigten Parameter wie Richtung der Lichtquelle
und des Betrachters werden dem Shader als Parameter übergeben. Die Beleuchtungsberechnung des Fragments zeigt Quellcode 4.6.
Die Shadersprache Cg stellt bereits eine Funktion lit() bereit, welche die Beleuchtungskoeffizienten berechnet. Die lit()-Funktion basiert auf dem Beleuchtungsmodell von Blinn. Das Blinn Beleuchtungsmodell ist dem Phong
Modell sehr ähnlich, verwendet aber zur Berechnung des Glanzlichts nicht
den Reflexionsvektor ~r, sondern einen normierten Halbvektor ~h. Dieser ist
KAPITEL 4. IMPLEMENTIERUNG
65
Quelltext 4.5 Bestimmung der Normalen im Fragmentshader
1
2
// The normal vector
float3 N;
3
4
5
6
7
8
9
10
11
12
13
// Central difference
N.x = 0.5f *
(tex3D(texture3d,
tex3D(texture3d,
N.y = 0.5f *
(tex3D(texture3d,
tex3D(texture3d,
N.z = 0.5f *
(tex3D(texture3d,
tex3D(texture3d,
float3(IN.Tex0.x+offs.dx, IN.Tex0.y, IN.Tex0.z)).x float3(IN.Tex0.x-offs.dx, IN.Tex0.y, IN.Tex0.z)).x );
float3(IN.Tex0.x, IN.Tex0.y+offs.dy, IN.Tex0.z)).x float3(IN.Tex0.x, IN.Tex0.y-offs.dy, IN.Tex0.z)).x );
float3(IN.Tex0.x, IN.Tex0.y, IN.Tex0.z+offs.dz)).x float3(IN.Tex0.x, IN.Tex0.y, IN.Tex0.z-offs.dz)).x );
14
Quelltext 4.6 Anwendung der Beleuchtungsgleichung im Fragmentshader
1
float4 k = lit( abs(dot( N, L )), abs(dot( N, H )), s );
2
3
4
5
color.rgb =
tex2D( transferFunc, float2( fIndex, Ia ) ).rgb +
Ip*( tex2D( transferFunc, float2( fIndex, k.y ) ).rgb + k.z );
KAPITEL 4. IMPLEMENTIERUNG
definiert als:
~
~h = l + ~v
k~l + ~v k
66
(4.1)
Dabei ist ~l die Richtung aus der das Licht kommt und ~v die Richtung zum
Betrachter. Die lit()-Funktion benötigt dazu drei Parameter, das Skalarprodukt zwischen der Normalen des Fragments (N) und des Lichtvektors (L),
das Skalarprodukt zwischen der Normalen (N) und des Halbvektors (H) und
einen Parameter der die Grösse des Glanzlichts bestimmt, auch bekannt als
shininess oder Phong Exponent (s). Die Funktion liefert einen Vektor bestehend aus 4 Komponenten zurück, wobei die erste Komponente die ambiente
Intensität beschreibt. Diese ist immer 1. Die zweite Komponente beschreibt
die diffuse Intensität und die dritte Komponente die Intensität des Glanzlichts. Die vierte Komponente wird nicht verwendet und ist immer 1. Mit
Hilfe dieser Intensitäten wird eine gewichtete Summe der einzelnen Beleuchtungskomponenten gebildet.
Um im Shader Rechenzeit zu sparen, ist die Transferfunktion in Form einer
2D-Textur gespeichert. In dieser ist das Produkt c · k kodiert, wobei c der
Wert der 1D Transferfunktion ist und k ein Wert zwischen 0 und 1. Auf diese
Weise lassen sich im Shader einige Multiplikationen sparen.
Die Isoflächenklasse
Für die Implementation des in Kapitel 3.4 vorgestellten shaderbasierten Isoflächenrenderers wurde eine weitere Renderklasse C3dtCgIsoValueRenderEngine
von der Klasse C3dtCgRenderEngine abgeleitet und die Methode RenderVolume()
überladen. Diese benutzt nun den entsprechenden Fragmentshader zum rendern der Isoflächen. Den Pseudocode eines Fragmentshaders mit der in Kapitel 3.4 vorgestellten Funktionalität zeigt Quellcode 4.7.
Hinsichtlich heutiger Fragmentprozessoren besteht bei der Umsetzung des
Pseudocodes noch ein technisches Problem. Selbst aktuelle Fragmentprozessoren unterstützen noch keine dynamische Verzweigung (dynamic branching).
Schleifen werden beim Kompilieren komplett abgerollt, das heisst die Anzahl
der Iterationen die die Schleife durchläuft muss zum Zeitpunkt der Kompilation bekannt sein. Die for-Schleife im Pseudocode des Shaders läuft von 0 bis
numIntervals. Da numIntervals aber ein Parameter ist der vom Anwender
eingestellt werden soll, ist dieser nicht konstant. Die Anzahl der Iterationen
ist also zum Zeitpunkt der Kompilation unbekannt.
Eine Methode um dieses Problem zu umgehen liegt in der Möglichkeit, den
Shader erst zur Laufzeit zu kompilieren. Dadurch besteht die Möglichkeit
KAPITEL 4. IMPLEMENTIERUNG
67
Quelltext 4.7 Pseudocode des Fragmentshaders zur Darstellung von
Isoflächen
1
2
3
4
5
6
7
8
void FS_IsoSurface( in uniform float2 isovals[],
in uniform int numIntervals,
out float4 color )
{
// Get the current density
CurrentIndex = GetGreyValueForCurrentFragment();
// Set output color to 0.0, which is completely transparent
color.rgba = 0.0;
9
// Iterate iso intervals and check current fragment
for( int i = 0; i < numIntervals; i++ )
{
if( ( isovals[i].x <= CurrentIndex ) && ( isovals[i].y >= CurrentIndex ) )
{
// Apply transfer function
color = GetRGBAFromTransferFunction();
}
}
10
11
12
13
14
15
16
17
18
19
}
den Shader selbst auch erst zur Laufzeit zu generieren. Da die Anzahl der
Intervalle, die dargestellt werden, bekannt sein muss bevor das Volumen visualisiert wird, kann ein Shader speziell für diese Anzahl Iterationen zur
Laufzeit generiert werden. Natürlich bedeutet die Neukompilation eines Shaders für jeden Frame zusätzliche Performanceeinbußen, allerdings sind diese,
da die Shader sehr kurz sind, eher gering im Vergleich zu zusätzlichen Renderzyklen bei nicht-shaderbasierten Verfahren. Ausserdem kann der Shader
solange ohne Neukompilation benutzt werden, wie sich die Anzahl der Intervalle nicht ändert. Eine private Methode std::string GenerateShader(
int nIntervals ) der Klasse generiert den Quellcode eines solchen Shaders für die angegebene Anzahl Intervalle. Dieser wird in Form eines Strings
zurückgegeben und kann dann der Klasse CCgShaderManager mittels der
Methode AddFragmentShader() zugänglich gemacht werden. Einen solchen
generierten Fragmentshader für zwei Intervalle zeigt Quellcode 4.8.
Der Shader holt sich zunächst den Grauwert aus der 3D-Textur (fIndex).
Der Ausgabewert color wird komplett auf 0.0 gesetzt. Die im Pseudocode
enthaltene Schleife ist im Quellcode des generierten Shaders bereits abgerollt. Da der Beispielcode für zwei Intervalle generiert wurde, enthält er zwei
if-Blöcke die prüfen, ob der Index des aktuellen Fragments im entsprechenden Intervall liegt. Ist dies der Fall, wird die Transferfunktion angewendet,
KAPITEL 4. IMPLEMENTIERUNG
68
Quelltext 4.8 Generierter Shadercode für die Darstellung von zwei
Isoflächen
1
2
3
4
5
6
7
8
9
void FS_IsoSurface( in uniform sampler3D texture3d,
in uniform sampler1D transferFunc,
in uniform float
fScale,
in uniform float2
isovals[2],
in float3 pos
: TEXCOORD0,
out float4 color : COLOR )
{
float fIndex = tex3D( texture3d, pos ).x;
color.rgba = 0.0;
10
if( ( isovals[0].x <= fIndex ) && ( isovals[0].y >= fIndex ) )
{
color = tex1D( transferFunc, fIndex );
color.rgb *= fScale;
}
11
12
13
14
15
16
if( ( isovals[1].x <= fIndex ) && ( isovals[1].y >= fIndex ) )
{
color = tex1D( transferFunc, fIndex );
color.rgb *= fScale;
}
17
18
19
20
21
22
}
KAPITEL 4. IMPLEMENTIERUNG
69
andernfalls wird der Initialwert für color beibehalten. Die zusätzliche Multiplikation des RGB-Wertes mit der Variablen fScale hat für das Isoflächenrendering selbst keine Bedeutung.
Mittels der Transferfunktion lassen sich den einzelnen Isointervallen unterschiedliche Farb- und Opazitätswerte zuordnen. Dadurch lassen sich auf einfache Weise Objekte semitransparent rendern (Abbildung 3.9).
4.2.3
Progressives Rendering
Um das in Kapitel 3.5 vorgestellte progressive Rendering zu realisieren, muss
das Modul VS um zwei Kernkomponenten erweitert werden:
• Dem progressiven Renderer
• Einem parallelen Renderthread
Der progressive Renderer stellt eine Klasse dar, welche das sukzessive Rendern der einzelnen Detailstufen des Volumendatensatzes steuert. Das eigentliche Rendering des Volumens wird dabei weiterhin von der jeweiligen Renderklasse übernommen. Der progressive Renderer ruft dazu die Zeichenmethode
der Renderklasse mit einer entsprechenden Detailstufe (Level of Detail ) auf.
Das gerenderte Bild wird der Grafikpipeline in Form einer Textur erneut
zugänglich gemacht. Diese wird dann, entsprechend der Detailstufe des zuvor gerenderten Volumens, hochskaliert und in den Framebuffer geschrieben.
Damit der Rendervorgang bei Interaktion durch den Anwender abgebrochen
werden kann, muss das Rendern in einem eigenen Thread erfolgen. Dieser
muss parallel zum Hauptthread laufen, welcher die Benutzereingaben verarbeitet. Ändert der Anwender einen Parameter, wird der Renderthread benachrichtigt den aktuellen Renderzyklus abzubrechen und mit den neuen
Parametern zu rendern.
Um VS um diese Funktionalität zu erweitern, wurden zwei weitere Klassen eingeführt: CRenderThread und CProgressiveRenderer. Die Integration
dieser Klassen in VS zeigt Abbildung 4.7.
Der progressive Renderer
Ursprünglich wurde das Rendern eines Frames innerhalb der paintGL-Methode
der Klasse CRenderWidget erledigt. Bei der Methode paintGL handelt es sich
um einen Callback der jedesmal automatisch aufgerufen wird, sobald das GLFenster aktualisiert werden muss. Um die Funktionalität des progressiven
1
1
VS::VoxelSculpture
1
VS::CTexture2d
n
1
n VS::CBrick
ITWM::CBox
1
VS::CCgVertexShader
VS::CCgFragmentShader
VS::CCgShader
1
1
VS::StShader
n
1
VS::CCgShaderManager
VS::C3dtCgLightingRenderEngine
VS::C3dtCgRenderEngine
VS::C3dtCgIsoValueRenderEngine
VS::StIntersectionPoint
1
VS::CTexture3d 1
VS::CTexture
VS::CRenderEngine
1
1
VS::C3dtRenderEngine
1
1
VS::CRenderThread
VS::CProgressiveRenderer
1
1
1 VS::CRenderWidget
QGLWidget
VS::CMultiTexture2d
n
VS::C2d3tsRenderEngine
ITWM::CImage
1
KAPITEL 4. IMPLEMENTIERUNG
70
Abbildung 4.7: Statisches UML-Klassendiagramm des Moduls Voxel-Sculpture, erweitert
um progressives Rendering
KAPITEL 4. IMPLEMENTIERUNG
71
Renderings zu kapseln, wurde eine neue Klasse CProgressiveRenderer eingeführt. Der Code zum Rendern eines Frames wurde somit in die progressive
Renderklasse verlagert und um entsprechende Teile erweitert.
Der progressive Renderer besitzt dazu eine Methode Render(). Diese Methode entscheidet zunächst, ob progressives Rendering durchgeführt werden
soll und rendert dann entsprechend den Frame (siehe Quellcode 4.9).
Soll progressiv gerendert werden, wird zunächst über die private Methode
RenderSingleFrame() ein Frame mit dem entsprechend niedrig aufgelösten
Datensatz gerendert. Anschliessend wird überprüft, ob der Rendervorgang
aufgrund von Benutzereingaben bereits abgebrochen werden soll. Ist das nicht
der Fall, wird ein Datensatz mittlerer Auflösung gerendert. Dies entspricht
der ersten Verfeinerung des bereits gerenderten, grob aufgelösten Volumens.
Wird anschliessend immer noch nicht abgebrochen, wird der Originaldatensatz in höchster Auflösung gerendert, der letzten Verfeinerungsstufe.
Das progressive Rendering besteht derzeit also aus drei Renderzyklen mit
entsprechenden Qualitätsabstufungen. Der Datensatz für die niedrigste Qualitätsstufe besitzt nur ein Viertel der Grösse des Originalvolumens pro Dimension. Insgesamt reduziert sich die Anzahl der Voxel für diesen Datensatz
1
der ursprünglichen Grösse. Der mittlere Datensatz besitzt für jede
so auf 64
Dimension die halbe Grösse, reduziert die Anzahl der Voxel somit auf 81 .
Die Methode RenderSingleFrame() besitzt ausserdem zwei Parameter:
unsigned int nLOD und bool bOffscreen. Der erste Parameter gibt das
Level of Detail an, also welche Detailstufe gerendert werden soll, der zweite
Parameter gibt an, ob ein Offscreen-Buffer für das Rendern benutzt werden soll, oder nicht. Im Falle der höchsten Detailstufe macht es keinen Sinn
zunächst in einen Offscreen-Buffer zu rendern, weil das Resultat nicht mehr
skaliert werden muss. Es entspricht ja automatisch der richtigen Grösse. Aus
Performancegründen wird die höchste Detailstufe des Volumens deshalb direkt in den Framebuffer gerendert.
Während die Methode Render() dafür sorgt, das die einzelnen Qualitätstufen
sukzessiv gerendert werden, geschieht das Rendern in den Offscreen-Buffer
und dessen anschliessende Darstellung im Framebuffer, innerhalb der Methode RenderSingleFrame(). Diese Methode ist recht umfangreich. Sie behandelt das Offscreen-Rendering sowohl für die normale, als auch für die rot/grün
Darstellung[Hir05]. Weiterhin behandelt sie auch das ursprünglich implementierte, direkte Rendern in den Framebuffer. Das direkte Rendern unterstützt
zur Zeit aber kein progressives Rendering. Die in Quellcode 4.10 und 4.11
gezeigten Ausschnitte dieser Methode zeigen das Rendern in den OffscreenBuffer und die anschliessende Verwendung dessen Inhalts als Textur (verglei-
KAPITEL 4. IMPLEMENTIERUNG
72
Quelltext 4.9 Eine progressive Renderfolge
1
2
3
4
5
6
7
8
9
void CProgressiveRenderer::Render()
{
if( m_bRender )
{
if( m_bRenderProgressive && !m_bDrawProxyGeometry )
{
// Render progressive
RenderSingleFrame( 2, true );
m_pGLWidget->swapBuffers();
10
if( m_bStopRendering )
{
m_bStopRendering = false;
return;
}
RenderSingleFrame( 1, true );
m_pGLWidget->swapBuffers();
11
12
13
14
15
16
17
18
if( m_bStopRendering )
{
m_bStopRendering = false;
return;
}
19
20
21
22
23
24
// Render high quality frame directly to the framebuffer (performance reason)
RenderSingleFrame( 0, false );
m_pGLWidget->swapBuffers();
m_bStopRendering = false;
25
26
27
28
}
else
{
// Render non progressive
RenderSingleFrame( 0, false );
m_pGLWidget->swapBuffers();
}
29
30
31
32
33
34
35
}
36
37
}
KAPITEL 4. IMPLEMENTIERUNG
73
che auch Abbildung 3.10). Prinzipiell ist es ebenso möglich, anstelle eines
Offscreen-Buffers den normalen Framebuffer zum Rendern zu benutzen. Der
Inhalt kann anschliessend ausgelesen und in den Speicherbereich einer Textur kopiert werden. Die Funktionalität wäre dieselbe. Diese Vorgehensweise
wäre aber langsamer, da der Inhalt des Framebuffers kopiert werden muss.
Die OpenGL-Erweiterung GL EXT framebuffer object erlaubt es sogenannte framebuffer-attachable images als Renderziel anzugeben. Wird als Renderziel eine Textur angegeben, erlaubt diese Erweiterung das direkte Rendern in
eben diese Textur. Dadurch wird das Kopieren des Inhalts des Framebuffers
in eine Textur vermieden und erlaubt eine wesentlich effizientere Implementierung.
Der Renderthread
Damit die Interaktivität während des progressiven Renderings erhöht wird,
muss eine Möglichkeit bestehen, den aktuellen Rendervorgang bei Eintritt
einer Interaktion seitens des Anwenders abzubrechen. Dann kann ein neuer Rendervorgang mit den entsprechend aktualisierten Parametern gestartet
werden. Damit eine Eingabe während der Rendervorgangs überhaupt verarbeitet werden kann, muss das Rendering parallel zur Hauptanwendung ausgeführt werden. Die Hauptanwendung reagiert so auf Benutzereingaben und
kann entsprechende Signale an den parallel laufenden Renderthread senden.
Die Klasse CRenderThread implementiert den Renderthread. Sie ist von der
Klasse QThread abgeleitet. QThread ist Bestandteil der Qt-Bibliothek und
liefert die Funktionalität um einen parallelen Thread zu erzeugen.
Die eigentliche Funktionalität ist in der Methode run() implementiert. Bei
run() handelt es sich um eine pur-virtuelle Methode der Basisklasse QThread.
Wird der Thread gestartet, so läuft die run()-Methode parallel zum übrigen
Programm. Sämtlicher Code, den diese Methode enthält, also auch Methodenaufrufe aggregierter Klassen, werden parallel zum Rest der Anwendung
ausgeführt.
Da im Renderthread nur das Rendern des Volumens stattfindet, ist die Klasse sehr einfach aufgebaut. Die run()-Methode enthält eine Schleife, die bei
jedem Durchlauf einen Frame rendert. Damit keine unnötige Rechenzeit beansprucht wird, legt der Thread sich nach jedem vollständigen Schleifendurchlauf schlafen. Um einen weiteren Frame zu rendern muss er von aussen
geweckt werden.
Da das eigentliche Rendering von der jeweiligen Renderklasse erledigt wird,
diese jedoch von der Klasse CRenderWidget, beziehungsweise jetzt indirekt
vom progressiven Renderer CProgressiveRenderer, verwaltet werden, ruft
KAPITEL 4. IMPLEMENTIERUNG
74
Quelltext 4.10 Progressives Rendern eines Frames (Teil 1)
1
2
3
void CProgressiveRenderer::RenderSingleFrame( unsigned int nLOD, bool bOffscreen )
{
...
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Activate offscreen buffer
VS::CGlExtensions::g_pGlBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_nFramebuffer );
switch( nLOD )
{
case 0: ...
case 1: ...
// Bind texture to offscreenbuffer (to render into the texture)
case 2: VS::CGlExtensions::g_pGlFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
m_nFBOTextureLow, 0 );
// Generate depth buffer for offscreen rendering
VS::CGlExtensions::g_pGlRenderbufferStorageEXT( GL_RENDERBUFFER_EXT,
GL_DEPTH_COMPONENT, m_nViewportWidth/4, m_nViewportHeight/4 );
// Set viewport to match offscreen buffers size
glViewport( 0, 0, m_nViewportWidth/4, m_nViewportHeight/4 );
m_pEngine->SetViewport( m_nViewportWidth/4, m_nViewportHeight/4 );
break;
default: ...
}
// Attach depth buffer to offscreen framebuffer
VS::CGlExtensions::g_pGlFramebufferRenderbufferEXT(
GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_nRenderbuffer );
// Clear the offscreen frame- and depthbuffer
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
29
30
...
31
32
33
// Render the volume with the given LOD to the offscreen buffer
m_pEngine->RenderVolume( nLOD );
34
35
36
37
38
39
// Rebind the normal framebuffer and reset viewport
VS::CGlExtensions::g_pGlBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
glViewport( 0, 0, m_nViewportWidth, m_nViewportHeight );
m_pEngine->SetViewport( m_nViewportWidth, m_nViewportHeight );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
40
41
42
43
44
45
// Set up a orthographic projection matrix to display texture
glMatrixMode( GL_PROJECTION );
...
glLoadIdentity();
glOrtho( 0.0, 1.0, 0.0, 1.0, 1.0, 10.0 );
46
47
48
49
50
51
52
53
glMatrixMode( GL_MODELVIEW );
...
glLoadIdentity();
gluLookAt( 0.0f, 0.0f, -4.0,
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f);
...
KAPITEL 4. IMPLEMENTIERUNG
75
Quelltext 4.11 Progressives Rendern eines Frames (Teil 2)
// Bind the texture containing the offscreen buffers content
switch( nLOD )
{
case 0: ...
case 1: ...
case 2: glBindTexture( GL_TEXTURE_2D, m_nFBOTextureLow );
break;
default: ...
}
1
2
3
4
5
6
7
8
9
10
...
11
12
// Draw the texture onto a quad that matches the
// Thus, the texture will be scaled accordingly
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 0.0 ); glVertex2f( 0.0, 0.0
glTexCoord2f( 1.0, 0.0 ); glVertex2f( 0.0, 1.0
glTexCoord2f( 1.0, 1.0 ); glVertex2f( 1.0, 1.0
glTexCoord2f( 0.0, 1.0 ); glVertex2f( 1.0, 0.0
glEnd();
13
14
15
16
17
18
19
20
framebuffers viewport size.
);
);
);
);
21
...
22
23
// Render the volume again, but only to the depth buffer, to restore the depth values
m_pEngine->RenderVolume( nLOD, true );
24
25
26
// Render auxiliaries
m_pEngine->RenderBBox( nLOD );
m_pEngine->RenderControls();
m_pEngine->RenderAxis();
m_pGLWidget->DrawGrid();
m_pGLWidget->DrawAxis();
27
28
29
30
31
32
33
...
34
35
36
}
KAPITEL 4. IMPLEMENTIERUNG
76
der Renderthread lediglich eine Methode RenderFrame() des Renderwidgets
auf.
Das Renderwidget bildet den zentralen Knoten, von dem aus die Aufrufe an
die entsprechenden Instanzen weitergesendet werden, in diesem Fall an den
den progressiven Renderer. Auf diese Weise ist es einfach möglich, das Modul VS um weitere Module zu erweitern, da alle zentral vom Renderwidget
gesteuert werden. Da sämtliche Aufrufe innerhalb der Methode run() des
Renderthreads natürlich ebenfalls parallel zur Hauptanwendung laufen, wird
das Rendern des Volumens, welches indirekt durch den Renderthread angestossen wird, parallel zum Hauptthread durchgeführt.
Ein Problem bei der Parallelisierung von VS besteht darin, das die 3dGrafikbibliothek OpenGL nicht threadsicher ist. Da OpenGL-Funktionen
sowohl aus dem Hauptthread, als auch aus dem Renderthread aufgerufen
werden, müssen diese synchronisiert werden, um Inkonsistenzen zu vermeiden. Dies geschieht über einen Mutex. Die Qt-Bibliothek stellt mit der Klasse QMutex eine solche Funktionalität bereit. Ein Mutex kann die Zustände
locked oder unlocked haben. Damit beide Threads OpenGL-Funktionen nicht
gleichzeitig aufrufen, müssen sämtliche Sektionen die OpenGL-Aufrufe beinhalten mit einem Mutex zunächst gesichert werden. Dabei wird der Mutex in
den Zustand locked versetzt und die OpenGL-Funktionen können ausgeführt
werden. Hat ein anderer Thread den Mutex bereits in den Zustand locked
versetzt, blockiert dieser Aufruf solange, bis der andere Thread den Mutex
wieder freigibt. Dadurch ist sichergestellt, dass OpenGL-Funktionen immer
nur von einem Thread aufgerufen werden.
Im nächsten Kapitel wird das Laufzeitverhalten mit eingeschalteter Beleuchtung, sowie der Isoflächendarstellung untersucht. Weiter wird gezeigt wie sich
das progressive Rendering auf die Framerate auswirkt. Ausserdem werden einige Anregungen für die Weiterentwicklung gegeben.
Kapitel 5
Ergebnisse
In diesem Kapitel wird das Laufzeitverhalten der neu implementierten Renderklassen untersucht. Von besonderem Interesse ist dabei der Vergleich zwischen einem mit Beleuchtung gerenderten Volumen und einem ohne Beleuchtung. Bei der Isoflächendarstellung wird untersucht, wie sich die Laufzeit
mit zunehmender Anzahl dargestellter Isointervalle verändert. Bei sämtlichen Messungen wird ausserdem der Einfluß des progressiven Renderings
näher untersucht. Anschliessend wird ein Ausblick für die Weiterentwicklung
von Voxel-Sculpture gegeben.
5.1
Messungen
Um das Laufzeitverhalten zu untersuchen, wurde die Anzahl der gerenderten
Bilder pro Sekunde (FPS, frames per second) unter unterschiedlichen Bedingungen für verschiedene Bilder gemessen. Dazu wurde ein Zähler integriert,
der die Zeit zum Rendern von 20 Frames misst und daraus anschliessend
einen Durchschnittswert für das Rendern eines einzigen Frames berechnet.
Für die Messungen standen zwei Hardwarekonfigurationen zur Verfügung:
Rechner A
Rechner B
CPU
RAM
Grafikkarte
Betriebssystem
CPU
RAM
Grafikkarte
Betriebssystem
Dual Intel Xeon 1,7GHz
2,0GB
NVidia GeForce FX 5200
SuSE Linux 9.0
AMD Athlon XP 2400+ 2,0GHz
1,5GB
NVidia GeForce 6800 GT
SuSE Linux 9.0
Die Leistungsfähigkeit beider CPUs unterscheidet sich deutlich voneinander.
Allerdings, hat sich bereits bei den Messungen in [Hir05] gezeigt, dass diese
77
KAPITEL 5. ERGEBNISSE
78
die Visualisierung nur sehr gering beeinflussen. Die erreichten Frameraten
werden hauptsächlich durch den Grafikprozessor beeinflusst. Da die Renderklassen hauptsächlich Fragmentshader zur Visualisierung verwenden, ist die
Leistungsfähigkeit des Fragmentprozessors von besonderem Interesse.
Für die Messungen stehen zwei, von der Leistungsfähigkeit recht unterschiedliche Grafikkarten zur Verfügung. Konfiguration A ist mit der NVidia Geforce
FX 5200 ausgestattet. Diese Grafikkarte bietet eine eher mäßige Performance
bezüglich Fragmentshading. Die NVidia GeForce 6800 GT aus Konfiguration B dagegen, ist allgemein leistungsfähiger und führt Fragmentshader mit
einer vergleichsweise hohen Geschwindigkeit aus. Die Messungen wurden mit
folgenden Bildern durchgeführt:
Sinterkupfer
Ersteller
K. Pischang, TU Dresden
Abbildung A.1, Seite 88
Schädel
Datenerfassung
Dimensionen (Pixel)
Größe
Ersteller
Abbildung A.2, Seite 90
Feuerbeton
Datenerfassung
Dimensionen (Pixel)
Größe
Ersteller
Abbildung A.3, Seite 92
Datenerfassung
Dimensionen (Pixel)
Größe
Computertomographie (XCT)
330 x 328 x 222
22,9MB
Siemens Medical Systems,
Forchheim
Rotations-Röntgenscan
256 x 256 x 256
16,0MB
S. Gondrom, IzfP Saarbrücken
Prof. Schlegel, TU Freiburg
Computertomographie (XCT)
128 x 128 x 128
2,0MB
Wie in Kapitel 3.5 bereits erwähnt, ist das Laufzeitverhalten der Fragmentshader direkt von dem verwendeten Zoomfaktor abhängig, da bei größerem
Zoom mehr Fragmente erzeugt werden, für die der Shader jeweils ausgeführt
wird. Um vergleichbare Ergebnisse zu erhalten, wurden sämtliche Messungen
mit demselben Zoomfaktor durchgeführt. Dadurch ist es möglich die Messergebnisse in Zusammenhang mit der Anzahl der Pixel der einzelnen Bilder
zu setzen.
Einige Besonderheiten gelten für die Messungen mit aktiviertem progressiven Rendering. Eine komplette Renderfolge des progressiven Renderers dauert natürlich länger als das Rendern eines einzelnen hochaufgelösten Frames,
da der progressive Renderer das Volumen sukzessiv, zunächst in niedriger,
mittlerer und anschliessend in hochaufgelösender Form rendert. Der Rendervorgang kann derzeit nur abgebrochen werden, wenn eine der drei Stufen
KAPITEL 5. ERGEBNISSE
79
komplett gerendert worden ist. Dadurch ist es problematisch, die Laufzeit
des progressiven Renderers, beziehungsweise dessen effektiven Beitrag zur
Erhöhung der Framerate während einer Interaktion, zu messen. Bei den Messungen wurden dem Renderthread, mittels einer Endlosschleife, permanent
Signale zum Neuzeichnen des Frames gesendet. Im Bezug auf das progressive
Rendering bedeutet dies, dass meist nur die niedrig aufgelöste Variante gerendert wurde. Weitere Besonderheiten des progressiven Renderings werden
in den entsprechenden Kapiteln behandelt.
5.1.1
Die Beleuchtungsklasse
Von besonderem Interesse ist ein Vergleich der Laufzeit zwischen beleuchtetem und unbeleuchtetem Volumen. Folgende Tabellen fassen die Ergebnisse
zusammen:
Messungen ohne progressives Rendering
Konfiguration Bild
FPS mit Beleuchtung FPS ohne Beleuchtung
Rechner A
Sinterkupfer
0,029
0,716
Schädel
0,059
1,020
Feuerbeton
0,316
7,632
Rechner B
Sinterkupfer
1,004
11,770
Schädel
1,562
14,638
Feuerbeton
11,347
78,571
Messungen mit progressivem Rendering
Konfiguration Bild
FPS mit Beleuchtung FPS ohne Beleuchtung
Rechner A
Sinterkupfer
2,166
6,210
Schädel
3,215
9,402
Feuerbeton
20,708
53,907
Rechner B
Sinterkupfer
26,813
50,135
Schädel
85,450
122,583
Feuerbeton
149,105
181,851
Ein weiterer interessanter Aspekt, ist ein Vergleich der Laufzeit zwischen der
ursprünglichen 3d-Renderklasse, die ohne Shader arbeitet, und der neuen 3dRenderklasse, bei der die Transferfunktion mit Hilfe eines Fragmentshaders
angewendet wird. Ansonsten unterscheiden sich die beiden Renderklassen
nicht. Daraus lassen sich weitere Schlüsse bezüglich des Laufzeitverhaltens
der Shader ziehen. Da die GeForce 6800 GT aufgrund einer nicht mehr unterstützten OpenGL Erweiterung nur die shaderbasierte Visualisierung unterstützt, konnte der Test ausschliesslich auf Rechner A durchgeführt werden.
Die Messung ergab folgende Ergebnisse:
KAPITEL 5. ERGEBNISSE
80
Messungen ohne progressives Rendering
Konfiguration Bild
FPS ohne Shader FPS mit Shader
Rechner A
Sinterkupfer
1,417
0,716
Schädel
1,523
1,020
Feuerbeton
10,899
7,632
Messungen mit progressivem Rendering
Konfiguration Bild
FPS ohne Shader FPS mit Shader
Rechner A
Sinterkupfer
18,596
6,210
Schädel
21,676
9,402
Feuerbeton
92,028
53,907
5.1.2
Die Isoflächenklasse
Die Isoflächenklasse generiert die verwendeten Shader, wie in Kapitel 4.2.2
beschrieben, anhand der Anzahl der Isointervalle, zur Laufzeit. Dadurch ergeben sich natürlich unterschiedliche Laufzeiten für eine verschiedene Anzahl
Isointervalle. Die Messungen wurden jeweils mit 1, 2, 4 und 8 verwendeten
Isointervallen durchgeführt.
Messungen ohne progressives Rendering
Konfiguration Bild
1
2
4
8
Rechner A
Sinterkupfer 0,425 0,220 0,121 0,095
Schädel
0,671 0,313 0,173 0,090
Feuerbeton
4,542 2,483 1,383 0,722
Rechner B
Sinterkupfer 8,441 7,709 4,918 2,443
Schädel
17,002 12,681 6,927 3,418
Feuerbeton 18,387 15,988 9,680 10,569
Messungen mit progressivem Rendering
Konfiguration Bild
1
2
4
8
Rechner A
Sinterkupfer 3,457 1,814 0,979 0,510
Schädel
5,012 2,606 1,409 0,733
Feuerbeton 30,064 18,895 10,764 5,752
Rechner B
Sinterkupfer 18,278 15,360 8,890 9,375
Schädel
22,893 19,217 9,951 11,454
Feuerbeton 23,173 19,378 10,018 11,278
5.1.3
Auswertung
Allgemein fallen zunächst die geringen Frameraten von Rechner A, insbesondere bei der Visualisierung grösserer Datenmengen, auf. Dies bestätigt die
KAPITEL 5. ERGEBNISSE
81
Eingangs erwähnte, mäßige Shaderperformance der verwendeten Grafikkarte.
Ein weiteres Indiz dafür liefert der direkte Vergleich mit der ursprünglichen
3d-Renderklasse, welche keine Shader einsetzt. Obwohl die shaderbasierte Variante im Fragmentshader lediglich zwei Texturzugriffe und eine Multiplikation durchführt, ist sie durchschnittlich etwa doppelt so langsam, sowohl mit
als auch ohne progressivem Rendering. Mit zunehmender Grösse des Volumens steigt die Laufzeit durch die zunehmende Anzahl der Shaderdurchläufe
weiter.
Beleuchtung
Mit eingeschalteter Beleuchtung erhöht sich die Laufzeit für Rechner A um
etwa Faktor 22, für Rechner B um etwa Faktor 10. Für Rechner B unterscheidet sich die Laufzeit zwischen beleuchtetem und unbeleuchtetem Volumen
beim Sinterkupfer um etwa Faktor 11, sinkt beim Feuerbeton aber auf etwa
Faktor 7. Bei Rechner A ist der Faktor 22 relativ konstant für alle Volumen.
Daraus lässt sich folgern, dass die Shader bei Rechner A in allen Fällen den
Flaschenhals bilden, während sie bei Rechner B für kleine Datensätze nicht
so stark ins Gewicht fallen, was auf die effektivere Shadereinheit bei Rechner
B zurückzuführen ist. Auch lässt sich Erkennen, dass die Laufzeit ungefähr
linear mit mit der Anzahl der gerenderten Pixel wächst. Das war zu Erwarten, da die Anzahl der Shaderaufrufe direkt von dieser Grösse abhängig ist.
Durch das progressive Rendering lassen sich die Laufzeiten erheblich verkürzen.
Es ist eine deutliche Erhöhung der Frameraten sowohl für Rechner A, als auch
für Rechner B zu erkennen. Der Steigerungsfaktor liegt je nach Grösse des
Datensatzes und aktivierter Beleuchtung zwischen 2 und 74. Insbesondere die
Beleuchtung profitiert vom progressiven Rendering, da sich die Shaderaufrufe
durch das niedriger aufgelöste Volumen erheblich reduzieren. Die Laufzeitunterscheide zwischen beleuchtetem und unbeleuchtetem Objekt liegen, für
Rechner A, nur noch bei etwa Faktor 2.8 und bei Rechner B um etwa 1.5.
Auffällig dabei ist, dass das lineare Kostenwachstum bezüglich der Anzahl
der Pixel des Volumens für Rechner A weiterhin gegeben ist, für Rechner B
jedoch nicht. Die Laufzeitunterschiede zwischen dem Schädel und dem Feuerbeton betragen bei Rechner A etwa den Faktor 6, während sie bei Rechner
B nur etwa 1.5 betragen. Dies lässt sich durch eine Auffälligkeit des progressiven Renderings während der Messungen erklären. Wie eingangs erwähnt,
werden ständig Signale zum Neuzeichnen an den Renderthread gesendet. Dadurch wird beim progressiven Rendering meist nur die niedrige Detailstufe
KAPITEL 5. ERGEBNISSE
82
dargestellt. Bei Rechner B fiel während der Messungen auf, das die Grafikkarte diese niedrige Detailstufe sogar schneller rendert, als Signale zum
Neuzeichnen des Volumens eintreffen. Dadurch wird nicht nach der niedrigen
Detailstufe abgebrochen, sondern bereits die mittlere gerendert, erst danach
wurde das entsprechende Signal empfangen und die Renderfolge abgebrochen. Da Rechner B also mehr als nur die niedrigste Detailstufe gerendert
hat, sinkt die Framerate natürlich entsprechend und die Messungen zwischen
beiden Systemen sind nicht mehr vergleichbar. Zum Vergleich werden die Ergebnisse in Abbildung 5.1 und 5.2 nochmal grafisch verdeutlicht.
Abbildung 5.1: Ergebnisse der Frameratenmessung für die Beleuchtungsklasse
Abbildung 5.2: Ergebnisse der Frameratenmessung für die Beleuchtungsklasse mit progressivem Rendering
KAPITEL 5. ERGEBNISSE
83
Isoflächendarstellung
Bei der Isoflächendarstellung fällt auch das bereits erwähnte, lineare Kostenwachstum bezüglich der Pixel des Volumens für Rechner A auf. Dieses
gilt auch für die progressive Visualisierung. Bei Rechner B hingegen sind die
Laufzeitunterschiede zwischen dem Schädel und dem Feuerbeton eher gering.
Das deutet wieder darauf hin, dass die Shaderperformance bei Rechner A den
Flaschenhals bildet. Für Rechner B scheint dies, zumindest für kleinere Volumendaten, nicht zuzutreffen.
Die Laufzeit des Shaders nimmt mit steigender Anzahl dargestellter Isointervalle zu. Die folgenden Grafiken setzen die erreichte Framerate in Bezug zur
Anzahl gerenderter Isointervalle. Die Anzahl der verwendeten Isointervalle
wurde bei jeder Messung jeweils verdoppelt.
Generell lässt sich erkennen, das die Laufzeit der Isoflächendarstellung un-
Abbildung 5.3: Grafische Darstellung der Messergebnisse der Isoflächen für Rechner A
gefähr linear mit der Anzahl der Isointervalle wächst. Verdoppelt man die
Anzahl der Intervalle, halbiert sich die Framerate. Bei Rechner B bleibt die
Framerate für kleine Volumendatensätze und einer hohen Intervallzahl allerdings ungefähr konstant. Eine Erklärung für dieses Phänomen konnte bisher
nicht gefunden. Auch wiederholte Messungen kamen zu dem gleichen Ergebnis.
KAPITEL 5. ERGEBNISSE
84
Abbildung 5.4: Grafische Darstellung der Messergebnisse der Isoflächen für Rechner B
Die Tests haben gezeigt, das das Laufzeitverhalten direkt von der Leistungsfähigkeit der Grafikkarte abhängt. Insbesondere ist hierbei die Leistung
der Fragmentprozessoren von Bedeutung. Der Prozessor und die CPU wirken sich kaum auf die Framerate aus. Obwohl Rechner A diesbezüglich besser
ausgestattet war, wurden auf Rechner B, mit einer deutlich leistungsfähigeren Grafikkarte, wesentlich höhere Frameraten erzielt. Als Fazit kann man
sagen, dass eine Volumenvisualisierung mit Beleuchtung und Schatten bei
interaktiven Frameraten möglich ist. Allerdings fordert diese den Einsatz
modernster Grafikkarten, da, wie am Beispiel der GeForce FX 5200 gezeigt,
die Frameraten sonst zu stark sinken. Progressives Rendering hilft dabei, die
Frameraten, gerade auf älteren Grafikkarten, wieder auf ein akzeptables Maß
zu heben. Es ist zu erwarten, dass die Effizienz der GPUs weiter steigen wird,
so dass immer grössere Datenmengen interaktiv, oder sogar in Echtzeit, mit
Beleuchtung visualisiert werden können. Durch den besseren visuellen Eindruck, unter Einsatz von Beleuchtung, ist eine Entwicklung in diese Richtung
gerechtfertigt. Anhang A enthält einige Visualisierungen. Dabei zeigen sich
die Unterschiede zwischen beleuchtetem und unbeleuchtetem Volumen sehr
deutlich.
KAPITEL 5. ERGEBNISSE
5.1.4
85
Ausblick
Im Hinblick auf zukünftige Entwicklungen, kann Voxel-Sculpture um weitere
Renderverfahren erweitert werden. Denkbar wäre zum Beispiel ein polygonbasiertes Visualisierungsverfahren. So könnte ein, derzeit als eigenständiges
Modul vorliegendes, Oberflächenrendering, welches auf dem Marching-CubesAlgorithmus basiert in Voxel-Sculpture integriert werden. Aber auch ein raycastingbasiertes Verfahren wäre denkbar. Dieses ist sehr rechenaufwändig
und wird typischerweise nur auf CPUs implementiert, obwohl bereits erste
Ansätze existieren, Raycaster auch auf der GPU zu implementieren. Deshalb wird es auf herkömmlicher PC-Hardware kaum interaktive Frameraten
erreichen, würde aber bezüglich der Darstellungsqualität eine hochwertige
Alternative zu den derzeit implementierten Verfahren darstellen. Eventuell
wäre auch ein hybrides Verfahren denkbar, so könnte man das texturbasierte
Rendering zum Einstellen der Parameter nutzen, um anschliessend das Bild
mittels Raycaster in sehr hoher Qualität zu rendern.
Bei der Implementierung der Beleuchtung haben sich auch Schwächen bezüglich
des Renderklasseninterfaces gezeigt. Die Basisklasse CRenderEngine ist für
eine Unterstützung beliebiger Renderklassen noch zu speziell. Insbesondere
die enge Anbindung an das Renderwidget bereitete viele Probleme. Um eine flexible Einbindung beliebiger Renderklassen zu gewähren, müssten diese
sämtliche Manipulationswerkzeuge für die Darstellung selbst bereitstellen.
Bisher übernimmt das Renderwidget Aufgaben wie die Rotation oder das
setzen benutzerdefinierter Clippingebenen. Die Funktionalität des Renderwidgets würde sich darauf beschränken, das OpenGL-Fenster bereitzustellen
und die entsprechende Renderklasse zu initialisieren. Dabei gilt es einen geeigneten Mechanismus zu entwickeln, der es erlaubt, die von der Renderklasse bereitgestellten Einstellungsdialoge und Manipulationswerkzeuge automatisch auf der GUI des Renderwidgets zu plazieren. Auf diese Weise würde die
Abhängigkeit zwischen Renderklasse und Renderwidget auf ein Minimum
reduziert, wodurch man flexibel beliebige, völlig unterschiedliche, Renderklassen implementieren könnte.
Eine Möglichkeit, die Beleuchtungsklasse zu verbessern besteht in der Verwendung von sogenannten Deep Shadow Maps[LV]. Im Gegensatz zu einfachen Shadow Maps, welche nur einen einzigen Tiefenwert für den der Kamera
nächsten Pixel speichern, speichern Deep Shadow Maps den Sichtbarkeitsanteil sämtlicher Pixel. Dadurch bietet sich die Möglichkeit, Schatten auch für
halbtransparente Medien korrekt darzustellen. Derzeit wird jeder dargestellte Pixel für die Schattengenerierung, unabhängig von seiner eingestellten
Transparenz, als völlig opak angenommen. Ein Pixel ist dadurch entweder
vollständig beleuchtet oder liegt vollständig im Schatten. Deep Shadow Maps
KAPITEL 5. ERGEBNISSE
86
erlauben eine sukzessive Abschwächung der Lichtintensität anhand der Opazität der betroffenen Pixel. Auch eignen sich Deep Shadow Maps besser für die
Darstellung filigraner Strukturen und bieten bei einer geringeren Auflösung
eine höhere Bildqualität.
Um die Visualisierung weiter zu beschleunigen, besteht die Möglichkeit zwei
Grafikkarten zu bündeln. Der Hersteller NVidia bietet unter dem Namen SLI
eine solche Technologie. Theoretisch lässt sich dadurch die Rechenleistung
verdoppeln. Es ist zu untersuchen, ob und wie Voxel-Sculpture angepasst
werden muss, damit es von dieser Methode profitiert.
Moderne Grafikkarten unterstützen mittlerweile beliebige Texturgrössen. VoxelSculpture kann dies nutzen um die Geometrieberechnung zu beschleunigen.
Beim progressiven Rendering fällt negativ auf, dass die Renderfolge noch
nicht an beliebiger Stelle abgebrochen werden kann, sondern nur jeweils
nach einer komplett fertiggerenderten Detailstufe. Es wäre möglich, die Überprüfung des Abbruchsignals nicht nach jeder Detailstufe vorzunehmen, sondern nach jeder gerenderten Texturscheibe. Der interaktive Eindruck wird
dadurch weiter verbessert.
Die Normalenberechnung für die Beleuchtung findet zurzeit im Fragmentshader statt. Es ist möglich diese vorzuberechnen und dem Shader als Textur
zugänglich zu machen. Dadurch verringert sich die Laufzeit des Shaders. Da
die Vorberechnung der Normalen nicht zeitkritisch ist, können Algorithmen
verwendet werden, die bessere Ergebnisse als die derzeit verwendeten zentralen Differenzen liefern, wodurch die Qualität der Beleuchtung weiter erhöht
wird.
Durch die Flexibilität moderner Shader lassen sich noch weitere Effekte realisieren oder optimieren. Viele der vorgestellten Techniken liessen sich ohne
Shader nicht, oder nur wesentlich ineffizienter realisieren.
Anhang A
Gallerie
A.1
Sinterkupfer
Dieses Bild wurde für die Frameratenmessung verwendet. Die Dimensionen
betragen 330 x 328 x 222 Pixel. Das Volumen wurde über die Transferfunktion rot eingefärbt. Die Lichtquelle befindet sich vorne oben links. Die
Oberflächenstruktur ist unter der Beleuchtung deutlich zu erkennen und das
Objekt wirkt durch die Selbstschattierung wesentlich plastischer als ohne
Beleuchtung.
87
ANHANG A. GALLERIE
88
Abbildung A.1: Sinterkupfer Oben: Unbeleuchtete Darstellung Unten: Von vorne oben
links mit gelber Lichtquelle beleuchtet
ANHANG A. GALLERIE
A.2
89
Schädel
Dieses Bild wurde für die Frameratenmessung verwendet. Die Dimensionen
betragen 256 x 256 x 256 Pixel. Die Lichtquelle befindet sich vorne oben
links. Besonders die Zähne wirken durch das Glanzlicht glatter als in der
unbeleuchteten Darstellung.
ANHANG A. GALLERIE
90
Abbildung A.2: Schädel Oben: Unbeleuchtete Darstellung Unten: Von vorne oben links
mit blaugrüner Lichtquelle beleuchtet
ANHANG A. GALLERIE
A.3
91
Feuerbeton
Ein kleinerer Ausschnitt von diesem Bild wurde für die Frameratenmessung
verwendet. Die Dimensionen betragen 340 x 360 x 350 Pixel. In diesem Beispiel wurden die Korundeinschlüsse visualisiert. Das Volumen wurde über
die Transferfunktion rot eingefärbt. Die Lichtquelle befindet sich vorne oben
links. Dieses Beispiel verdeutlicht sehr gut die wesentlich verbesserte Darstellung der Oberflächenstruktur. Die Selbstschattierung hilft bei der Erkennung
der einzelnen kugelförmigen Einschlüsse.
ANHANG A. GALLERIE
92
Abbildung A.3: Korundeinschlüsse im Feuerbeton Oben: Unbeleuchtete Darstellung Unten: Von vorne oben links mit gelber Lichtquelle beleuchtet
ANHANG A. GALLERIE
A.4
93
Motorblock
Dieses Bild zeigt den Teil eines Motorblocks. Die Dimensionen betragen 256
x 256 x 256 Pixel. Die Lichtquelle befindet sich rechts oben. An diesem Beispiel lässt sich die Selbstschattierung sehr gut erkennen. Auch die glatte
Oberfläche wird durch das Glanzlicht gut veranschaulicht.
ANHANG A. GALLERIE
94
Abbildung A.4: Motorblock Oben: Unbeleuchtete Darstellung Unten: Von rechts oben
mit blaßgelber Lichtquelle beleuchtet
ANHANG A. GALLERIE
A.5
95
Menschlicher Kopf
Dieses Bild zeigt einen menschlichen Kopf. Die Dimensionen betragen 256 x
256 x 256 Pixel. Die Lichtquelle befindet sich vorne oben links. Durch die
Selbstschattierung wirkt das beleuchtete Bild wieder wesentlich plastischer.
ANHANG A. GALLERIE
96
Abbildung A.5: menschlicher Kopf Oben: Unbeleuchtete Darstellung Unten: Von vorne
oben links mit hellgrauer Lichtquelle beleuchtet
ANHANG A. GALLERIE
A.6
97
Aluminiumschaum
Dieses Beispiel von der Darstellung eines Aluminiumschaums soll die Auswirkungen durch das Fehlen der Selbstschattierung verdeutlichen. Die Dimensionen betragen 400 x 400 x 400 Pixel. Die Lichtquelle befindet sich vorne
oben. Das Objekt wurde über die Transferfunktion rot eingefärbt und wird
mit einer gelben Lichtquelle beleuchtet. Das erste Bild wurde unter völligem
Fehlen der Selbstschattierung gerendert, das heisst jeder Pixel ist beleuchtet.
Dadurch fällt es schwer eine Beziehung zwischen den filigranen Strukturen
herzustellen. Im zweiten Bild fällt dies aufgrund der Selbstschattierung wesentlich leichter. Besonders die große Aushöhlung am linken Rand ist ohne
Schatten nur äusserst schwer wahrzunehmen.
ANHANG A. GALLERIE
98
Abbildung A.6: Aluminiumschaum Oben: Darstellung ohne Selbstschattierung. Eine gelbe Lichtquelle befindet sich vorne oben. Unten: Die gleiche Darstellung wie oben mit
Selbstschattierung
Abbildungsverzeichnis
1.1
Shaderbeispiele . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.1
2.2
2.3
7
8
2.6
2.7
2.8
2.9
2.10
2.11
2.12
2.13
Licht-Materie Interaktion allgemein . . . . . . . . . . . . . .
Differentieller Raumwinkel . . . . . . . . . . . . . . . . . . .
Der differentielle Raumwinkel dω als Fläche auf der Einheitskugel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Projektion des Raumwinkels dω auf die relevante Fläche da .
Das gesamte einfallende Licht bestimmt die Intensität in Reflexionsrichtung ωr . . . . . . . . . . . . . . . . . . . . . . .
Diffuse Reflexion . . . . . . . . . . . . . . . . . . . . . . . .
Spiegelnde Reflexion . . . . . . . . . . . . . . . . . . . . . .
Flat Shading . . . . . . . . . . . . . . . . . . . . . . . . . . .
Gouraud Shading . . . . . . . . . . . . . . . . . . . . . . . .
Phong Shading . . . . . . . . . . . . . . . . . . . . . . . . .
Phong Komponenten . . . . . . . . . . . . . . . . . . . . . .
Wirkung von Schatten . . . . . . . . . . . . . . . . . . . . .
Schatten und Lichtquelle . . . . . . . . . . . . . . . . . . . .
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
Schematischer Aufbau der Grafikpipeline . . . . . .
Schematische Darstellung eines Vertexprozessors . .
Schematische Darstellung eines Fragmentprozessors
Polygonbasiertes Rendering . . . . . . . . . . . . .
Texturbasiertes Volumenrendering . . . . . . . . . .
Prinzip des Shadowmapping . . . . . . . . . . . . .
Transformation beim Shadowmapping . . . . . . . .
Probleme beim Shadowmapping . . . . . . . . . . .
Semitransparente Darstellung eines Motorblocks . .
Rendern eines niedrig aufgelösten Datensatzes . . .
Progressives Rendering . . . . . . . . . . . . . . . .
2.4
2.5
99
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 9
. 10
.
.
.
.
.
.
.
.
.
11
14
15
17
17
18
21
21
23
.
.
.
.
.
.
.
.
.
.
.
27
29
31
34
35
41
42
43
46
48
49
ABBILDUNGSVERZEICHNIS
4.1
4.2
4.3
4.4
4.5
4.6
4.7
100
Statisches UML-Klassendiagramm der Kernklassen des Moduls Voxel-Sculpture . . . . . . . . . . . . . . . . . . . . . . .
Statisches UML-Klassendiagramm der beteiligten Shaderklassen
Statisches UML-Klassendiagramm der neuen Renderklassen.
Die alten Klassen grau dargestellt. . . . . . . . . . . . . . . . .
Resultate bei der pre- und post-classification . . . . . . . . . .
Artefakte bei der post-classification . . . . . . . . . . . . . . .
Abstand der Lichtquelle beim Shadowmapping . . . . . . . . .
Statisches UML-Klassendiagramm des Moduls Voxel-Sculpture,
erweitert um progressives Rendering . . . . . . . . . . . . . . .
51
53
56
58
59
60
70
5.1 Ergebnisse der Frameratenmessung für die Beleuchtungsklasse
5.2 Ergebnisse der Frameratenmessung für die Beleuchtungsklasse
mit progressivem Rendering . . . . . . . . . . . . . . . . . . .
5.3 Grafische Darstellung der Messergebnisse der Isoflächen für
Rechner A . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.4 Grafische Darstellung der Messergebnisse der Isoflächen für
Rechner B . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
84
A.1
A.2
A.3
A.4
A.5
A.6
88
90
92
94
96
98
Sinterkupfer . . . . .
Schädel . . . . . . .
Korundeinschlüsse im
Motorblock . . . . .
menschlicher Kopf .
Aluminiumschaum .
. . . . . . .
. . . . . . .
Feuerbeton
. . . . . . .
. . . . . . .
. . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
82
82
83
Quelltextverzeichnis
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
4.10
4.11
Bestimmung ob mit Beleuchtung gerendert wird. . . . . . . . .
Erzeugen der Shadowmap . . . . . . . . . . . . . . . . . . . .
Vertexshader für das Shadowmapping . . . . . . . . . . . . . .
Bestimmung ob das Fragment beleuchtet ist . . . . . . . . . .
Bestimmung der Normalen im Fragmentshader . . . . . . . . .
Anwendung der Beleuchtungsgleichung im Fragmentshader . .
Pseudocode des Fragmentshaders zur Darstellung von Isoflächen
Generierter Shadercode für die Darstellung von zwei Isoflächen
Eine progressive Renderfolge . . . . . . . . . . . . . . . . . . .
Progressives Rendern eines Frames (Teil 1) . . . . . . . . . . .
Progressives Rendern eines Frames (Teil 2) . . . . . . . . . . .
101
61
62
63
64
65
65
67
68
72
74
75
Literaturverzeichnis
[AMH02] Akenine-Möller, Tomas ; Haines, Eric: Real-time Rendering.
2nd edition. A K Peters, Ltd., 2002
[ATI]
ATI, Grafikkartenhersteller und Mitglied im ARB. http://www.
ati.com
[Bau00] Bauer, Michael. Optimierung der Volumenvisualisierung auf PCHardware. 2000
[CG05] Cg Toolkit User’s Manual 1.4. ftp://download.nvidia.com/
developer/cg/Cg 1.4/Docs/CG UserManual 1-4.pdf. 2005
[Chr01] Christof, Rezk-Salama: Volume Rendering Techniques for General Purpose Graphics Hardware, Technische Fakultät der Universität
Erlangen-Nürnberg, Doktorarbeit, 2001
[Cro77] Crow, Franklin: Shadow algorithms for computer graphics. (1977).
– Zitiert nach: [WW92]
[GL]
OpenGL, Industriestandard für Crossplattform 2D/3D Computergrafik. http://www.opengl.org
[Hir05] Hirschenberger, Falco: Schnelle Volumenvisualisierung großer
dreidimensionaler Bilddaten unter Verwendung von OpenGL,
FHOOW, Diplomarbeit, 2005
[HLS]
The High Level Shading Language. http://msdn.microsoft.
com/library/default.asp?url=/library/en-us/directx9 c/
dx9 graphics reference hlsl.asp
[KBR] Kessenich, John ; Baldwin, Dave ; Rost, Randi. The OpenGL
Shading Language. http://oss.sgi.com/projects/ogl-sample/
registry/ARB/GLSLangSpec.Full.1.10.59.pdf
102
LITERATURVERZEICHNIS
103
[LC87] Lorensen, W. ; Cline, H.: Marching Cubes: A High Resolution
3D Surface Construction Algorithm. In: Computer Graphics (1987),
S. 163–169
[Len02] Lengyel, Eric: Mathematics for 3D Game Programming & Computer Graphics. 1st edition. Charles River Media, 2002
[LV]
Lokovic, Tom ; Veach, Eric: Deep Shadow Maps.
[MAV] MAVI - Modular Algorithms for Volume Images. http://www.itwm.
fhg.de/mab/projects/MAVI
[MDX] DirectX, Microsofts multimedia API für hardwarebeschleunigte
Computergrafik. http://www.microsoft.com/windows/directx
[MS]
Microsoft. http://www.microsoft.com
[NV]
NVidia, Grafikkartenhersteller und Mitglied im ARB. http://www.
nvidia.com
[Pho75] Phong, B.T.: Illumination for Computer Generated Pictures. In:
Communications of the ACM 18 (1975)
[Pix]
Pixar.
The RenderMan Interface Specification.
renderman.pixar.com/products/rispec
[Tro]
Trolltech. Qt, das Multiplattform C++ GUI/API Toolkit. http:
//www.trolltech.com
https://
[Wil78] Williams, Lance: Casting Curved Shadows on curved Surfaces.
(1978)
[Win02] Winter, Andrew S.: Volume Graphics: Field-based Modelling and
Rendering, University of Wales, Swansea, Ph.D. Dissertation, 2002
[WW92] Watt, Alan ; Watt, Mark: Advanced Animation and Rendering
Techniques, Theory and Practice. Addison-Wesley, 1992
[Wyn] Wynn, Chris.
An Introduction to BRDF-Based Lighting.
http://developer.nvidia.com/attach/6568
[YCK] Yagel, Roni ; Cohen, Daniel ; Kaufman, Arie: Normal Estimation
in 3D Discrete Space.