Echtzeit-Raytracing algebraischer Flächen auf der

Transcription

Echtzeit-Raytracing algebraischer Flächen auf der
Diplomarbeit
Echtzeit-Raytracing algebraischer Flächen
auf der Graphics Processing Unit
Christian Stussak
12. Juli 2007
betreut durch
Dr. rer. nat. habil. Peter Schenzel
Martin-Luther-Universität Halle-Wittenberg
Mathematisch-naturwissenschaftliche Fakultät
Institut für Informatik
Erklärung
Ich versichere hiermit, dass ich diese Diplomarbeit ohne fremde Hilfe eigenständig verfasst
und nur die angegebenen Quellen und Hilfsmittel benutzt habe. Wörtlich oder dem Sinn nach
aus anderen Werken entnommene Stellen sind unter Angabe der Quellen kenntlich gemacht.
Datum
Unterschrift
iii
iv
Inhaltsverzeichnis
1 Einleitung
1.1 Zielstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Wertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Grundlagen der GPU-Programmierung
2.1 Grafikkartenarchitektur und Programmiermodelle
2.1.1 Die Grafikpipeline . . . . . . . . . . . . .
2.1.2 Programmierbare Pipelinestufen . . . . .
2.1.3 Datenparallele Verarbeitung . . . . . . . .
2.1.4 Stream-Processing . . . . . . . . . . . . .
2.2 Programmiersprachen . . . . . . . . . . . . . . .
2.2.1 Shading-Languages . . . . . . . . . . . . .
2.2.2 Stream-Processing-Languages . . . . . . .
2.3 Einführung in die OpenGL-Shading-Language .
2.3.1 OpenGL-API für GLSL . . . . . . . . .
2.3.2 GLSL-Sprachelemente . . . . . . . . . . .
2.3.3 GLSL-Beispiel . . . . . . . . . . . . . . .
2.4 Werkzeuge und Bibliotheken . . . . . . . . . . . .
2.5 Zusammenfassung . . . . . . . . . . . . . . . . .
1
2
2
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
4
6
7
9
9
10
11
12
12
15
17
19
19
3 Grundlagen des GPU-basierten Raytracings
3.1 Der Raytracing-Algorithmus . . . . . . . . . . . . . . . .
3.1.1 Beschleunigung des Raytracings . . . . . . . . . .
3.2 Anpassung des Algorithmus für die GPU . . . . . . . . .
3.2.1 Aufgaben des Vertex-Shaders . . . . . . . . . . .
3.2.2 Aufgaben des Fragment-Shaders . . . . . . . . .
3.2.3 Koordinatensystem der Beleuchtungsberechnung
3.2.4 Alternative Ansätze . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
22
22
23
24
24
25
4 Raycasting algebraischer Flächen
4.1 Schnittpunktberechnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Clipping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Flächennormale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
28
28
29
5 Berechnung der Polynomkoeffizienten
5.1 Termumformungen auf der CPU .
5.2 Termumformungen auf der GPU .
5.2.1 Polynomoperationen . . . .
5.3 Polynominterpolation . . . . . . . .
31
31
32
33
34
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
v
5.4
5.3.1 Lagrange-Interpolation . . . . .
5.3.2 Newton-Interpolation . . . . . .
5.3.3 Weitere Interpolationsverfahren
Zusammenfassung . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6 Berechnung der Nullstellen univariater Polynome
6.1 Vorbemerkungen . . . . . . . . . . . . . . . . .
6.1.1 Anzahl und Vielfachheit von Nullstellen
6.1.2 Das Horner-Schema . . . . . . . . . . .
6.1.3 Polynomdivision . . . . . . . . . . . . .
6.2 Lösungsformeln . . . . . . . . . . . . . . . . . .
6.2.1 Lineare Gleichungen . . . . . . . . . . .
6.2.2 Quadratische Gleichungen . . . . . . . .
6.2.3 Kubische Gleichungen . . . . . . . . . .
6.2.4 Biquadratische Gleichungen . . . . . . .
6.3 Lokal konvergente Iterationsverfahren . . . . . .
6.3.1 Newton-Iteration . . . . . . . . . . . . .
6.3.2 Einschlussverfahren . . . . . . . . . . . .
6.4 Nullstellenisolation . . . . . . . . . . . . . . . .
6.4.1 Das D-Chain-Verfahren . . . . . . . . .
6.4.2 Der Algorithmus von Sturm . . . . . . .
6.4.3 Wertebereichanalyse . . . . . . . . . . .
6.5 Global konvergente Iterationsverfahren . . . . .
6.5.1 Die Muller-Methode . . . . . . . . . . .
6.5.2 Die Laguerre-Methode . . . . . . . . . .
6.6 Zusammenfassung . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
35
36
38
38
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
41
41
42
44
44
44
45
45
46
47
47
48
50
50
51
52
54
55
56
57
7 Implementierung
7.1 Umsetzung des Raycasting-Algorithmus in GLSL . . . . . . . .
7.1.1 Verwendete Grafikhardware und Grafiktreiber . . . . . .
7.1.2 Modifikation der Raycasting-Algorithmen für GLSL . .
7.2 Erzeugung der Shader aus den Formeln algebraischer Flächen .
7.3 Grafische Benutzeroberfläche . . . . . . . . . . . . . . . . . . .
7.3.1 Überführung einer Flächenformel in ein gerendertes Bild
7.3.2 Transformationen und Darstellungsparameter . . . . . .
7.3.3 Flächenparameter . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
59
59
59
60
63
65
65
67
67
8 Ergebnisse
8.1 Optischer Vergleich der implementierten Verfahren . . .
8.1.1 Berechnung der Polynomkoeffizienten . . . . . . .
8.1.2 Berechnung der Polynomnullstellen . . . . . . . .
8.2 Skalierbarkeit der Raycasting-Algorithmen . . . . . . . .
8.3 Laufzeitvergleich der implementierten Verfahren . . . . .
8.3.1 Berechnung der Polynomkoeffizienten . . . . . . .
8.3.2 Berechnung der Polynomnullstellen . . . . . . . .
8.4 Abschließende Bewertung der implementierten Verfahren
8.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
69
69
69
70
74
74
75
75
76
77
vi
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8.6
Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78
A Renderings und Flächenbeschreibungen
A.1 Zum Laufzeitvergleich genutzte algebraische Flächen . . . . . . . . . . . . . .
A.2 Weitere bekannte algebraische Flächen . . . . . . . . . . . . . . . . . . . . . .
79
79
81
B Grammatik der Formeln algebraischer Flächen
B.1 Grammatik für Flex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
B.2 Grammatik für Bison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83
83
84
C Inhalt der DVD
85
Literaturverzeichnis
87
Abbildungsverzeichnis
93
Tabellenverzeichnis
95
vii
viii
1 Einleitung
In vielen wissenschaftlichen Bereichen ist die grafische Veranschaulichung seit jeher ein unerlässliches Hilfsmittel zum Verständnis komplizierter Strukturen und Zusammenhänge. Betrachtet man zum Beispiel eine Gleichung in drei reellen Variablen, so bilden die Lösungen
der Gleichung eine Teilmenge des dreidimensionalen Raumes R3 . Die zur Lösung gehörenden
Punkte setzen sich dabei häufig zu Flächen zusammen, die meist nur schwer vorstellbar sind.
Einfache Flächen, wie Kugeln, Ellipsoide oder Paraboloide, sind in vielen mathematischen
Modellsammlungen in Form von Draht- oder Gipsmodellen zu finden. Eine derartige Modellierung stößt jedoch für komplexere Flächen schnell an ihre Grenzen, so dass deren genaue
Gestalt oftmals nur durch Verfahren der Computergrafik ermittelt werden kann.
Ein wichtiges Teilgebiet in diesem Kontext ist die Visualisierung der Lösungsmengen algebraischer Gleichungen in drei Variablen, so genannter algebraischer Flächen. Derartige Flächen
treten beispielsweise im Zusammenhang mit partiellen Differentialgleichungen auf, welche zur
mathematischen Modellierung physikalischer und chemischer Vorgänge, sowie in vielen Ingenieurdisziplinen eingesetzt werden. Die Klassifizierung der Lösungsmengen algebraischer Gleichungen entsprechend ihrer geometrischen Eigenschaften ist daher ein wichtiges Forschungsgebiet der Mathematik. Eine geeignete Visualisierung kann in vielen Fällen die Klärung der
geometrischen Eigenschaften wesentlich unterstützen.
Auch die Verwendung algebraischer Flächen im Bereich des Computer Aided Design ist Gegenstand wissenschaftlicher Forschungen. Trotz der kompakten Darstellungsweise in Form von
algebraischen Gleichungen ist deren Visualisierung eine rechenintensive Aufgabe. Ein häufig
verfolgter Ansatz ist die Polygonalisierung der Fläche mit Hilfe des Marching-Cubes-Algorithmus. Das damit erstellte Gitternetz kann zwar performant gerendert werden, erlaubt aber
in der Regel keine interaktive Veränderung von Flächenparametern. Eine andere Herangehensweise besteht im so genannten Raytracing- beziehungsweise Raycasting-Verfahren. Dabei
wird ausgehend von einer virtuellen Kamera der Verlauf von Strahlen durch eine 3D-Szene
untersucht. Die notwendige Berechnung der Schnittpunkte eines solchen Strahls mit einer algebraischen Fläche ist mit den üblicherweise eingesetzten Algorithmen sehr rechenaufwendig.
Raytracer, wie POV-Ray [pov] oder Surf [ESSB], sind daher auch unter Verwendung aktueller Prozessoren kaum in der Lage die anfallenden Berechnungen in Echtzeit durchzuführen.
Durch den rasanten Anstieg der Geschwindigkeit von Grafikhardware, verbunden mit einer
ständigen Erweiterung ihrer Programmierbarkeit, entwickelte sich die Graphics Processing
Unit (GPU) moderner Grafikkarten zu einer erfolgversprechenden Implementierungsplattform
rechenintensiver Algorithmen. Aufgrund der Parallelrechnerarchitektur von Grafikprozessoren
ist gewöhnlich eine Überarbeitung der benötigten Algorithmen erforderlich. Die neuen Möglichkeiten, aber auch die Grenzen der GPU-Programmierung müssen erörtert und analysiert
werden, um eine erfolgreiche Implementierung des GPU-beschleunigten Raycastings algebraischer Flächen zu ermöglichen.
1
1 Einleitung
1.1 Zielstellung
Im Rahmen dieser Diplomarbeit soll die Verwendbarkeit moderner Grafikhardware für die
Visualisierung implizit gegebener, algebraischer Flächen untersucht werden. Es soll ermittelt
werden, wie das Raytracing-Verfahren und insbesondere die Schnittpunktberechnungen zwischen Raytracing-Strahl und algebraischer Fläche auf Grafikprozessoren umgesetzt werden
können, welche Algorithmen sich wie gut dafür eignen und welche Probleme dabei auftreten.
Die Bildsynthese sollte, soweit dies möglich ist, in Echtzeit geschehen, um ein interaktives
Betrachten und Verändern der Fläche zu ermöglichen.
1.2 Aufbau der Arbeit
Zur Einführung in die Thematik wird im zweiten Kapitel dieser Arbeit auf die Grundlagen der
GPU-Programmierung eingegangen. Es werden verschiedene Programmiermodelle und Programmiersprachen vorgestellt und die für die Implementierung genutzte OpenGL-ShadingLanguage genauer betrachtet. Das dritte Kapitel beschreibt die Grundlagen des Raytracings
beziehungsweise Raycastings und deren Umsetzung auf die Grafikhardware. Im vierten Kapitel wird das Raycasting algebraischer Flächen näher untersucht. Beim Raycasting müssen die
Koeffizienten gewisser univariater Polynome, sowie die Nullstellen dieser Polynome bestimmt
werden. Mit den dafür verwendbaren Algorithmen beschäftigen sich die Kapitel fünf und sechs.
Kapitel sieben geht auf Details und Schwierigkeiten der Implementierung der Raycasting-Algorithmen in der OpenGL-Shading-Language ein. Darauf folgt in Kapitel acht eine Bewertung
der umgesetzten Verfahren bezüglich Bildqualität und Laufzeit.
1.3 Wertung
Der verfolgte Ansatz des Raycastings eignet sich gut zur Visualisierung algebraischer Flächen.
Der hohe Aufwand des Verfahrens kann durch leistungsfähige Rechner und Parallelisierung
kompensiert werden. Durch die Verwendung moderner Grafikprozessoren kann das Raycasting algebraischer Flächen so beschleunigt werden, dass auch interaktive Anwendungen möglich sind. Mit steigender Flächenkomplexität wächst auch der Bedarf an Ressourcen, die im
Grafikprozessor nur in geringem Umfang zur Verfügung stehen. Die begrenzte Codegröße und
Laufzeit der Shader-Programme verhindert daher oftmals die Visualisierung komplexer algebraischer Flächen vom Grad größer als zehn.
Trotz der erfolgreichen Umsetzung des Raycasting-Algorithmus stellt die Implementierung
eine Herausforderung dar. Viele der untersuchten Algorithmen erfordern eine aufwendige Anpassung an das Programmiermodell der Grafikhardware. Zudem ruft die für dieses Anwendungsgebiet geringe Genauigkeit von Fließkommazahlen nicht unerhebliche numerische Probleme hervor.
2
2 Grundlagen der GPU-Programmierung
Die Entwicklung von Grafikhardware wurde in den neunziger Jahren maßgeblich durch die steigenden Anforderungen von Computerspielen beeinflusst. Detailgetreue 3D-Szenen verlangten
nach höheren Taktraten und zusätzlichen Funktionen der Grafikbibliotheken, die der Hardware mit jeder neuen Generation hinzugefügt wurden. Spieleentwickler konnten lange Zeit nur
die fest eingebaute Funktionalität der Grafikkarten verwenden, um möglichst realistisch wirkende Bilder zu erzeugen. Die Verwendung neuartiger Effekte in 3D-Anwendungen war daher
maßgeblich an die Einführung neuer Grafikkarten gebunden.
Als Microsoft im Jahr 2001 seine Multimediaschnittstelle DirectX in der Version 8
verabschiedete, änderte sich dies schlagartig. Grafikkarten, die diese Schnittstelle implementierten, waren in der Lage kleine Assemblerprogramme zu verarbeiten, mit denen sich die
Transformation von Punkten und Vektoren beziehungsweise die Farbe eines Pixels berechnen
ließ. Da DirectX schon zum damaligen Zeitpunkt in der Spieleproduktion etabliert war, verbreiteten sich derartige Grafikkarten schnell auf dem Massenmarkt. Umfang und Komplexität
der Programme waren jedoch sehr beschränkt und so forderten Spieleprogrammierer schnell
verbesserte Programmiermodelle und -sprachen. [Sch]
Die enorme Geschwindigkeit, mit der die Grafikprozessoren ihre neuen Aufgaben erledigten,
weckte schon bald Interesse in anderen Bereichen von Wirtschaft und Forschung. Man erkannte, dass deren Anwendungsgebiete nicht länger auf die Darstellung von 3D-Szenen beschränkt
waren. Schnell eröffnete sich das so genannte GPGPU-Feld (General-Purpose computation
on GPUs), welches die GPU als Koprozessor für die verschiedensten rechenintensiven Aufgaben verwendet. Derzeit profitieren vorwiegend Anwendungen in der Echtzeitvisualisierung
und -simulation von diesem Trend (Abbildung 2.1). Doch die ständige Verbesserung der Programmiermodelle und -möglichkeiten bereitet den Weg für eine Reihe anderer Einsatzgebiete.
Grafikkarten mit Unterstützung für DirectX 10 werden beispielsweise zahlreiche Ganzzahlund Bitoperationen erlauben und damit auch für Kryptographiesysteme interessant [NVi06b].
(a)
(b)
(c)
(d)
Abbildung 2.1: Beispielanwendungen, welche von der Verwendung der GPU als Koprozessor profitieren: (a) und (b) Beschleunigung von globalen Beleuchtungsverfahren wie
Radiosity [CHL04], Raytracing und Photon-Mapping [PDC+ 03]. (c) Physikalische Simulation
von Flüssigkeiten, Rauch und Feuer [Cra]. (d) Interaktive Visualisierung und Segmentierung
von MRT-Datensätzen [LKHW05].
3
2 Grundlagen der GPU-Programmierung
GFLOPS
300
200
NVidia GPUs
ATI GPUs
Intel CPUs
100
0
2002
2004
2006
Jahr
Abbildung 2.2: Die Geschwindigkeit von Fließkommaoperationen auf GPUs erhöhte
sich in den letzten vier Jahren im Vergleich zur CPU drastisch. Die Daten wurden
aus [OLG07] entnommen.
Heute übertrifft die Leistung einer aktuellen Grafikkarte die von Desktop-Prozessoren bei
Weitem (Abbildung 2.2). Hinzu kommt, dass Grafikkarten meist deutlich günstiger zu erwerben sind als vergleichbare CPUs. Zum Beispiel kostete ein 3.0 GHz Intel Core2 Duo
(Woodcrest Xeon 5160) im November 2006 circa $874 und besitzt dabei eine theoretische
Spitzenleistung von 48 GFLOPS und eine Speicherbandbreite von 21 GB/s. Eine NVidia
Geforce 8800 GTX war bereits für $599 zu haben. Mit einer empirisch bestimmten Verarbeitungsgeschwindigkeit von 330 GFLOPS und einer Speicherbandbreite von 55.2 GB/s ist
sie der CPU deutlich überlegen. Hierbei darf natürlich nicht verschwiegen werden, dass GPUs
ihre volle Leistungsfähigkeit architekturbedingt nur bei datenparallelen Prozessen entfalten
können. [Lue]
In den folgenden Abschnitten soll genauer auf die Architektur und das Programmiermodell
von programmierbarer Grafikhardware eingegangen werden. Verschiedene GPU-Programmiersprachen werden vorgestellt und es wird eine Einführung in die für diese Arbeit verwendete
Programmiersprache vorgenommen.
2.1 Grafikkartenarchitektur und Programmiermodelle
Die Domäne interaktiver 3D-Grafik besitzt einige Charakteristika, die sich stark von anderen,
weniger spezifischen Anwendungsbereichen abgrenzen. Beispielsweise benötigen interaktive
3D-Anwendungen sehr hohe Verarbeitungsgeschwindigkeiten der zugrunde liegenden Hardware, erlauben aber auch ein hohes Maß an Parallelisierung. Die Teilaufgaben beim interaktiven
3D-Rendering werden daher traditionell in Pipelines organisiert. Der Aufbau der Grafikpipeline bestimmt hierbei maßgeblich die verschiedenen Programmiermodelle.
2.1.1 Die Grafikpipeline
Die Grafikpipeline besteht aus sehr vielen verschiedenen Stufen, die sich in Anzahl und Aufgabe von Hersteller zu Hersteller unterscheiden. Abbildung 2.3 zeigt eine vereinfachte Grafikpipeline, welche die wichtigsten Verarbeitungsblöcke enthält. Da die Funktionalität der einzelnen
Stufen immer gleich ist, bezeichnet man diese auch als Fixed-Function-Pipeline.
4
2.1 Grafikkartenarchitektur und Programmiermodelle
CPU
Application
GPU
Per Vertex
Operations
Primitive
Assembly
Rasterizer
Per
Fragment
Operations
Video Memory
(Textures, Framebuffer, etc.)
Abbildung 2.3: Vereinfachter Aufbau einer Fixed-Function-Grafikpipeline. Die gebräuchlichen englischen Stufenbezeichnungen wurden übernommen. Pfeile in der Abbildung
kennzeichnen Datenflüsse. Quelle: [Lue].
Die Stufe Application sendet die Eckpunkte (Vertices) grafischer Primitive, also Punkte,
Linien oder Polygone, mit Zusatzattributen wie Normalen, Texturkoordinaten und Materialinformationen, von der CPU oder vom Hauptspeicher an die GPU. Die Stufe Per Vertex Operations transformiert jeweils einen Vertex in das 2D-Koordinatensystem des Bildraumes. Je
nach verwendeter Grafik-API können die Eckpunkte bereits hier mit Hilfe der Normalen- und
Materialinformationen beleuchtet werden. Im Primitive Assembly werden die Punkte wieder
zum ursprünglichen Primitiv zusammengesetzt und geclippt1 , wodurch Primitive verschwinden und entstehen können. Für die durch Clipping neu entstandenen Eckpunkte werden alle
benötigten Attribute aus den Nachbarecken interpoliert. Der Rasterizer konvertiert nun die
Geometriedaten in so genannte Fragmente, also Bildpunkte mit Eigenschaften wie Normale,
Texturkoordinate und Tiefenwert, welche wiederum aus den Attributen der Eckpunkte interpoliert werden. In der Stufe Per Fragment Operations wird jeweils ein einzelnes Fragment
verarbeitet. Dieses wird texturiert und gegebenenfalls noch beleuchtet. Das fertige Fragment
wird nun in den Framebuffer im Video Memory geschrieben. Dabei hängt der letztendliche
Farbwert des Pixels von weiteren Schritten wie Alpha-Blending, Tiefen- und Stenciltest ab.
Der Zugriff auf den Video Memory unterliegt hierbei gewissen Regeln. Die Stufe Application
ist je nach verwendeter 3D-API in der Lage Speicherbereiche wie Texturen und Framebuffer
zwischen Grafikspeicher und Hauptspeicher zu transferieren. Dabei werden bei Bedarf automatische Konvertierungen zwischen den Datentypen der GPU und der 3D-API vorgenommen.
Der Datentransfer geschieht im Allgemeinen relativ langsam und sollte daher so wenig wie
möglich genutzt werden. Die Stufe Per Fragment Operations ist zwar in der Lage Texturdaten
zu lesen, kann aber immer nur die Daten, die zum gerade verarbeiteten Pixel gehören, in den
Framebuffer oder in eine Textur schreiben. Da in der Regel mehrere Fragmente gleichzeitig
verarbeitet werden, sind die Resultate häufig undefiniert, wenn man für Lese- und Schreiboperationen dieselbe Textur benutzt.
In der geschilderten Darstellung der Grafikpipeline wird nicht betrachtet, wie der Datenaustausch zwischen den einzelnen Stufen erfolgt. Einige Stufen müssen darüber hinaus aufwendigere Berechnungen durchführen als andere und es ist nicht davon auszugehen, dass alle
Stufen gleich ausgelastet sind. Rendert man beispielsweise ein bildschirmfüllendes Rechteck,
1
Unter Clipping versteht man das Entfernen nicht sichtbarer oder nicht benötigter Teile grafischer Primitive.
Bei der Verarbeitung von Dreiecken kann durch das Abschneiden einer Ecke ein Viereck entstehen, welches
anschließend wieder in zwei Dreiecke zerlegt wird.
5
2 Grundlagen der GPU-Programmierung
so müssen lediglich vier Punkte transformiert und zwei Dreiecke geclippt werden. Zu jedem
Dreieck gehören aber sehr viele Fragmente, wodurch die hinteren Stufen zu einem Engpass
werden. Betrachtet man sehr fein unterteilte Gitternetze, deren einzelne Dreiecke sich auf die
Größe eines Pixels reduzieren, so entsteht der Engpass bei der Verarbeitung der Vertices. Aus
diesem Grund werden in Grafikkarten meist mehrere Pipelines implementiert, die zu einer
gewissen Lastverteilung untereinander in der Lage sind. Die idealisierte Pipeline soll jedoch
weiterhin als theoretisches Modell dienen.
2.1.2 Programmierbare Pipelinestufen
Das grundlegende Modell der Grafikpipeline hat sich bis zur Einführung programmierbarer
Grafikkarten kaum verändert. Gelegentlich wurden die 3D-APIs um neue Funktionen wie beispielsweise Bump Mapping erweitert. Anfangs wurden die benötigten Algorithmen in Hardware umgesetzt, doch die steigende Anzahl an Erweiterungen machte die Hardwareumsetzung
zunehmend kompliziert. Da die Neuerungen sich meist auf die Verarbeitung einzelner Vertices oder Fragmente beschränkten, begannen die GPU-Hersteller, ihre Prozessoren flexibler zu
gestalten. Die betreffenden Teile der Grafikpipeline wurden programmierbar und so konnten
neue Funktionen oft schon durch Aufspielen einer neuen Firmware oder eines neuen Grafikkartentreibers realisiert werden. Die Freigabe der Programmierschnittstelle für die Entwicklergemeinde erlaubte eine Vielzahl neuer Grafikalgorithmen und Effekte, die rasch Einzug in
Computerspiele hielten.
Die Veränderungen der Grafikpipeline werden in Abbildung 2.4 dargestellt. Die Stufe Per
Vertex Operations wurde zum so genannten Vertex Processor, die Stufe Per Fragment Operations zum Fragment Processor. Beide Stufen werden im folgenden Abschnitt erläutert.
Vertex-Prozessor
Der Vertex-Prozessor verarbeitet spezielle Programme, welche als Vertex-Shader, manchmal
auch als Vertex-Programm, bezeichnet werden. Es dient im Allgemeinen dazu, Geometriedaten
vom 3D-Weltkoordinatensystem in das 2D-Koordinatensystem der Bildebene zu transformieren. In der Fixed-Function-Pipeline werden immer alle Vertices eines Polygons der gleichen
Transformation unterworfen. Dem Vertex-Shader wird aber jeder Vertex einzeln übergeben,
CPU
Application
GPU
Vertex
Processor
Primitive
Assembly
Rasterizer
Fragment
Processor
Video Memory
(Textures, Framebuffer, etc.)
Abbildung 2.4: Vereinfachter Aufbau einer programmierbaren Grafikpipeline. Die
gebräuchlichen englischen Stufenbezeichnungen wurden übernommen. Pfeile in der Abbildung
kennzeichnen Datenflüsse. Quelle: [Lue].
6
2.1 Grafikkartenarchitektur und Programmiermodelle
welcher dann parameterabhängig verändert werden kann. So sind selbst aufwendige Deformationen und Animationen dreidimensionaler Objekte möglich.
Als Eingabe für den Shader dienen die bereits erwähnten Geometriedaten. Zusätzlich kann
auf Texturdaten aus dem gemeinsam genutzten Videospeicher und eine Vielzahl von Variablen
und Konstanten zugegriffen werden. Die Ausgabe eines Vertex-Shaders ist mindestens ein
transformierter Vertex. Zusätzlich können noch Variablen an den Fragment-Shader übergeben
werden, die mit dem transformierten Vertex assoziiert werden. Wird das zugehörige Dreieck
gerastert, so werden diese Variablen für jedes Fragment entsprechend interpoliert.
Fragment-Prozessor
Der Fragment-Prozessor verarbeitet so genannte Fragment-Shader oder Fragment-Programme.
Da die Begriffe Pixel und Fragment oft synonym verwendet werden, existieren auch die Bezeichnungen Pixel-Shader und Pixel-Programm. Ein Fragment-Shader wird dazu genutzt die
Farbe eines Pixels zu bestimmen. In einigen Shader-Sprachen kann zusätzlich zur Pixelfarbe
auch noch ein Tiefenwert an die folgenden (in Abbildung 2.4 nicht eingezeichneten) Pipelinestufen übergeben werden. Für die Berechnungen können Texturen, die Variablen, die aus dem
Vertex-Shader stammen, sowie einige globale Variablen und Konstanten verwendet werden.
Die hier vorliegende Arbeit stützt sich auf die beschriebenen programmierbaren Stufen. Aufgrund der rasanten Entwicklung im Bereich der Computergrafik existieren bereits neue Architekturen mit zusätzlichen programmierbaren Prozessoren. DirectX 10 führt beispielsweise
den Geometry Processor ein, welcher in der Lage ist, grafische Primitive auf der Grafikkarte
selbst zu generieren. Das ungewöhnliche Programmiermodell und die schwierige Umsetzung
gängiger Algorithmen für die GPU veranlassten mehrere Grafikkartenhersteller zur Entwicklung spezieller Frameworks, die die GPU als datenparallele, virtuelle Maschine abstrahieren
[NVi06a, PSG06].
2.1.3 Datenparallele Verarbeitung
Die Leistungsfähigkeit moderner Grafikkarten beruht hauptsächlich auf Parallelverarbeitung
unabhängiger Vertices oder Fragmente. Im Gegensatz zu den hoch getakteten, komplexen
CPUs arbeitet in einer GPU eine Vielzahl einfacher Prozessoren mit verhältnismäßig geringem Takt. Bei der GPU-Konstruktion konzentriert man sich vorwiegend auf Vektoroperationen und Texturzugriffe, wobei der Hardware zur Realisierung von Programmverzweigungen
meist weniger Bedeutung beigemessen wird. Daraus resultiert eine Klassifikation der GPUArchitektur ähnlich zum Klassifikationsschema von Flynn [OLG07, KGGK94, S. 16ff].
Ältere GPU-Implementierungen besitzen keinerlei Verzweigungshardware. Stattdessen werden beide Pfade der Verzweigung ausgewertet und die Ergebnisse eines Pfades abhängig von
der Verzweigungsbedingung verworfen. Ähnlich wird bei Zählschleifen verfahren. Statt abhängig von der Schleifenbedingung immer wieder zum Schleifenanfang zu springen, wird der Code
des Schleifenrumpfes entsprechend oft kopiert. Die Compiler von Shader-Hochsprachen sind
meist in der Lage, den dafür benötigten Code automatisch zu generieren. WHILE-Schleifen stellen jedoch ein großes Problem dar, da nicht vorberechnet werden kann, wie oft die Schleife
insgesamt durchlaufen wird. Daher limitieren Shader-Compiler häufig die maximale Anzahl
an Durchläufen, um derartige Schleifen zumindest teilweise zu unterstützen.
Wie von Flynn beschrieben, können in Parallelrechnerarchitekturen, die nach dem MultipleInstruction-Multiple-Data-Prinzip (MIMD) arbeiten, verschiedene Prozessoren auch verschie-
7
2 Grundlagen der GPU-Programmierung
dene Befehle auf verschiedenen Daten durchführen und damit unterschiedlichen Verzweigungspfaden folgen. In Single-Instruction-Multiple-Data-Architekturen (SIMD) können dagegen die
aktiven Prozessoren nur den gleichen Befehl auf verschiedenen Daten ausführen und sind damit
auch nur bedingt für Verzweigungen geeignet. Derzeit weisen nur wenige Grafikkarten in der
Vertex-Prozessor-Stufe eine echte MIMD-Architektur auf. Die Klassifizierung von FragmentProzessoren ist schwieriger. Laut [OLG07] verarbeiten GPUs Fragmente in SIMD-Gruppen.
Wird für alle Fragmente innerhalb einer SIMD-Gruppe eine Verzweigungsbedingung gleich
ausgewertet, so wird nur der zugehörige Verzweigungspfad durchlaufen. Unterscheiden sich
die Werte, dann werden für alle Pixel beide Pfade ausgewertet und anschließend für jeden
Pixel nur die benötigten Ergebnisse übernommen. Da die SIMD-Gruppen meist aus benachbarten Pixeln bestehen, können beim Entwurf von Fragment-Shadern eventuell die daraus
resultierenden Kohärenzen berücksichtigt werden.
Des Weiteren sind die einzelnen Shader-Einheiten für Operationen auf Quadrupeln, wie
RGBA-Farbwerten oder Punkten beziehungsweise Vektoren in homogenen Koordinaten, optimiert. Abbildung 2.5 erläutert am Beispiel der NVidia GeForce 6 Serie, wie Berechnungen
auf Tripeln, Paaren oder skalaren Werten organisiert sein können. GPUs verschiedener Hersteller unterscheiden sich erwartungsgemäß in solchen Details.
Aus der Sicht des beschriebenen Programmiermodells haben die verschiedenen GPUs aber
gemeinsam, dass weder die Vertex-Prozessoren noch die Fragment-Prozessoren untereinander
kommunizieren können. Die Verarbeitung eines Vertex ist aus Softwaresicht vollkommen unabhängig von den anderen, gleichzeitig ablaufenden Vertex-Shadern. Für Fragmente gilt dies
analog, so dass nach dem Auslösen einer Zeichenoperation von der Anwendung bis zum Speichern aller zugehörigen Pixel im Framebuffer keine weitere Möglichkeit zur Synchronisation
besteht.
Man kann also zusammenfassen, dass sich aktuelle GPU-Architekturen am besten in die
SIMD-Kategorie einordnen lassen. Vertex- und Fragment-Prozessoren sind programmierbar.
Ein Vertex-Shader kann zwar Variablen an den folgenden Fragment-Shader übergeben, aber
es existiert keine Möglichkeit Informationen zwischen gleichartigen, gleichzeitig ablaufenden
Shadern auszutauschen. Das beschriebene Programmiermodell ist ungewöhlich im Vergleich
zur CPU, wodurch viele Algorithmen schwierig umzusetzen sind. Weitere Hürden bei der
Programmierung werden in den folgenden Abschnitten beschrieben.
3:1
Operation 1
2:2
Operation 2
Operation 1
Operation 2
Abbildung 2.5: Eine Shader-Einheit der NVidia GeForce 6 kann pro Takt zwei
unabhängige Operationen auf den Elementen eines Quadrupels ausführen. Dabei
kann das Quadrupel entweder im Verhältnis 3:1 oder 2:2 aufgeteilt werden. Können bedingt
durch Datenabhängigkeiten nicht gleichzeitig Operationen auf passenden Datentypen ausgeführt werden, so befinden sich die übrigen Berechnungseinheiten im Leerlauf. Quelle: [KF05].
8
2.2 Programmiersprachen
2.1.4 Stream-Processing
Aufgrund der besonderen Ausrichtung auf Grafikverarbeitung lassen sich viele Algorithmen
aus anderen Bereichen der Informatik nur schwer an das beschriebene Programmiermodell
anpassen. Berechnungen werden gestartet, indem der Grafik-Pipeline grafische Primitive übermittelt werden. Größere Datenmengen können nur in Texturen gespeichert und dann im Shader ausgelesen werden. Die Ergebnisse der Berechnung müssen in den Bereich des Framebuffers
gelegt werden, den das Primitiv abdeckt. Die Ergebnisse aus dem Framebuffer sind aber erst
in einem zweiten Renderingdurchlauf nutzbar, so dass die meisten Algorithmen nur durch
Multipass-Verfahren2 realisiert werden können. Viele Algorithmen sind mit Hilfe des StreamProcessing-Modells leichter umzusetzen, welches kurz erläutert werden soll. Abschnitt 2.2.2
bietet anschließend einen Überblick über gängige GPU-Stream-Processing-Sprachen, die die
Abbildung des allgemeinen Stream-Processings auf das GPU-Programmiermodell vornehmen.
Beim Stream-Processing werden Datenabhängigkeiten und Kommunikationmuster explizit
modelliert, indem Berechnungen durch so genannte Streams und Kernels ausgedrückt werden.
Ein Stream ist eine geordnete Menge von Daten, wobei alle Elemente vom gleichen Datentyp
sind. Ein Kernel ist ein Funktionsblock, dessen Ein- und Ausgabe Streams sind. Die Berechnung eines Elementes im Ausgabestream hängt dabei ausschließlich von der Eingabe ab und
ist unabhängig von den Berechnungen anderer Ausgabeelemente. Ein Kernel besitzt also keinen inneren Zustand. Dank dieser Eigenschaften sind SIMD-Architekturen wie GPUs meist
gut zum Stream-Processing geeigenet. Ein Kernel wird dabei als Fragment-Shader realisiert.
Als Ein- und Ausgabeströme dienen Texturen. Eine Berechnung erfolgt, indem ein Rechteck
in die Ausgabetextur gezeichnet wird, welches die Ergebnisse enthalten soll. Die Texturkoordinaten der Eckpunkte werden so gewählt, dass sie jedem Pixel die passenden Indizes zur
Adressierung der Eingabeströme liefern.
Da Texturen als Eingabeströme dienen, bieten die meisten Grafikkarten die Möglichkeit
auch direkt in eine Textur zu rendern (Render-To-Texture). Dadurch wird der langsame Datentransfer zwischen Haupt- und Grafikspeicher vermieden, der sonst zur Verwendung des
Framebuffers als Textur notwendig wäre. Häufig wird zusätzlich die Ping-Pong-Tecknik verwendet. Diese Technik arbeitet mit zwei Texturen. Im ersten Renderdurchgang enthält die
erste Textur die Eingabedaten, während die zweite Textur für die Ausgabe genutzt wird. In
jedem folgenden Renderingdurchgang werden die Rollen jeweils getauscht, da die Eingabedaten des vorherigen Durchgangs in der Regel nicht mehr benötigt werden. So entsteht ein
effizienter, wenn auch umständlicher Weg zur Nutzung der GPU-Performance. Glücklicherweise werden diese Details größtenteils durch die Stream-Processing-Sprachen verborgen.
Einen guten Überblick über weitere Methoden, die beim Stream-Processing auf GPUs zum
Einsatz kommen, liefert beispielsweise [OLG07].
2.2 Programmiersprachen
Um die Möglichkeiten der GPUs auch nutzen zu können, müssen natürlich entsprechende Programmiersprachen vorhanden sein. Dieser Abschnitt geht auf die bekanntesten Hochsprachen
und deren Unterschiede ein. Im Bereich des interaktiven 3D-Renderings werden meist Shading-Languages mit direkter Programmierung von Vertex- und Fragment-Shadern genutzt.
2
Multipass-Verfahren nutzen mehrere Renderingdurchläufe um ein bestimmtes Ergebnis zu berechnen. Dabei
werden für einen Durchlauf jeweils die Teilergebnisse der bereits durchgeführten Renderings verwendet.
9
2 Grundlagen der GPU-Programmierung
Im GPGPU-Bereich sind dagegen Stream-Processing-Sprachen gebräuchlich. Die zahlreich
vorhandenen Assembler-Sprachen3 sind nicht mehr zeitgemäß und werden daher nicht näher
betrachtet. Eine Einführung in die OpenGL-Shading-Language, die in dieser Arbeit Verwendung fand, wird separat in Abschnitt 2.3 gegeben.
2.2.1 Shading-Languages
Ein Shading-Language-Programm besteht aus einem Vertex-Shader und einem zugehörigen
Fragment-Shader. Es ist nicht möglich nur einen Shader zu verwenden und anstelle des zweiten
Shaders auf Teile der Fixed-Function-Pipeline zurückzugreifen. Es existieren sowohl von der
3D-API abhängige, als auch API-übergreifende Sprachen. Die wichtigsten Vertreter sind hier
HLSL, GLSL und Cg. Alle drei Sprachen werden bei der Übersetzung in Zwischensprachen
überführt, die teilweise starken Restriktionen unterliegen.
HLSL
Microsoft entwickelte für DirectX die Sprache HLSL (High Level Shading Language).
Sie ist nur mit Microsoft Windows und DirectX nutzbar und wurde in dieser Arbeit
nicht verwendet. Da bei der Spieleproduktion jedoch weitestgehend DirectX eingesetzt wird,
orientieren sich viele Grafikkartenhersteller an diesem Standard.
HLSL baut auf speziellen Assemblersprachen für Vertex- und Fragment-Shader auf, die in
verschiedenen Versionen existieren. Die Fähigkeiten dieser Assemblersprachen werden im so
genannten Shader-Model beschrieben. Die der OpenGL-Shading-Language (GLSL) zugrundeliegenden Assemblersprachen besitzen kein solches Versionssystem. Daher wird auch im
Zusammenhang mit GLSL auf das Shader-Model Bezug genommen.
Ältere Versionen des Shader-Model variierten hauptsächlich in der Größe der verarbeitbaren
Programme und der Anzahl und Art der Texturzugriffe. Mit dem Shader-Model 3, welches
erstmals in DirectX 9.0c verfügbar wurde, enthielten Fragment-Prozessoren die nötigen Fähigkeiten zum Raytracing komplexer dreidimensionaler Objekte [KF05]:
Erhöhte Instruktionsanzahl Der Quelltext eines Fragment-Programms darf im Shader-Model 3 maximal 65535 Maschinenbefehle erzeugen. Kürzere Programme, die bedingt durch
Schleifen und Funktionsaufrufe mehr als 65535 Befehle ausführen, werden abgebrochen.
Hierbei ist allerdings nicht klar, wie Befehle einer Hoch- oder Assemblersprache auf die
Maschinenbefehle umgesetzt werden, da jeder GPU-Hersteller einen eigenen Instruktionssatz verwendet.
32-Bit Fließkommagenauigkeit Fragment-Shader können nur mit 32-Bit Fließkommazahlen
arbeiten. Das Zahlendarstellung entspricht dabei der IEEE-754-Norm für einfache Genauigkeit:
VZ
1 Bit
3
Exponent
8 Bit
Mantisse
23 Bit
Als Beispiele seien hier die Assembler-Sprachen GL_ARB_vertex_program, GL_ARB_fragment_program,
GL_NV_vertex_program, GL_NV_fragment_program, GL_ATI_vertex_shader, GL_ATI_fragment_shader,
GL_NV_register_combiners und GL_NV_texture_shader genannt, die jeweils in verschiedenen Versionen
vorliegen.
10
2.2 Programmiersprachen
Allerdings gibt es keine Darstellung für ungültige und unendliche Werte und es kommt
hinzu, dass die Implementierung der Fließkommaoperationen nicht genau spezifiziert ist,
um den GPU-Herstellern etwas Optimierungsspielraum zu lassen. Verschiedene GPUs
können also für die gleiche Berechnung unterschiedliche Ergebnisse liefern.
Verzweigungen Shader-Model 3 erlaubt bedingte Sprünge und Schleifen. Shading-LanguageCompiler haben leider immer noch große Probleme mit bedingten Array-Zuweisungen
(siehe Abschnitt 7.1.1).
Cg
Cg steht für „C for graphics“ und wurde von NVidia entworfen, um GPU-Programme über
mehrere Plattformen und APIs hinweg nutzbar zu machen. Cg kann nur über den Compiler
des Cg-Toolkits benutzt werden. Die Übersetzung von Cg erfolgt profilabhängig in eine andere
Shading-Language, die von der gerade verwendeten API unterstützt werden muss. Die Wahl
des Profils bestimmt daher den Funktionsumfang von Cg. So existieren beispielsweise für die
Shader-Models von DirectX verschiedene Profile.
Um die Funktionalität des Shader-Model 3 im Cg-Toolkit 1.5 [NVi07] und unter OpenGL
nutzen zu können, stehen zwei Profile zur Verfügung:
vp40, fp40 Profile, welche Cg in die Assemblersprachen übersetzen, die durch die OpenGLErweiterungen NV_vertex_program2 und NV_fragment_program2 spezifiziert sind. Sie
werden nur durch NVidia-Grafikkarten unterstützt.
glslv, glslf Profile, die Cg nach GLSL übersetzen.
Cg bietet durch die profilabhängige Übersetzung einen Vorteil für Computerspiele, da Effekte
leichter für verschiedene Hardware umgesetzt werden können. Benötigt man jedoch die Funktionalität von Shader-Model 3, so ist die direkte Verwendung von GLSL vorzuziehen, da die
Zwischenschicht, die der Cg-Compiler darstellt, eine weitere potentielle Fehlerquelle ist und
zudem die Compilezeit erhöht.
GLSL
Aufgrund der oben genannten Gründe und bereits vorhandener Kenntnisse wurde für diese Arbeit die OpenGL-Shading-Language (GLSL) verwendet. Sie kann ausschließlich mit
der OpenGL-API genutzt werden und ist seit OpenGL 2.0 fest im OpenGL-Standard
integriert. GLSL wurde durch das Architectural Review Board (ARB), welches aus Mitarbeitern von Wirtschaft und Forschung besteht, entworfen und spezifiziert. Wie die meisten
ARB-Entwicklungen kann auch GLSL 1.0 als OpenGL-Erweiterung genutzt werden. Dazu
werden in OpenGL 1.5 die Erweiterungen ARB_Shader_Objects, GL_ARB_Vertex_Shader,
GL_ARB_Fragment_Shader und GL_ARB_Shading_Language_100 benötigt. Eine Einführung
in GLSL wird in Abschnitt 2.3 gegeben.
2.2.2 Stream-Processing-Languages
Stream-Processing-Sprachen verwenden Streams, um Daten auszutauschen und Kernels um
Berechnungen durchzuführen. Sie abstrahieren von der konkreten Struktur der Grafikpipeline und ermöglichen so eine deutlich vereinfachte Verwendung der GPU als Koprozessor. Es
existieren zwei bekannte Frameworks, die dieses Konzept umsetzen: BrookGPU und Sh.
11
2 Grundlagen der GPU-Programmierung
BrookGPU
Brook ist eine Erweiterung des ANSI-C-Standards durch die Universität von Stanford. Die
Erweiterungen bestehen aus Konstrukten zur Beschreibung von Kernels und Streams und
aus einer API, in der häufig verwendete Algorithmen umgesetzt sind. Das BrookGPU-System besteht aus einem eigenen Compiler und einem Laufzeitsystem. Letzteres setzt die im
Programm auftretenden Berechnungen auf die jeweilige GPU um. [BFH+ 04]
Sh
Sh ist eine in C++ eingebettete Sprache. Sie benötigt keinen eigenen Compiler, sondern ist
als C++-Bibliothek implementiert. [Rap06]
Stream-Processing-Sprachen wurden in dieser Arbeit aus Zeitgründen nicht berücksichtigt.
Möglicherweise lässt sich das Raytracing algebraischer Flächen im Stream-Processing-Modell
leichter und stabiler auf die GPU umsetzen, als dies beim direkten Einsatz von ShadingLanguages der Fall ist.
2.3 Einführung in die OpenGL-Shading-Language
Die im Juni 2003 freigegebene Sprachbeschreibung der OpenGL-Shading-Language ist eine
der wichtigsten Neuerungen von OpenGL seit Veröffentlichung der OpenGL-Version 1.0 im
Jahre 1992. Sie erlaubt die Programmierung von Vertex- und Fragment-Shadern, wodurch die
Standardfunktionalität der zugehörigen Stufen in der OpenGL-Grafikpipeline ersetzt wird.
An dieser Stelle wird Bezug auf die Spezifikationen von OpenGL 2.0 [SA04] und der
OpenGL-Shading-Language 1.10 [KBR04] genommen. Die OpenGL-Spezifikation enthält
nur die zur Ausführung und Ansteuerung der Shader notwendigen Funktionen. GLSL selbst
wird in einem separaten Dokument beschrieben. Die folgenden Abschnitte beschreiben beide
Aspekte. Ein abschließendes Beispiel verdeutlicht die Nutzung von GLSL.
2.3.1 OpenGL-API für GLSL
Der Quelltext von GLSL-Shadern wird in OpenGL in Shader-Objekten organisiert. Mehrere
Shader-Objekte zusammen bilden ein Programm-Objekt. In einem Programm-Objekt muss
mindestens ein Vertex-Shader-Objekt und ein Fragment-Shader-Objekt vorhanden sein. Der
Code aller Shader-Objekte eines Typs bildet jeweils eine Einheit, so dass Funktionen, die in
einem Shader-Objekt definiert sind, auch in den anderen Shader-Objekten verwendet werden
können. Dies soll die Erstellung von Shader-Bibliotheken erleichtern. Leider wird diese Funktionalität von den getesteten Grafikkartentreibern nicht korrekt umgesetzt (siehe Abschnitt
7.1.1).
Shader-Objekte
Ein Shader-Objekt wird in der Anwendung über einen Index referenziert, welcher über die
Funktion
GLuint glCreateShader( GLenum type );
12
2.3 Einführung in die OpenGL-Shading-Language
angefordert werden kann. Der Wert von type ist dabei entweder GL_VERTEX_SHADER oder
GL_FRAGMENT_SHADER. Einem Shader-Objekt wird mittels
void glShaderSource( GLuint shader,
GLsizei count,
const GLchar **string,
const GLint *length );
ein Shader-Quelltext zugewiesen. shader gibt die ID des Shaders und count die Anzahl der
zuzuweisenden Quelltextteile an. string enthält diese Quelltextteile und length speichert die
Längen der einzelnen Strings. Wurde dem Shader-Objekt bereits ein Quelltext zugewiesen, so
wird dieser überschrieben. Ist ein Shader-Objekt mit GLSL-Quelltext versehen, so kann es
mit
void glCompileShader( GLuint shader );
kompiliert werden. Nicht mehr benötigte Shader-Objekte können und sollten mit
void glDeleteShader( GLuint shader );
gelöscht werden, um den belegten Speicher wieder freizugeben.
Programm-Objekte
GLSL-Programm-Objekte werden in OpenGL durch
GLuint glCreateProgram( void );
erzeugt. An ein solches Programm-Objekt können mehrere Shader-Objekte angehängt werden.
Dafür steht die Funktion
void glAttachShader( GLuint program, GLuint shader );
zur Verfügung. Ein Shader-Objekt kann einem Programm-Objekt bereits hinzugefügt werden,
bevor es einen Quelltext besitzt. Sind alle nötigen Shader-Objekte kompiliert und angefügt,
so müssen die einzelnen Teile das GLSL-Programms noch mit
void glLinkProgram( GLuint program );
gebunden werden. Damit sind die Schritte zum Erstellen eines vollständigen GLSL-Programms unter OpenGL abgeschlossen. Nun kann die Funktionalität der Fixed-Function-Pipeline mit
void glUseProgram( GLuint program );
durch das GLSL-Programm ersetzt werden. Der Aufruf glUseProgram( 0 ); schaltet wieder
zur Standard-Funktionalität zurück.
Wird ein GLSL-Programm nicht mehr benötigt, so müssen zuerst alle Shader-Objekte
vom Programm-Objekt entfernt werden bevor dieses gelöscht werden kann. Dazu dienen die
nachfolgenden Funktionen.
void glDetachShader( GLuint program, GLuint shader );
void glDeleteProgram( GLuint program );
glDetachShader löscht hier nicht den Shader, sondern trennt nur Shader- und Programm-
Objekte voneinander.
13
2 Grundlagen der GPU-Programmierung
Fehlerbehandlung
Beim Kompilieren und Binden des GLSL-Programms können natürlich Fehler auftreten. Ob
diese Operationen erfolgreich waren, lässt sich mit den Funktionen
void glGetShaderiv( GLuint shader, GLenum pname, GLint *params );
void glGetProgramiv( GLuint program, GLenum pname, GLint *params );
prüfen. pname gibt dabei an, welche Daten ausgelesen und in params gespeichert werden
sollen. Für einen Shader kann GL_COMPILE_STATUS und für ein Programm GL_LINK_STATUS
und GL_VALIDATE_STATUS abgefragt werden. Die Validierung mit
void glValidateProgram( GLuint program );
ist ein zusätzlicher Schritt, welcher prüft, ob das GLSL-Programm auch tatsächlich auf der
vorhandenen Hardware und mit dem aktuellen OpenGL-Status lauffähig ist. Enthält die
Ergebnisvariable params den Wert GL_FALSE, so trat bei der betreffenden Aktion ein Fehler auf.
Um die genaue Fehlerursache zu ermitteln, stehen zwei so genannte Info-Logs zur Verfügung.
Diese können mit den Befehlen
void glGetShaderInfoLog( GLuint shader,
GLsizei bufSize,
GLsizei *length,
GLchar *infoLog );
void glGetProgramInfoLog( GLuint program,
GLsizei bufSize,
GLsizei *length,
GLchar *infoLog );
ausgelesen werden. Die notwendige Puffergröße für das Info-Log kann man zuvor als das Attribut GL_INFO_LOG_LENGTH mit glGetShaderiv beziehungsweise glGetProgramiv bestimmen.
Shader-Variablen
Ein Shader kann auf eine Vielzahl von Variablen zugreifen, die sich in drei Gruppen einteilen
lassen. Die Variablen der verschiedenen Gruppen werden im Shader-Code unterschiedlich deklariert. Um von der Anwendung auf Shader-Variablen zugreifen zu können, muss das ShaderProgramm vollständig kompiliert und gebunden sein.
uniform-Variablen
Globale Variablen werden als uniform deklariert. Sie können in Vertex- und Fragment-Shadern ausgelesen, aber nicht beschrieben werden. Derartige Variablen können nur von der Anwendung verändert werden und repräsentieren meist Teile des aktuellen Systemzustands wie
beispielsweise Zeitparameter einer Animation.
Der Zugriff auf uniform-Variablen innerhalb der Anwendung erfolgt über einen Index. Um
die Verbindung zwischen den textuellen Bezeichnern im Shader und dem Index im Programm
herzustellen, wird die Funktion
GLint glGetUniformLocation( GLuint program, const GLchar *name );
verwendet. Die Zuweisung eines Wertes an eine uniform-Variable erfolgt je nach Variablentyp
über eine der folgenden Funktionen. Für eine genauere Erläuterung der einzelnen Varianten
sei auf die OpenGL-Spezifikation [SA04] verwiesen.
14
2.3 Einführung in die OpenGL-Shading-Language
void glUniform{1234}{if}( GLint location, T value );
void glUniform{1234}{if}v( GLint location, GLsizei count, T value );
void glUniformMatrix{234}{f}v( GLint location,
GLsizei count,
GLboolean transpose,
const GLfloat *value );
uniform-Variablen sind über ein komplettes grafisches Primitiv konstant und können daher
nur außerhalb eines glBegin/glEnd-Blockes verändert werden.
attribute-Variablen
Beim Übergeben von Vertices an die Grafikpipeline ist es meist wünschenswert auch zusätzliche Attribute wie Materialien, Normalen und Texturkoordinaten angeben zu können. Dies
kann wie gewohnt über die Standardfunktionen von OpenGL erfolgen. Im Vertex-Shader
sind die entsprechenden Variablen vordefiniert. Möchte man zusätzlich Attribute übergeben,
die OpenGL nicht bekannt sind, so können diese im Vertex-Shader als attribute-Variablen
deklariert werden, deren Index in der Anwendung mit
GLint glGetAttributeLocation( GLuint program, const GLchar *name );
bestimmt werden kann. Bei der Übergabe eines Vertex kann der Attributwert je nach Variablentyp über eine der folgenden Funktionen an den Vertex gebunden werden.
void glVertexAttrib{1234}{sfd}( GLuint index, T values );
void glVertexAttrib{123}{sfd}v( GLuint index, T values );
void glVertexAttrib4{bsifd ubusui}v( GLuint index, T values );
varying-Variablen
Im Fragment-Shader sind anstelle von Attributen die so genannten varying-Variablen verfügbar. Sie entstehen bei der Interpolation der Ausgaben des Vertex-Shaders. Diese Art von
Variablen kann daher nicht direkt von OpenGL aus angesteuert werden.
2.3.2 GLSL-Sprachelemente
Die Sprachbeschreibung von GLSL [KBR04] ist sehr umfassend und kann nicht im Detail
besprochen werden. Da GLSL sehr stark an C angelehnt ist, wird nur auf die wichtigsten
Unterschiede eingegangen.
Datentypen
Die primitiven Datentypen beschränken sich in GLSL auf float, int und bool. GLSL-Fließkommazahlen entsprechen in ihrer Darstellung den IEEE-Fließkommazahlen einfacher Genauigkeit (32 Bit). Es ist jedoch nicht festgelegt, wie die Operationen auf den Fließkommazahlen
genau durchzuführen sind. Lediglich die OpenGL-Richtlinien für Fließkommaberechnungen
müssen erfüllt werden (siehe [SA04, S. 6]). Der int-Datentyp existiert nur als Programmierhilfe und muss nicht direkt in der Hardware umgesetzt werden. Die Zahlendarstellung sieht
16 Bit für den Betrag der Zahl und 1 Bit für das Vorzeichen vor. Die zugrunde liegende
Hardware kann den int-Datentyp auf float abbilden und muss nur sicherstellen, dass die
Operationen für den 16 Bit Wertebereich [−32.767, 32.767] korrekte Ergebnisse liefern. Der
15
2 Grundlagen der GPU-Programmierung
Datentyp bool enthält nur die Elemente true und false. Diese können bei Bedarf in Ganzoder Fließkommazahlen mit den Werten 1 oder 0 konvertiert werden.
Für jeden der primitiven Datentypen existieren Vektordatentypen verschiedener Dimension:
vec2, vec3, vec4 für float-Vektoren; ivec2, ivec3, ivec4 für int-Vektoren; bvec2, bvec3,
bvec4 für bool-Vektoren. Matrizen sind nur für Fließkommazahlen vorhanden: mat2, mat3,
mat4.
Texturzugriffe erfolgen über so genannte sampler-Variablen. GLSL definiert entsprechende
Datentypen für ein- und mehrdimensionale Texturen, Cube- und Shadow-Maps: sampler1D,
sampler2D, sampler3D, samplerCube, sampler1DShadow und sampler2DShadow.
Benutzerdefinierte Datentypen können durch Verbunde (struct) realisiert werden, die auch
geschachtelt werden können. Weiterhin können in GLSL auch eindimensionale Arrays verwendet werden. Mehrdimensionale Arrays können nur simuliert werden, indem Arrays von Verbunden gebildet werden, die wiederum Arrays enthalten. Allerdings muss die Größe eines Arrays
bereits zur Übersetzungszeit feststehen. Es gibt zur Laufzeit keine Möglichkeit zusätzlichen
Speicherplatz zu belegen.
Qualifizierer
Variablendeklarationen können einen oder mehrere Qualifizierer enthalten. Mit const deklarierte Variablen sind nach ihrer Initialisierung nicht mehr änderbar. Für globale Shader-Variablen sind die bereits in Abschnitt 2.3.1 erläuterten Qualifizierer uniform, attribute und
varying erlaubt, wobei varying-Variablen keine Verbunde und attribute-Variablen weder
Verbunde noch Arrays enthalten dürfen.
Da es in GLSL keine Referenzen beziehunhsweise Zeiger gibt, erfolgen Zuweisungen und Parameterübergaben immer durch Wertekopien. Um auch Funktionen mit Rückgabeparametern
zu realisieren, können Funktionsparameter mit out beziehungsweise inout deklariert werden.
Das Standardverhalten ist gleichwertig zu einer Deklaration mit in.
Rekursion
Der Quelltext von GLSL-Programmen kann analog zur Programmiersprache C in Funktionen
beziehungsweise Prozeduren gegliedert werden. Allerdings verbietet die GLSL-Spezifikation
jede Form von Rekursion. Viele rekursive Algorithmen können auch durch Schleifen implementiert werden, wobei Zwischenergebnisse in einem Stack verwaltet werden. Die beschränkten
Zugriffsmöglichkeiten auf Arrays (siehe Abschnitt 7.1.1) verhindern aber möglicherweise die
erfolgreiche Umsetzung einiger rekursiver Algorithmen.
Standardbibliothek und Operatoren
In GLSL steht eine Vielzahl arithmetischer Operationen zur Verfügung. Neben den komponentenweise durchgeführten Operationen auf Vektoren und Matrizen, sind auch Skalarprodukt,
Matrixprodukt, Multiplikation von Matrizen und Vektoren und vieles mehr definiert. In der
Standardbibliothek sind unter anderem Trigonometrie-, Exponential- und Interpolationsfunktionen vorhanden.
GLSL verfügt darüberhinaus über komplexe Zugriffsoperationen für Vektordatentypen. Die
Einzelkomponenten eines Vektors können über den (.)-Operator gefolgt vom Komponentennamen angesprochen werden:
16
2.3 Einführung in die OpenGL-Shading-Language
vec4
v.x;
v.r;
v.s;
v;
v.y; v.z; v.w;
v.g; v.b; v.a;
v.t; v.p; v.q;
// Syntax für Raumkoordinaten
// Syntax für Farbwert
// Syntax für Texturkoordinaten
Dabei kann auch auf mehrere Komponenten gleichzeitig und in beliebiger Reihenfolge zugegriffen werden. Die verschiedenen Bezeichnungsschemata dürfen jedoch nicht vermischt werden.
vec4 v4 = vec4( 1.0, 2.0, 3.0, 4.0 );
vec2 v2 = vec2( 5.0, 6.0 );
v2 = v4.xz;
v2 = v4.ww;
v4.rgba = v4.wzyx + v2.xxxx
v2 = v4.rx;
//
//
//
//
v2 == ( 1.0, 3.0 )
v2 == ( 4.0, 4.0 )
v4 == ( 8.0, 7.0, 6.0, 5.0 )
Fehler (gemischtes Bezeichnungsschema)
Vektoren können auch wie Arrays indiziert werden. Der gleichzeitige Zugriff auf mehrere Elemente ist damit allerdings nicht möglich.
Vordefinierte Variablen
GLSL-Programme können auf einen Großteil der aktuellen OpenGL-Zustandsvariablen zugreifen. Dazu zählen insbesondere Modelview- und Projektionsmatrix, sowie Parameter der
Lichtquellen. Darüberhinaus verfügen Vertex- und Fragment-Shader jeweils über eigene Variablen, die zum Teil im nächsten Abschnitt beschrieben werden.
Shadereintrittspunkte und -ausgaben
Der Einstiegspunkt von Vertex- und Fragment-Shader ist jeweils die main-Routine. Diese hat
weder Parameter noch Rückgabewerte. Die Kommunikation mit den benachbarten Teilen der
Grafikpipeline erfolgt ausschließlich über globale Variablen.
Als Eingabe für den Vertex-Shader dient insbesondere die Variable gl_Vertex, welche den
zu bearbeitenden Vertex enthält. Der Vertex-Shader transformiert den Vertex, speichert ihn in
gl_Position und übergibt ihn damit dem Rasterizer. Andere als varying deklarierte Variablen werden ebenfalls vom Rasterizer interpoliert und an den Fragment-Shader weitergereicht.
Der Fragment-Shader benutzt die eingehenden Shader-Variablen, um den Farbwert des
zugehörigen Pixels zu bestimmen. Dieser wird in gl_FragColor abgelegt. Zusätzlich zum
Farbwert kann auch die Tiefe eines Pixels bestimmt werden und über gl_FragDepth zum
Tiefentest weitergegeben werden.
2.3.3 GLSL-Beispiel
Zum besseren Verständnis wird nun ein kurzes GLSL-Beispiel vorgestellt. Ziel ist es mit Hilfe
eines GLSL-Programms aus einem ebenen Gitternetz eine farbige, wehende Fahne zu erzeugen.
Abbildung 2.6 zeigt die Schritte von der Ebene bis zur fertigen Fahne.
Das gezeigte Gitternetz wird zwischen den Punkten (0, 0, 0) und (3, 2, 0) aufgespannt. Um
das Wehen der Fahne zu erzeugen, wird die z-Koordinate eines Gitterpunktes durch eine
Sinusschwingung modelliert. Als Parameter für die Sinusschwingung dienen die x-Koordinate
des Punktes sowie ein Zeitparameter timestamp. Eine Seite einer Fahne wird meist an einem
17
2 Grundlagen der GPU-Programmierung
(a)
(b)
(c)
Abbildung 2.6: Einzelschritte des GLSL-Beispiels: (a) regelmäßiges, planares Gitter; (b)
Gitternetz der wehenden Fahne; (c) eingefärbte, wehende Fahne.
Mast befestigt, wodurch sich die Fahne an dieser Seite kaum bewegen kann. Zur Modellierung
dieses Verhaltens wird die Schwingung abhängig von der x-Koordinate gedämpft. Es ergibt
sich folgende Berechungsvorschrift für die z-Koordinate eines Vertex:
z = sin(x + timestamp) ∗ x
(2.1)
Um die Schwingung besser der Fahnengröße anzupassen, kann die Vorschrift bei der Implementierung im Vertex-Shader noch leicht modifiziert werden. Der Vertex-Shader hat außer
der Transformation zusätzlich die Aufgabe, die Farbattibute, die jedem Vertex angehängt
wurden, an den Rasterizer weiterzugeben. Der Fragment-Shader übernimmt das Färben der
Fahne. Da der Rasterizer den benötigten Farbwert bereits über die Fläche interpoliert hat,
muss der Farbwert lediglich der Ausgabevariable gl_FragColor zugewiesen werden. Die Shader müssen nun noch von einem OpenGL-Programm geladen und angesteuert werden. Die
dieser Arbeit beiliegende DVD enthält den zum Rendering der Fahne notwendigen Quelltext.
Der Effekt aus dem Beispiel hätte auch leicht ohne GLSL realisiert werden können. Die
Verwendung von Shadern hat jedoch gegenüber dem konventionellen Vorgehen einen klaren
Vorteil. Durch die Animation der Fahne verändern sich die Positionen der einzelnen Gitterpunkte in jedem Frame. Soll die Berechnung ohne Shader durchgeführt werden, so müssten
die Vertices und Normalen der Fahne in jedem Bild abhängig vom Animationsparameter auf
der CPU neu berechnet und zur Grafikkarte gesendet werden. Dies erhöht die CPU-Last und
den Datenaustausch mit der Grafikkarte. Erfolgt die Berechnung der Animation auf der GPU,
so ist das von der CPU erzeugte Gitternetz immer gleich und kann daher in einer OpenGLDisplay-Liste gespeichert werden. Dadurch wird der gesamte Code zum Rendern der Fahne
im Grafikspeicher gehalten4 . Die CPU muss nur den Animationsparameter verändern und den
Zeichenvorgang auslösen. Zu Testzwecken wurde eine CPU-basierte Variante der Fahnenanimation erstellt. Auf einem System bestehend aus einer AMD Athlon 2000+ CPU und einer
NVidia GeForce 6600 GT wurde eine Szene mit 100 Fahnen in jeweils verschiedenen Animationsstadien gerendert. Die CPU-basierte Animation konnte mit 31 Bildern pro Sekunde
dargestellt werden. Der Einsatz von Display-Listen und Shadern steigerte die Bildwiederholrate auf 131 Bilder pro Sekunde.
4
Die OpenGL-Spezifikation schreibt die Speicherung der Display-Listen im Grafikspeicher nicht vor. Dennoch
unterstützen die meisten modernen Grafikkarten diese Beschleunigungstechnik.
18
2.4 Werkzeuge und Bibliotheken
2.4 Werkzeuge und Bibliotheken
Um die GLSL-Programmierung zu erleichtern wurde eine Reihe von Programmen und Bibliotheken entwickelt. Einige davon wurden in dieser Arbeit als Hilfmittel genutzt und sollen
daher kurz vorgestellt werden.
ATI RenderMonkey
RenderMonkey [ATI] ist ein Rapid-Prototyping-Werkzeug zur Shader-Entwicklung, welches
besonders für den Einstieg in die Thematik geeignet ist. Es erledigt die Shader-Übersetzung
und die Übergabe von Shader-Variablen. Der Nutzer kann Shader-Parameter über eine GUI
einstellen und die Wirkung der erstellten Shader unmittelbar an vorgefertigten 3D-Modellen
überprüfen. Sogar Multipass-Verfahren können mit RenderMonkey leicht umgesetzt werden.
NVidia NVemulate
Auf Systemen mit NVidia-Grafikkarten kann NVemulate [NVi] als Debugging-Werkzeug
verwendet werden. Einerseits kann durch NVemulate das Verhalten verschiedener NVidiaGPUs emuliert werden und sogar auf einen Software-Renderer umgeschaltet werden. Andererseits können diverse Logfiles generiert werden, die die Shader-Nutzung einer Anwendung
protokolieren. Unter den gesammelten Daten finden sich Shader-Quelltexte, der Assemblercode der Zwischensprachen und Fehlermeldungen, die beim Kompilieren und Binden der Shader
entstehen.
3Dlabs GLSLvalidate
Mit GLSLvalidate [3Dl] lassen sich GLSL-Shader auf Verstöße gegen die Spezifikation von
GLSL prüfen. In einigen Fällen sind die generierten Fehlermeldungen aussagekräftiger als die
der Grafikkartentreiber.
GLee
GLee [Woo] steht für GL Easy Extension und erleichtert den Zugriff auf die Funktionalität
von aktuellen OpenGL-Versionen. Da sich die mitgelieferten OpenGL-Bibliotheken einiger
Compiler (darunter auch Microsoft Visual Studio) seit Jahren nicht verändert haben,
können beispielsweise die Funktionen zur Verwendung der OpenGL-Shading-Language nur
umständlich über den OpenGL-Erweiterungsmechanismus erreicht werden. GLee besteht
aus einer aktuellen OpenGL-Headerdatei und einer Bibliothek, die das Laden der benötigten
Erweiterungen automatisch übernimmt, sofern diese von der vorhandenen Grafikhardware
unterstützt werden.
2.5 Zusammenfassung
Durch den Einsatz von Shadern können die Berechnungen in der Grafikpipeline flexibel gestaltet werden. Im betrachteten Modell können Vertex- und Fragment-Shader mit Hilfe von
Hochsprachen programmiert werden. Auch wenn das ungewöhnliche Programmiermodell die
Umsetzung einiger Algorithmen erschwert, können viele berechnungsintensive Anwendungen
durch die Parallelverarbeitung auf aktuellen GPUs beschleunigt werden.
19
2 Grundlagen der GPU-Programmierung
20
3 Grundlagen des GPU-basierten
Raytracings
Zur Darstellung einer 3D-Szene sind eine Reihe verschiedener Verfahren bekannt. Die Idee
des so genannten Raycasting-Algorithmus wurde 1968 von Appel [App68] entwickelt und
1980 durch Whitted zum rekursiven Raytracing [Whi80] (auch Whitted-Raytracing genannt)
erweitert.
Beim Raytracing können globale Beleuchtungsphänomene, also Wechselwirkungen aller Objekte und Lichtquellen der Szene, berücksichtigt werden, wodurch die Erzeugung photorealistischer Bilder möglich wird. Der damit verbundene hohe Berechnungsaufwand verhinderte
lange Zeit den Einsatz des Raytracings in der Echtzeitvisualisierung. Programmierbare GPUs
und Spezialhardware, wie die Ray-Processing-Unit der Universität des Saarlandes [WSS05],
ermöglichten erstmals Bildwiederholraten, wie sie für interaktive Anwendungen benötigt werden.
3.1 Der Raytracing-Algorithmus
Der ursprüngliche Raycasting-Algorithmus [App68] geht von einer virtuellen Kamera, bestehend aus Augpunkt und Bildebene, aus, durch die die Szene betrachtet wird. Vom Augpunkt
werden durch jeden Pixel der Bildebene ein oder mehrere Strahlen, so genannte Primärstrahlen, in die Szene ausgesendet, um den Farbwert des Pixels zu bestimmen. Dabei wird das erste
Szenenobjekt ermittelt, welches den Strahl schneidet. Anhand der Lichtquellen in der Szene
kann für den Schnittpunkt ein lokaler Beleuchtungswert ermittelt werden, der die Pixelfarbe
bestimmt. Hierfür nutzt man beispielsweise das Phong-Beleuchtungsmodell, wie es auch in
OpenGL zur Anwendung kommt [SA04, S. 59ff]. Leider kann dieses einfache Verfahren keine
globalen Beleuchtungseffekte realisieren.
Das rekursive Raytracing [Whi80] ermöglicht hingegen Spiegelungen, Brechungen und Schatten. Dies geschieht wie in Abbildung 3.1 durch die Generierung so genannter Sekundärstrahlen
und Schattenfühler. Die Sekundärstrahlen entstehen, wenn ein Strahl an einem Objektschnittpunkt reflektiert oder gebrochen wird. Ein Schattenfühler ist lediglich ein weiterer Strahl vom
Objektschnittpunkt in Richtung einer Lichtquelle. Durch ihn kann bei der Beleuchtung eines Punktes bestimmt werden, ob ein weiteres Szenenobjekt die Lichtquelle verdeckt und der
Punkt damit im Schatten liegt. Die Primär- und Sekundärstrahlen definieren einen Baum von
Strahlen, der sich jeweils an Schnittpunkten mit Objekten verzweigt. In hinreichend großen
Szenen kann dieser Baum sehr tief werden. Da tief liegende Knoten meist nur einen sehr
geringen Beitrag zur Farbe des Pixels liefern, werden ab einer bestimmten Baumtiefe keine
weiteren Sekundärstrahlen erzeugt. Die Farbe an einem Schnittpunkt zwischen Strahl und Objekt wird nun durch ein lokales Beleuchtungsmodell unter Berücksichtigung der Schattenfühler
bestimmt und mit den rekursiv ermittelten Farbinformationen der zugehörigen Sekundärstrahlen verrechnet.
21
3 Grundlagen des GPU-basierten Raytracings
reflektierter Strahl
Flächennormale
Primärstrahl
Augpunkt
Bildebene
Schattenfühler
gebrochener
Strahl
Szenenobjekt
Abbildung 3.1: Generierung der Strahlen beim rekursiven Raytracing: Ein Primärstrahl verläuft vom Augpunkt durch einen Pixel der Bildebene in die Szene. Schneidet der
Primärstrahl ein Objekt, so werden Schattenfühler, reflektierter und gebrochener Strahl weiterverfolgt.
Das vorgestellte Verfahren nach Whitted liefert für viele Szenen gute Ergebnisse. Es scheitert aber, sobald Lichtbündelungen (Kaustiken) oder diffuse Lichtreflektionen die Beleuchtung
der Szene maßgeblich beeinflussen. Um diese Beleuchtungsphänomene auch beim Raytracing
zu berücksichtigen, wurden komplexere Algorithmen wie beispielsweise Path-Tracing [LWS93]
und Photon-Mapping [Jen96] entwickelt, die jedoch auch einen erheblichen Anstieg der Renderingzeit mit sich bringen. Abbildung 3.2 verdeutlicht die Unterschiede zwischen Raycasting,
dem Raytracing nach Whitted und Photon-Mapping.
3.1.1 Beschleunigung des Raytracings
Bereits Whitted [Whi80] stellte in seinem Artikel über das rekursive Raytracing fest, dass
die Berechnung der Schnittpunkte zwischen den Strahlen und den in der Szene befindlichen
Objekten die rechenintensivste Aufgabe des Verfahrens darstellt. Der naive Ansatz testet jeden
Strahl mit jedem grafischen Primitiv der Szene. Die meisten Beschleunigungsverfahren legen
daher die Primitive in speziellen Datenstrukturen ab, mit denen sich leicht abfragen lässt,
welche Primitive überhaupt vom Strahl getroffen werden können. Der Schnittpunkttest wird
nur auf die verbliebenen Primitive angewandt. Gängige Beschleunigungsverfahren wie uniform
grids, k-d trees und bounding volume hierarchies wurden bereits für die GPU umgesetzt. Ein
Vergleich der Implementierungen findet sich in [TS05].
Die genannten Verfahren entfalten ihre Wirkung in Szenen mit vielen, meist einfachen Objekten, nicht jedoch beim Raytracing einzelner, komplexer Primitive wie algebraischer Flächen.
Die zum Rendern der Fläche benötigte Zeit hängt daher maßgeblich von den verwendeten
Schnittpunkttests ab. Sie werden in den Kapiteln 5 und 6 näher erläutert.
3.2 Anpassung des Algorithmus für die GPU
Das Raytracing algebraischer Flächen ist eine sehr rechenintensive Aufgabe. Um eine echtzeittaugliche Bildwiederholrate von etwa 15 Bildern pro Sekunde [AMMH02, S. 1] erzielen zu
können, wurde in dieser Arbeit auf die Generierung der Sekundärstrahlen und Schattenfühler
22
3.2 Anpassung des Algorithmus für die GPU
(a) Raycasting
(b) rekursives Raytracing
(c) Photon-Mapping
Abbildung 3.2: Vergleich zwischen Raycasting, rekursivem Raytracing nach Whitted
und Photon-Mapping: (a) Raycasting kann verdeckte Flächen eliminieren und Lichtintensitäten nach einem lokalen Beleuchtungsmodell berechnen. (b) Beim rekursiven Raytracing
werden Spiegelungen und Brechungen korrekt dargestellt, es fehlen Kaustiken sowie diffuse
Reflektionen. Schatten erscheinen hart, da das Flächenlicht nur als Punkt betrachtet wird.
(c) Die Szene wird durch Photon-Mapping korrekt global beleuchtet. Alle Szenen wurden mit
SunFlow [Sun] gerendert.
verzichtet. Die Darstellung der algebraischen Flächen erfolgt also durch die einfachste Form
des Raytracings, dem Raycasting.
Das Raycasting kann in konventionellen 3D-APIs wie OpenGL und DirectX dazu genutzt
werden, komplexere Primitive als nur Polygone darzustellen. Dazu wird mit Hilfe der 3D-API
die umgebende Box1 des Primitives gezeichnet. Beim Rastern der Box kann im FragmentShader für jeden Pixel der betreffende Teil des Primitives durch Raycasting bestimmt und
beleuchtet werden. Statt einer umgebenden Box kann natürlich auch ein beliebiges anderes
Polygon gezeichnet werden. Es dient lediglich dazu, das Raycasting auf einen Ausschnitt der
Bildebene zu begrenzen. Abbildung 3.3 verdeutlicht noch einmal das grundlegende Vorgehen
beim GPU-basierten Raycasting.
3.2.1 Aufgaben des Vertex-Shaders
Zur Simulation des Raycastings in der umgebenden Box müssen zuerst die Primärstrahlen
erzeugt werden. Beim Zeichnen der Box gelangen deren Eckpunkte v untransformiert in den
Vertex-Shader und können dort mit Hilfe der Modelview-Matrix M an die gewünschte Position
in der Szene transformiert werden. Da der Augpunkt im Koordinatenursprung o liegt, ergibt
sich der Primärstrahl eines Eckpunktes als
r1 (t) = o + t · (M v − o)
(3.1)
mit t ∈ [1, ∞]. Das Primitiv, welches durch die Box repräsentiert wird, müsste nun ebenfalls
dieser Transformation unterzogen werden. Einfacher ist es aber die inverse Transformation
auf den Strahl anzuwenden. Analog können zusätzliche Transformationen des Primitives innerhalb der Box in die Berechnung des Strahls einbezogen werden. Sind diese zusätzlichen
Transformationen in der Matrix S zusammengefasst2 , so ergibt sich der Strahl, mit dem die
1
2
Es ist natürlich ausreichend nur die Vorderseiten der Box zu zeichnen.
Die benötigten Matrizen müssen dem Shader gegebenenfalls als uniform-Parameter übergeben werden.
23
3 Grundlagen des GPU-basierten Raytracings
Primärstrahl
Augpunkt
Szenenobjekt mit umgebender Box
Bildebene
Abbildung 3.3: GPU-basiertes Raycasting: Die Anwendung zeichnet die umgebende Box
der darzustellenden Objekte. Beim Rendern der Box berechnen Shader, was der Betrachter
beim Blick in die Box sehen würde.
Schnittpunktberechnung durchgeführt wird, als
r2 (t) = S −1 M −1 (o + t · (M v − o))
(3.2)
Ursprung und Richtung der so erzeugten Primärstrahlen werden anschließend in varyingVariablen gespeichert und so dem Rasterizer übergeben, der sie für jedes Fragment interpoliert.
3.2.2 Aufgaben des Fragment-Shaders
Der Fragment-Shader führt nun das eigentliche Raycasting durch. Er enthält den Code zur
Berechung des Schnittpunktes phit und der Normale ~nphit , sowie zur Beleuchtung des ermittelten Schnittpunktes. Der Schnittpunkt von Strahl und Fläche wird im Allgemeinen nicht
direkt auf der Vorderseite der Box liegen. Im Fragment-Shader sollte daher zusätzlich zur
Pixelfarbe noch der Tiefenwert des Schnittpunktes ausgegeben werden. Auf diese Weise kann
der Tiefenpuffertest auch weiterhin zur Elimination verdeckter Flächen genutzt werden, was
die Darstellung mehrerer Primitive, deren umgebende Boxen sich überschneiden, stark vereinfacht.
Die Realisierung des Raycastings über Fragment-Shader hat trotz der Geschwindigkeit moderner GPUs einen Nachteil. Obwohl die zu benachbarten Primärstrahlen gehörenden Schnittpunkte meist nahe beieinander liegen, kann diese Kohärenz aufgrund der unabhängigen Verarbeitung der Primärstrahlen nicht direkt zur Beschleunigung der Schnittpunktberechnung
genutzt werden. Über ein Multipass-Verfahren könnte jedoch eine Beschleunigung erzielt werden. Im ersten Durchgang rendert man die Szene mit einer verringerten Auflösung. Der zweite
Durchgang rendert in voller Auflösung und verwendet die Ergebnisse des ersten Renderings
als Startnäherung für die Schnittpunktberechnung. Die Tauglichkeit dieses Ansatzes konnte
aus Zeitgründen leider nicht mehr untersucht werden.
3.2.3 Koordinatensystem der Beleuchtungsberechnung
Die Beleuchtungsberechnung findet erwartungsgemäß im Koordinatensystem des Augpunktes statt. Der in dieses System zurücktransformierte Schnittpunkt ergibt sich als M Sphit . Die
24
3.2 Anpassung des Algorithmus für die GPU
Transformation der Normale kann jedoch nicht auf diese Weise vorgenomen werden. Enthalten
die Matrizen M und S beispielsweise nicht-uniforme Skalierungen, so steht die transformierte
Normale nicht mehr senkrecht zur Fläche. Wie in [Tur90] gezeigt wurde, bleibt diese Eigenschaft hingegen erhalten, wenn man die Matrix ((M S)−1 )T verwendet.
3.2.4 Alternative Ansätze
Die Ergebnisse des vorgestellten Raycasting-Algorithmus lassen sich hervorragend mit konventionellem Polygon-Rendering kombinieren, da die genutzten Beleuchtungsinformationen
weitestgehend gleich sind. Für mehr Realismus kann man zum Preis einer geringeren Bildwiederholrate auch rekursives Raytracing oder gar Photon-Mapping einsetzen. Komplette oder
teilweise Portierungen dieser Algorithmen sind für die GPU bereits erfolgt [PBMH02, WMK04,
PDC+ 03].
Bedingt durch die begrenzte Programmlänge von Shadern kann der Farbwert eines Pixels
jedoch nicht mehr in einem Renderingdurchlauf bestimmt werden. Die Daten der generierten
Sekundärstrahlen werden daher vom Fragment-Shader ausgegeben und im nächsten Durchlauf
als Eingabe verwendet. Bei der Schnittpunktberechnung müssen zudem alle Objekte der Szene
betrachtet werden, was den Einsatz von Beschleunigungsstrukturen sinnvoll macht.
Man kann leicht erahnen, dass die hohe Komplexität der Algorithmen und die daraus resultierenden Multipass-Verfahren eine direkte Umsetzung in Shader-Programme recht umständlich machen. Möglicherweise eignet sich das Stream-Processing-Modell, wie es in [PBMH02]
beschrieben wird, besser für derartige Anwendungen.
25
3 Grundlagen des GPU-basierten Raytracings
26
4 Raycasting algebraischer Flächen
Implizite und insbesondere auch algebraische Flächen sind ein wichtiges Hilfsmittel in der
wissenschaftlichen Modellierung und Visualisierung. Eine implizite Fläche ist definiert als die
Nullstellenmenge einer Funktion F : R3 → R, das heißt die Fläche wird durch die Menge aller
Punkte (x, y, z) ∈ R3 gebildet, die die Gleichung F (x, y, z) = 0 erfüllen1 . Durch F wird der
Raum also in ein Gebiet mit F (x, y, z) < 0, ein Gebiet mit F (x, y, z) > 0 und ein Gebiet mit
F (x, y, z) = 0 aufgeteilt, wobei jeweils zwei der Gebiete auch leer sein können.
Implizite Flächen, die durch ein Polynom in x, y und z beschrieben sind, werden algebraische
Flächen genannt. Sie können durch die Summe
X
ai,j,k xi y j z k
(4.1)
F (x, y, z) =
i,j,k∈N
beschrieben werden, wobei man die ai,j,k als Koeffizienten und die Produkte ai,j,k xi y j z k als
Monome bezeichnet. Der Grad eines solchen Monoms ist die Summe seiner Potenzen. Der
Grad einer algebraischen Fläche ist als das Maximum der Grade seiner Monome definiert,
wobei nur Monome mit von Null verschiedenen Koeffizienten betrachtet werden. Abbildung
4.1 zeigt einige algebraische Flächen vom Grad 2.
Zur Visualisierung algebraischer Flächen kann beispielsweise das im vorigen Kapitel erläuterte Raytracing- beziehungsweise Raycasting-Verfahren zum Einsatz kommen. Nachfolgend
wird ein Überblick über die Besonderheiten beim Raycasting algebraischer Flächen gegeben.
Die Erzeugung der Primärstrahlen und die Beleuchtungsberechnung unterscheiden sich nicht
vom Raycasting anderer Primitive und werden daher nicht näher untersucht.
1
Prinzipiell lassen sich auch Flächen in höherdimensionalen Räumen definieren. Zur Visualisierung müssen
sie jedoch in den R3 projiziert werden.
(a) Kugel: x2 + y 2 + z 2 − 1 = 0
(b) Zylinder: x2 + z 2 − 1 = 0
(c) Doppelkegel: x2 − y 2 + z 2 = 0
Abbildung 4.1: Einige algebraische Flächen vom Grad 2, so genannte Quadriken.
27
4 Raycasting algebraischer Flächen
4.1 Schnittpunktberechnung
Um die Schnittpunkte zwischen algebraischer Fläche und Raycasting-Strahl zu bestimmen
kann man den Strahl
r(t) = (x(t), y(t), z(t))T = o + t · d~
(4.2)
in die Flächengleichung einsetzen:
(4.3)
F (x(t), y(t), z(t)) = f (t) = 0
Die Schnittpunktberechnung reduziert sich damit auf die Nullstellenbestimmung eines univariaten Polynoms f : R → R. Die Transformation in das univariate Polynom
f (t) =
n
X
bi ti .
(4.4)
i∈N
muss gegebenenfalls explizit erfolgen, da einige Algorithmen zur Nullstellenbestimmung die
Koeffizienten bi benötigen. Kapitel 5 und 6 beschäftigen sich ausführlich mit der Berechnung
der Polynomkoeffizienten und der Nullstellen.
4.2 Clipping
Algebraische Flächen, wie beispielsweise der in Abbildung 4.1b dargestellte Zylinder, dehnen
sich häufig bis ins Unendliche aus. Bei der Visualisierung betrachtet man daher meist nur
einen kleinen Ausschnitt der Fläche, der zum Beispiel von einer Kugel oder einem Würfel
begrenzt ist. Der Raycasting-Strahl r(t) kann an dem Begrenzungskörper geclippt werden, so
dass man ein Intervall [a, b] erhält, in dem sämtliche zu berücksichtigenden Schnittpunkte der
Fläche mit dem Strahl liegen.
Die Kenntnis des Intervalls [a, b] kann weiterhin dazu genutzt werden, die Rechengenauigkeit bei der Bestimmung der Schnittpunkte zu erhöhen, welche auf der GPU mit 32-Bit
Fließkommazahlen erfolgt. Für eine fest vorgegebene algebraische Fläche wird die Genauigkeit
aller Berechnungen maßgeblich von der Wahl des Strahlursprungs o und der Strahlrichtung d~
beeinflusst. In Tests hat sich die folgende Konstruktion eines optimierten Strahls
ropt (topt ) = oopt + topt · d~opt
bewährt:
oopt = r
a+b
2
und d~opt = 0.5
(4.5)
d~
~.
|d|
(4.6)
Sie entstand aus der Beobachtung, dass im Intervall [−1, 1] in etwa genauso viele IEEE-754Fließkommazahlen liegen, wie außerhalb dieses Intervalls. Durch die Wahl des optimierten
Strahlursprungs in der Mitte des „interessanten“ Intervalls befinden sich die zur Darstellung
wichtigen Nullstellen in der Nähe von topt = 0 und können daher gegebenenfalls genauer als
entferntere Nullstellen bestimmt werden. Die Bedingung |d~opt | = 0.5 verhindert außerdem bei
vielen Flächen ein zu starkes Anwachsen der Polynomkoeffizienten und den damit verbundenen
Genauigkeitsverlust, der zu einer Verschiebung der Nullstellen führen kann. Natürlich handelt
es sich hierbei nur um eine Heuristik. Es lassen sich leicht Flächenformeln konstruieren, bei
denen diese Vorgehensweise fehlschlägt.
28
4.3 Flächennormale
4.3 Flächennormale
Zur Bestimmung des Normalenvektors ~n der algebraischen Fläche kann die als Gradient bezeichnete Funktion
∂F ∂F ∂F
grad(F ) =
,
,
(4.7)
∂x ∂y ∂z
genutzt werden. Der Vektor grad(F )(x, y, z) steht senkrecht zur Tangentialebene von F im
Punkt (x, y, z) und zeigt immer in Richtung des größten Anstiegs der Funktionswerte von F ,
also in das Gebiet mit F (x, y, z) > 0 [Kön04, S. 52ff]. Der Gradient kann auch numerisch
berechnet werden, indem man die partiellen Ableitungen durch Differenzenquotienten ersetzt:
∂F
F (x + h, y, z) − F (x, y, z)
(x) ≈
∂x
h
∂F
F (x, y + h, z) − F (x, y, z)
(y) ≈
∂y
h
F (x, y, z + h) − F (x, y, z)
∂F
(z) ≈
∂z
h
(4.8)
(4.9)
(4.10)
Mit Hilfe des Gradienten lässt sich auch bestimmen, ob der Betrachter von einem Gebiet mit
F (x, y, z) < 0 oder F (x, y, z) > 0 auf die Fläche schaut, so dass für beide Fälle verschiedene Oberflächenmaterialien gewählt werden können. Dazu betrachtet man den Cosinus des
Winkels zwischen dem Normalenvektor ~n und der Richtung d~ des Raycasting-Strahls, die der
Blickrichtung des Betrachters entspricht (siehe Abbildung 4.2). Es reicht nicht aus, wenn nur
das Vorzeichen von F am Augpunkt ermittelt wird, da der Raycasting-Strahl die Fläche aufgrund des Clippings mehrmals durchdringen kann, bis er auf einen Punkt der Fläche innerhalb
des Clipping-Intervalls trifft.
Häufig wird das Gebiet mit F (x, y, z) < 0 als das Innere und das Gebiet mit F (x, y, z) > 0
als das Äußere der Fläche bezeichnet. Für eine Kugel x2 + y 2 + z 2 − r2 = 0 ist dies intuitiv klar, jedoch können die Rollen der Gebiete leicht durch Multiplikation der Gleichung mit
−1 vertauscht werden. Weiterhin gibt es so genannte nicht-orientierbare Flächen, die im mathematischen Sinn nur eine Seite besitzen. Beispiele hierfür sind das Möbius-Band und die
Kleinsche Flasche (Abbildung 4.3).
~n
Augpunkt
α
F (x, y, z) < 0
Augpunkt
d~
F (x, y, z) > 0
F (x, y, z) > 0
~n
α
d~
F (x, y, z) < 0
(a) Der Betrachter schaut vom einem Gebiet mit (b) Der Betrachter schaut vom einem Gebiet
F (x, y, z) < 0 auf die Fläche. Der Winkel α ist klei- mit F (x, y, z) > 0 auf die Fläche. Der Win~· ~
n
kel α ist größer als 90◦ und dementsprechend
ner als 90◦ und dementsprechend cos(α) = |dd||~
~ n| > 0.
~· ~
n
cos(α) = |dd||~
~ n| < 0.
Abbildung 4.2: Die Bestimmung des Gebietes, durch das die Fläche betrachtet wird,
kann anhand des Winkels zwischen dem Normalenvektor und der Richtung des
Raycasting-Strahls erfolgen.
29
4 Raycasting algebraischer Flächen
(a) Möbius-Band
(b) Kleinsche Flasche mit eingebettetem Möbius-Band
Abbildung 4.3: Beispiele nicht-orientierbarer Flächen. Jeder Punkt des Möbius-Bandes
(a) kann erreicht werden, ohne die Fläche zu durchdringen oder den Rand zu überqueren. Die
Fläche besitzt also nur eine Seite. Die in (b) dargestellte Kleinsche Flasche enthält ein MöbiusBand, so dass Innen- und Außenseite nicht unterschieden werden können. Quelle: [Pol03].
30
5 Berechnung der Polynomkoeffizienten
Das Einsetzen des Raycasting-Strahls (x(t), y(t), z(t))T = o+td~ in die Gleichung F (x, y, z) = 0
einer algebraischen Fläche vom Grad n reduziert die Berechnung formal auf die Nullstellenbestimmung eines univariaten Polynoms f (t) vom Grad n, da jeweils nur lineare Polynome
durch lineare Polynome ersetzt werden. Obwohl das Polynom f (t) vielfältig dargestellt werden kann, verwenden die meisten Verfahren zur Nullstellenberechnung die Darstellung in der
bereits vorgestellten Monombasis
n
X
ai ti .
(5.1)
f (t) =
i=0
Als Ausgangspunkt der Berechnung der Koeffizienten ai dient natürlich das Polynom F (x, y, z),
welches direkt aus einer Benutzereingabe stammt. Vom Benutzer kann nicht erwartet werden,
dass er die Flächenformel in einer Basisdarstellung formuliert, da diese meist deutlich mehr
Terme enthält und weniger aussagekräftig ist als die folgende, rekursive Darstellung der Polynome: Die einfachsten Polynome werden durch reelle Konstanten und durch die Variablen x,
y und z gebildet. Ist P ein Polynom, so auch −P und P i mit i ∈ N. Sind P1 und P2 Polynome,
so auch P1 + P2 , und P1 · P2 .
Die beschriebene Notation soll anhand der Huntschen Fläche motiviert werden. Sie lässt
sich als
4(x2 + y 2 + z 2 − 13)3 + 27(3x2 + y 2 − 4z 2 − 12)2 = 0
(5.2)
beschreiben. In der Monombasis entsteht daraus die unhandliche Gleichung
4x6 + 12x4 y 2 + 12x4 z 2 + 12x2 y 4 + 24x2 y 2 z 2 + 12x2 z 4
+ 4y 6 + 12y 4 z 2 + 12y 2 z 4 + 4z 6 + 87x4 − 150x2 y 2 − 960x2 z 2
− 129y 4 − 528y 2 z 2 + 276z 4 + 84x2 + 1380y 2 + 4620z 2 − 4900 = 0. (5.3)
Die nachfolgend beschriebenen Verfahren sind daher so formuliert, dass sie auch mit rekursiv
aufgebauten Polynomen umgehen können.
5.1 Termumformungen auf der CPU
Die Funktionen der einzelnen Strahlkoordinaten




ox + td~x
x(t)

y(t) = o + td~ = 
oy + td~y 
z(t)
oz + td~z
(5.4)
werden für die Variablen x, y und z in F (x, y, z) eingesetzt. Auf der CPU kann dieses Einsetzen nur für den generischen Strahl aus Gleichung (5.4) vorgenommen werden, da die genauen
Werte von o und d~ erst beim eigentlichen Raycasting im Fragment-Shader bekannt sind. Die
31
5 Berechnung der Polynomkoeffizienten
Berechnungen müssen also symbolisch durchgeführt werden. Das Polynom f (t) besteht nun
aus Termen, die mit Hilfe von Polynomaddition, Polynommultiplikation und Polynompotenzierung zur Monomdarstellung vereinfacht werden können. Aus der Monomdarstellung lassen
sich dann die Formeln für die einzelnen Koeffizienten ablesen. Dies soll am Beispiel einer Kugel
erläutert werden.
Eine Kugel mit Radius 1 und Mittelpunkt im Koordinatenursprung ist durch die Gleichung
0 = x2 + y 2 + z 2 − 1
(5.5)
gegeben. Setzt man die Strahlkoordinaten ein, so erhält man
0 = (ox + td~x )2 + (oy + td~y )2 + (oz + td~z )2 − 1
= (o2 + 2ox td~x + t2 d~ 2 ) + (o2 + 2oy td~y + t2 d~ 2 ) + (o2 + 2oz td~z + t2 d~ 2 ) − 1
=
x
2
(ox
x
y
y
z
z
2
+ o2y + o2z − 1) + (2ox d~x + 2oy d~y + 2oz d~z )t + (d~x2 + d~y2 + d~z2 )t
(5.6)
(5.7)
(5.8)
und kann die Koeffizienten leicht ablesen.
a0 = (o2x + o2y + o2z − 1)
a1 = (2ox d~x + 2oy d~y + 2oz d~z )
(5.10)
a2 = (d~x2 + d~y2 + d~z2 )
(5.11)
(5.9)
Die durch Termumformungen ermittelten Berechnungsvorschriften für die Koeffizienten müssen nun als Codefragmente in den Fragment-Shader eingefügt werden. Anschließend können
dort die Koeffizienten durch das Einsetzen konkreter Strahlen berechnet werden.
Leider hat dieses CPU-basierte Verfahren den Nachteil, dass die Berechnungsvorschriften
auch bei einfachen Polynomen ohne geeignete Optimierungsstrategien sehr lang werden können. Derartige Optimierungen wurden in dieser Arbeit zugunsten des folgenden Verfahrens
nicht näher untersucht.
5.2 Termumformungen auf der GPU
Führt man die im vorigen Abschnitt beschriebenen Umformungen im Fragment-Shader durch,
so sind Ursprung und Richtung des Strahls bekannt und man kann mit konkreten Werten
rechnen. Funktionen zur Polynomaddition, -multiplikation und -potenzierung, die am Ende
dieses Abschnitts kurz erläutert werden, müssen dazu einmalig in der Shading-Language programmiert werden. Der zur darzustellenden Fläche gehörende Umformungscode kann dann als
Folge dieser Polynomoperationen realisiert werden. Man beginnt mit den linearen Polynomen
für x, y und z, sowie mit konstanten Polynomen für alle in der Flächenformel enthaltenen
Konstanten und setzt diese zum Polynom f (t) zusammen.
Diese Vorgehensweise soll erneut am Beispiel der Kugel verdeutlicht werden. Als Strahlursprung soll o = (1, 1, 1) dienen, die Strahlrichtung wird auf d~ = (2, 3, 4) festgelegt. Daraus
resultieren die linearen Polynome
x = 1 + 2t,
32
y = 1 + 3t und z = 1 + 4t,
(5.12)
5.2 Termumformungen auf der GPU
die nun in die Kugelgleichung eingesetzt werden.
0 = x2 + y 2 + z 2 − 1
2
(5.13)
2
2
(5.14)
= (1 + 2t) + (1 + 3t) + (1 + 4t) − 1
2
2
2
= (1 + 4t + 4t ) + (1 + 6t + 9t ) + (1 + 8t + 16t ) − 1
2
2
= (2 + 10t + 13t ) + (1 + 8t + 16t ) − 1
2
= (3 + 18t + 29t ) − 1
= 2 + 18t + 29t2
(5.15)
(5.16)
(5.17)
(5.18)
Die Koeffizienten sind also a0 = 2, a1 = 18 und a2 = 29.
Da keinerlei symbolische Berechnungen durchgeführt werden müssen, lässt sich das geschilderte Verfahren deutlich leichter umsetzen als die Termumformungen auf der CPU. Das Polynom F (x, y, z) muss jedoch in einer Darstellung vorliegen, aus der sich der Umformungscode
leicht ableiten lässt. Syntaxbäume eignen sich dafür besonders gut.
5.2.1 Polynomoperationen
Um das beschriebene Verfahren anwenden zu können, benötigt man Operationen zur Addition,
Multiplikation und Potenzierung von Polynomen. Seien dazu die beiden Polynome
X
X
pn (x) =
ai xi und qm (x) =
bj xj
(5.19)
i∈N
j∈N
vom Grad n beziehungsweise m gegeben, wobei die Koeffizienten ai mit i > n beziehungsweise
bj mit j > m zur einfacheren Definition der Operationen gleich Null gesetzt werden. Die
Addition dieser Polynome ist dann gegeben durch
max(n,m)
pn (x) + qm (x) =
X
(ai + bi )xi .
(5.20)
i=0
Die Multiplikation kann durch eine Faltung der Koeffizientenarrays realisiert werden:
pn (x) · qm (x) =
n+m
X
xi
i=0
Die Potenz pn (x)k für k ∈ N wird üblicherweise mit

k
2

(pn (x) 2 )
pn (x)k = pn (x) · pn (x)k−1


1
i
X
aj bi−j
(5.21)
j=0
Hilfe der Vorschrift
für gerade k 6= 0
für ungerade k
für k = 0
(5.22)
berechnet, welche lediglich O(log k) Iterationen anstelle der O(k) Iterationen des trivialen
Algorithmus
(
pn (x) · pn (x)k−1 für k > 0
k
pn (x) =
(5.23)
1
für k = 0
33
5 Berechnung der Polynomkoeffizienten
benötigt. Aufgrund ihrer Einfachheit kann die letztgenannte Berechnungsvorschrift für kleine
k und Polynome von niedrigem Grad aber in der Praxis dennoch schneller sein.
5.3 Polynominterpolation
Anhand von (n + 1) Wertepaaren (xi , fi ) mit xi , fi ∈ R, i ∈ {0, . . . , n} und paarweise verschiedenen xi lässt sich ein Polynom
n
X
ak xk
(5.24)
pn (x) =
k=0
vom Grad n bestimmen, welches die Wertepaare, auch Interpolationsstellen genannt, exakt
interpoliert. Es gilt also
pn (xi ) =
n
X
ak xki = fi
k=0
∀i ∈ {0, . . . , n}
(5.25)
Das Interpolationspolynom pn (x) ist eindeutig, das heißt es existiert kein von pn (x) verschiedenes Polynom vom gleichen Grad, welches ebenfalls durch die gegebenen Wertepaare verläuft.
pn (x) ist genau dann Lösung der gestellten Interpolationsaufgabe, wenn dessen Koeffizientenvektor (a0 , . . . , an ) Lösung des linearen Gleichungssystems

   
1 x0 x20 · · · xn0
a0
f0
 .. ..
..
..
..   ..  =  .. 
(5.26)
. .
.
.
.  .   . 
1 xn x2n · · · xnn
an
fn
|
{z
}
Vn
ist. Die Matrix Vn ist eine so genannte Vandermonde-Matrix, deren Determinante sich über
die Formel
n−1
n
Y Y
det(Vn ) =
(xj − xi )
(5.27)
i=0 j=i+1
berechnen lässt. Da angenommen wurde, dass die xi paarweise verschieden sind, ist diese Determinante niemals Null und die Matrix Vn regulär. Das Gleichungssystem ist somit eindeutig
lösbar1 und das Interpolationspolynom durch die Koeffizienten ai eindeutig bestimmt (vgl.
[EMNW05, S. 351f]).
Derartige Vandermonde-Systeme sind häufig schlecht konditioniert, so dass kaum ein numerisches Verfahren die Koeffizienten akkurat bestimmen kann [PTVF96, S. 90ff, S. 120]. Die
Verwendung dieser Methode beim Raycasting konnte die schlechte Konditionierung bestätigen
(siehe Abschnitt 8.1.1).
Nichtsdestotrotz werden nun Algorithmen erläutert, die die Berechnung der Koeffizienten
bei der Transformation von F (x, y, z) nach f (t) mit Hilfe eines Interpolationpolynoms durchführen. Nimmt man an, dass der Grad der algebraischen Fläche bekannt ist, so kann man
entlang des Strahls (n + 1) verschiedene Stützstellen ti mit ti ∈ R, i ∈ {0, . . . , n} wählen und
damit (n + 1) Punkte (x(ti ), y(ti ), z(ti )) auf dem Strahl berechnen. Diese Punkte können in
1
Sind alle fi gleich Null, so wäre auch (a0 , . . . , an ) = (0, . . . , 0) Lösung des Gleichungssystems. Allerdings
wäre das resultierende Interpolationspolynom nicht mehr vom Grad n.
34
5.3 Polynominterpolation
f (t)
pn (t)
pn (t)
(6,5)
(6,5)
(7,5)
(6,5)
(5,4)
(5,4)
(4,2.99)
(4,2.9)
(3,2)
(4,2.99)
(3,2)
(2,1)
(2,1)
t
(a)
(2,1)
t
(b)
(8,2.5)
t
(c)
Abbildung 5.1: Sensibiltät der Polynominterpolation gegenüber Störungen: (a) zeigt
den Verlauf des gesuchten Polynoms f (t) und für die Interpolation ungünstige Stützstellen,
die fast auf einer Geraden liegen. Nimmt man nun wie in (b) an, dass die Interpolation durch
Rechenfehler bei der Auswertung von f (t) an der Stützstelle t = 4 mit dem Wertepaar (4, 2.99)
statt mit (4, 2.9) durchgeführt wird, so unterscheidet sich das resultierende Polynom deutlich
von f (t). In (c) wurden bessere Stützstellen gewählt, die nicht zu einer Geraden degenerieren
können. Das fehlerhaft berechnete Wertepaar (4, 2.99) hat kaum Einfluss auf das Ergebnis.
die Flächenformel eingesetzt werden und man erhält (n + 1) Werte fi = F (x(ti ), y(ti ), z(ti )).
Die Wertepaare (ti , fi ) dienen als Grundlage für die Interpolation. Da das Interpolationspolynom pn (t) das einzige Polynom ist, welches die berechneten Wertepaare interpoliert, muss es,
analytisch betrachtet, mit dem gesuchten Polynom f (t) übereinstimmen.
Durch ungünstige Wahl der Stützstellen und durch die begrenzte Genauigkeit von Fließkommaoperationen können die berechneten Koeffizienten jedoch von denen des gesuchten Polynoms abweichen und damit wichtige Eigenschaften wie beispielsweise die Lage und Existenz
von Nullstellen verändern. Abbildung 5.1 verdeutlicht diese Problematik. In der Numerik
werden zur Polynominterpolation in der Regel die so genannten Tschebyscheff-Knoten, die
Nullstellen der Tschebyscheff-Polynome, verwendet, da diese den Interpolationsfehler gering
halten. Die hier durchgeführte Transformation bestimmt jedoch die unbekannten Koeffizienten
eines Polynoms durch Abtastung und anschließende Interpolation. Der theoretische Interpolationsfehler ist daher unabhängig von der Wahl der Stützstellen gleich Null. Lediglich die
Rundungsfehler der Fließkommaberechnungen beeinflussen das Ergebnis.
Auch wenn bereits einige Nachteile der Interpolationsmethode aufgezeigt wurden, besitzt
sie dennoch den Vorteil der leichteren Implementierung. Die Funktionen, die das Interpolationspolynom berechnen, sind unabhängig von der konkreten Formel von F (x, y, z), da sie nur
mit den Interpolationsstellen arbeiten. Setzt man voraus, dass auch der Gradient numerisch
berechnet wird, unterscheiden sich die zu verschiedenen algebraischen Flächen gehörenden
Fragment-Shader nur in der Funktion, welche die Wertepaare (ti , fi ) anhand der Flächenformel
berechnet. Bei geeigneter Syntaxprüfung kann diese Formel direkt von einer Benutzereingabe
übernommen werden.
5.3.1 Lagrange-Interpolation
Es wurde gezeigt, dass das Interpolationspolynom vom Grad n durch (n + 1) Interpolationsstellen eindeutig bestimmt ist. Als erstes wird das Lagrange-Interpolationsverfahren zur
35
5 Berechnung der Polynomkoeffizienten
Bestimmung dieses Polynoms erläutert. Die Lagrange-Interpolationformel ist durch
pn (x) =
n
X
j=0
n
Y
x − xk
fj Lj (x) mit Lj (x) =
xj − xk
(5.28)
k=0
k6=j
gegeben. Es ist offensichtlich vom Grad n und interpoliert wie gewünscht die (xi , fi ):
pn (xi ) =
n
X
j=0
=
=
n
X
j=0
j6=i
n
X
j=0
j6=i
=
n
X
j=0
j6=i
n
Y
xi − xk
fj
xj − xk
fj
k=0
k6=j
n
Y
k=0
k6=j
(5.29)
n
Y xi − xk
xi − xk
+ fi
xj − xk
xi − xk
(5.30)
k=0
k6=i
fj · (xi − xi ) ·
n
n
Y
Y
xi − xk
+ fi
1
xj − xk
k=0
k6=j
k6=i
n
Y
xi − xk
+ fi = fi
fj · 0 ·
xj − xk
(5.31)
k=0
k6=i
(5.32)
k=0
k6=j
k6=i
Die Formel ist in der beschriebenen Form nicht nur zur Berechnung von Funktionswerten
geeignet. Durch symbolisches Ausmultiplizieren der Produkte und anschließendes Aufsummieren kann das Polynom in die Monomdarstellung überführt werden, so dass die Koeffizienten
ablesbar sind. Die Gesamtlaufzeit des Verfahrens beträgt O(n3 ). Sie kann aber durch die
Umformung
n
Y
(x − xk )
n
n
n
X
Y
X
fj
x − xk
k=0
·
fj
(5.33)
=
n
Y
xj − xk
(x − xj )
j=0
j=0
k=0
(xj − xk )
k6=j
k=0
k6=j
Q
leicht auf O(n2 ) reduziert werden. Das Produkt nk=0 (x−xk ) lässt sich in O(n2 ) auswerten. Es
ist unabhängig von j und muss daher nur einmal berechnet werden. Für jedes j führt man nun
in O(n) eine Polynomdivision mit (x − xj ) durch. Alle weiteren von j abhängigen Aufgaben
benötigen ebenfalls O(n) Schritte, so dass sich ein Gesamtaufwand von O(n2 ) + n · O(n) =
O(n2 ) ergibt.
5.3.2 Newton-Interpolation
Weit verbreitet sind auch die Interpolationsformeln von Newton. Sie beruhen auf der Darstellung eines Polynoms in der Newton-Basis
pn (x) =
n
X
j=0
36
bj
j−1
Y
k=0
(x − xk ).
(5.34)
5.3 Polynominterpolation
f [x0 ] = f0
f [x0 , x1 ] =
f [x1 ]−f [x0 ]
x1 −x0
f [x1 ] = f1
f [x0 , x1 , x2 ] =
f [x1 , x2 ] =
f [x1 ,x2 ]−f [x0 ,x1 ]
x2 −x0
f [x2 ]−f [x1 ]
x2 −x1
f [x2 ] = f2
Abbildung 5.2: Rechenschema zur Bestimmung der dividierten Differenzen für drei
Stützstellen. Alle Einträge außer die f [xi ] können aus den zwei benachbarten Tabelleneinträgen der vorigen Spalte in O(1) berechnet werden. Die unterstrichenen Einträge werden für
die Newton-Interpolationsformel benötigt. Bei der Hinzunahme weiterer Stützstellen entsteht
nur eine neue Zeile. Die bereits berechneten Werte können weiterverwendet werden.
Unter der Interpolationsbedingung pn (xi ) = fi entsteht das lineare Gleichungssystem
f0 = b0
(5.35)
f1 = b0 + b1 (x1 − x0 )
(5.36)
(5.37)
f2 = b0 + b1 (x2 − x0 ) + b2 (x2 − x0 )(x2 − x1 )
..
.
fn = b0 + b1 (x2 − x0 ) + b2 (x2 − x0 )(x2 − x1 ) + · · · + bn
n−1
Y
k=0
(xn − xk )
(5.38)
zur Berechnung der bj . Die Koeffizientenmatrix des Gleichungsystems ist eine obere Dreiecksmatrix, wodurch sich die bj sukzessive berechnen lassen. Dazu werden die so genannten
dividierten Differenzen f [xi , . . . , xi+j ] verwendet, die rekursiv definiert sind als
f [xi ] = fi ,
i = 0, . . . , n
(5.39)
f [xi+1 , . . . , xi+j ] − f [xi , . . . , xi+j−1 ]
f [xi , . . . , xi+j ] =
, i = 0, . . . , n − 1, j = 1, . . . , n − i.
xi+j − xi
(5.40)
Die Koeffizienten der Newton-Basis sind dann durch bj = f [x0 , . . . , xj ] gegeben und damit
auch das Newtonsche Interpolationspolynom.
pn (x) =
n
X
j=0
f [x0 , . . . , xj ]
j−1
Y
(x − xk )
(5.41)
k=0
Abbildung 5.2 veranschaulicht noch einmal das Rechenschema zur Bestimmung der benötigten
dividierten Differenzen. Für eine ausführliche Herleitung des Zusammenhangs zwischen den
bj und den dividierten Differenzen sei an dieser Stelle auf [Sto02, S. 48ff] verwiesen.
Die Berechnung der Koeffizienten der Monombasis ist bei der Newton-Interpolation leicht
zu realisieren. Zuerst werden die (n + 1) dividierten Differenzen f [x0 , . . . , xj ] berechnet, wofür
O(n2 ) Rechenschritte benötigt werden. Wie in Abbildung 5.3 deutlich wird, kann die Berechnung in einem Array der Länge n + 1 durchgeführt werden. Nun folgt die Summation
37
5 Berechnung der Polynomkoeffizienten
Initialisierung
f [x0 ]
f [x1 ]
f [x2 ]
f [x0 ]
f [x0 x1 ]
f [x1 x2 ]
f [x0 ]
f [x0 x1 ]
f [x0 x1 x2 ]
1. Iteration
2. Iteration
..
.
Abbildung 5.3: Berechnung der (n + 1) dividierten Differenzen auf einem Array der
Länge (n + 1): Nach der Initialisierung des Arrays mit den Stützwerten fi wird das Array
in jeder Iteration von rechts nach links durchlaufen und man berechnet aus dem aktuellen
und dem linken Nachbarwert die neue dividierte Differenz der aktuellen Arrayzelle. So wird
sichergestellt, dass kein später noch benötigter Zwischenwert überschrieben wird. Nach jeder
Iteration enthält das Array eine weitere der benötigten dividierten Differenzen (unterstrichen
dargestellt).
entsprechend Gleichung (5.41). Dafür benötigt man zwei weitere Arrays P und N der Länge
(n + 1), die jeweils die Koeffizienten von Polynomen in der Monombasis repräsentieren.
Qj−1 In Iteration j speichert P das Zwischenergebnis für pn (x) und N die Newton-Basis k=0
(x − xk ).
Sie muss nicht in jeder Iteration neu berechnet werden, sondern kann in O(n) um ein weiteres
Glied (x − xk ) erweitert werden. Die Einträge des Arrays N werden nun mit f [x0 , . . . , j] multipliziert auf P aufsummiert. Nach n solchen Iterationen enthält P die gesuchten Koeffizienten.
Der Gesamtaufwand des Verfahrens beträgt daher O(n2 ). Obwohl die asymptotische Laufzeit
mit der der Lagrange-Formeln übereinstimmt, ist die konkrete Laufzeit der Berechnung nach
Newton geringer.
5.3.3 Weitere Interpolationsverfahren
Außer den Interpolationsformeln von Lagrange und Netwon existieren noch weitere Verfahren,
die jedoch im Rahmen dieser Arbeit nicht untersucht werden konnten. Numerisch günstige Eigenschaften werden beispielsweise der Tschebyscheff-Interpolation zugeschrieben [Boy02,
Gie02]. Dabei werden die Koeffizienten des Interpolationspolynoms in der Tschebyscheff-Basis
berechnet und anschließend in die Monombasis transformiert. Genau diese Transformation,
die in ähnlicher Form auch schon bei den Verfahren von Lagrange und Newton durchgeführt
wurde, kann, bedingt durch die schlechte Konditionierung der Vandermonde-Matrix, große
Rundungsfehler erzeugen. Es wäre daher sinnvoll die Polynomnullstellen direkt in der Tschebyscheff-Basis zu berechnen, wie es in [GG83] beschrieben wird. Die meisten der im folgenden
Kapitel dargelegten Algorithmen zur Nullstellenbestimmung arbeiten unabhängig von der
Darstellung des Polynoms und können daher nahezu unverändert übernommen werden.
5.4 Zusammenfassung
Es wurde dargelegt, wie aus der algebraischen Fläche F (x, y, z) = 0 durch Einsetzen des
Raycasting-Strahls r(t) ein univariates Polynom f (t) entsteht und wie dessen Koeffizienten be-
38
5.4 Zusammenfassung
rechnet werden können. Auf der CPU können mittels Termumformungen direkte Berechnungsvorschriften für jeden einzelnen Koeffizienten bestimmt werden. Da die entstehenden Terme
meist sehr lang sind, empfiehlt sich die Verlagerung der Termumformungen auf die GPU, wo
die Berechnungen nicht auf Variablen beruhen, sondern mit konkreten Werten durchgeführt
werden können. Eine weitere, scheinbar vielversprechende Methode ist die Polynominterpolation. Sie kann die Koeffizienten anhand einiger Stützstellen und -werte ermitteln und benötigt
daher nur wenig Wissen über den Aufbau der Flächenformel. Leider ist dieses Verfahren sehr
empfindlich gegenüber Rundungsfehlern.
39
5 Berechnung der Polynomkoeffizienten
40
6 Berechnung der Nullstellen univariater
Polynome
Im vorigen Abschnitt wurde dargelegt, wie ein multivariates Polynom F (x, y, z) durch Einsetzen einer Strahlgleichung (x(t), y(t), z(t))T = o + td~ in das univariate Polynom f (t) transformiert werden kann. Es verbleibt die Berechung der Nullstellen von f (t), welche die Schnittpunkte der algebraischen Fläche F (x, y, z) = 0 mit dem Raycastingstrahl darstellen.
Der erste Abschnitt dieses Kapitels beschäftigt sich mit allgemeinen Eigenschaften der Nullstellen von Polynomen sowie mit häufig benötigten Standardalgorithmen. Anschließend werden die Verfahren zur Berechnung von Polynomnullstellen detailliert erläutert.
6.1 Vorbemerkungen
6.1.1 Anzahl und Vielfachheit von Nullstellen
Um die maximale Anzahl an Nullstellen, die ein Polynom vom Grad n besitzen kann, zu
ermitteln, kann der Fundamentalsatz der Algebra herangezogen werden. Er besagt, dass jedes
Polynom
n
X
ai xi
(6.1)
pn (x) =
i=0
vom Grad n ≥ 1 mit komplexen Koeffizienten ai eine komplexe Nullstelle besitzt [Chi00,
S. 269ff]. Ist beispielsweise ξ1 ∈ C diese Nullstelle, so läßt sich der Linearfaktor (x − ξ1 ) mit
Hilfe der Pseudodivision (siehe Abschnitt 6.1.2) von pn (x) abspalten [Chi00, S. 241f].
pn (x) =
n
X
i=0
ai xi = (x − ξ1 )
n−1
X
i=0
bi xi = (x − ξ1 )pn−1 (x)
(6.2)
Der Vorgang kann nun analog für pn−1 (x) fortgesetzt werden, so dass sich nach n Abspaltungen
die so genannte Linearfaktorzerlegung von pn (x) ergibt.
pn (x) =
n
X
i=0
ai xi = an (x − ξ1 ) · · · (x − ξn )
(6.3)
Jedes Polynom vom Grad n mit komplexen Koeffizienten hat also genau n komplexe Nullstellen. Am Beispiel von
x3 − x2 + x − 1 = (x − 1)(x − i)(x + i) mit i2 = −1
(6.4)
wird klar, dass Polynome mit reellen Koeffizienten ebenfalls komplexe Nullstellen besitzen
können. Das Beispiel verdeutlicht eine weitere wichtige Eigenschaft der Polynome mit reellen
ai . Ist ξ = a + bi mit a, b ∈ R eine Lösung von pn (x) = 0, so ist es auch deren konjugiert
41
6 Berechnung der Nullstellen univariater Polynome
Komplexe ξ = a + bi = a − bi:
pn (ξ) =
n
X
i
ai ξ =
i=0
n
X
(6.5)
ai ξ i = pn (ξ) = 0.
i=0
Mehrfache Nullstellen
Enthält die Linearfaktorzerlegung von pn (x) einen Linearfaktor (x − ξ) genau m–mal mit
m > 0, so wird ξ als Nullstelle mit Vielfachheit m bezeichnet. Für m = 1 nennt man ξ eine
einfache, für m > 1 eine mehrfache Nullstelle. Wie in [Mig92, S. 102] gezeigt wurde, ist ξ nun
nicht nur Nullstelle von pn (x), sondern auch Nullstelle der ersten m − 1 Ableitungen. Es gilt
also
pn (ξ) = p′n (ξ) = . . . = p(m−1)
(ξ) = 0 und p(m)
(6.6)
n
n (ξ) 6= 0.
In der Praxis und insbesondere bei technischen Anwendungen kommt es aufgrund von Rundungsfehlern in den Eingabedaten nur sehr selten vor, dass das Polynom sowie mehrere Ableitungen an der gleichen Stelle zu Null werden. Ist p′n (ξ) = 0 und |pn (ξ)| < ε, so wird aber
meist angenommen, dass sich an der Stelle ξ eine mehrfache Nullstelle befindet.
6.1.2 Das Horner-Schema
In [EMNW05, S. 92ff] wird das Horner-Schema zur schnellen und rundungfehlergünstigen
Berechnung der Funktions- und Ableitungswerte eines Polynoms, sowie zum Abdividieren
reeller und komplexer Nullstellen vorgestellt. Das Polynom
pn (x) = an xn + an−1 xn−1 + · · · + a1 x + a0
(6.7)
kann offensichtlich auch in der Form
pn (x) = (· · · (( an x + an−1 ) x + an−2 ) x + · · · + a1 ) x + a0
|{z}
|
|
|
|
bn
{z
}
bn−1 =bn x+an−1
{z
bn−2 =bn−1 x+an−2
{z
b1 =b2 x+a1
{z
}
}
b0 =b1 x+a0
(6.8)
}
mit den Zwischenergebnissen bn , . . . , b0 geschrieben werden. Durch diese Umformung benötigt
man bei der Auswertung von pn (x) an einer Stelle ξ deutlich weniger Multiplikationen. Die
Berechnung der bi für ein konkretes ξ kann mit Hilfe einer Tabelle geschehen, die als einfaches
Horner-Schema bezeichnet wird:
an
an−1
+0
+bn ξ
= bn = bn−1
an−2
...
a1
a0
+bn−1 ξ . . . +b2 ξ +b1 ξ
= bn−2
...
= b1
(6.9)
= b0
Es lässt sich zeigen, dass die bi , i = 1, . . . , n, in der dritten Zeile des einfachen Horner-
42
6.1 Vorbemerkungen
Schemas gleichzeitig die Koeffizienten des Polynoms pn−1 (x) darstellen, dass durch Abdividieren der Nullstelle ξ von pn (x), also der Polynomdivision von pn (x) mit (x − ξ), entsteht. b0
entspricht dabei dem Divisionsrest.
Sind alle Koeffizienten von pn (x) reell, so entstehen beim Abdividieren einer komplexen
Nullstelle ξ auch komplexe Koeffizienten in pn−1 (x). Dies kann vermieden werden, indem die
konjugiert komplexe Nullstelle ξ gleichzeitig abdividiert wird. Das Produkt
(x − ξ)(x − ξ) = x2 − (ξ + ξ)x + ξ · ξ = x2 − px − q
(6.10)
mit reellen p und q bildet einen quadratischen Faktor von pn (x), so dass das Polynom pn−2 (x),
welches aus der Polynomdivision von pn (x) mit x2 − px − q hervorgeht, ebenfalls nur reelle
Koeffizienten besitzt. Das Nacheinanderausführen der Division durch (x − ξ) und (x − ξ) lässt
sich, wie in [EMNW05, S. 95f] beschrieben, im doppelreihigen Horner-Schema zusammenfassen, welches ausschließlich reelle Arithmetik benötigt:
an
an−1
an−2
...
+0
+0
+cn q
. . . +c4 q +c3 q +c2 q
+0
+cn p
= cn = cn−1
a2
a1
+cn−1 p . . . +c3 p +c2 p
= cn−2
...
= c2
= c1
a0
(6.11)
+0
= c0
Die ci , i = 2, . . . , n, in der vierten Zeile des doppelreihigen Horner-Schemas entsprechen
den gesuchten Koeffizienten von pn−2 (x). c1 und c0 sind die Koeffizienten des Divisionsrestes
c1 x + c0 und entsprechend gleich Null, wenn ξ Nullstelle von pn (x) ist. Die vorgestellten
Divisionsverfahren mit linearem beziehungsweise quadratischem Divisor verwenden keinerlei
Divisionen reeller oder komplexer Zahlen und werden daher als Pseudodivision oder auch als
synthetische Division bezeichnet.
Eine weitere Anwendung des Horner-Schemas ist die Berechnung von Ableitungswerten.
Als Ausgangspunkt dient die aus der Pseudodivision von pn (x) mit (x − ξ) resultierende
Schreibweise von pn (x):
pn (x) = (x − ξ)pn−1 (x) + b0
(6.12)
Durch Bildung der Ableitung entsteht
p′n (x) = pn−1 (x) + (x − ξ)p′n−1 (x),
(6.13)
so dass man mit x = ξ eine alternative Vorschrift zur Berechnung von p′n (x) an der Stelle ξ
erhält, die durch zweimalige Anwendung des Horner-Schemas realisiert werden kann:
p′n (ξ) = pn−1 (ξ) + (ξ − ξ)p′n−1 (ξ) = pn−1 (ξ)
(6.14)
Analog lassen sich Berechnungsvorschriften für höhere Ableitungen herleiten. Es ergibt sich
p(k)
n (ξ) = k!pn−k (ξ),
(6.15)
wobei pn−k (x) durch Pseudodivision von pn−(k−1) (x) mit (x − ξ) bestimmt wird.
43
6 Berechnung der Nullstellen univariater Polynome
6.1.3 Polynomdivision
Das einfache Horner-Schema ist zur euklidischen Division mit beliebigem Divisor ungeeignet.
Die Lösung der Gleichung
pn (x) = s(x)qm (x) + r(x)
(6.16)
bei gegebenem pn (x) und qm (x) mit m ≤ n, das heißt die Bestimmung des Quotienten s(x)
und des Divisionsrestes r(x), kann jedoch leicht mit Hilfe des folgenden Algorithmus bestimmt
werden:
Zu Beginn des Algorithmus wird pn (x) als aktueller Rest r(x) angenommen. Der Grad
von r(x) sei mit l bezeichnet. Nun teilt man den höchsten Koeffizienten von r(x) durch den
höchsten Koeffizienten von qm (x). Der entstehende Quotient ist der Koeffizient von xl−m in
s(x). Er wird mit qm (x) multipliziert und von r(x) subtrahiert, so dass ein neuer Rest r(x)
entsteht, mit dem analog weitergerechnet wird bis l kleiner als m ist.
6.2 Lösungsformeln
Einfache Polynomgleichungen bis zum Grad vier können durch geschlossene Formeln gelöst
werden, welche lediglich endlich viele Rechenschritte benötigen, um bei exakter Arithmetik
ein exaktes Ergebnis zu liefern. Dabei werden ausschließlich die vier Grundrechenarten und
Wurzeloperationen verwendet.
Bereits 1600 v. Chr. waren die Babylonier in der Lage einfache quadratische Gleichungen auf
diese Art zu lösen. Die vollständige Lösungsformel wird auf den indischen Mathematiker und
Astronomen Aryabatthiya (5. Jahrhundert) zurückgeführt. Zu Beginn des 16. Jahrhunderts
leistete Scipione del Ferro den wesentlichen Beitrag für die Lösung kubischer Gleichungen. Er
zeigte, wie Gleichungen der Form x3 + ax + b = 0 aufgelöst werden können. Niccolò Tartaglia
erweiterte die Berechnungsvorschrift auf alle Gleichungen vom Grad drei und übersandte seine Entdeckung dem italienische Mathematiker Gerolamo Cardano, den er zur Geheimhaltung
verpflichtete. Als Cardanos Schüler, Lodovico Ferrari, einige Jahre später die Lösungsformel
für biquadratische Gleichungen entwickelte, beschloss Cardano 1545 beide Methoden in seinem Werk Ars magna de Regulis Algebraicis zu veröffentlichen. Tartaglia brach daraufhin
sämtlichen Kontakt zu Cardano ab. Quelle: [Mat78, S. 288, S. 367f, S. 544f]
In den darauffolgenden Jahren stellten sich viele Mathematiker die Frage nach der Existenz
ähnlicher Lösungsformeln für Gleichungen höheren Grades. Wie Niels Henrik Abel 1824 in
[Abe26] bewies, kann es solche Formeln jedoch nicht geben.
Im Folgenden werden die Lösungsformeln für lineare, quadratische, kubische und biquadratische Gleichungen beschrieben. Die in der betreffenden Literatur häufig verwendete Bezeichnung der Polynomkoeffizienten mit den Buchstaben a bis d wird hier übernommen. In
allen Berechnungen wird davon ausgegangen, dass sowohl der höchste als auch der niedrigste
Koeffizient von Null verschieden sind.
6.2.1 Lineare Gleichungen
Die Auflösung der linearen Polynomgleichung ax+b = 0 sei hier nur der Vollständigkeit wegen
erwähnt und geschieht trivialerweise nach
b
x1 = − .
a
44
(6.17)
6.2 Lösungsformeln
6.2.2 Quadratische Gleichungen
Die Lösungen der quadratrischen Polynomgleichung ax2 + bx + c = 0 können durch die Formel
q b 2
− ac
−b ±
2
(6.18)
x1,2 =
2a
oder auch durch
x1,2 =
−b ±
2c
q b 2
2
(6.19)
− ac
berechnet werden [PTVF96, S. 183f]. Beide Lösungsformeln haben ihre Berechtigung. Jedoch
entstehen numerische Instabilitäten, sobald a, c oder beide Koeffizienten
√ klein sind. Der Term
√
2
b − 4ac reduziert sich dann annähernd auf b, was den Term −b ± b2 − 4ac für eine der
beiden Nullstellen nahezu auslöscht. Die betreffende Nullstelle kann auf diesem Wege nicht
mehr genau bestimmt werden. Daher wird in [PTVF96, S. 183f] vorgeschlagen beide Methoden
wie folgt zu kombinieren. Man setzt
p
q = −b − sgn(b) b2 − 4ac
(6.20)
und berechnet x1 mit (6.18) und x2 mit (6.19):
x1 =
q
2a
x2 =
2c
q
(6.21)
Gleichung (6.21) nutzt somit die Vorteile beider Verfahren aus, um eine in den meisten Fällen
stabile Lösungsformel für quadratische Gleichungen zu synthetisieren.
6.2.3 Kubische Gleichungen
Die Lösung kubischer Gleichungen wird in [MMWW99, S. 12] wie folgt vorgenommen. Die
normierte kubische Gleichung x3 + ax2 + bx + c = 0 wird durch die Substitution x = y − a3 in
2
3
die reduzierte Form y 3 + py + q = 0 überführt. Dabei ist p = 3b−a
und q = 2a
− ab
3
27
3 + c. Nun
p 3
q 2
kann man aus der Diskriminante D = 3 + 2 der reduzierten Form drei Fälle ableiten:
D>0
eine reelle, zwei zueinander konjugiert komplexe Nullstellen
D=0
drei reelle Nullstellen, davon mindestens zwei gleiche Lösungen
D<0
drei paarweise verschiedene reelle Nullstellen
Die nach Cardano benannten Formeln liefern die Lösungen für alle drei Fälle. Dazu setzt man
r
√
p
1
3
u = − q + D und v = − .
(6.22)
2
3u
45
6 Berechnung der Nullstellen univariater Polynome
und berechnet
y1 = u + v
1
y2 = − (u + v) +
2
1
y3 = − (u + v) −
2
(6.23)
√
1
(u − v) 3i
2
√
1
(u − v) 3i.
2
(6.24)
(6.25)
Aus den yk der reduzierten Form erhält man leicht die Lösungen der Normalform:
xk = yk −
a
3
(6.26)
Die obigen Formeln haben einen gewissen Nachteil für den Fall D < 0. Obwohl alle √
drei
Nullstellen reell sind, muss man dennoch mit komplexen Zwischenwerten rechnen, da D
nicht reell ist. Für diesen Fall existiert jedoch eine alternative Vorschrift, welche erstmals
1615 in De Emendatione Aequationum von François Viete erschien. Man berechnet
r q
p 3
und ϕ = arccos −
.
(6.27)
r= −
3
2r
Unter Verwendung von D < 0 lässt sich durch Umstellen der Gleichung für die Diskriminante
3
q
verifizieren, dass − p3 im Intervall [0, ∞) und − 2r
im Intervall [−1, 1] liegt. Damit sind r
und ϕ stets definiert und reell. Für r wird jeweils die positive der beiden möglichen Wurzeln
verwendet. Die yk erhält man nun aus
ϕ
√
y1 = 2 3 r cos
(6.28)
3
√
ϕ + 2π
(6.29)
y2 = 2 3 r cos
3
√
ϕ + 4π
.
(6.30)
y3 = 2 3 r cos
3
6.2.4 Biquadratische Gleichungen
Wie in [MMWW99, S. 13] beschrieben wird, kann ausgehend von der normalisierten Form
x4 + ax3 + bx2 + cx + d = 0 die reduzierte Form y 4 + py 2 + qy + r = 0 durch die Substitution
x = y − a4 gewonnen werden, wobei
3
p = b − a2 ,
8
q =c−
ab a3
+
2
8
und r = d −
ac a2 b 3a4
+
−
.
4
16
256
(6.31)
Aus der reduzierten Form leitet sich die so genannte kubische Resolvante ab:
z 3 + 2pz 2 + (p2 − 4r)z − q 2 = 0
46
(6.32)
6.3 Lokal konvergente Iterationsverfahren
Sind z1 , z2 , z3 die mit Hilfe der Cardanischen Formeln ermittelten Lösungen der kubischen
Resolvante, so berechnet man
w1 als eine Lösung von w2 = z1 ,
(6.33)
2
w2 als eine Lösung von w = z2 und setzt
q
w3 = −
.
w1 w2
(6.34)
(6.35)
Die wk setzen sich nun wie folgt zu den Lösungen der reduzierten Form zusammen:
y1 = (+w1 + w2 + w3 )/2
(6.36)
y2 = (+w1 − w2 − w3 )/2
(6.37)
y4 = (−w1 − w2 + w3 )/2
(6.39)
(6.38)
y3 = (−w1 + w2 − w3 )/2
Für die biquadratische Gleichung können die Nullstellen nun mit xk = yk − a4 bestimmt
werden. Aus den Lösungen der kubischen Resolvante lässt sich hierbei auch der Anteil reeller
beziehungsweise komplexer Lösungen an der Lösungsmenge der biquadratischen Gleichung
ablesen:
kubische Resolvente
alle Nullstellen reell und positiv
alle Nullstellen reell, eine positiv, zwei negativ
eine Nullstellen reell, zwei komplex
biquadratische Gleichung
→
→
→
vier reelle Nullstellen
vier komplexe Nullstellen
zwei reelle, zwei komplexe Nullstellen
6.3 Lokal konvergente Iterationsverfahren
Zur numerischen Berechnung von Nullstellen einer Funktion ist eine Vielzahl von iterativen
Verfahren bekannt, deren Konvergenz nur dann garantiert werden kann, wenn sich die Startwerte der Iteration bereits nahe genug an einer Nullstelle befinden beziehungsweise gewisse
Zusatzkriterien erfüllen. Man bezeichnet dies als lokale Konvergenz.
Eine lokal konvergente Methode ist für sich gesehen selten zur Berechnung aller Nullstellen
einer Funktion geeignet. In den meisten Fällen benutzt man zuvor ein Verfahren, welches
eine Nullstelle so gut annähert oder isoliert, dass die Bedingungen für lokale Konvergenz
erfüllt sind. Mit den ermittelten Startwerten wird dann auf eine lokal konvergente Methode
umgeschalten, die im Allgemeinen eine höhere Konvergenzgeschwindigkeit aufweist als die zur
Vorberechnung der Startwerte genutzten Algorithmen.
6.3.1 Newton-Iteration
Die Newton-Iteration ist ein wichtiges und bekanntes Verfahren zur numerischen Lösung von
nichtlinearen Gleichungen und Gleichungssystemen. Für eine stetig differenzierbare Funktion
f : R → R und eine bereits bekannte Näherung xk einer Nullstelle konstruiert das NewtonVerfahren im Punkt (xk , f (xk )) eine Tangente an den Graphen von f und verwendet die
Nullstelle dieser Tangente als neue Näherung xk+1 . Aus der Gleichung der Tangente
y = f ′ (xk )(x − xk ) + f (xk )
(6.40)
47
6 Berechnung der Nullstellen univariater Polynome
folgt mit y = 0 und x = xk+1 die Iterationsvorschrift des Newton-Verfahrens:
xk+1 = xk −
f (xk )
f ′ (xk )
(6.41)
Es kann gezeigt werden, dass die Newton-Iteration für hinreichend nahe an einer einfachen
Nullstelle gelegene Startwerte x0 quadratisch konvergiert. In der Nähe einer mehrfachen Nullstelle konvergiert das Verfahren nur linear. Eine Herleitung der Konvergenzbereiche und
-ordnungen findet sich beispielsweise in [EMNW05, S. 51ff]. Leider konnte die Newton-Iteration in dieser Arbeit nicht verwendet werden, da keines der untersuchten global konvergenten Verfahren geeignete Startwerte für die Iteration liefert und die Konvergenz somit nicht
garantiert werden kann.
6.3.2 Einschlussverfahren
Als Einschlussverfahren bezeichnet man Algorithmen, welche eine Folge von geschachtelten
Intervallen erzeugen, die gegen eine Nullstelle konvergieren. Ausgangspunkt dieser Verfahren
bildet der so genannte Zwischenwertsatz von Bolzano. Er besagt, dass eine reelle Funktion f :
R → R, die auf einem abgeschlossenen Intervall [a, b] stetig ist, jeden Funktionswert zwischen
f (a) und f (b) annimmt [Heu00, S. 223]. Haben f (a) und f (b) verschiedene Vorzeichen, das
heißt es gilt f (a)f (b) < 0, so muss es in (a, b) zwangsläufig eine Nullstelle der Funktion f geben.
Es lässt sich leicht herleiten, dass das Intervall sogar eine ungerade Anzahl von Nullstellen
enthalten muss.
Ausgehend von dem Einschlussintervall [a, b] werden die Startwerte x1 = a, f1 = f (a),
x2 = b und f2 = f (b) gesetzt, so dass die Eigenschaft f1 f2 < 0 erfüllt ist. Die Einschlussverfahren berechnen nun ein x3 , welches zwischen x1 und x2 liegt, und teilen damit das
Einschlussintervall. Befindet sich die gesuchte Nullstelle nicht bei x3 , so muss eines der beiden
Teilintervalle den Vorzeichenwechsel enthalten. Gilt f2 f3 < 0 mit f3 = f (x3 ), so liegt die
Nullstelle zwischen x2 und x3 und die Intervallgrenzen und Funktionswerte werden wie folgt
umbenannt:
x1 := x2
f1 := f2
(6.42)
x2 := x3
f2 := f3
(6.43)
Ist hingegen f2 f3 > 0, so befindet sich die Nullstelle zwischen x1 und x3 und es wird
x2 := x3
f2 := f3
(6.44)
gesetzt. Da alle Intervalle dieser Folge per Konstruktion eine Nullstelle enthalten, ist die
Konvergenz von Einschlussverfahren garantiert. Die einzelnen Algorithmen unterscheiden sich
vorrangig in der Wahl eines geeigneten x3 und damit auch in der Konvergenzgeschwindigkeit.
Die Größe |x2 −x1 | des Einschlussintervalls gibt gleichzeitig den maximalen absoluten Fehler
an. Er erlaubt eine Aussage über die Anzahl der korrekten Kommastellen des Ergebnisses.
1
Eine bessere Abschätzung kann jedoch über den relativen Fehler | x2x−x
| gemacht werden, da
2
dieser die insgesamt gültigen Stellen berücksichtigt. Als Abbruchkriterium verwendet man
also wahlweise
x2 − x1 < ε.
(6.45)
|x2 − x1 | < ε oder x2 48
6.3 Lokal konvergente Iterationsverfahren
f (x)
x
5
4
3
2
1
Iterationen
Abbildung 6.1: Beispielhafte Ausführung des Bisektionsverfahrens. Die Größe des Einschlussintervalls halbiert sich mit jeder Iteration.
Bisektion
Das Bisektionsverfahren ist das einfachste und bekannteste Einschlussverfahren. Es wählt
x3 =
x1 + x2
2
(6.46)
und teilt damit das Einschlussintervall in der Mitte (Abbildung 6.1). Obwohl das Bisektionsverfahren nur linear gegen die Nullstelle konvergiert, hat es doch den Vorteil der geringsten
Zusatzkosten pro Iteration. Übersteigt der Aufwand, der bei Verfahren mit hoher Konvergenzordnung in die Berechnung der ck investiert wird, die Kosten zur Auswertung von f , so lohnt
sich möglicherweise die Verwendung des Bisektionsverfahrens.
Regula-Falsi und verwandte Verfahren
Die Regula-Falsi verwendet den Schnittpunkt der x-Achse mit der Geraden, die die Punkte
(x1 , f1 ) und (x2 , f2 ) verbindet, als Näherung für die Nullstelle von f (Abbildung 6.2). Aus der
Geradengleichung
f1 − f2
(x − x1 )
(6.47)
y = f1 +
x1 − x2
lässt sich mit y = 0 die Berechnungsvorschrift für x3 herleiten:
x3 = x = x1 −
f1
(x1 − x2 )
f1 − f2
(6.48)
Analytisch betrachtet liegt die neue Näherung x3 zwischen x1 und x2 , da man aus der Bedin1
gung f1 f2 < 0 auch 0 < f1f−f
< 1 folgern kann. Testimplementierungen auf der GPU ergaben
2
aber, dass x3 bedingt durch die geringe Rechengenauigkeit durchaus das Einschlussintervall
verlassen kann. Insbesondere bei schlecht separierten Nullstellen, wie sie in der Nähe von
Singularitäten algebraischer Flächen auftreten, bewirkt dies häufig die Konvergenz zu einer
anderen Nullstelle außerhalb des Startintervalls. Stellt man fest, dass x3 nicht zwischen x1
und x2 oder zu nahe an den Intervallgrenzen liegt, so könnte man stattdessen einen Bisekti-
49
6 Berechnung der Nullstellen univariater Polynome
f (x)
x
3
2
1
Iterationen
Abbildung 6.2: Beispielhafte Ausführung der Regula-Falsi. Durch die Randpunkte des
Graphen im aktuellen Einschlussintervall wird eine Gerade gelegt. Das Einschlussintervall
wird am Schnittpunkt dieser Geraden mit der x-Achse geteilt.
onsschritt durchführen. Der dadurch entstehende Zusatzaufwand verlangsamt die Konvergenz
des Verfahrens jedoch deutlich.
In [EMNW05, S. 74ff] werden zur Steigerung der Konvergenzgeschwindigkeit zahlreiche Variationen der Regula-Falsi vorgeschlagen. Das Pegasus-, das Illinois- und das Anderson-BjörkVerfahren wurden in der OpenGL-Shading-Language implementiert und ebenfalls getestet.
Sie zeigten jedoch ein ähnlich instabiles Verhalten bei schlecht separierten Nullstellen und
eine für Echtzeitansprüche ungenügende Konvergenzgeschwindigkeit, die sich wahrscheinlich
auf die in Abschnitt 2.1.3 beschriebene Behandlung von Programmverzweigungen und Schleifen zurückführen lässt. Aus den genannten Gründen wurden diese Algorithmen nicht näher
untersucht.
6.4 Nullstellenisolation
Ziel der in diesem Abschnitt vorgestellten Algorithmen ist die Bestimmung von Intervallen,
in denen ein Polynom pn (x) mindestens eine reelle Nullstelle besitzt. Einige Verfahren sind
selbstständig in der Lage die Intervalle weiter zu verkleinern, bis die Nullstellen vollständig
separiert und hinreichend gut angenähert sind, während andere die Intervalle so wählen, dass
lokal konvergente Iterationsverfahren darauf angewandt werden können.
6.4.1 Das D-Chain-Verfahren
Zwischen zwei benachbarten Nullstellen der ersten Ableitung eines Polynoms pn (x) verhält
sich dieses streng monoton, das heißt es gilt entweder p′n (x) < 0 oder p′n (x) > 0. Kennt man
also zwei benachbarte Nullstellen a und b von p′n (x) und gilt zusätzlich pn (a)pn (b) < 0, so
hat pn (x) nach dem Zwischenwertsatz (siehe Abschnitt 6.3.2) und aufgrund der Monotonie in
(a, b) genau eine reelle (gegebenenfalls mehrfache) Nullstelle.
[a, b] erfüllt gleichzeitig die Voraussetzungen zur Anwendung eines Einschlussverfahrens, so
dass die weitere Eingrenzung der Nullstelle kein Problem darstellt. Die Berechnung der Nullstellen von pn (x) lässt sich also auf die Berechnung der Nullstellen der ersten Ableitung p′n (x)
50
6.4 Nullstellenisolation
zurückführen. Da p′n (x) wiederum ein Polynom ist, kann der Algorithmus rekursiv angewandt
(k)
werden1 . Der Grad der k-ten Ableitung pn (x) ist dabei (n − k), so dass nach k = n − 1 Ableitungen ein Polynom vom Grad eins entsteht, dessen Nullstelle sich leicht bestimmen lässt. Es
wäre natürlich auch möglich, Polynome vom Grad zwei, drei oder vier als Basisfälle anzusehen. Allerdings sind die mit Hilfe der Lösungsformeln (Abschnitt 6.2) berechneten Nullstellen
häufig unpräzise und daher für eine weitere Verwendung in diesem rekursiven Algorithmus
ungeeignet.
(k)
In der bisher beschriebenen Form kann das Verfahren keine Nullstellen von pn bestimmen,
(k+1)
die vor der kleinsten beziehungsweise nach der größten Nullstelle der Ableitung pn
(x)
(k+1)
auftreten. Sind die Nullstellen von pn
(x) der Größe nach als ζ0 , . . . , ζm mit m ≤ n − k − 2
bezeichnet, so ist der Algorithmus für die Intervalle [ζi , ζi+1 ] klar. Die Randintervalle [−∞, ζ0 ]
und [ζm , +∞] müssen jedoch gesondert behandelt werden, um feste Intervallgrenzen für die
Einschlussverfahren zu ermitteln. Da beim Raycasting die algebraische Fläche in der Regel
geclippt wird und daher nur Schnittpunkte in einem gewissen Bereich des Strahls von Interesse
sind, kann man auch die Nullstellensuche von Vornherein auf ein Intervall [l, u] eingrenzen,
wobei l die untere und u die obere Clippinggrenze darstellt. Ist nun ζl die kleinste Nullstelle von
(k+1)
pn
(x) mit ζl > l so kann man [l, ζl ] als unteres Randintervall verwenden. Alle Nullstellen
unterhalb von l bleiben somit unberücksichtigt. Analog lässt sich ein maximales ζu mit ζu < u
und ein entsprechendes oberes Randintervall [ζu , u] finden.
6.4.2 Der Algorithmus von Sturm
Mit Hilfe des Sturmschen Theorems lässt sich die Anzahl voneinander verschiedener reeller
Nullstellen eines Polynoms pn (x) in einem Intervall [a, b] bestimmen. Dazu wird eine Folge
von Polynomen f0 , f1 , . . . , fn fallenden Grades mit
f0 = pn (x),
f1 = p′n (x),
und fi = −fi−2
mod fi−1 , i = 2, . . . , n
(6.49)
verwendet, die eine so genannte Sturmsche Kette bildet. Die Anzahl Vorzeichenwechsel V (x)
in der Kette für eine bestimmte Stelle x lässt sich bestimmen, indem man zunächst alle Werte
mit fk (x) = 0 aus der Kette streicht und anschließend abzählt, wie oft aufeinander folgende
Terme verschiedene Vorzeichen haben. Das Theorem von Sturm besagt nun, dass die Anzahl
verschiedener reeller Nullstellen r(a, b) von pn (x) in [a, b] durch
r(a, b) = V (a) − V (b)
(6.50)
gegeben ist. Eine detaillierte Herleitung des Theorems von Sturm ist in [Sto02, S. 377ff], aber
auch in [Mig92, S. 193ff] zu finden.
Die vorgestellte Abschätzung der reellen Nullstellen in einem Intervall [a, b] erlaubt in einfacher Weise die iterative Berechnung der kleinsten reellen Nullstelle dieses Intervalls. Enthält
[a, b] mindestens eine Nullstelle, das heißt es gilt r(a, b) > 0, so startet man die Iteration mit
a0 = a und b0 = b und konstruiert anschließend eine Folge von Intervallen, die gegeben ist
1
Der rekursive Algorithmus berechnet die Nullstellen eines Polynoms, indem die Nullstellen aller Ableitungen
des Polynoms berechnet werden. Die Bezeichnung D-Chain resultiert aus dieser „Kette von Ableitungen“
(engl. chain of derivatives).
51
6 Berechnung der Nullstellen univariater Polynome
durch
[ak+1 , bk+1 ] =
(
[ak , ck ] r(ak , ck ) > 0
[ck , bk ] sonst
mit ck =
ak + bk
.
2
(6.51)
Das Intervall [ak , bk ] wird also in der Mitte geteilt und für das vordere Teilintervall [ak , ck ] die
Anzahl an Nullstellen bestimmt. Befinden sich in [ak , ck ] noch Nullstellen, so wird die Iteration
damit fortgesetzt. Andernfalls wird das Intervall [ck , bk ] untersucht. Es gibt zwei Möglichkeiten
die Iteration zu beenden. Die erste Möglichkeit verkleinert das Intervall solange bis es nur noch
eine Nullstelle enthält (r(ak , bk ) = 1) und die Intervallgröße |bk − ak | eine gewisse Größe ε
unterschreitet. Um eine schnellere Konvergenz zu erzielen, sollte man jedoch zusätzlich das
Abbruchkriterium f (ak )f (bk ) < 0 verwenden und gegebenfalls ein Einschlussverfahren wie
beispielsweise das Bisektionsverfahren anschließen.
Beim Raycasting hat der Algorithmus von Sturm einen Vorteil, der sich besonders bei
Flächen höheren Grades bemerkbar macht. Viele Verfahren müssen im schlechtesten Fall alle
n Nullstellen des Polynoms pn (x) berechnen, bis feststeht, welches die kleinste Nullstelle im
Intervall [a, b] ist. Der soeben beschriebene Algorithmus berechnet unabhängig vom Grad
des Polynoms nur eine einzige Nullstelle. Dennoch kann er leicht auf die Berechnung aller
Nullstellen in [a, b] erweitert werden, indem man jeweils die kleinste Nullstelle ξ berechnet
und dann mit dem Restintervall [ξ, b] genauso verfährt.
6.4.3 Wertebereichanalyse
Können von einer Funktion f : R → R sowohl eine untere Schranke F als auch eine obere
Schranke F innerhalb eines Intervalls [a, b] berechnet oder zumindest gut genug abgeschätzt
werden, so lässt sich ein Bisektionsalgorithmus konstruieren, der die kleinste Nullstelle in diesem Intervall bestimmt. Gilt F ≤ 0 ≤ F auf [a, b], dann besteht die Möglichkeit, dass f in
[a, b] eine Nullstelle besitzt. Man teilt dann [a, b] in der Mitte und untersucht die beiden entstea+b
henden Intervalle [a, a+b
2 ] und [ 2 , b] rekursiv mit dem gleichen Algorithmus bis das Intervall
hinreichend verkleinert wurde und man annehmen kann, dass man eine Nullstelle gefunden
hat. Gilt F ≤ 0 ≤ F jedoch nicht, so verläuft der Graph von f in [a, b] gänzlich oberhalb
oder unterhalb der Achse und man kann sicher sein, dass in diesem Zweig der Bisektion keine
Nullstelle zu finden ist.
Da die OpenGL-Shading-Language nicht mit den hierfür benötigten rekursiven Funktionsaufrufen umgehen kann, muss der beschriebene Algorithmus leicht abgewandelt werden. Sei
[a, b] das Startintervall, in dem nach einer Nullstelle gesucht werden soll, [c, d] das gerade
betrachtete Teilintervall und e = c+d
2 die Mitte des Intervalls [c, d]. Nach der Teilung von [c, d]
berechnet man zuerst die obere Schranke F [c,e] und die untere Schranke F [c,e] von f in [c, e].
Gilt nun F [c,e] ≤ 0 ≤ F [c,e] , so wird [c, e] weiter verfeinert. Andernfalls wird nicht [e, d] sondern
[e, b] näher untersucht. Beim rekursiven Algorithmus musste für jede Rekursionsstufe jeweils
die aktuelle obere und untere Intervallgrenze auf den Stack gelegt werden. Dies ist nun nicht
mehr nötig, wodurch sich ein iterativer, in der OpenGL-Shading-Language implementierbarer
Algorithmus ergibt.
Intervallarithmetik
In jeder Iteration des Algorithmus müssen F und F , also der Wertebereich von f in einem
Intervall, berechnet werden. Dazu kann man die so genannte Intervallarithmetik verwenden,
wie sie in [Kea96] beschrieben wird. Sie definiert Arithmetik nicht auf reellen Zahlen, sondern
52
6.4 Nullstellenisolation
auf reellen Intervallen. Sind x = [x, x] und y = [y, y] solche Intervalle, dann sind die drei
algebraischen Operationen +, − und × für die idealisierte Intervallarithmetik gegeben durch
x ⊙ y = {x ⊙ y : x ∈ x ∧ y ∈ y} für ⊙ ∈ {+, −, ×}.
(6.52)
Für die praktische Realisierung ist aber die folgende Definition nützlicher:
x + y = [x + y, x + y]
(6.53)
x − y = [x − y, x − y]
(6.54)
x × y = [min{xy, xy, xy, xy}, max{xy, xy, xy, xy}]
(6.55)
Operationen zwischen einer reellen Zahl x und einem Intervall lassen sich leicht hinzufügen, wenn man für x das Intervall x = [x, x] verwendet. Das Ergebnis einer Operation
⊙ ∈ {+, −, ×} auf reellen Zahlen x ∈ x und y ∈ y liegt nun garantiert im Intervall x⊙y. Diese
fundamentale Eigenschaft macht man sich beispielsweise in der Numerik für die Abschätzung
von Rundungsfehlern zunutze. An dieser Stelle ist aber die Abschätzung des Wertebereichs
eines Polynoms f auf [a, b] von größerer Bedeutung. Dazu kann man f auf [a, b] anwenden
und dabei alle Berechnungen bei der Auswertung von f auf Intervallen durchführen. Für jedes
x ∈ [a, b] liegt f (x) in f ([a, b]). Soll zum Beispiel für f (x) = x(x − 1) der Wertebereich auf
[0, 1] abgeschätzt werden, so gilt
f ([0, 1]) ⊆ [0, 1]([0, 1] − 1) = [0, 1][−1, 0] = [−1, 0].
(6.56)
Anhand des exakten Wertebereichs von [−0.25, 0] wird bereits klar, dass diese Methode zu
einer Überschätzung neigt. Um die Überschätzung zu verringern, kann zur Bestimmung des
Wertebereichs von Polynomen beispielsweise die rekursive Taylor-Methode verwendet werden,
die im nächsten Abschnitt erläutert wird. Zuvor soll jedoch die praktische Realisierung der
Intervalloperationen beschrieben werden.
Computerarithmetik ist keineswegs exakt, sondern unterliegt aufgrund der begrenzten Stellenanzahl Rundungsfehlern. Um sicherzustellen, dass das Ergebnis einer Intervalloperation
auch sämtliche möglichen Werte einschließt, muss bei der Berechnung der unteren Intervallgrenze des Ergebnisses abgerundet und entsprechend bei der oberen Grenze aufgerundet werden. Diese Art der Rundung ist in jedem IEEE-754-kompatiblen Fließkommaprozessor verfügbar. Leider ist die Rundungsart in der OpenGL-Shading-Language weder beeinflussbar
noch bekannt, so dass eine korrekte Implementierung der Intervallarithmetik nicht ohne weiteres möglich ist. Daher wurden die Intervalloperationen – in der Hoffnung, dass derartige
Rundungsfehler beim Raycasting algebraischer Flächen nur einen geringen Einfluss haben –
in der Shading-Language ohne spezielle Rundung implementiert.
Rekursive Taylor-Methode
In [SSS+ 06] wird die so genannte rekursive Taylor-Methode zum Raycasting algebraischer
Flächen eingesetzt. Andere, ebenfalls in [SSS+ 06] getestete Methoden, wie zum Beispiel affine
Arithmetik, benötigten im Mittel mehr Iterationen und längere Renderingzeiten als die rekursive Taylor-Methode. Dies lässt auf eine genauere Bestimmung der Wertebereiche schließen.
Daher soll hier lediglich die rekursive Taylor-Methode erläutert werden.
Um nun also den Wertebereich von f (x) auf [x, x] zu bestimmen, wird f am Mittelpunkt
53
6 Berechnung der Nullstellen univariater Polynome
x0 des Intervalls [x, x] mit Hilfe der Taylorschen Formel expandiert:
f (x) = f (x0 ) + hf ′ (x0 ) + 12 h2 f ′′ (x0 + ϑh)
(6.57)
Dabei gilt
x ∈ [x, x],
x0 =
x+x
2 ,
x−x
0 < ϑ < 1 und h = x − x0 ∈ [− x−x
2 , 2 ]=
x−x
2 [−1, 1].
(6.58)
Nimmt man an, dass der Wertebereich [G, G] der zweiten Ableitung f ′′ (x) im Intervall [x, x]
bekannt ist, so lässt sich der Wertebereich [F , F ] von f in [x, x] als
[F , F ] = f (x0 ) +
x−x
′
2 [−1, 1]f (x0 )
ausdrücken. Mit der Substitution x1 =
einfachen:
x−x
2
+
1
2
x−x
2 [−1, 1]
2
[G, G]
kann man die Gleichung zudem noch etwas ver-
[F , F ] = f (x0 ) + x1 f ′ (x0 )[−1, 1] + 12 x21 [−1, 1]2 [G, G]
= f (x0 ) +
(6.59)
x1 f ′ (x0 )[−1, 1]
+
1 2
2 x1 [0, 1][G, G]
(6.60)
(6.61)
Da die zweite Ableitung f ′′ (x) wieder ein Polynom ist, kann [G, G] auf die gleiche Weise
berechnet werden. Die sukzessive Differentiation eines Polynoms f vom Grad n endet in einer
konstanten Funktion f (n) (x) = c. Der Wertebereich dieser Funktion im Intervall [x, x] ist
offensichtlich [c, c].
6.5 Global konvergente Iterationsverfahren
Bei der Nullstellenberechnung von Polynomen sind in der Praxis besonders solche Verfahren von Interesse, die für eine große Anzahl von Polynomen pn (x) und ohne Kenntnis von
Einschlussintervallen oder Startwerten zur einer Nullstelle konvergieren. Die so ermittelte
Nullstelle kann abdividiert werden und der Algorithmus erneut auf das verbleibende Polynom
angewendet werden. Konvergiert ein derartiges Verfahren bei einem Polynom mit reellen Koeffizienten zu einer komplexen Nullstelle ξ, dann kann, wie in Abschnitt 6.1.2 beschrieben, der
quadratische Faktor ξ · ξ abgespalten werden, so dass wieder ein Polynom mit ausschließlich reellen Koeffizienten entsteht. Das Abdividieren einer ungenau bestimmten Nullstelle verändert
meist die Lage der verbleibenden Nullstellen, so dass Algorithmen zu bevorzugen sind, welche
tendenziell die betragsmäßig kleinste Nullstelle liefern, die sich in vielen Fällen am genauesten bestimmen lässt. Da beim Raycasting nur die reellen Nullstellen benötigt werden, kann
die Berechnung der komplexen Nullstellen und die damit verbundene komplexe Arithmetik
außerdem einen erheblichen Zusatzaufwand zur Folge haben.
Nachfolgend werden die Muller- und die Laguerre-Methode beschrieben. Darüberhinaus
existieren noch andere global konvergente Iterationsverfahren wie zum Beispiel das Verfahren
von Jenkins und Traub [JT70] und der QR-Algorithmus [Wat82], die aber aufgrund ihrer
Komplexität für eine GPU-basierte Implementierung ungeeignet erscheinen und daher nicht
im Detail untersucht wurden.
54
6.5 Global konvergente Iterationsverfahren
6.5.1 Die Muller-Methode
Die Methode von Muller [Mul56] interpoliert pn (x) an drei bekannten Näherungen xi , xi−1 und
xi−2 mit Hilfe einer Parabel ϕ(x). Eine der beiden Lösungen der Parabelgleichung ϕ(x) = 0
wird als neue Näherung xi+1 verwendet. Die Iteration arbeitet mit komplexen Zahlen, da die
Nullstellen der Parabel komplex sein können. Aus der Lagrangeschen Interpolationsformel für
die Punkte (xi−2 , pn (xi−2 )), (xi−1 , pn (xi−1 )) und (xi , pn (xi )) sowie den Substitutionen
q=
xi − xi−1
xi−1 − xx−2
und y =
x − xi
xi − xi−1
(6.62)
lässt sich die Parabel
ϕ(x) = ϕ(xi + (xi − xi−1 )y) =
1
(Ay 2 + By + C)
1+q
(6.63)
mit
A = qpn (xi ) − q(1 + q)pn (xi−1 ) + q 2 pn (xi−2 )
(6.64)
C = (1 + q)pn (xi )
(6.66)
2
2
B = (2q + 1)pn (xi ) − (1 + q) pn (xi−1 ) + q pn (xi−2 )
(6.65)
i
herleiten (siehe dazu [EMNW05, S. 102ff]). Die durch y = xix−x
−xi−1 herbeigeführte Verschiebung der interpolierenden Parabel in die Nähe des Koordinatenursprungs dient hierbei der
Erhöhung der numerischen Stabilität für von Null entfernte Nullstellen. Nun lässt sich unter
1
die Lösungsformel für quadratische Gleichungen anwenden
Vernachlässigung des Faktors 1+q
und man erhält
−2C
√
y1/2 =
.
(6.67)
B ± B 2 − 4AC
Durch die Rücksubstitution x = xi + (xi − xi−1 )y ergibt sich der neue Näherungswert
xi+1 = xi + (xi − xi−1 )
−2C
√
.
B ± B 2 − 4AC
(6.68)
Das Vorzeichen im Nenner des Bruches
wird so gewählt, dass xi+1 und xi möglichst nahe
√
2
beieinander liegen. Im Fall B ± B − 4AC = 0 benutzt man y1/2 = 1. Muller schlägt in
[Mul56] außerdem die Verwendung der Startwerte
x0 = −1,
x1 = 1 und x2 = 0
(6.69)
vor. Die Funktionswerte an diesen Stellen werden allerdings nicht mit pn (x) = an xn + · · · + a0
berechnet, sondern
a0 − a1 + a2
an x0
(6.70)
a0 + a1 + a2
an x1
(6.71)
und a0
an x2
(6.72)
verwendet. Als Interpolationspolynom entsteht dann
ϕ(x) = a2 x2 + a1 x + a0 ,
(6.73)
55
6 Berechnung der Nullstellen univariater Polynome
wodurch pn (x) in der Umgebung von x = 0 gut approximiert wird. Das Verfahren liefert daher
meist die betragskleinste Nullstelle von pn (x), was sich beim Abdividieren der Nullstelle positiv
auswirkt.
Für das Muller-Verfahren konnte nur lokale Konvergenz nachgewiesen werden. Die Konvergenzordnung beträgt für einfache Nullstellen p ≈ 1.84 und für mehrfache Nullstellen p ≈ 1.23.
Durch eine weitere Modifikation erreichte Muller zu dem angegebenen Startprozess dennoch
eine Konvergenz in allen getesteten Fällen. Nach jeder Iteration wird dazu die Bedingung
pn (xi+1 ) (6.74)
pn (xi ) ≤ 10
geprüft. Ist der Wert > 10, so wird q halbiert, xi+1 neu berechnet und die Bedingung erneut
kontrolliert.
6.5.2 Die Laguerre-Methode
In [PTVF96, S. 372ff] wird die so genannte Laguerre-Methode zur numerischen Bestimmung
der Lösungen algebraischer Gleichungen vorgeschlagen. Geht man von der Linearfaktorzerlegung
pn (x) = (x − ξ1 )(x − ξ2 ) · · · (x − ξn )
(6.75)
aus, so kann man leicht induktiv nachweisen, dass die Ableitungen p′n (x) und p′′n (x) durch
1
1
1
′
+
+ ··· +
(6.76)
pn (x) = pn (x)
x − ξ1 x − ξ2
x − ξn
2
1
1
1
′′
+
+ ··· +
pn (x) = pn (x)
x − ξ1 x − ξ2
x − ξn
1
1
1
− pn (x)
+
+ ··· +
(6.77)
(x − ξ1 )2 (x − ξ2 )2
(x − ξn )2
gegeben sind. Betrachtet man
ln |pn (n)| = ln |x − ξ1 | + ln |x − ξ2 | + · · · + ln |x − ξn |,
(6.78)
so stellt man fest, dass sich die zugehörigen Ableitungen in einfacher Weise aus pn (x) und
dessen Ableitungen ergeben:
d ln |pn (n)|
1
1
1
p′ (x)
=
+
+ ··· +
= n
dx
x − ξ1 x − ξ2
x − ξn
pn (x)
2
d ln |pn (n)|
1
1
1
H(x) = −
=
+
+ ··· +
dx2
(x − ξ1 )2 (x − ξ2 )2
(x − ξn )2
′
p′′ (x)
pn (x) 2 p′′n (x)
−
= G(x)2 − n
=
pn (x)
pn (x)
pn (x)
G(x) =
(6.79)
(6.80)
(6.81)
Nun wird in der Laguerre-Methode die Annahme gemacht, dass sich die Nullstelle ξ1 in einer
gewissen Entfernung a von der aktuellen Näherung xi befindet und alle anderen Nullstellen
56
6.6 Zusammenfassung
sich in einer Entfernung b befinden, das heißt es gilt
xi − ξ1 = a und xi − ξk = b für k = 2, 3, . . . , n.
(6.82)
Damit lassen sich die Gleichungen (6.79) und (6.80) zu
1 n−1
+
a
b
n−1
1
H(xi ) = 2 + 2
a
b
G(xi ) =
(6.83)
(6.84)
umformen, wodurch sich die folgenden Berechnungsvorschriften für die Distanz a und die neue
Näherung xi+1 ergeben:
a=
n
p
G(xi ) ± (n − 1)(nH(xi ) − G(xi )2 )
xi+1 = xi − a
(6.85)
(6.86)
Das Vorzeichen im Nenner von a wird analog zur Muller-Methode so gewählt, dass a möglichst
klein wird. Negative Terme in der Wurzel erzeugen komplexe Näherungswerte, so dass das
Verfahren auch in der Lage ist, die komplexen Nullstellen zu finden. Nimmt man als Startwert
x0 = 0, so tendiert die Laguerre-Methode in der Regel dazu, gegen die betragsmäßig kleinste
Nullstelle zu konvergieren. Obwohl die Konvergenz für Polynome mit ausschließlich reellen
Nullstellen nachgewiesen wurde, ist über die Konvergenz bei Polynomen, die auch komplexe
Nullstellen besitzen, wenig bekannt. Es konnte aber gezeigt werden, dass die Laguerre-Methode
kubisch gegen einfache Nullstellen solcher Polynome konvergiert, wenn der Startwert nahe
genug an der Nullstelle liegt.
In der Praxis hat sich herausgestellt, dass das vorgestellte Verfahren fast immer konvergiert
und die Näherungswerte nur in Ausnahmefällen einen Zyklus bilden, der aber leicht durchbrochen werden kann, indem gelegentlich nicht xi − a, sondern xi − δa als neue Näherung
verwendet wird, wobei man δ in jedem solchen Schritt zufällig im Intervall (0, 1) wählt.
6.6 Zusammenfassung
Die Bestimmung der reellen Nullstellen von Polynomen ist ein notwendiger Schritt beim Raycasting algebraischer Flächen. Diese Nullstellen können mit Hilfe des D-Chain-Verfahrens oder
des Algorithmus von Sturm isoliert werden, so dass sie anschließend durch lokal konvergente Verfahren, wie Bisektion oder Regula-Falsi, bis zur gewünschten Genauigkeit angenähert
werden können. Die Anwendung lokal konvergenter Verfahen ist bei der Wertebereichsanalyse
nicht ohne weiteres möglich, da das Verfahren nicht in der Lage ist die genaue Anzahl von Nullstellen innerhalb eines Intervalls zu ermitteln. Global konvergente Verfahren, wie die Mulleroder Laguerre-Methode, benötigen hingegen keine besonderen Startwerte, sondern konvergieren stets zu einer (gegebenenfalls komplexen) Nullstelle des Polynoms. Diese Nullstelle kann
abdividiert und der Algorithmus auf das entstehende Polynom erneut angewendet werden.
Für Polynome bis zum Grad vier existieren darüberhinaus geschlossene Lösungsformeln.
57
6 Berechnung der Nullstellen univariater Polynome
58
7 Implementierung
Die in den vorhergehenden Kapiteln beschriebenen Verfahren zum Raycasting algebraischer
Flächen wurden zusammen mit einer grafischen Benutzeroberfläche und einem Compiler, der
aus der Formel einer algebraischen Fläche den benötigten GLSL-Code erzeugt, in einem Prorgamm namens RealSurf umgesetzt. Die folgenden Abschnitte beschäftigen sich mit der
Programmarchitektur und den genannten Teilaspekten der Implementierung.
7.1 Umsetzung des Raycasting-Algorithmus in GLSL
Eine der Hauptaufgaben dieser Arbeit ist die Umsetzung verschiedener Algorithmen zur Berechnung der Schnittpunkte einer algebraischen Fläche mit einem Strahl in der OpenGL-Shading-Language. Dabei ist eine Vielzahl spezieller Eigenschaften des GPU-Programmiermodells
und der Shader-Programmierung zu beachten. Hinzu kommen spezifische Beschränkungen der
Grafikhardware und -treiber, die nicht aus der Spezifikation der OpenGL-Shading-Language
hervorgehen. Diese Beschränkungen und die daraus resultierenden Hilfskonstruktionen, die
die Implementierung einiger Algorithmen erst ermöglichten, werden nun näher betrachtet.
7.1.1 Verwendete Grafikhardware und Grafiktreiber
Die Lauffähigkeit der in dieser Arbeit erstellen Shader-Programme hängt maßgeblich von der
verwendeten Grafikhardware und dem Grafikkartentreiber ab. Die Hardware muss die Funktionalitäten des Shader Model 3 unterstützen und der Treiber, der den GLSL-Compiler enthält,
muss in der Lage sein, die GLSL-Programme korrekt auf den Befehlssatz der Hardware umzusetzen.
Die gesamte Shader-Entwicklung wurde auf NVidia-GPUs der Serien GeForce 6 und GeForce 7 durchgeführt. Beide Serien verfügen, soweit bekannt, über gleichwertige Fähigkeiten
beim Umgang mit Shadern. Als Grafiktreiber für beide Serien diente der NVidia-Forceware-Treiber in der Version 93.71.
Tests der entwickelten Programme konnten weiterhin auf einer NVidia Quadro FX 4000
und einer ATI Radeon X1600 durchgeführt werden. Leider konnte die ATI-Grafikkarte die
Raytracing-Shader nicht verarbeiten. Der ATI-Treiber Catalyst 7.1 schaltete mit der wenig
hilfreichen Meldung „Unsupported language feature used“ auf den Software-Renderer um. Die
genaue Ursache für dieses Verhalten konnte nicht ermittelt werden.
In der Spezifikation der OpenGL-Shading-Language werden keine Einschränkungen bezüglich Größe oder Ausführungszeit der Programme vorgenommen. Programmverzweigungen,
Schleifen und Array-Zugriffe können beliebig eingesetzt werden. Leider existieren in der Praxis
bei den genannten Punkten sehr wohl Einschränkungen. Hinzu kommen verschiedene Probleme mit dem Shading-Language-Compiler des verwendeten Grafikkartentreibers Forceware 93.71:
59
7 Implementierung
Array-Zugriffe
Der Zugriff auf Arrayelemente kann nur durch Konstanten oder durch Variablen, deren Werte
zur Übersetzungszeit bekannt sind, erfolgen. Der Compiler akzeptiert die meisten Zählschleifen, die auf Arrays zugreifen. Werden Schleifen geschachtelt oder sind die Berechnungen der
Array-Indizes zu komplex, so ist der Compiler nicht immer in der Lage, die Array-Indizes im
Voraus zu berechnen und bricht den Übersetzungsvorgang ab. Die Verwendung von breakBefehlen zum vorzeitigen Verlassen einer Schleife wirkt sich auf die selbe Weise aus.
Schleifen
Schleifen, deren Schleifenbedingung von einer uniform-Variable abhängt, werden nach 255
Iterationen abgebrochen. Durch Schachtelung von Schleifen kann diese Beschränkung aber
abgeschwächt werden.
Bedingte Rücksprünge
Der bedingte Rücksprung aus Funktionen ist nicht möglich. Pro Funktion kann es also nur
eine return-Anweisung geben. Dieses Verhalten verursacht lediglich einen Performanceverlust.
Statt dem bedingten Rücksprung wird eine Ergebnisvariable verwendet, der abhängig von der
Bedingung das Ergebnis zugewiesen wird. Die Funktion gibt immer diese Ergebnisvariable
zurück.
Interne Compilerfehler
Eine sehr große Hürde bei der Implementierung komplexer Algorithmen in GLSL ist ein
Abbruch des Übersetzungvorgang mit der Fehlermeldung „internal compiler error“, der ohne
erkennbaren Grund bei größeren Programmen auftritt. Hinter einem solchen Fehler verbirgt
sich meist ein einfacher Syntaxfehler in Form eines vergessenen Semikolons oder auch ein
Schreibfehler in einem Variablennamen. Da bei internen Compilerfehlern meist keine Zeilennummern ausgegeben werden, ist die Fehlersuche jedoch sehr zeitaufwendig und die eigentliche
Fehlerursache in der Praxis nur sehr schwer zu ermitteln.
Modularisierung des Quelltextes
OpenGL erlaubt eine Aufteilung des GLSL-Quelltextes in einzelne Module, die unabhängig
voneinander kompiliert und erst beim Aufruf von glLinkProgram zusammengefügt werden.
Die Shader-Programme zum Raycasting verschiedener algebraischer Flächen unterscheiden
sich nur wenig. Zur Performacesteigerung könnten die flächenunabhängigen Shader-Komponenten daher einmalig in einem Vorverarbeitungsschritt übersetzt werden. Diese Funktionalität wird jedoch vom NVidia-GLSL-Compiler nicht unterstützt, so dass der gesamte Quelltext
des zum Raycasting verwendeten Shader-Programms zu einer Zeichenkette zusammengefügt
werden muss, die dann dem Compiler übergeben wird.
7.1.2 Modifikation der Raycasting-Algorithmen für GLSL
Aufgrund der vorgestellten Einschränkungen bei der GPU-Programmierung müssen einige
der in Kapitel 5 und 6 erläuterten Verfahren modifiziert werden. Besondere Auswirkungen
auf die Implementierung haben Array-Zugriffe innerhalb von Schleifen. Alle Array-Indizes
müssen dem GLSL-Compiler zur Übersetzungszeit bekannt sein. Sie dürfen daher nicht vom
Raycasting-Strahl oder anderen Variablen abhängig sein, die erst während des Renderingvorgangs zur Verfügung stehen. Die notwendigen Modifikationen der einzelnen Algorithmen
sind sich sehr ähnlich und werden nachfolgend am Beispiel des D-Chain-Verfahrens und des
Abdividierens von Nullstellen beschrieben.
60
7.1 Umsetzung des Raycasting-Algorithmus in GLSL
Das D-Chain-Verfahren
(k)
Beim D-Chain-Verfahren (vgl. Abschnitt 6.4.1) werden die Nullstellen von pn (x) mit Hilfe
(k)
(k+1)
(k)
der Nullstellen der Ableitung (pn (x))′ = pn
(x) berechnet. Jedes pn (x) besitzt maximal
(k+1)
n − k Nullstellen. Sind alle Nullstellen von pn
(x) innerhalb des Clipping-Intervalls (l, u)
bereits berechnet und zusammen mit l und u sortiert in einem Array gespeichert1 , so bilden
(k)
jeweils zwei benachbarte Einträge ein Intervall, welches maximal eine Nullstelle von pn (x)
(k+1)
enthält. Nun kann es jedoch vorkommen, dass pn
(x) nur m < n − k reelle Nullstellen innerhalb von [l, u] besitzt und sich damit die Anzahl der zu untersuchenden Intervalle verringert.
(k+1)
(x), welches
Die verwendeten Array-Indizes verändern sich also in Abhängigkeit von pn
wiederum aus dem Raycasting-Strahl und der Formel der algebraischen Fläche gebildet wird
und somit nicht zur Übersetzungszeit bekannt ist.
(k)
Nachfolgend wird zur Speicherung der Nullstellen für jedes pn (x) ein Array r(k) der Länge
(k)
(k)
n − k + 2 verwendet. r0 enthält stets die untere Clipping-Grenze l und rn−k+1 stets die
obere Clipping-Grenze u. Zusätzlich wird eine Markierung ◦ eingeführt, die ein Array-Element
von r(k) als gültige obere Grenze eines Intervalls definiert, in dem nach einer Nullstelle von
(k−1)
(k)
pn
(x) gesucht werden soll. Nicht markierte Elemente ri , i = 1, . . . , n − k werden während
(k)
der Iteration über das Array jeweils mit dem Vorgänger ri−1 belegt, so dass ein sortiertes Array
entsteht, welches nur die Werte von markierten Elementen, sowie von l und u enthält.
Für ein Polynom sechsten Grades mit zwei reellen Nullstellen ξ1 und ξ2 in (l, u) würde
der unmodifizierte Algorithmus lediglich eine Liste mit den Werten l, ξ1 , ξ2 und u verwenden,
während r(0) beispielsweise die folgende Struktur aufweisen könnte (die gestrichelt umrandeten
Felder sind immer gleich und werden nicht berechnet):
r(0) =
l
l
ξ1 ◦
ξ1
ξ1
ξ2 ◦
ξ2
u ◦
Gelangt der modifizierte Algorithmus nun bei der Iteration über r(k) an ein nicht markiertes
(k)
(k−1)
Array-Element ri mit i = 1, . . . , n − k, so wird dieses einfach übersprungen. ri
wird
(k−1)
(k)
dabei auf ri−1 gesetzt und nicht markiert. Ist ri hingegen markiert, so wird das Intervall
(k)
(k)
(k−1)
(ri−1 , ri ) auf eine Nullstelle von pn
(k−1)
ri
(x) untersucht. Wird eine Nullstelle ξ gefunden, dann
(k−1)
ri
setzt man
= ξ und markiert
als gültig. Andernfalls verfährt man wie im Falle
(k)
des unmarkierten ri . Abbildung 7.1 zeigt das Verfahren anhand des Polynoms pn (x) =
x4 − 11x3 + 44x2 − 74x + 40.
Durch dieses recht umständliche Vorgehen kann die Adressierung der r(k) für alle Polynome pn (x) auf die gleiche Weise erfolgen und somit in GLSL und unter den beschriebenen
Einschränkungen durch Grafiktreiber und -hardware realisiert werden.
Abdividieren von Nullstellen
Die Muller- und die Laguerre-Methode berechnen jeweils eine Nullstelle des reellen Polynoms
pn (x), welche sowohl reell als auch komplex sein kann. Im Falle einer komplexen Nullstelle
ξ wird nicht der Linearfaktor (x − ξ), sondern der quadratische Faktor (x − ξ)(x − ξ) mit
Hilfe des Horner-Schemas (siehe Abschnitt 6.1.2) abdividiert, wodurch wieder ein Polynom
1
Andere brauchbare Datenstrukturen, wie beispielsweise verkettete Listen, können nicht in GLSL realisiert
werden, da es keine Zeigertypen gibt.
61
7 Implementierung
Nullstellen von p′′′
n (x) :
r(3) =
Nullstellen von p′′n (x) :
Nullstellen von
p′n (x)
r(2) =
r(1)
:
Nullstellen von pn (x) :
r(0)
=
=
l
l
l
l
2.75 ◦
2.27 ◦
3.23 ◦
1.0
1.0
1.71 ◦
1.0 ◦
u ◦
1.71
1.71
u ◦
u ◦
4.0 ◦
u ◦
Abbildung 7.1: Inhalt der r (k) bei der Berechnung der Nullstellen des Polynoms
pn(x) = x4 − 11x3 + 44x2 − 74x + 40 mit dem D-Chain-Verfahren. Die Arrays sind
so angeordnet, dass die beiden Array-Elemente von r(k+1) , die über einem Element von r(k)
stehen, das zu untersuchende Einschlussintervall dieses Elementes definieren. Der Algorithmus
(1)
(1)
soll am Beispiel der Berechnung von r(1) beschrieben werden. Zuerst wird r0 = l und r4 = u
gesetzt. Das erste zu untersuchende Intervall (l, 2.27) enthält die Nullstelle 1.71 von p′n (x).
(1)
Daher wird r1 = 1.71 gesetzt und mit ◦ markiert. Die folgenden Intervalle (2.27, 3.23) und
(3.23, u) enthalten keine Nullstellen von p′n (x). Die zuletzt gültige obere Intervallgrenze 1.71
(1)
(1)
(1)
wird in r2 und r3 kopiert, aber nicht mit ◦ versehen. r4 = u ist immer eine gültige obere
Intervallgrenze und erhält daher ein ◦. Bei der Berechnung der Nullstellen von pn (x) werden
nun alle Intervalle übersprungen, deren obere Intervallgrenze nicht mit ◦ markiert ist. Die mit ◦
versehenen Elemente von r(0) sind, abgesehen von u, die reellen Nullstellen von pn (x) innerhalb
des Clipping-Intervalls (l, u). Alle in der Abbildung enthaltenen Zwischenwerte wurden auf
zwei Nachkommastellen gerundet.
mit ausschließlich reellen Koeffizienten entsteht. Anschließend wird die nächste Nullstelle des
verbleibenden Polynoms ermittelt. Ob der Grad dieses Polynom um eins oder um zwei kleiner
ist als der Grad von pn (x), hängt also von der gefundenen Nullstelle ab, die natürlich nicht
zur Übersetzungszeit bekannt ist.
Die Modifikation des Verfahrens besteht nun darin, eine Zählschleife zu verwenden, welche
annimmt, dass eine reelle Nullstelle gefunden und abdividiert wird. Dadurch reduziert sich
der Polynomgrad in jeder Iteration um eins. Tritt nun eine komplexe Nullstelle auf, so wird
der quadratische Faktor abdividiert und in der nächsten Iteration der Zählschleife keine neue
Nullstelle berechnet. Dies kann leicht durch eine Variable skip_next realisiert werden, die
jeweils am Anfang des Schleifenrumpfes geprüft wird:
...
bool skip_next = false;
for( int i = n; i >= 1; i-- )
{
if( skip_next )
{
// Nullstellensuche für eine Iteration aussetzen
skip_next = false;
}
else
{
// Nullstellensuche durchführen
root = calculate_root( p );
62
7.2 Erzeugung der Shader aus den Formeln algebraischer Flächen
if( is_real( root ) )
{
// linearen Faktor abdividieren
p = deflate1( p, root );
}
else
{
// quadratischen Faktor abdividieren
p = deflate2( p, root );
skip_next = true;
}
}
}
...
7.2 Erzeugung der Shader aus den Formeln algebraischer
Flächen
Der Fragment-Shader zum Raycasting algebraischer Flächen besteht aus zwei Teilen: einem
flächenunabhängigen Teil mit den Funktionen zur Berechnung der Polynomkoeffizienten und
der Nullstellen von Polynomen und einem flächenabhängigen Teil, welcher die Funktionen zur
Berechnung der Polynomkoeffizienten ansteuert. Der letztgenannte Teil muss für jede algebraische Fläche neu geschrieben beziehungsweise generiert werden. Zu diesem Zweck wurde ein
Compiler entwickelt, der in der Lage ist die Flächenformel in einen Syntaxbaum zu überführen
und daraus wiederum den benötigten Shader-Code zu erzeugen. Hierfür wurden die unter der
GNU General Public License veröffentlichten Werkzeuge Flex [Frea] und Bison [Freb]
verwendet, die anhand der grammatikalischen Beschreibung einer Sprache so genannte Scanner beziehungsweise Parser für diese Sprache generieren können. Der Scanner zerlegt einen
Eingabetext in logisch zusammengehörige Einheiten, auch Token genannt, die durch reguläre
Ausdrücke beschrieben werden. Mit Hilfe dieser Token führt der Parser die Syntaxanalyse der
Eingabe durch, so dass daraus beispielsweise ein Syntaxbaum erzeugt werden kann. Auf eine
detaillierte Darstellung der Funktionsweise dieser Werkzeuge wird in dieser Arbeit verzichtet.
Eine gute Referenz hierfür liefern [LMB95] und [Her95].
Die dem Parser zugrunde gelegte Grammatik (siehe Anhang B) ermöglicht es, Polynome
in den drei Variablen x, y und z zu verarbeiten. Hinzu kommen beliebige Variablen, die
als reellwertige Flächenparameter dienen und ein interaktives Verändern der Fläche erlauben
sollen. Der Parser erzeugt aus der Eingabe einen Syntaxbaum, wie er in Abbildung 7.2 zu
sehen ist. Ist der Syntaxbaum einmal erstellt, so kann der GLSL-Code zur Berechnung der
Polynomkoeffizienten leicht durch Traversieren des Baumes erstellt werden.
Für die in Abschnitt 5.2 beschriebenen Termumformungen auf der GPU würde beispielsweise der folgende Code erzeugt:
// Basispolynome mit den Daten des
polynomial x = create_poly_1( o.x,
polynomial y = create_poly_1( o.y,
polynomial z = create_poly_1( o.z,
Raycasting-Strahls o+t*d füllen
d.x );
d.y );
d.z );
63
7 Implementierung
+
^
+
^
∗
^
x
−
3 x
y
z
2
2
1
Abbildung 7.2: Syntaxbaum zur Eingabe x3 ∗ (x − 1) + y 2 + z 2 . Die Klammerung der
Teilausdrücke ist implizit in der Baumstruktur enthalten.
// p durch Termumformungen berechnen
polynomial p = add( add( mult( power( x, 3, 1 ), sub( x, create_poly_0(
1.0 ), 1 ), 4 ), power( y, 2, 1 ), 4 ), power( z, 2, 1 ), 4 );
Der dritte Parameter der Funktionen add, sub, mult und power bestimmt jeweils den Grad des
resultierenden Polynoms, welcher als Konstante zur Übersetzungszeit des Shaders vorliegen
muss2 .
Zur Berechnung der Polynomkoeffizienten durch Interpolation (Abschnitt 5.3) wird lediglich
eine Funktion zur Auswertung der algebraischen Fläche F in einem Punkt benötigt:
float F( float x, float y, float z )
{
return x * x * x * ( x - 1 ) + y * y + z * z;
}
Der Code zur numerischen Berechnung des Gradienten von F mit Hilfe von Differenzenquotienten besitzt eine ähnlich einfache Struktur.
Zur Verbesserung der numerischen Stabilität des Raycasting-Verfahrens ist es allerdings
∂F
∂F
sinnvoll die Formeln der partiellen Ableitungen ∂F
∂x , ∂y und ∂z mit Hilfe symbolischer Berechnungen aus F herzuleiten. Der Baum wird dazu beginnend mit der Wurzel entsprechend
den Regeln der Differentialrechnung umstrukturiert. In einem Vorverarbeitungsschritt werden für jeden Knoten die in seinem Unterbaum vorkommenden Variablen bestimmt. Dadurch
kann man leicht ermitteln, ob sich ein (gegebenenfalls sehr komplexer) Unterbaum durch die
2
Die Funktionen sind so implementiert, dass der Grad der Eingabepolynome nicht benötigt wird.
64
7.3 Grafische Benutzeroberfläche
Differentiation auf Null reduziert. Die Differentiation von
−
+
3
x +y∗z−1
=
b
1
ˆ
x
∗
3
y
z
nach y würde beispielsweise folgendermaßen ablaufen (die mit
me müssen jeweils noch differenziert werden):
∂
− ∂y
∂
∗ ∂y
1
^
⇒
^
∗
3
gekennzeichneten Unterbäu-
∂
+ ∂y
+
x
∂
∂y
y
x
⇒
∗
3 y
y
⇒ z
z
z
z
Aus den Syntaxbäumen der partiellen Ableitungen kann nun der Code zur Berechnung des
Gradienten erstellt werden, der zusammen mit dem Code zur Berechnung der Polynomkoeffizienten den flächenabhängigen Teil des Fragment-Shaders bildet. Dieser wird durch den
flächenunabhängigen Teil zu einem vollständigen Fragment-Shader für das Raycasting algebraischer Flächen ergänzt.
7.3 Grafische Benutzeroberfläche
Die Bedienoberfläche von RealSurf (Abbildung 7.3) wurde mit Hilfe der Qt-Bibliothek
entwickelt. Sie erlaubt die Eingabe von Flächenformeln und Flächenparametern, sowie die
Einstellung zahlreicher Darstellungsoptionen wie beispielsweise Materialien und Beleuchtung.
Die jeweils aktuelle Ansicht einer Fläche kann zudem abgespeichert und wieder geladen werden.
Die für das Raycasting relevanten Aufgaben der Benutzeroberfläche sind in Abbildung 7.4 als
Aktivitätsdiagramm zusammengefasst. Sie werden im Anschluss erläutert.
7.3.1 Überführung einer Flächenformel in ein gerendertes Bild
Gibt der Nutzer eine Flächenformel in die Eingabezeile ein oder wird eine gespeicherte Fläche
geladen, so wird die Formel an den in Abschnitt 7.2 beschriebenen Compiler weitergeleitet.
Dieser generiert den GLSL-Raycasting-Code und liefert außerdem alle in der Formel enthaltenen Flächenparameter, das heißt alle Variablen außer x, y und z. Nach dem Übersetzen des
Shader-Codes durch den GLSL-Compiler wird im OpenGL-Widget mit diesem Shader eine
Box gezeichnet, in der das Raycasting durchgeführt wird.
65
7 Implementierung
Abbildung 7.3: Startansicht der grafischen Benutzeroberfläche von RealSurf. Das
Hauptfenster gliedert sich in ein OpenGL-Widget zum Betrachten der Fläche (links), eine
Zeile zur Eingabe einer Flächenformel (unten) und einen Bereich zur Steuerung der Flächenparameter, verschiedener Anzeigeoptionen und zum Laden und Speichern von Flächen (rechts).
Programmstart
auf Eingabe warten
neue Flächenformel eingegeben
Fläche gedreht, skaliert oder verschoben
Flächen- oder Darstellungs- parameter verändert
Fehlermeldung
anzeigen
Formel parsen
Parameter an
Shader-Programm
übergeben
Transformation an
Shader-Programm
übergeben
Raycasting-Shader
ausführen
Eingabe fehlerhaft
Eingabe fehlerfrei
GLSL-Quelltext des
Raycasting-Shaders
generieren
Abbildung 7.4: Vereinfachte Darstellung der für das Raycasting relevanten Aufgaben in RealSurf.
66
7.3 Grafische Benutzeroberfläche
7.3.2 Transformationen und Darstellungsparameter
RealSurf erlaubt das interaktive Drehen, Skalieren und Verschieben der Fläche, also die interaktive Veränderung einer Transformationsmatrix. Diese Transformationsmatrix wird dem
Raycasting-Shader als uniform-Variable übergeben. Ähnlich verhält es sich mit den Materialeigenschaften der Fläche, sowie mit den Parametern der Lichtquellen. Hierfür bietet die
OpenGL-Shading-Language bereits vordefinierte uniform-Variablen an [KBR04, S. 45ff], die
im wesentlichen den Parametern des Beleuchtungsmodells nach Phong entsprechen, welches
von OpenGL verwendet wird [SA04, S. 59ff]. Daher benutzt auch RealSurf dieses Beleuchtungsmodell.
7.3.3 Flächenparameter
Durch die Veränderung von Flächenparametern, wie beispielsweise dem inneren und äußeren
Radius eines Torus, kann auch die Form einer Fläche interaktiv geändert werden. Solche
Parameter werden vom Flächenformel-Compiler als uniform-Variable in den Shader integriert,
um dort in die Berechnung der Polynomkoeffizienten einzufließen. Die Parameter können in
RealSurf bequem über Schieberegler eingestellt werden. Abbildung 7.5 zeigt die Animation
zwischen verschiedenen Flächen vierten Grades.
Mit Hilfe von Flächenparametern lassen sich auch leicht Morphings zwischen beliebigen
algebraischen Flächen erzeugen. Dazu wird einfach eine Interpolation zwischen den Flächen
vorgenommen. Für die Variable t und die Punkte (−1, A), (0, B) und (1, C) ergibt sich beispielsweise mit der Interpolationsformel von Lagrange
A
t(t − 1)
(t + 1)t
− B(t + 1)(t − 1) + C
.
2
2
(7.1)
Setzt man für A, B und C jeweils Formeln algebraischer Flächen ein, so entsteht bei der
Variation von t zwischen −1 und 1 eine Animation, die alle drei Flächen enthält und teilweise
sehr interessante Zwischenergebnisse hervorbringt. Das Rendering einer solchen zusammengesetzten Formel ist natürlich aufwendiger als das Rendering einer der Einzelflächen.
(a) µ2 =
1
3
(b) µ2 =
2
3
(c) µ2 = 1
(d) µ2 =
√
2
(e) µ2 = 3
Abbildung 7.5: Animation zwischen verschiedenen Flächen der Kummer-Familie
durch Veränderung des Flächenparameters µ. Kummer-Flächen
genügen √
der algebrai√
2 )(x2 + y 2 + z 2 − µ2 w 2 )2 − (3µ2 − 1)(w − z − 2x)(w − z + 2x)(1 + z +
schen
Gleichung
(3
−
µ
√
√
2y)(w + z − 2y) = 0 vierten Grades [Wei99]. w wurde in dieser Animation auf 1 festgelegt.
67
7 Implementierung
68
8 Ergebnisse
Beim Raycasting algebraischer Flächen werden Algorithmen zur Berechnung der Koeffizienten
des Polynoms f (t), welches durch Einsetzen des Raycasting-Strahls in die Flächengleichung
F (x, y, z) = 0 entsteht, und zur Berechnung der Nullstellen von f (t) benötigt. Die theoretischen Grundlagen, sowie die Besonderheiten der Implementierung in der OpenGL-ShadingLanguage wurden bereits erläutert. Hinsichtlich der Laufzeiten der Berechnungen und der
Qualität der erzielten Ergebnisse gibt es teilweise erhebliche Unterschiede zwischen den verschiedenen Algorithmen. Zur kompakteren Darstellung der Resultate werden die Verfahren
in den folgenden Abschnitten gegebenenfalls durch ihre Anfangsbuchstaben abgekürzt: tugpu
(Termumformungen auf der GPU), li (Lagrange-Interpolation), ni (Newton-Interpolation), dcb
(D-Chain-Verfahren mit Bisektion), dcrf (D-Chain-Verfahren mit Regula-Falsi), sk (Algorithmus von Sturm), wba (Wertebereichsanalyse), m (Muller-Iteration), l (Laguerre-Iteration) und
lf (Lösungsformeln).
8.1 Optischer Vergleich der implementierten Verfahren
Ein wichtiges Bewertungskriterium für Algorithmen, die beim Raycasting eingesetzt werden,
ist die Qualität der erzeugten Renderings. Ein Verfahren mit hoher Laufzeit und guter Bildqualität ist beispielsweise deutlich besser geeignet als ein schnelles Verfahren, welches häufig
Bildfehler verursacht. Besonders hervorzuheben ist die in Abschnitt 4.2 vorgestellte Heuristik
zur Optimierung des Raycasting-Strahls. Sie stabilisiert mit geringem Aufwand alle nachfolgenden Berechnungen und erhöht die Bildqualität enorm (Abbildung 8.1). Die Bewertung der
einzelnen Verfahren wird unter Verwendung dieser Optimierungsheuristik durchgeführt.
8.1.1 Berechnung der Polynomkoeffizienten
Während die Berechnung der Polynomkoeffizienten durch Termumformungen auf der GPU
stets gute bis sehr gute Ergebnisse liefert, hängt die Korrektheit der mit den Interpolationsverfahren berechneten Koeffizienten sehr stark von den gewählten Stützstellen ti entlang
(a) Ohne Optimierung
(b) Mit Optimierung
Abbildung 8.1: Verbesserung der Stabilität der verwendeten numerischen Verfahren
durch Optimierung des Raycasting-Strahls am Beispiel der Kleinschen Flasche.
69
8 Ergebnisse
des Raycasting-Strahls ab. Spiegeln die aus den Stützstellen berechneten Punkte (ti , f (ti )) die
charakteristischen Eigenschaften von f nur schlecht wieder, so können selbst kleine Rundungsfehler eine wesentliche Veränderung der Nullstellenlage zur Folge haben (siehe Abbildung 5.1).
Da die charakteristischen Eigenschaften jedoch nicht im Voraus bekannt sind, wurden die
Stützstellen äquidistant im Clipping-Intervall [a, b] gewählt. Das Auftreten von Bildfehlern
ist somit stark flächenabhängig und besonders in der Nähe von Singularitäten zu beobachten.
Des Weiteren lassen sich bei einigen Flächen Bildfehler durch Skalieren der Fläche provozieren. Dieses unerwünschte Verhalten ist exemplarisch in Abbildung 8.2 dargestellt. Dennoch
liefern auch die Interpolationsverfahren bei den meisten der getesteten Flächen befriedigende
bis gute Resultate.
Die Polynomkoeffizienten sollten also bevorzugt durch Termumformungen auf der GPU
berechnet werden. Aufgrund der stark flächenabhängigen Renderingqualität sind die Interpolationsverfahren nur bedingt geeignet. Natürlich lassen sich leicht Beispiele konstruieren, bei
denen alle getesteten Verfahren allein aufgrund der endlichen Zahlendarstellung fehlschlagen.
So entstehen beispielsweise beim Raycasting der Fläche x2 = 0 nur Polynome mit doppelten Nullstellen, die durch die Verwendung von Fließkommazahlen häufig zu Polynomen ohne
Nullstelle degenerieren.
8.1.2 Berechnung der Polynomnullstellen
Die in Kapitel 6 vorgenommene Klassifizierung der Algorithmen zur Bestimmung von Polynomnullstellen wird auch für die Bewertung dieser Verfahren übernommen, da Algorithmen
derselben Kategorie häufig ähnliche Vor- beziehungsweise Nachteile aufweisen. Der Vergleich
wird anhand verschiedener Beispielrenderings illustriert, da es nur schwer möglich ist, eine
algebraische Fläche zu finden, an der sich die typischen Eigenschaften aller Verfahren gut
erläutern lassen.
Lösungsformeln
Die Lösungsformeln für Polynome vom Grad eins und zwei sind aufgrund ihres äußerst geringen Berechnungsaufwands sehr robust und liefern exzellente Ergebnisse. Durch Divisionen und
Wurzelberechnungen entstehenden Rundungsfehler verändern die Ergebnisse nur unwesentlich
und fallen daher bei der Visualisierung algebraischer Flächen nicht ins Gewicht.
Für Polynome der Form x3 + ax2 + bx + c schlägt die Lösungsformel (vgl. Abschnitt 6.2.3)
fehl, sobald a der dominierende Koeffizient ist. Die genaue Ursache konnte aufgrund fehlender
Debugging-Mechanismen bei der GPU-Programmierung nicht ermittelt werden. Eine mögliche Ursache der Instabilität könnte die im Algorithmus enthaltene hohe Potenz a6 sein. Die
Koeffizienten b und c tauchen maximal in dritter Potenz auf.
Leider setzt die Formel zur Lösung biquadratischer Gleichungen auf der Lösungsformel
kubischer Gleichungen auf, so dass sich die Fehler entsprechend fortpflanzen. Ein beispielhafter
Qualitätsvergleich der einzelnen Lösungsformeln ist in Abbildung 8.3 zu finden.
Nullstellenisolation
Die Verfahren zur Nullstellenisolation bestimmen Intervalle mit jeweils einer Nullstelle, welche
anschließend mit Hilfe lokal konvergenter Verfahren, wie der Bisektion, weiter verkleinert
werden, bis die Nullstelle genau genug approximiert ist. Bildfehler können entstehen, wenn
70
8.1 Optischer Vergleich der implementierten Verfahren
die berechneten Intervalle beispielsweise nicht alle zur Berechnung der Pixelfarbe benötigten
Nullstellen enthalten oder wenn ein Intervall mehrere Nullstellen enthält.
Das D-Chain-Verfahren erwies sich in Kombination mit der Bisektion in den durchgeführten
Tests als äußerst stabil. Die getesteten Flächen, die mit dem D-Chain-Verfahren nicht korrekt
dargestellt werden, können auch mit keinem der anderen Algorithmen fehlerfrei gerendert
werden. Das Scheitern aller Algorithmen lässt darauf schließen, dass bereits die Polynomkoeffizienten nicht exakt berechnet werden. Zusätzlich zur Bisektion wurde hier auch die RegulaFalsi untersucht. Sie zeigt in der GLSL-Implementierung sehr schlechte Konvergenzeigenschaften und führt teilweise sogar zu einem Abbruch des Shader-Programms.
Renderings, die mit dem Algorithmus von Sturm erzeugt werden, enthalten oftmals Bildfehler entlang bestimmter Kurven. Diese verändern sich je nach Blickrichtung und Position
der Fläche und fallen nicht nur mit Singularitäten zusammen. Tests ergaben, dass diese Darstellungsfehler genau dann auftreten, wenn das zu untersuchende Polynom f (t) und dessen
Ableitung f ′ (t) ähnliche Linearfaktoren enthalten. Dadurch entsteht bei den im Algorithmus
von Sturm verwendeten Polynomdivisionen ein Divisionsrest, dessen höchster Koeffizient nahe
bei Null liegt. Dieser Divisionsrest wird in der nächsten Polynomdivision wiederum als Divisor
eingesetzt und trägt somit entscheidend zur Anhäufung von Rundungsfehlern bei.
Wie bereits in Abschnitt 6.4.3 beschrieben wurde, kann die zur Wertebereichsanalyse eingesetzte Intervallarithmetik aufgrund fehlender Einstellungsmöglichkeiten der Rundungsart
nicht korrekt in der OpenGL-Shading-Language implementiert werden. Die ohne korrekte
Rundung implementierte Wertebereichsanalyse mit Hilfe der rekursiven Taylor-Methode (vgl.
Abschnitt 6.4.3) liefert erstaunlich gute Ergebnisse, die mit denen des D-Chain-Verfahrens
vergleichbar sind. In äußerst seltenen Fällen treten einzelne Fehlerpixel auch in glatten Flächenbereichen auf.
Typische Ergebnisse der einzelnen Verfahren zur Nullstellenisolation sind in Abbildung 8.4
zusammengefasst.
Global konvergente Iterationsverfahren
Die untersuchten global konvergenten Iterationsverfahren berechnen jeweils eine Nullstelle des
Polynoms, die anschließend abdividiert wird, wodurch ein Restpolynom niedrigeren Grades
entsteht, auf das das Verfahren erneut angewendet wird. Die Iterationsverfahren konvergieren
zuverlässig zu einer Nullstelle des jeweils betrachteten Polynoms, jedoch werden durch das
wiederholte Abdividieren die Koeffizienten des Restpolynoms so stark gestört, dass die zuletzt
berechneten Nullstellen stark von denen des Ursprungspolynoms abweichen. Da die Verfahren
komplexe Arithmetik verwenden, ist es teilweise schwierig reelle und komplexe Nullstellen
voneinander zu unterscheiden. In der Regel besitzt jede berechnete Nullstelle einen komplexen
Anteil, so dass ein Schwellwertkriterium für die Auswahl der reellen Nullstellen eingeführt
werden muss. Leider führt ein fest gewählter Schwellwert insbesondere bei Polynomen vom
Grad größer sechs häufig zu Fehlentscheidungen. Abbildung 8.5 verdeutlicht die Probleme bei
der Verwendung der global konvergenten Iterationsverfahren. Die Darstellungsqualität ist für
Flächen bis zum Grad sechs noch sehr gut, nimmt dann aber rapide ab.
Der optische Vergleich der Verfahren legt nahe, zur Nullstellenberechnung bevorzugt das
D-Chain-Verfahren mit Bisektion oder die Wertebereichsanalyse mit Hilfe der rekursiven Taylor-Methode zu verwenden. Für Flächen vom Grad eins und zwei sind darüberhinaus die
Lösungsformeln empfehlenswert.
71
8 Ergebnisse
Bildfehler in der Umgebung von Singularitäten
Bildfehler bei skalierten Flächen
Termumformungen
auf der GPU
NewtonInterpolation
LagrangeInterpolation
Abbildung 8.2: Exemplarischer Qualitätsvergleich der Algorithmen zur Berechnung
der Polynomkoeffizienten. Mit Hilfe der Termumformungen auf der GPU können die Koeffizienten in den meisten Fällen sehr genau berechnet werden. Die Interpolationsverfahren
scheitern hingegen in der Umgebung singulärer Punkte und Kurven, wie sie die Kleinsche
Flasche aufweist. Durch eine Skalierung werden die maximal zwei Schnittpunkte, die der abgerundete Würfel x6 + y 6 + z 6 − 1 = 0 mit dem Raycasting-Strahl haben kann, so weit
zusammengeschoben, daß sich nur noch wenige der Interpolationsstellen in deren Umgebung
befinden. Die charakterischen Eigenschaften der Polynoms f (t) werden damit nur ungenügend
erfasst.
(a) Grad 1: Ebene
(b) Grad 2: Kegel
(c) Grad 3: Cayley-Fläche
(d) Grad 4: Steiner-Fläche
Abbildung 8.3: Beispielhafter Qualitätsvergleich der Lösungsformeln für Polynome
vom Grad eins bis vier. Flächen vom Grad eins und zwei werden fehlerfrei gerendert. Die
Lösungsformel für Polynome der Form x3 + ax2 + bx + c schlägt fehl, sobald der Koeffizient
a eine dominante Rolle einnimmt. Da die Lösungsformel für Polynome vom Grad vier die
Lösungsformel kubischer Polynome verwendet, treten auch hier Darstellungsfehler auf.
72
8.1 Optischer Vergleich der implementierten Verfahren
(a) D-Chain-Verfahren
mit Bisektion
(b) D-Chain-Verfahren
mit Regula-Falsi
(c) Algorithmus
Sturm
von
(d) Wertebereichsanalyse
mit rekursiver Taylor-Methode
Abbildung 8.4: Vergleich der Darstellungsqualität der Verfahren zur Nullstellenisolation anhand der Boyschen Fläche. Das D-Chain-Verfahren liefert in Kombination
mit der Bisektion hervorragende Ergebnisse. Lediglich in unmittelbarer Nähe von Singularitäten kann es zu einzelnen Pixelfehlern kommen. Die GLSL-Implementierung der RegulaFalsi konvergiert häufig zur falschen Nullstelle (gelbe Bereiche) oder überschreitet die maximal
zulässige Ausführungsdauer (Block-Artefakte). Beim Algorithmus von Sturm entstehen Bildfehler entlang bestimmter Kurven. Die Ergebnisse der Wertebereichsanalyse sind mit denen
des D-Chain-Verfahrens mit Bisektion vergleichbar.
(a) gewünschtes Ergebnis
(b) Muller-Iteration
(c) Laguerre-Iteration
Abbildung 8.5: Darstellungsfehler der global konvergenten Iterationsverfahren bei
der Bartschen Fläche vom Grad 10. Da die von beiden Verfahren berechneten Nullstellen
in der Regel einen komplexen Anteil besitzen, ist die schwellwertabhängige Auswahl der Nullstellen, die als Schnittpuntke verwendet werden, schwierig und fehleranfällig. Zudem verfälscht
das wiederholte Abdividieren von Nullstellen die verbleibenden Nullstellen. Die unterschiedlichen Ergebnisse der Muller- und der Laguerre-Methode entstehen durch die unterschiedliche
Reihenfolge, in der die Verfahren die Nullstellen berechnen.
73
8 Ergebnisse
GeForce 6600 GT
GeForce 6800 GT
Quadro FX 4000
GeForce 7600 GS
Beschleunigungsfaktor
1.6
1.4
1.2
1
0.8
0.6
0.4
0.2
0
dcb
dcrf
sk
wba
m
l
lf
Algorithmen zur Berechnung der Polynomnullstellen
Abbildung 8.6: Skalierbarkeit der Raycasting-Algorithmen anhand von Testrenderings der Steiner-Fläche. Die Messungen der Bildwiederholraten wurden bezüglich der
GeForce 6600 GT normiert, um ausgehend von dieser Grafikkarte einen Beschleunigungsfaktor zu ermitteln. Obwohl die Bescheunigungsfaktoren einer Grafikkarte für verschiedene
Algorithmen in etwa gleich sein sollten, gibt es teilweise große Unterschiede. Die Faktoren für
die Quadro FX 4000 schwanken beispielsweise zwischen 0.97 (l) und 1.5 (dcrf).
8.2 Skalierbarkeit der Raycasting-Algorithmen
Die GPU einer modernen Grafikkarte besteht aus mehreren Berechnungseinheiten zur Ausführung der Fragment-Shader. Da die Berechnungseinheiten der GPU die einzelnen Raycasting-Strahlen unanhängig voneinander verarbeiten und dabei keinerlei Daten untereinander
austauschen können, ist zu erwarten, dass alle Algorithmen etwa in gleichem Maße durch
den Einsatz einer schnelleren Grafikkarte mit höherer Taktfrequenz oder einer größeren Anzahl Berechnungseinheiten beschleunigt werden. Abbildung 8.6 zeigt, dass die Beschleunigung
teilweise sehr unterschiedlich ausfällt. Möglicherweise werden beim Übersetzen der ShaderProgramme für verschiedene Grafikkarten jeweils unterschiedliche Optimierungsalgorithmen
eingesetzt, so dass die Schwankungen in den Beschleunigungsfaktoren auf die Unterschiede im
erzeugten Maschinencode zurückzuführen sind.
8.3 Laufzeitvergleich der implementierten Verfahren
In interaktiven Anwendungen ist die Laufzeit der eingesetzten Algorithmen von besonderem
Interesse. Werden etwa 15 Bilder pro Sekunde erreicht, so nimmt das menschliche Auge eine
Bewegung als fließend war [AMMH02, S. 1]. Zur Bewertung der untersuchten Algorithmen
wurden Laufzeitmessungen mit verschiedenen algebraischen Flächen vom Grad eins bis zwölf
durchgeführt (vgl. Anhang A.1). Dazu wurde auf einer NVidia Geforce 6600 GT pro Fläche und Algorithmus die Anzahl Bilder pro Sekunde (fps) bei einer Auflösung von 512 × 512
74
8.3 Laufzeitvergleich der implementierten Verfahren
Flächen
Name
plane
hyperboloid
cayley
clebsch
steiner
kummer
stagnaro
boy
barth6
septic
octic
barth10
roundedcube
Grad
1
2
3
3
4
4
5
6
6
7
8
10
12
Messergebnisse in fps
tugpu
li
ni
136.00
45.63
20.00
10.11
10.18
7.70
5.60
4.33
3.68
5.86
1.74
0.62
1.90
127.00
40.00
19.35
9.93
8.79
6.69
5.77
3.82
2.70
2.87
1.12
–
–
130.00
39.19
19.69
9.69
9.84
5.96
7.39
5.37
3.55
4.50
1.53
0.47
0.66
Tabelle 8.1: Laufzeitvergleich der Verfahren zur Berechnung der Polynomkoeffizienten. Der jeweils schnellste Algorithmus ist grün, der zweit schnellste gelb hinterlegt.
Pixeln bestimmt.1 Leider war es nicht möglich die Messungen für die einzelnen Teilaufgaben
des Raycastings separat durchzuführen, da der verwendete NVidia-Forceware-Treiber die
GLSL-Shader, die nur die Berechnung der Polynomkoeffizienten enthielten, aus unbekannten Gründen als fehlerhaft zurückwies. Daher wird im Folgenden jeweils die Gesamtzeit zur
Berechnung eines Bildes betrachtet.
8.3.1 Berechnung der Polynomkoeffizienten
Zum Laufzeitvergleich der verschiedenen Algorithmen zur Berechnung der Polynomkoeffizienten wurde bei allen Tests für die Berechnung der Polynomnullstellen das D-Chain-Verfahren
mit Bisektion fest eingestellt. Tabelle 8.1 enthält die Ergebnisse der Messungen. Die Termumformungen auf der GPU erreichen bei fast allen getesteten Flächen die höchste Bildwiederholrate. Interessant sind die Messergebnisse der Flächen boy und insbesondere stagnaro,
deren Formeln im Verhältnis zu den anderen Testflächen sehr komplex sind. Während bei den
Interpolationsverfahren nur die Berechnung der Interpolationspunkte von der Flächenformel
abhängt, werden bei den Termumformungen auf der GPU komplexe Operationen wie Polynomaddition und -multiplikation anhand dieser Formel durchgeführt. Diese wirken sich in
solchen Fällen aufgrund ihrer linearen beziehungsweise quadratischen Laufzeit besonders auf
die Gesamtlaufzeit des Raycastings aus. Deshalb ist es ratsam, die dem Programm übergebene
Formel so weit wie möglich zu vereinfachen.
8.3.2 Berechnung der Polynomnullstellen
Die Polynomnullstellen können bei den getesteten Flächen vom Grad eins bis vier am schnellsten mit den Lösungsformeln berechnet werden (Tabelle 8.2). Die Lösungsformel für lineare
1
Die Messung erfolgte durch wiederholtes Rendern der Szene in einem Zeitraum t von mindestens fünf
Sekunden. Die Anzahl gerenderter Bilder in diesem Zeitraum wurde anschließend durch t geteilt.
75
8 Ergebnisse
Flächen
Name
plane
hyperboloid
cayley
clebsch
steiner
kummer
stagnaro
boy
barth6
septic
octic
barth10
roundedcube
Grad
dcb
dcrf
1
2
3
3
4
4
5
6
6
7
8
10
12
136.00
45.63
20.00
10.11
10.18
7.70
5.60
4.33
3.68
5.86
1.74
0.62
1.90
127.50
5.82
2.01
0.98
0.96
0.74
0.64
0.48
0.34
0.65
0.23
0.11
0.24
Messergebnisse in fps
sk
wba
m
135.00
58.50
40.50
21.66
11.07
13.68
8.60
6.00
12.40
2.63
2.61
1.20
–
19.19
12.90
8.60
4.33
4.17
3.63
2.80
4.14
2.58
2.29
1.37
0.54
0.84
129.50
115.50
14.50
14.66
8.06
7.70
3.27
2.67
3.29
2.05
1.99
0.35
0.57
l
lf
133.00
115.00
16.61
14.49
4.09
8.73
3.00
2.34
2.67
2.04
1.40
0.74
0.58
137.50
118.50
70.50
35.71
43.00
31.50
–
–
–
–
–
–
–
Tabelle 8.2: Laufzeitvergleich der Verfahren zur Berechnung der Polynomnullstellen. Der jeweils schnellste Algorithmus ist grün, der zweit schnellste gelb hinterlegt. Die
Lösungsformeln (lf) nehmen eine gewisse Sonderstellung ein, da sie nur für die Grade eins bis
vier anwendbar sind, und wurden deshalb nicht eingefärbt.
Gleichungen dient bei allen anderen Algorithmen außer der Wertebereichsanalyse als Basisfall,
wodurch sich auch die ähnlichen Messergebnisse beim Raycasting der Ebene erklären lassen.
Die Lösungsformel für quadratische Gleichungen kommt bei der Muller- und der LaguerreIteration zum Einsatz, so dass diese Algorithmen für die Fläche vom Grad zwei noch eine
verhältnismäßig hohe Bildwiederholrate erzielen können. Zur Beschleunigung des D-ChainVerfahrens, der Muller- und der Laguerre-Iteration könnten auch die Lösungsformeln vom
Grad größer eins beziehungsweise zwei als Basisfall verwendet werden. Dies führt jedoch in
der derzeitigen Implementierung zu ähnlichen Bildfehlern wie in Abbildung 8.3 und wurde
daher nicht näher untersucht.
Für alle getesteten Flächen schneidet auch der Algorithmus von Sturm sehr gut ab. Im Gegensatz zur Wertebereichsanalyse, die in den Tests eine relativ hohe Laufzeit aufweist, kann
damit bestimmt werden, ob in einem Intervall genau eine Nullstelle und damit genau ein
Vorzeichenwechsel liegt. Daher kann frühzeitig auf ein schnelles, lokal konvergentes Verfahren
umgeschaltet werden. Zudem berechnet der Algorithmus von Sturm, wie auch die Wertebereichsanalyse, unabhängig vom Flächengrad nur die zum Raycasting benötigte Nullstelle.
Das D-Chain-Verfahren mit Bisektion ist in Tabelle 8.2 häufig als zweit schnellster und
teilweise auch als schnellster Algorithmus markiert. Die GLSL-Implementierung der RegulaFalsi konvergiert hingegen so schlecht, dass schon für Flächen vom Grad drei keine interaktiven
Bildwiederholraten erzielt werden.
8.4 Abschließende Bewertung der implementierten Verfahren
Die Algorithmen zum Raycasting algebraischer Flächen wurden eingehend bezüglich Bildqualität und Laufzeit untersucht. Die Termumformungen auf der GPU und das D-Chain-Verfahren
76
8.5 Zusammenfassung
mit Bisektion konnten in beiden Kategorien überzeugen. Darstellungsqualität und Laufzeit
sind bei allen Testflächen im oberen Bereich angesiedelt. Daher wird in RealSurf standardmäßig die Kombination dieser Algorithmen zum Raycasting der Flächen genutzt. Alle anderen
Verfahren außer der Wertebereichsanalyse verursachen flächenabhängig Bildfehler und sind daher in ihrer bisherigen Implementierung zum Raycasting algebraischer Flächen nur bedingt
geeignet. Trotz der guten Darstellungsqualität der Wertebereichsanalyse ist die Laufzeit im
Vergleich zum D-Chain-Verfahren mit Bisektion eher schlecht.
Obwohl man für eine echtzeitfähige Darstellung im Allgemeinen eine Bildwiederholrate
von mindestens 15 Bildern pro Sekunde fordert, ist zum interaktiven Betrachten der Flächen
eine Bildwiederholrate von etwa 8 Bildern pro Sekunde, wie sie vom D-Chain-Verfahren mit
Bisektion auf der getesteten Hardware beispielsweise noch für den abgerundeten Würfel x6 +
y 6 + z 6 − 1 = 0 vom Grad 6 erreicht wird, dennoch akzeptabel.
In den Tabellen 8.1 und 8.2 wurden Testrenderings von Flächen bis zum Grad zwölf betrachtet. Aufgrund der beschränkten Ressourcen von GPUs bezüglich Codegröße, Ausführungszeit
und Registeranzahl können auf den getesteten Grafikkarten nur wenige Flächen vom Grad
größer als zehn gerendert werden. Die Flächen vom Typ xk + y k + z k − 1 = 0 konnten bis
k = 13 erfolgreich dargestellt werden.
8.5 Zusammenfassung
In dieser Arbeit wurde die Verwendbarkeit moderner Grafikhardware zum Raycasting algebraischer Flächen untersucht. Der Einsatz von Spezialhardware erfordert häufig Anpassungen
an den benötigten Algorithmen. Diese Anpassungen, die auf Einschränkungen durch Hardware, Programmiermodell, Programmiersprache und fehlerbehaftete Grafikkartentreiber zurückzuführen sind, binden die entwickelten Programme sehr stark an die der Implementierung
zugrunde gelegten Hardware. Die Implementierungsphase dieser Arbeit erwies sich als sehr
zeitaufwendig und kompliziert, da einige Einschränkungen zu diesem Zeitpunkt nicht bekannt
waren und erst während der Programmierung der Algorithmen entdeckt wurden. Hinzu kommt
die mit 32 Bit für dieses Einsatzgebiet geringe Genauigkeit von Fließkommazahlen.
Dennoch wurden verschiedene Algorithmen für die Berechnung der Schnittpunkte zwischen
algebraischer Fläche und Raycasting-Strahl erfolgreich in der OpenGl-Shading-Language umgesetzt. Die meisten der untersuchten Algorithmen sind zum Raycasting algebraischer Flächen
nur bedingt geeignet. Die erste Teilaufgabe, der Berechnung der Koeffizienten des Polynoms
f (t), welches durch Einsetzen des Raycasting-Strahls in die Formeln F (x, y, z) der algebraischen Fläche entsteht, kann zuverlässig und effizient durch Termumformungen auf der GPU
erledigt werden. Bei der anschließenden Berechnung der Nullstellen des Polynoms f (t) wird
durch das D-Chain-Verfahren mit Bisektion sowohl eine gute Bildqualität als auch ein schnelles
Rendering ermöglicht.
Aufbauend auf der Leistungsfähigkeit programmierbarer GPUs ist es in dieser Arbeit gelungen, eine Anwendung zur interaktiven Visualisierung algebraischer Flächen zu realisieren.
Die erreichten Bildwiederholraten sind allerdings stark flächen- und blickrichtungsabhängig.
Bei einer Auflösung von 512 × 512 Bildpunkten können auf einer GeForce 6600 GT algebraische Flächen teilweise bis zum Grad vier in Echtzeit gerendert werden. Mit zunehmender
Flächenkomplexität erhöht sich natürlich die Berechnungsdauer. Die Grenze der verfügbaren Ressourcen ist auf NVidia-Grafikkarten der Serien GeForce 6 und 7 bei Flächen vom
Grad 13 erreicht.
77
8 Ergebnisse
8.6 Ausblick
Der beschriebene Raycasting-Algorithmus kann auch auf der der Implementierung zugrunde
gelegten Hardware noch verbessert werden. Die derzeitige Implementierung untersucht pro
Pixel genau einen Raycasting-Strahl. Dadurch entstehen Treppeneffekte an den Rändern der
Flächen. In einem Multipass-Verfahren könnten die Randpixel bestimmt und in einem weiteren Durchgang verfeinert werden. Weiterhin könnte die Beschränkung der Flächenkomplexität
durch Multipass-Verfahren abgeschwächt werden. Dazu ist es nötig, den Raycasting-Algorithmus so aufzuteilen, dass die Zwischenergebnisse geeignet im Framebuffer abgelegt werden
können.
In dieser Arbeit wird eine Beschleunigung des Raycasting-Algorithmus durch direkten Einsatz von Fragment-Shadern erzielt. Wie bereits in Kapitel 2 beschrieben wurde, existieren
spezielle Frameworks zur Umsetzung datenparalleler Prozesse auf die GPU. Sie verbergen die
Details der GPU-Programmierung und ermöglichen dadurch eine effiziente Softwareentwicklung. Möglicherweise kann die durchaus komplizierte Implementierung der oben genannten
Multipass-Algorithmen durch den Einsatz solcher Frameworks umgangen werden. Auch eine
Erweiterung des Raycasting-Algorithmus auf ein rekursives Raytracing zur Realisierung von
Spiegelungen, Brechungen und Schatten ist denkbar.
78
A Renderings und Flächenbeschreibungen
Zur Abrundung und Vervollständigung der Thematik enthalten die folgenden Abschnitte dieses Anhangs mit RealSurf erstellte Renderings verschiedener algebraischer Flächen. Dazu
zählen die im Ergebnisteil verwendeten Testflächen, sowie eine Sammlung weiterer bekannter
Flächen, die zum Teil in den vorhergehenden Kapiteln erwähnt wurden.
A.1 Zum Laufzeitvergleich genutzte algebraische Flächen
Ebene (plane)
Zweischaliges Hyperboloid
(hyperboloid)
Cayley-Fläche (cayley)
Clebsch-Fläche (clebsch)
Steiner-Fläche (steiner)
Kummer-Fläche (kummer)
Stagnaro-Fläche (stagnaro)
Boysche Fläche (boy)
Barthsche Fläche vom
Grad 6 (barth6)
79
A Renderings und Flächenbeschreibungen
Septik mit singulärer
Kreislinie (septic)
Oktik mit 168 singulären
Punkten (octic)
Barthsche Fläche vom
Grad 10 (barth10)
Abgerundeter Würfel vom
Grad 12 (roundedcube)
Kürzel
80
Grad
Flächenbeschreibung
plane
1
x=0
hyperboloid
2
x2 − y 2 + z 2 + 1 = 0
cayley
3
clebsch
3
steiner
4
kummer
4
stagnaro
5
boy
6
barth6
6
4(τ 2 x2√
−y 2 )(τ 2 y 2 −z 2 )(τ 2 z 2 −x2 )−(1+2τ )(x2 +y 2 +z 2 −1)2 = 0 mit τ =
1
2 (1 + 5)
septic
7
(x2 + y 2 − 1)3 y + z 2 = 0
octic
8
barth10
10
roundedcube
12
x2 + y 2 + z 2 + 2xyz − 1 = 0
81(x3 + y 3 + z 3 ) − 189(x2 (y + z) + y 2 (x + z) + z 2 (x + y)) + 54(xyz) +
126(xy + xz + yz) − 9(x2 + y 2 + z 2 ) − 9(x + y + z) + 1 = 0
x2 y 2 + y 2 z 2 + z 2 x2 − xyz = 0
√
−x4 − y 4 − z 4 + 4(x2 + y 2 z 2 + y 2 + x2 z 2 + z 2 + y 2 x2 ) − 12 3xyz − 1 = 0
(p + r)2 (q 3 + s3 ) + (q + s)2 (p3 + r3 ) + 8(qrs + prs + pqs +√pqr)(q +
2 r 2 (q + s) = 0 mit p = z − 1 + 2x, q =
s)(p + r)√+ 4q 2 s2 (p + r) + 4p√
√
z − 1 − 2x, r = −(z + 1 + 2y) und s = −(z + 1 − 2y)
64(1 − z)3 z 3 − 48(1√
− z)2 z 2 (3x2 + 3y 2 + 2z 2 ) + 12(1 − z)z(27(x2 + y 2 )2 −
2
2
2
24z (x + y ) + 36 2yz(y 2√
− 3x2 ) + 4z 4 ) + (9x2 + 9y 2 − 2z 2 )(−81(x2 +
2
2
2
2
2
y ) − 72z (x + y ) + 108 2xz(x2 − 3y 2 ) + 4z 4 ) = 0
(8x4 − 8x2 + 1)2 + (8y 4 − 8y 2 + 1)2 + (8z 4 − 8z 2 + 1)2 − 1 = 0
2 )+
8(x2 −τ 4 y 2 )(y 2 −τ 4 z 2 )(z 2 −τ 4 x2 )(x4 +y 4 +z 4 −2x2 y 2 −2y 2 z 2 −2z 2 x√
(3+5τ )(x2 +y 2 +z 2 −1)2 (x2 +y 2 +z 2 −(2−τ ))2 = 0 mit τ = 21 (1+ 5)
x12 + y 12 + z 12 − 1 = 0
A.2 Weitere bekannte algebraische Flächen
A.2 Weitere bekannte algebraische Flächen
Whitney-Umbrella (whitney)
Ding Dong (dingdong)
weitere Kubik (cubic)
Octdong (octdong)
Quartik mit tetraedrischer
Symmetrie (tetrahedral)
weitere Quartik (quartic)
Torus (torus)
Tropfen (blob)
Cassini-Fläche (cassini)
Chubs (chubs)
Cross Cap (crosscap)
Tangled Cube (tangledcube)
Enzensberger Stern
(enzensberger)
Herz (heart)
Huntsche Fläche (hunt)
81
A Renderings und Flächenbeschreibungen
Kleinsche Flasche
(kleinbottle)
Kürzel
82
Doppeltorus (doubletorus)
Grad
Fläche vom Grad 13 (surf13)
Flächenbeschreibung
z 2 − x2 y = 0
whitney
3
dingdong
3
cubic
3
octdong
4
tetrahedral
4
quartic
4
torus
4
blob
4
cassini
4
chubs
4
crosscap
4
x4 +
tangledcube
4
x4 +
enzensberger
6
400(x2 y 2 + y 2 z 2 + z 2 x2 ) − (1 − x2 − y 2 − z 2 )3 = 0
heart
6
hunt
6
kleinbottle
6
doubletorus
8
surf13
13
x2 + y 3 − y 2 + z 2 = 0
4(x3 + y 3 + z 3 + 1) − (x + y + z + 1)3 = 0
x2 + y 2 + z 4 − z 2 = 0
(x2 + y 2 + z 2 )2 + 8xyz − 10(x2 + y 2 + z 2 ) + 25 = 0
x4 + y 4 + z 4 − 15xyz = 0
(x2 + y 2 + z 2 + R2 − r2 )2 − 4R2 (x2 + y 2 ) = 0 mit r = 0.3 und R = 1
4(x2 + y 2 ) − 1 + 2z − 2z 3 + z 4 = 0
((x − 1)2 + z 2 )((x + 1)2 + z 2 ) − y 4 = 0
1
2 =0
3 2 2
5 2 2
2
2
2
x y − 3x y + 2 x z + 2 y z − 3yz 2 + 23 z 4
y 4 + z 4 − 1.6(x2 + y 2 + z 2 ) + 1.024 = 0
x4 + y 4 + z 4 − x2 − y 2 − z 2 +
(2x2 + y 2 + z 2 − 1)3 −
1 2 3
10 x z
=0
− y2 z3 = 0
4(x2 + y 2 + z 2 − 13)3 + 27(3x2 + y 2 − 4z 2 − 12)2 = 0
(x2 + y 2 + z 2 + 2y − 1)((x2 + y 2 + z 2 − 2y − 1)2 − 8z 2 ) + 16xz(x2 + y 2 +
z 2 − 2y − 1) = 0
(x2 (1 − x2 ) − y 2 )2 + 12 z 2 −
x13 + y 13 + z 13 − 1 = 0
1
80 (1
+ x2 + y 2 + z 2 ) = 0
B Grammatik der Formeln algebraischer
Flächen
Der in RealSurf implementierte Parser akzeptiert die zur folgenden Grammatik gehörende
Sprache. Der erste Teil der Grammatik ist regulär und wird vom Scanner-Generator Flex
[Frea] verwendet. Der daraus erstellte Scanner bestimmt Token für den Parser-Generator
Bison [Freb]. Zusätzlich entfernt er Kommentare und Leerzeichen zwischen Token aus der
Eingabe. Als Kommentar werden alle Zeichen zwischen /∗ und ∗/ und zwischen // und dem
Zeilenende betrachtet. Der zweite Grammatikteil müsste zur Eingabe in Bison als LALR(1)Grammatik vorliegen. Die hier abgedruckte kontextfreie Variante der Grammatik ist jedoch
deutlich übersichtlicher. Die LALR(1)-Grammatik ist im RealSurf-Quelltext enthalten. Der
generierte Parser prüft die syntaktische Korrektheit einer Eingabe und baut daraus einen Syntaxbaum für die weitere Verarbeitung auf. Beide Grammatikteile sind in erweiterter BackusNaur-Form nach ISO/IEC 14977:1996(E) [JTC96] verfasst.
B.1 Grammatik für Flex
letter
= "a"
| "l"
| "w"
| "H"
| "S"
|
|
|
|
|
"b"
"m"
"x"
"I"
"T"
|
|
|
|
|
"c"
"n"
"y"
"J"
"U"
|
|
|
|
|
"d"
"o"
"z"
"K"
"V"
|
|
|
|
|
"e"
"p"
"A"
"L"
"W"
|
|
|
|
|
"f"
"q"
"B"
"M"
"X"
|
|
|
|
|
"g"
"r"
"C"
"N"
"Y"
|
|
|
|
|
"h"
"s"
"D"
"O"
"Z"
|
|
|
|
;
"i"
"t"
"E"
"P"
|
|
|
|
"j"
"u"
"F"
"Q"
|
|
|
|
"k"
"v"
"G"
"R"
nonzerodigit
= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
digit
= "0" | nonzerodigit ;
integer
= "0" | ( nonzerodigit { digit } ) ;
float
= integer [ "." digit { digit } ]
[ ( "E" | "e" ) [ "+" | "-" ] digit { digit } ] ;
x
y
z
= "x" | "X" ;
= "y" | "Y" ;
= "z" | "Z" ;
83
B Grammatik der Formeln algebraischer Flächen
pi
e
= "PI" ;
= "E" ;
function1
= "sin" | "cos" | "tan" | "asin" | "acos" | "atan" | "exp"
| "log" | "sqrt" | "ceil" | "floor" | "abs" | "sign" ;
function2
= "pow" | "atan2" ;
reserved
= x | y | z | pi | e | function1 | function2 ;
identifier
= ( letter { letter | digit } ) - reserved ;
B.2 Grammatik für Bison
constant
= integer | float ;
parameter
= constant
| identifier
| "-" parameter
| parameter "+"
| parameter "-"
| parameter "*"
| parameter "/"
| parameter "^"
| "(" parameter
| function1 "("
| function2 "("
parameter
parameter
parameter
parameter
integer
")"
parameter ")"
parameter "," parameter ")" ;
(* Startsymbol *)
polynomial
= x
| y
| z
| parameter
| "-" polynomial
| polynomial "+"
| polynomial "-"
| polynomial "*"
| polynomial "/"
| polynomial "^"
| "(" polynomial
84
polynomial
polynomial
polynomial
parameter
integer
")" ;
C Inhalt der DVD
Die dieser Arbeit beliegende DVD enthält
• den Quelltext der entwickelten Anwendung RealSurf
• eine für Microsoft Windows (32 Bit) übersetzte Version des Programms
• ein RealSurf-Demonstrationsvideo, da das Programm nicht mit allen OpenGL 2.0
Grafikkarten kompatibel ist (getestet wurden die Grafikkarten NVidia Quadro FX
4000, GeForce 6600 GT, GeForce 6800 GT und GeForce 7600 GS)
• das in Abschnitt 2.3.3 vorgestellte GLSL-Beispiel
• diese Diplomarbeit im PDF-Format.
85
C Inhalt der DVD
86
Literaturverzeichnis
[3Dl] 3Dlabs: GLSLvalidate. http://developer.3dlabs.com/downloads/glslvalidate/
index.htm, Abruf: 23. April 2007
[Abe26] Abel, Niels H.: Beweis der Unmöglichkeit algebraische Gleichungen von höheren
Graden als dem vierten allgemein aufzulösen. In: Journal für die reine und
angewandte Mathematik, 1826, 65–84
[AMMH02] Akenine-Möller, Tomas ; Moller, Tomas ; Haines, Eric: Real-Time Rendering. A. K. Peters, Ltd., 2002. – ISBN 1–56881–182–9
[App68] Appel, Arthur: Some techniques for shading machine renderings of solids. In:
Proceedings of the Spring Joint Computer Conference 32 (1968), S. 37–49
[ATI] ATI: RenderMonkey. http://ati.amd.com/developer/rendermonkey/index.html,
Abruf: 23. April 2007
[BFH+ 04] Buck, I. ; Foley, T. ; Horn, D. ; Sugerman, J. ; Fatahalian, K. ; Houston,
M. ; Hanrahan, P.: Brook for GPUs: stream computing on graphics hardware.
In: ACM Transactions on Graphics (TOG) 23 (2004), Nr. 3, S. 777–786
[Boy02] Boyd, John P.: Computing Zeros on a Real Interval through Chebyshev Expansion and Polynomial Rootfinding. In: SIAM Journal on Numerical Analysis 40
(2002), Nr. 5, S. 1666–1682. – ISSN 0036–1429
[Chi00] Childs, Lindsay N.: A Concrete Introduction to Higher Algebra. 2. Springer,
2000. – ISBN 0–387–98999–4
[CHL04] Coombe, Greg ; Harris, Mark J. ; Lastra, Anselmo: Radiosity on graphics
hardware. In: GI ’04: Proceedings of the 2004 conference on Graphics interface,
Canadian Human-Computer Communications Society, 2004. – ISBN 1–56881–
227–2, S. 161–168
[Cra] Crane, Keenan: GPU Fluid Solver. http://graphics.cs.uiuc.edu/svn/kcrane/
web/project_fluid.html, Abruf: 19. März 2007
[EMNW05] Engeln-Müllges, Gisela ; Niederdrenk, Klaus ; Wodicka, Reinhard:
Numerik-Algorithmen: Verfahren, Beispiele, Anwendungen. Springer, 2005. –
ISBN 3–540–62669–7
[ESSB] Endrass, Stephan ; Schneider, Hans Huelf Ruediger Oertel K. ; Schmitt,
Ralf ; Beigel, Johannes: Surf - Visualization of real algebraic geometry. http://
surf.sourceforge.net/, Abruf: 10. Mail 2007
[Frea] Free Software Foundation: flex: a fast lexical analyzer.
sourceforge.net/, Abruf: 30. Mai 2007
http://flex.
87
Literaturverzeichnis
[Freb] Free Software Foundation: GNU Bison, the GNU parser generator. http://
www.gnu.org/software/bison/, Abruf: 30. Mai 2007
[GG83] Grant, J. A. ; Ghiatis, A.: Determination of the Zeros of a Linear Combination
of Chebyshev Polynomials. In: IMA Journal of Numerical Analysis 3 (1983), Nr.
2, S. 193
[Gie02] Giedat, Rainer: Numerische Interpolation mit Tschebyscheffschen Polynomen. Version: 2002. http://www2.informatik.hu-berlin.de/~haddenho/pdf/
tschebyscheff_interpolation.pdf, Abruf: 16. April 2007
[Her95] Herold, Helmut: lex und yacc. 2. Addison-Wesley, 1995. – ISBN 3–89319–879–2
[Heu00] Heuser, Harro G.: Lehrbuch der Analysis 1. 13. BG Teubner Verlag, 2000. –
ISBN 3–519–42235–2
[Jen96] Jensen, Henrik W.: Global illumination using photon maps. In: Rendering
Techniques 96 (1996), S. 21–30
[JT70] Jenkins, M. A. ; Traub, J. F.: A Three-Stage Algorithm for Real Polynomials
Using Quadratic Iteration. In: SIAM Journal on Numerical Analysis 7 (1970),
Nr. 4, S. 545–566
[JTC96] JTC, ISO: Information technology–Syntactic metalanguage–Extended BNF. In:
ISO-Standard ISO/IEC 14977 (1996)
[KBR04] Kessenich, J. ; Baldwin, D. ; Rost, R.: The OpenGL Shading Language version 1.10. 59. Version: 2004. http://oss.sgi.com/projects/ogl-sample/registry/
ARB/GLSLangSpec.Full.1.10.59.pdf, Abruf: 27. März 2007
[Kea96] Kearfott, R. B.: Interval computations: Introduction, uses, and resources. In:
Euromath Bulletin 2 (1996), Nr. 1, S. 95–112
[KF05] Kilgariff, Emmett ; Fernando, Randima: The GeForce 6 Series GPU Architecture. In: GPU Gems 2. Addison Wesley, März 2005, Kapitel 30, S. 471–491
[KGGK94] Kumar, V. ; Grama, A. ; Gupta, A. ; Karypis, G.: Introduction to parallel
computing: design and analysis of algorithms. Benjamin-Cummings Publishing
Co., Inc. Redwood City, CA, USA, 1994. – ISBN 0–8053–3170–0
[Kön04] Königsberger, Konrad: Analysis. 2. 5. Springer, 2004. – ISBN 978–3–540–
20389–6
[LKHW05] Lefohn, Aaron E. ; Kniss, Joe M. ; Hansen, Charles D. ; Whitaker, Ross T.:
A streaming narrow-band algorithm: interactive computation and visualization
of level sets. In: SIGGRAPH ’05: ACM SIGGRAPH 2005 Courses, ACM Press,
2005, S. 243
[LMB95] Levine, John R. ; Mason, Tony ; Brown, Doug: Lex & Yacc. 2. O’Reilly,
1995. – ISBN 1–56592–000–7
88
Literaturverzeichnis
[Lue] Luebke, David: GPGPU: General-Purpose Computation on Graphics Hardware. http://www.gpgpu.org/sc2006/slides/01.luebke.Introduction.pdf, Abruf:
19. März 2007
[LWS93] Lafortune, Eric P. ; Willems, Yves D. ; Santo, H. P.: Bi-directional Path
Tracing. In: Proceedings of CompuGraphics (1993), S. 145–153
[Mat78] Matthiessen, Ludwig: Grundzüge der antiken und modernen Algebra der litteralen Gleichungen. 1. BG Teubner Verlag, 1878 http://historical.library.cornell.
edu/cgi-bin/cul.math/docviewer?did=01920001&seq=7
[Mig92] Mignotte, Maurice: Mathematics for computer algebra. Springer-Verlag New
York, 1992. – ISBN 3–540–97675–2
[MMWW99] Merziger, Gerhard ; Mühlbach, Günter ; Wille, Detlef ; Wirth, Thomas:
Formeln und Hilfen zur höheren Mathematik. 3. Binomi Verlag, 1999. – ISBN
978–3–923923–35–9
[Mul56] Muller, David E.: A Method for Solving Algebraic Equations Using an Automatic Computer. In: Mathematical Tables and Other Aids to Computation 10
(1956), Nr. 56, S. 208–215
[NVi] NVidia: NVemulate. http://developer.nvidia.com/object/nvemulate.html, Abruf: 23. April 2007
[NVi06a] NVidia: CUDA Homepage. Version: 2006. http://developer.nvidia.com/object/
cuda.html, Abruf: 21. März 2007
[NVi06b] NVidia:
Microsoft DirectX 10: The Next-Generation Graphics API.
Version: 2006. http://www.nvidia.de/content/PDF/Geforce_8800/Microsoft_
DirectX_10_Technical_Brief.pdf, Abruf: 19. März 2007
[NVi07] NVidia: Cg Toolkit 1.5. Version: 2007. http://developer.nvidia.com/object/
cg_toolkit.html, Abruf: 23. April 2007
[OLG07] Owens, John D. ; Luebke, David ; Govindaraju, Naga: A Survey of GeneralPurpose Computation on Graphics Hardware. In: Computer Graphics Forum
26 (2007), Nr. 1, S. 80–113
[PBMH02] Purcell, Timothy J. ; Buck, Ian ; Mark, William R. ; Hanrahan, Pat:
Ray Tracing on Programmable Graphics Hardware. In: ACM Transactions on
Graphics 21 (2002), Nr. 3, S. 703–712
[PDC+ 03] Purcell, Timothy J. ; Donner, Craig ; Cammarano, Mike ; Jensen, Henrik W. ; Hanrahan, Pat: Photon Mapping on Programmable Graphics Hardware. In: Proceedings of the ACM SIGGRAPH/Eurographics Symposium on
Graphics Hardware, Eurographics Association, 2003. – ISBN 1–58113–739–7, S.
41–50
[Pol03] Polthier, Konrad: Imaging maths - Inside the Klein bottle. Version: 2003.
http://plus.maths.org/issue26/features/mathart/index-gifd.html,
Abruf:
30. Mai 2007
89
Literaturverzeichnis
[pov] POV-Ray - The Persistence of Vision Raytracer. http://www.povray.org/, Abruf: 10. Mail 2007
[PSG06] Peercy, Mark ; Segal, Mark ; Gerstmann, Derek: A performance-oriented
data parallel virtual machine for GPUs. In: SIGGRAPH ’06: ACM SIGGRAPH
2006 Sketches, ACM Press, 2006. – ISBN 1–59593–364–6, S. 184
[PTVF96] Press, William H. ; Teukolsky, Saul A. ; Vetterling, William T. ; Flannery, Brian P.: Numerical Recipes in C: The Art of Scientific Computing. 2.
Cambridge University Press, 1996. – ISBN 0–521–43108–5
[Rap06] RapidMind Inc.: Sh - Embedded Metaprogramming Language. Version: 2006.
http://www.libsh.org/, Abruf: 27. März 2007
[SA04] Segal, Mark ; Akeley, Kurt: The OpenGL Graphics System: A Specification, Version 2.0. Version: 2004. http://www.opengl.org/documentation/specs/
version2.0/glspec20.pdf, Abruf: 27. März 2007
[Sch] Schmidt, Meik: Wie alles begann - DirectX 5, DirectX 6, DirectX 7, DirectX
8 und DirectX 9. http://www.pc-erfahrung.de/grafikkarte/directx.html, Abruf:
19. März 2007
[SSS+ 06] Shou, Huahao ; Song, Wenhao ; Shen, Jie ; Martin, Ralph ; Wang, Guojin:
A Recursive Taylor Method for Ray Casting Algebraic Surfaces. In: CGVR,
2006, S. 196–204
[Sto02] Stoer, Josef: Numerische Mathematik 1. 8. Springer, 2002. – ISBN 3–540–
66154–9
[Sun] SunFlow Rendering
11. April 2007
System.
http://sunflow.sourceforge.net/,
Abruf:
[TS05] Thrane, Niels ; Simonsen, Lars O.: A Comparison of Acceleration Structures
for GPU Assisted Ray Tracing. Version: 2005. http://www.larsole.com/files/
GPU_BVHthesis.pdf
[Tur90] Turkowski, Ken: Properties of surface-normal transformations. In: Graphics
gems, Academic Press Professional, Inc., 1990. – ISBN 0–12–286169–5, S. 539–
547
[Wat82] Watkins, David S.: Understanding the QR Algorithm. In: SIAM Review 24
(1982), Nr. 4, S. 427–440
[Wei99] Weisstein, Eric W.: Kummer Surface. Version: 1999.
wolfram.com/KummerSurface.html, Abruf: 19. Juni 2007
http://mathworld.
[Whi80] Whitted, Turner: An Improved Illumination Model for Shaded Display. In:
Communications of the ACM 23 (1980), Nr. 6, S. 343–349. – ISSN 0001–0782
[WMK04] Wood, Andrew ; McCane, Brendan ; King, Scott A.: Ray Tracing Arbitrary
Objects on the GPU. In: Proceedings of Image and Vision Computing New
Zealand (2004), S. 327–332
90
Literaturverzeichnis
[Woo] Woodhouse, Ben: GL Easy Extension. http://elf-stone.com/glee.php, Abruf:
23. April 2007
[WSS05] Woop, Sven ; Schmittler, Jörg ; Slusallek, Philipp: RPU: A Programmable Ray Processing Unit for Realtime Ray Tracing. In: Proceedings of ACM
SIGGRAPH 2005, 2005, 434–444
91
Literaturverzeichnis
92
Abbildungsverzeichnis
2.1
2.2
2.3
2.4
2.5
2.6
Beispiele von GPGPU-Anwendungen . . . . . . . . . . . . . . .
Vergleich des Wachstums der CPU- und GPU-Geschwindigkeit
Vereinfachter Aufbau einer Fixed-Function-Grafikpipeline . . .
Vereinfachter Aufbau einer programmierbaren Grafikpipeline . .
Mögliche Organisation der Berechnungen auf Quadrupeln . . .
Einzelschritte des GLSL-Beispiels . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
5
6
8
18
3.1
3.2
3.3
Generierung der Strahlen beim rekursiven Raytracing . . . . . . . . . . . . . .
Vergleich zwischen Raycasting, rekursivem Raytracing und Photon-Mapping .
GPU-basiertes Raycasting . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
23
24
4.1
4.2
4.3
Abbildungen von Quadriken . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Verwendung des Normalenvektors bei der Beleuchtungsberechnung . . . . . .
Beispiele nicht-orientierbarer Flächen . . . . . . . . . . . . . . . . . . . . . . .
27
29
30
5.1
5.2
5.3
Sensibiltät der Polynominterpolation gegenüber Störungen . . . . . . . . . . .
Rechenschema zur Bestimmung der dividierten Differenzen . . . . . . . . . . .
Berechnung der dividierten Differenzen mit linearem Speicheraufwand . . . .
35
37
38
6.1
6.2
Beispielhafte Ausführung des Bisektionsverfahrens . . . . . . . . . . . . . . .
Beispielhafte Ausführung der Regula-Falsi . . . . . . . . . . . . . . . . . . . .
49
50
7.1
7.2
7.3
7.4
7.5
Erläuterung zur Implementierung des D-Chain-Verfahrens . . . .
Syntaxbaum zur Eingabe x3 ∗ (x − 1) + y 2 + z 2 . . . . . . . . . .
Startansicht der grafischen Benutzeroberfläche von RealSurf . .
Aktivitätsdiagramm der RealSurf-Benutzeroberfläche . . . . .
Animation zwischen verschiedenen Flächen der Kummer-Familie
.
.
.
.
.
62
64
66
66
67
8.1
8.2
8.3
8.4
8.5
8.6
Verbesserung der Bildqualität durch Optimierung der Strahlen . . . . . . . .
Qualitätsvergleich der Algorithmen zur Berechnung der Polynomkoeffizienten
Qualitätsvergleich der Lösungsformeln . . . . . . . . . . . . . . . . . . . . . .
Qualitätsvergleich der Verfahren zur Nullstellenisolation . . . . . . . . . . . .
Darstellungfehler bei Verwendung der global konvergenten Iterationsverfahren
Skalierbarkeit der Raycasting-Algorithmen . . . . . . . . . . . . . . . . . . . .
69
72
72
73
73
74
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
93
Abbildungsverzeichnis
94
Tabellenverzeichnis
8.1
8.2
Laufzeitvergleich der Verfahren zur Berechnung der Polynomkoeffizienten . . .
Laufzeitvergleich der Verfahren zur Berechnung der Polynomnullstellen . . . .
75
76
95