Professional Documents
Culture Documents
Christian Stussak
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.1 Zielstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Wertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
v
5.3.1 Lagrange-Interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.3.2 Newton-Interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.3.3 Weitere Interpolationsverfahren . . . . . . . . . . . . . . . . . . . . . . 38
5.4 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
7 Implementierung 59
7.1 Umsetzung des Raycasting-Algorithmus in GLSL . . . . . . . . . . . . . . . . 59
7.1.1 Verwendete Grafikhardware und Grafiktreiber . . . . . . . . . . . . . . 59
7.1.2 Modifikation der Raycasting-Algorithmen für GLSL . . . . . . . . . . 60
7.2 Erzeugung der Shader aus den Formeln algebraischer Flächen . . . . . . . . . 63
7.3 Grafische Benutzeroberfläche . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
7.3.1 Überführung einer Flächenformel in ein gerendertes Bild . . . . . . . . 65
7.3.2 Transformationen und Darstellungsparameter . . . . . . . . . . . . . . 67
7.3.3 Flächenparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
8 Ergebnisse 69
8.1 Optischer Vergleich der implementierten Verfahren . . . . . . . . . . . . . . . 69
8.1.1 Berechnung der Polynomkoeffizienten . . . . . . . . . . . . . . . . . . . 69
8.1.2 Berechnung der Polynomnullstellen . . . . . . . . . . . . . . . . . . . . 70
8.2 Skalierbarkeit der Raycasting-Algorithmen . . . . . . . . . . . . . . . . . . . . 74
8.3 Laufzeitvergleich der implementierten Verfahren . . . . . . . . . . . . . . . . . 74
8.3.1 Berechnung der Polynomkoeffizienten . . . . . . . . . . . . . . . . . . . 75
8.3.2 Berechnung der Polynomnullstellen . . . . . . . . . . . . . . . . . . . . 75
8.4 Abschließende Bewertung der implementierten Verfahren . . . . . . . . . . . . 76
8.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
vi
8.6 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Literaturverzeichnis 87
Abbildungsverzeichnis 93
Tabellenverzeichnis 95
vii
viii
1 Einleitung
In vielen wissenschaftlichen Bereichen ist die grafische Veranschaulichung seit jeher ein un-
erlässliches Hilfsmittel zum Verständnis komplizierter Strukturen und Zusammenhänge. Be-
trachtet 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 Model-
lierung 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 alge-
braischer 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 Inge-
nieurdisziplinen eingesetzt werden. Die Klassifizierung der Lösungsmengen algebraischer Glei-
chungen entsprechend ihrer geometrischen Eigenschaften ist daher ein wichtiges Forschungs-
gebiet 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 Ge-
genstand 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-Algorith-
mus. Das damit erstellte Gitternetz kann zwar performant gerendert werden, erlaubt aber
in der Regel keine interaktive Veränderung von Flächenparametern. Eine andere Herange-
hensweise 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 al-
gebraischen Fläche ist mit den üblicherweise eingesetzten Algorithmen sehr rechenaufwendig.
Raytracer, wie POV-Ray [pov] oder Surf [ESSB], sind daher auch unter Verwendung aktu-
eller 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ög-
lichkeiten, aber auch die Grenzen der GPU-Programmierung müssen erörtert und analysiert
werden, um eine erfolgreiche Implementierung des GPU-beschleunigten Raycastings algebrai-
scher 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 zwi-
schen 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.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 Raycas-
ting algebraischer Flächen so beschleunigt werden, dass auch interaktive Anwendungen mög-
lich 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 alge-
braischer 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 An-
passung an das Programmiermodell der Grafikhardware. Zudem ruft die für dieses Anwen-
dungsgebiet geringe Genauigkeit von Fließkommazahlen nicht unerhebliche numerische Pro-
bleme hervor.
2
2 Grundlagen der GPU-Programmierung
Die Entwicklung von Grafikhardware wurde in den neunziger Jahren maßgeblich durch die stei-
genden Anforderungen von Computerspielen beeinflusst. Detailgetreue 3D-Szenen verlangten
nach höheren Taktraten und zusätzlichen Funktionen der Grafikbibliotheken, die der Hardwa-
re mit jeder neuen Generation hinzugefügt wurden. Spieleentwickler konnten lange Zeit nur
die fest eingebaute Funktionalität der Grafikkarten verwenden, um möglichst realistisch wir-
kende 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 implemen-
tierten, 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, ver-
breiteten 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 erkann-
te, 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 Auf-
gaben verwendet. Derzeit profitieren vorwiegend Anwendungen in der Echtzeitvisualisierung
und -simulation von diesem Trend (Abbildung 2.1). Doch die ständige Verbesserung der Pro-
grammiermodelle und -möglichkeiten bereitet den Weg für eine Reihe anderer Einsatzgebiete.
Grafikkarten mit Unterstützung für DirectX 10 werden beispielsweise zahlreiche Ganzzahl-
und Bitoperationen erlauben und damit auch für Kryptographiesysteme interessant [NVi06b].
Abbildung 2.1: Beispielanwendungen, welche von der Verwendung der GPU als Ko-
prozessor 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
0 Jahr
2002 2004 2006
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 er-
werben 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 Ver-
arbeitungsgeschwindigkeit 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-Programmier-
sprachen werden vorgestellt und es wird eine Einführung in die für diese Arbeit verwendete
Programmiersprache vorgenommen.
4
2.1 Grafikkartenarchitektur und Programmiermodelle
CPU GPU
Per
Per Vertex Primitive
Application Rasterizer Fragment
Operations Assembly
Operations
Video Memory
(Textures, Framebuffer, etc.)
Die Stufe Application sendet die Eckpunkte (Vertices) grafischer Primitive, also Punkte,
Linien oder Polygone, mit Zusatzattributen wie Normalen, Texturkoordinaten und Material-
informationen, von der CPU oder vom Hauptspeicher an die GPU. Die Stufe Per Vertex Ope-
rations 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 verschwin-
den 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 inter-
poliert 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 auto-
matische 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 Schreibope-
rationen dieselbe Textur benutzt.
In der geschilderten Darstellung der Grafikpipeline wird nicht betrachtet, wie der Daten-
austausch zwischen den einzelnen Stufen erfolgt. Einige Stufen müssen darüber hinaus auf-
wendigere 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.
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 transformie-
ren. 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 GPU
Video Memory
(Textures, Framebuffer, etc.)
6
2.1 Grafikkartenarchitektur und Programmiermodelle
welcher dann parameterabhängig verändert werden kann. So sind selbst aufwendige Deforma-
tionen 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 Be-
zeichnungen 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) Pipeline-
stufen ü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. Auf-
grund der rasanten Entwicklung im Bereich der Computergrafik existieren bereits neue Archi-
tekturen 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 Entwick-
lung spezieller Frameworks, die die GPU als datenparallele, virtuelle Maschine abstrahieren
[NVi06a, PSG06].
7
2 Grundlagen der GPU-Programmierung
dene Befehle auf verschiedenen Daten durchführen und damit unterschiedlichen Verzweigungs-
pfaden 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 Fragment-
Prozessoren 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 benach-
barten 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, opti-
miert. 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 Her-
steller 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 un-
abhä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 Spei-
chern 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 2: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 ausge-
fü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 über-
mittelt werden. Größere Datenmengen können nur in Texturen gespeichert und dann im Sha-
der 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 Stream-
Processing-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 Berech-
nung 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 kei-
nen 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 Texturko-
ordinaten 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 Da-
tentransfer 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 ver-
wendet. 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 Eingabe-
daten 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ücklicherwei-
se 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 Pro-
grammiersprachen vorhanden sein. Dieser Abschnitt geht auf die bekanntesten Hochsprachen
und deren Unterschiede ein. Im Bereich des interaktiven 3D-Renderings werden meist Sha-
ding-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
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) zugrun-
deliegenden 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]:
VZ Exponent Mantisse
1 Bit 8 Bit 23 Bit
3
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-Language-
Compiler 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 OpenGL-
Erweiterungen 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 Funk-
tionalitä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 die-
se 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 Mitar-
beitern 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 Grafikpipeli-
ne 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-Sys-
tem 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]
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
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 );
Programm-Objekte
GLSL-Programm-Objekte werden in OpenGL durch
GLuint glCreateProgram( void );
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-Pro-
gramms unter OpenGL abgeschlossen. Nun kann die Funktionalität der Fixed-Function-Pi-
peline 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 Attri-
but 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 de-
klariert. Um von der Anwendung auf Shader-Variablen zugreifen zu können, muss das Shader-
Programm vollständig kompiliert und gebunden sein.
uniform-Variablen
Globale Variablen werden als uniform deklariert. Sie können in Vertex- und Fragment-Sha-
dern ausgelesen, aber nicht beschrieben werden. Derartige Variablen können nur von der An-
wendung 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
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ätz-
liche 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 Varia-
blentyp ü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 ver-
fü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 Genau-
igkeit (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 Programmier-
hilfe 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 Ganz-
oder 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 verwen-
det werden. Mehrdimensionale Arrays können nur simuliert werden, indem Arrays von Verbun-
den 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 dekla-
rierte Variablen sind nach ihrer Initialisierung nicht mehr änderbar. Für globale Shader-Va-
riablen 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 Pa-
rameterü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 implemen-
tiert 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.
16
2.3 Einführung in die OpenGL-Shading-Language
vec4 v;
v.x; v.y; v.z; v.w; // Syntax für Raumkoordinaten
v.r; v.g; v.b; v.a; // Syntax für Farbwert
v.s; v.t; v.p; v.q; // Syntax für Texturkoordinaten
Dabei kann auch auf mehrere Komponenten gleichzeitig und in beliebiger Reihenfolge zugegrif-
fen 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 );
Vektoren können auch wie Arrays indiziert werden. Der gleichzeitige Zugriff auf mehrere Ele-
mente ist damit allerdings nicht möglich.
Vordefinierte Variablen
GLSL-Programme können auf einen Großteil der aktuellen OpenGL-Zustandsvariablen zu-
greifen. Dazu zählen insbesondere Modelview- und Projektionsmatrix, sowie Parameter der
Lichtquellen. Darüberhinaus verfügen Vertex- und Fragment-Shader jeweils über eigene Va-
riablen, die zum Teil im nächsten Abschnitt beschrieben 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
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:
Um die Schwingung besser der Fahnengröße anzupassen, kann die Vorschrift bei der Imple-
mentierung 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 Sha-
der 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 Gitter-
punkte 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 OpenGL-
Display-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 Fahnenani-
mation 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 Ani-
mationsstadien gerendert. Die CPU-basierte Animation konnte mit 31 Bildern pro Sekunde
dargestellt werden. Der Einsatz von Display-Listen und Shadern steigerte die Bildwiederhol-
rate 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.5 Zusammenfassung
Durch den Einsatz von Shadern können die Berechnungen in der Grafikpipeline flexibel ge-
staltet 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 Ob-
jekte und Lichtquellen der Szene, berücksichtigt werden, wodurch die Erzeugung photorea-
listischer 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 wer-
den.
21
3 Grundlagen des GPU-basierten Raytracings
Flächen-
normale
gebrochener
Primärstrahl Strahl
Augpunkt
Szenenobjekt
Bildebene
Abbildung 3.1: Generierung der Strahlen beim rekursiven Raytracing: Ein Primär-
strahl 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 wei-
terverfolgt.
Das vorgestellte Verfahren nach Whitted liefert für viele Szenen gute Ergebnisse. Es schei-
tert 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 Ren-
deringzeit mit sich bringen. Abbildung 3.2 verdeutlicht die Unterschiede zwischen Raycasting,
dem Raytracing nach Whitted und Photon-Mapping.
22
3.2 Anpassung des Algorithmus für die GPU
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 Fragment-
Shader 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.
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 in-
nerhalb 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
Es ist natürlich ausreichend nur die Vorderseiten der Box zu zeichnen.
2
Die benötigten Matrizen müssen dem Shader gegebenenfalls als uniform-Parameter übergeben werden.
23
3 Grundlagen des GPU-basierten Raytracings
Primärstrahl
Augpunkt
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.
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 Eigen-
schaft hingegen erhalten, wenn man die Matrix ((M S)−1 )T verwendet.
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
F (x, y, z) = ai,j,k xi y j z k (4.1)
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äu-
terte 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.
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:
Die Schnittpunktberechnung reduziert sich damit auf die Nullstellenbestimmung eines univa-
riaten Polynoms f : R → R. Die Transformation in das univariate Polynom
n
X
f (t) = bi ti . (4.4)
i∈N
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 Rechengenau-
igkeit 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
bewährt: d~
oopt = r a+b
2 und d~opt = 0.5 ~. (4.6)
|d|
Sie entstand aus der Beobachtung, dass im Intervall [−1, 1] in etwa genauso viele IEEE-754-
Fließ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 be-
zeichnete 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) ≈ (4.8)
∂x h
∂F F (x, y + h, z) − F (x, y, z)
(y) ≈ (4.9)
∂y h
∂F F (x, y, z + h) − F (x, y, z)
(z) ≈ (4.10)
∂z h
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 verschie-
dene 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 auf-
grund 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 intui-
tiv 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 ma-
thematischen Sinn nur eine Seite besitzen. Beispiele hierfür sind das Möbius-Band und die
Kleinsche Flasche (Abbildung 4.3).
~n ~n
Augpunkt Augpunkt
α α
d~ d~
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
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 Nullstellen-
bestimmung 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 wer-
den kann, verwenden die meisten Verfahren zur Nullstellenberechnung die Darstellung in der
bereits vorgestellten Monombasis
Xn
f (t) = ai ti . (5.1)
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 Poly-
nome: 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
Die nachfolgend beschriebenen Verfahren sind daher so formuliert, dass sie auch mit rekursiv
aufgebauten Polynomen umgehen können.
werden für die Variablen x, y und z in F (x, y, z) eingesetzt. Auf der CPU kann dieses Einset-
zen 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 Polynompoten-
zierung 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)
32
5.2 Termumformungen auf der GPU
0 = x2 + y 2 + z 2 − 1 (5.13)
2 2 2
= (1 + 2t) + (1 + 3t) + (1 + 4t) − 1 (5.14)
2 2 2
= (1 + 4t + 4t ) + (1 + 6t + 9t ) + (1 + 8t + 16t ) − 1 (5.15)
2 2
= (2 + 10t + 13t ) + (1 + 8t + 16t ) − 1 (5.16)
2
= (3 + 18t + 29t ) − 1 (5.17)
= 2 + 18t + 29t2 (5.18)
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)
X
pn (x) + qm (x) = (ai + bi )xi . (5.20)
i=0
Die Multiplikation kann durch eine Faltung der Koeffizientenarrays realisiert werden:
n+m
X i
X
pn (x) · qm (x) = xi aj bi−j (5.21)
i=0 j=0
Die Potenz pn (x)k für k ∈ N wird üblicherweise mit Hilfe der Vorschrift
k
2 für gerade k 6= 0
(pn (x) 2 )
pn (x)k = pn (x) · pn (x)k−1 für ungerade k (5.22)
1 für k = 0
berechnet, welche lediglich O(log k) Iterationen anstelle der O(k) Iterationen des trivialen
Algorithmus (
k pn (x) · pn (x)k−1 für k > 0
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 verschie-
denen xi lässt sich ein Polynom
X n
pn (x) = ak xk (5.24)
k=0
vom Grad n bestimmen, welches die Wertepaare, auch Interpolationsstellen genannt, exakt
interpoliert. Es gilt also
n
X
pn (xi ) = ak xki = fi ∀i ∈ {0, . . . , n} (5.25)
k=0
Das Interpolationspolynom pn (x) ist eindeutig, das heißt es existiert kein von pn (x) verschie-
denes Polynom vom gleichen Grad, welches ebenfalls durch die gegebenen Wertepaare verläuft.
pn (x) ist genau dann Lösung der gestellten Interpolationsaufgabe, wenn dessen Koeffizienten-
vektor (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
Y Y n
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 De-
terminante 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 nu-
merisches 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 durch-
fü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
t t t
(a) (b) (c)
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 Interpolationspoly-
nom 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 Po-
lynoms 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 Interpo-
lationsfehler 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 Interpolati-
onspolynom 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
35
5 Berechnung der Polynomkoeffizienten
gegeben. Es ist offensichtlich vom Grad n und interpoliert wie gewünscht die (xi , fi ):
n
X n
Y xi − xk
pn (xi ) = fj (5.29)
xj − xk
j=0 k=0
k6=j
n
X Y n Y xi − xk n
xi − xk
= fj + fi (5.30)
xj − xk xi − xk
j=0 k=0 k=0
j6=i k6=j k6=i
X n n
Y n
Y
xi − xk
= fj · (xi − xi ) · + fi 1 (5.31)
xj − xk
j=0 k=0 k=0
j6=i k6=j k6=i
k6=i
n
X n
Y xi − xk
= fj · 0 · + fi = fi (5.32)
xj − xk
j=0 k=0
j6=i 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 Aufsummie-
ren 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
Yn
n n n
(x − xk )
X Y x − xk X fj k=0
fj = n · (5.33)
xj − xk Y (x − xj )
j=0 k=0 j=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 Darstel-
lung eines Polynoms in der Newton-Basis
n
X j−1
Y
pn (x) = bj (x − xk ). (5.34)
j=0 k=0
36
5.3 Polynominterpolation
f [x0 ] = f0
f [x1 ]−f [x0 ]
f [x0 , x1 ] = x1 −x0
f [x1 ,x2 ]−f [x0 ,x1 ]
f [x1 ] = f1 f [x0 , x1 , x2 ] = x2 −x0
f [x2 ]−f [x1 ]
f [x1 , x2 ] = 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 Tabellenein-
trä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.
f0 = b0 (5.35)
f1 = b0 + b1 (x1 − x0 ) (5.36)
f2 = b0 + b1 (x2 − x0 ) + b2 (x2 − x0 )(x2 − x1 ) (5.37)
..
.
n−1
Y
fn = b0 + b1 (x2 − x0 ) + b2 (x2 − x0 )(x2 − x1 ) + · · · + bn (xn − xk ) (5.38)
k=0
zur Berechnung der bj . Die Koeffizientenmatrix des Gleichungsystems ist eine obere Drei-
ecksmatrix, 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.
n
X j−1
Y
pn (x) = f [x0 , . . . , xj ] (x − xk ) (5.41)
j=0 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 Be-
rechnung 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 ]
1. Iteration
f [x0 ] f [x0 x1 ] f [x1 x2 ]
2. Iteration
f [x0 ] f [x0 x1 ] f [x0 x1 x2 ]
..
.
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 Ite-
ration 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] multi-
pliziert 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.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 Berechnungs-
vorschriften 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 Polynominterpola-
tion. 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 Einset-
zen einer Strahlgleichung (x(t), y(t), z(t))T = o + td~ in das univariate Polynom f (t) transfor-
miert werden kann. Es verbleibt die Berechung der Nullstellen von f (t), welche die Schnitt-
punkte 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 Null-
stellen von Polynomen sowie mit häufig benötigten Standardalgorithmen. Anschließend wer-
den 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
pn (x) = ai xi (6.1)
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].
n
X n−1
X
pn (x) = ai xi = (x − ξ1 ) bi xi = (x − ξ1 )pn−1 (x) (6.2)
i=0 i=0
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.
n
X
pn (x) = ai xi = an (x − ξ1 ) · · · (x − ξn ) (6.3)
i=0
Jedes Polynom vom Grad n mit komplexen Koeffizienten hat also genau n komplexe Nullstel-
len. Am Beispiel von
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:
n
X n
X
i
pn (ξ) = ai ξ = ai ξ i = pn (ξ) = 0. (6.5)
i=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)
n (ξ) = 0 und p(m)
n (ξ) 6= 0. (6.6)
In der Praxis und insbesondere bei technischen Anwendungen kommt es aufgrund von Run-
dungsfehlern in den Eingabedaten nur sehr selten vor, dass das Polynom sowie mehrere Ab-
leitungen 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.
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 Abdividie-
ren 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
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 zusammenfas-
sen, welches ausschließlich reelle Arithmetik benötigt:
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:
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 sei-
ne 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 sei-
nem 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 biqua-
dratische Gleichungen beschrieben. Die in der betreffenden Literatur häufig verwendete Be-
zeichnung 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.
44
6.2 Lösungsformeln
Die Lösungen der quadratrischen Polynomgleichung ax2 + bx + c = 0 können durch die Formel
q
b 2
−b ± 2 − ac
x1,2 = (6.18)
2a
oder auch durch
2c
x1,2 = q (6.19)
b 2
−b ± 2 − 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
b − 4ac reduziert sich dann annähernd auf b, was den Term −b ± b2 − 4ac für eine der
2
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)
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.
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
3 und q = 2a
27 − ab
3 + c. Nun
p 3
q 2
kann man aus der Diskriminante D = 3 + 2 der reduzierten Form drei Fälle ableiten:
Die nach Cardano benannten Formeln liefern die Lösungen für alle drei Fälle. Dazu setzt man
r
3 1 √ p
u = − q + D und v = − . (6.22)
2 3u
45
6 Berechnung der Nullstellen univariater Polynome
und berechnet
y1 = u + v (6.23)
1 1 √
y2 = − (u + v) + (u − v) 3i (6.24)
2 2
1 1 √
y3 = − (u + v) − (u − v) 3i. (6.25)
2 2
Aus den yk der reduzierten Form erhält man leicht die Lösungen der Normalform:
a
xk = yk − (6.26)
3
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
r= − und ϕ = arccos − . (6.27)
3 2r
Unter Verwendung von D < 0 lässt sich durch Umstellen der Gleichung für die Diskriminante
3
verifizieren, dass − p3 im Intervall [0, ∞) und − 2r q
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π
y2 = 2 3 r cos (6.29)
3
√ ϕ + 4π
y3 = 2 3 r cos . (6.30)
3
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 ab a3 ac a2 b 3a4
p = b − a2 , q =c− + und r = d − + − . (6.31)
8 2 8 4 16 256
Aus der reduzierten Form leitet sich die so genannte kubische Resolvante ab:
46
6.3 Lokal konvergente Iterationsverfahren
Sind z1 , z2 , z3 die mit Hilfe der Cardanischen Formeln ermittelten Lösungen der kubischen
Resolvante, so berechnet man
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:
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 Newton-
Verfahren 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
47
6 Berechnung der Nullstellen univariater Polynome
f (xk )
xk+1 = xk − (6.41)
f ′ (xk )
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 Null-
stelle konvergiert das Verfahren nur linear. Eine Herleitung der Konvergenzbereiche und
-ordnungen findet sich beispielsweise in [EMNW05, S. 51ff]. Leider konnte die Newton-Ite-
ration in dieser Arbeit nicht verwendet werden, da keines der untersuchten global konvergen-
ten 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 Einschluss-
verfahren 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.
Eine bessere Abschätzung kann jedoch über den relativen Fehler | x2x−x 2
1
| gemacht werden, da
dieser die insgesamt gültigen Stellen berücksichtigt. Als Abbruchkriterium verwendet man
also wahlweise
x2 − x1
|x2 − x1 | < ε oder < ε. (6.45)
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 Ein-
schlussintervalls halbiert sich mit jeder Iteration.
Bisektion
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
y = f1 + (x − x1 ) (6.47)
x1 − x2
lässt sich mit y = 0 die Berechnungsvorschrift für x3 herleiten:
f1
x3 = x = x1 − (x1 − x2 ) (6.48)
f1 − f2
Analytisch betrachtet liegt die neue Näherung x3 zwischen x1 und x2 , da man aus der Bedin-
gung f1 f2 < 0 auch 0 < f1f−f
1
2
< 1 folgern kann. Testimplementierungen auf der GPU ergaben
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)
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.
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.
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 Ablei-
tungen 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 anzuse-
hen. 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.
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
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
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 ein-
facher 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 , ck ] r(ak , ck ) > 0 ak + bk
[ak+1 , bk+1 ] = mit ck = . (6.51)
[ck , bk ] sonst 2
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 die-
sem 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 entste-
henden Intervalle [a, a+b 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 Funktions-
aufrufen 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
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
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 Stel-
lenanzahl Rundungsfehlern. Um sicherzustellen, dass das Ergebnis einer Intervalloperation
auch sämtliche möglichen Werte einschließt, muss bei der Berechnung der unteren Intervall-
grenze des Ergebnisses abgerundet und entsprechend bei der oberen Grenze aufgerundet wer-
den. Diese Art der Rundung ist in jedem IEEE-754-kompatiblen Fließkommaprozessor ver-
fügbar. Leider ist die Rundungsart in der OpenGL-Shading-Language weder beeinflussbar
noch bekannt, so dass eine korrekte Implementierung der Intervallarithmetik nicht ohne wei-
teres 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 rekur-
sive 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
Dabei gilt
x+x
x ∈ [x, x], x0 = 2 , 0 < ϑ < 1 und h = x − x0 ∈ [− x−x 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
2
x−x ′ 1 x−x
[F , F ] = f (x0 ) + 2 [−1, 1]f (x0 ) + 2 2 [−1, 1] [G, G] (6.59)
x−x
ausdrücken. Mit der Substitution x1 = 2 kann man die Gleichung zudem noch etwas ver-
einfachen:
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].
54
6.5 Global konvergente Iterationsverfahren
bung 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
Vernachlässigung des Faktors 1+q die Lösungsformel für quadratische Gleichungen anwenden
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
−2C
xi+1 = xi + (xi − xi−1 ) √ . (6.68)
B ± B 2 − 4AC
Das Vorzeichen im Nenner des Bruches
√ wird so gewählt, dass xi+1 und xi möglichst nahe
beieinander liegen. Im Fall B ± B − 4AC = 0 benutzt man y1/2 = 1. Muller schlägt in
2
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)
ϕ(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 Konver-
genzordnung 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.
so stellt man fest, dass sich die zugehörigen Ableitungen in einfacher Weise aus pn (x) und
dessen Ableitungen ergeben:
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
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 durch-
brochen 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 Ray-
casting 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 konvergen-
te 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 Null-
stellen innerhalb eines Intervalls zu ermitteln. Global konvergente Verfahren, wie die Muller-
oder Laguerre-Methode, benötigen hingegen keine besonderen Startwerte, sondern konvergie-
ren 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 Pror-
gamm namens RealSurf umgesetzt. Die folgenden Abschnitte beschäftigen sich mit der
Programmarchitektur und den genannten Teilaspekten der Implementierung.
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ählschlei-
fen, 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 break-
Befehlen 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 Zeilen-
nummern 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-Kompo-
nenten daher einmalig in einem Vorverarbeitungsschritt übersetzt werden. Diese Funktionali-
tä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.
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 inner-
halb von [l, u] besitzt und sich damit die Anzahl der zu untersuchenden Intervalle verringert.
(k+1)
Die verwendeten Array-Indizes verändern sich also in Abhängigkeit von pn (x), welches
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 (x) untersucht. Wird eine Nullstelle ξ gefunden, dann
(k−1) (k−1)
setzt man = ξ und markiert
ri als gültig. Andernfalls verfährt man wie im Falle
ri
(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 Polyno-
me pn (x) auf die gleiche Weise erfolgen und somit in GLSL und unter den beschriebenen
Einschränkungen durch Grafiktreiber und -hardware realisiert werden.
61
7 Implementierung
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
63
7 Implementierung
+ ^
∗ ^ z 2
^ − y 2
x 3 x 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.
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:
Der Code zur numerischen Berechnung des Gradienten von F mit Hilfe von Differenzenquoti-
enten besitzt eine ähnlich einfache Struktur.
Zur Verbesserung der numerischen Stabilität des Raycasting-Verfahrens ist es allerdings
sinnvoll die Formeln der partiellen Ableitungen ∂F ∂F ∂F
∂x , ∂y und ∂z mit Hilfe symbolischer Be-
rechnungen aus F herzuleiten. Der Baum wird dazu beginnend mit der Wurzel entsprechend
den Regeln der Differentialrechnung umstrukturiert. In einem Vorverarbeitungsschritt wer-
den 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
+ 1
3
x +y∗z−1 =
b
ˆ ∗
x 3 y z
∂
nach y würde beispielsweise folgendermaßen ablaufen (die mit ∂y gekennzeichneten Unterbäu-
me müssen jeweils noch differenziert werden):
∂
− ∂y
∂
+ ∂y
∂
+ 1 ∗ ∂y
⇒ ^ ∗ ⇒ ⇒ z
^ ∗ y z
x 3 y z
x 3 y 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 Polynomko-
effizienten 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 alge-
braischer Flächen ergänzt.
65
7 Implementierung
Programmstart
Raycasting-Shader
Eingabe fehlerhaft ausführen
Eingabe fehlerfrei
GLSL-Quelltext des
Raycasting-Shaders
generieren
Abbildung 7.4: Vereinfachte Darstellung der für das Raycasting relevanten Aufga-
ben in RealSurf.
66
7.3 Grafische Benutzeroberfläche
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 bei-
spielsweise mit der Interpolationsformel von Lagrange
t(t − 1) (t + 1)t
A − B(t + 1)(t − 1) + C . (7.1)
2 2
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 zusammenge-
setzten 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
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 theoreti-
schen Grundlagen, sowie die Besonderheiten der Implementierung in der OpenGL-Shading-
Language wurden bereits erläutert. Hinsichtlich der Laufzeiten der Berechnungen und der
Qualität der erzielten Ergebnisse gibt es teilweise erhebliche Unterschiede zwischen den ver-
schiedenen 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 (Algorith-
mus von Sturm), wba (Wertebereichsanalyse), m (Muller-Iteration), l (Laguerre-Iteration) und
lf (Lösungsformeln).
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 Rundungs-
fehler 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 provozie-
ren. 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 Interpo-
lationsverfahren 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 doppel-
ten Nullstellen, die durch die Verwendung von Fließkommazahlen häufig zu Polynomen ohne
Nullstelle degenerieren.
Lösungsformeln
Die Lösungsformeln für Polynome vom Grad eins und zwei sind aufgrund ihres äußerst gerin-
gen 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ögli-
che 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 Polynomkoef-
fizienten nicht exakt berechnet werden. Zusätzlich zur Bisektion wurde hier auch die Regula-
Falsi untersucht. Sie zeigt in der GLSL-Implementierung sehr schlechte Konvergenzeigenschaf-
ten und führt teilweise sogar zu einem Abbruch des Shader-Programms.
Renderings, die mit dem Algorithmus von Sturm erzeugt werden, enthalten oftmals Bild-
fehler 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 Dar-
stellungsfehler 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 ein-
gesetzte 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.
Der optische Vergleich der Verfahren legt nahe, zur Nullstellenberechnung bevorzugt das
D-Chain-Verfahren mit Bisektion oder die Wertebereichsanalyse mit Hilfe der rekursiven Tay-
lor-Methode zu verwenden. Für Flächen vom Grad eins und zwei sind darüberhinaus die
Lösungsformeln empfehlenswert.
71
8 Ergebnisse
(a) Grad 1: Ebene (b) Grad 2: Kegel (c) Grad 3: Cayley-Flä- (d) Grad 4: Steiner-Flä-
che che
72
8.1 Optischer Vergleich der implementierten Verfahren
73
8 Ergebnisse
1.4
1.2
1
0.8
0.6
0.4
0.2
0
dcb dcrf sk wba m l lf
74
8.3 Laufzeitvergleich der implementierten Verfahren
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 unbekann-
ten Gründen als fehlerhaft zurückwies. Daher wird im Folgenden jeweils die Gesamtzeit zur
Berechnung eines Bildes betrachtet.
75
8 Ergebnisse
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 Laguerre-
Iteration 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-Chain-
Verfahrens, 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 Ge-
gensatz 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 Wertebe-
reichsanalyse, 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 Regula-
Falsi konvergiert hingegen so schlecht, dass schon für Flächen vom Grad drei keine interaktiven
Bildwiederholraten erzielt werden.
76
8.5 Zusammenfassung
8.5 Zusammenfassung
In dieser Arbeit wurde die Verwendbarkeit moderner Grafikhardware zum Raycasting alge-
braischer Flächen untersucht. Der Einsatz von Spezialhardware erfordert häufig Anpassungen
an den benötigten Algorithmen. Diese Anpassungen, die auf Einschränkungen durch Hardwa-
re, Programmiermodell, Programmiersprache und fehlerbehaftete Grafikkartentreiber zurück-
zufü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 um-
gesetzt. 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 algebrai-
schen 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 ge-
lungen, 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 alge-
braische 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ügba-
ren 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 weite-
ren 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-Algorith-
mus so aufzuteilen, dass die Zwischenergebnisse geeignet im Framebuffer abgelegt werden
können.
In dieser Arbeit wird eine Beschleunigung des Raycasting-Algorithmus durch direkten Ein-
satz 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 Softwareentwick-
lung. 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 die-
ses 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.
79
A Renderings und Flächenbeschreibungen
Septik mit singulärer Oktik mit 168 singulären Barthsche Fläche vom
Kreislinie (septic) Punkten (octic) Grad 10 (barth10)
80
A.2 Weitere bekannte algebraische Flächen
81
A Renderings und Flächenbeschreibungen
82
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 Syn-
taxbaum für die weitere Verarbeitung auf. Beide Grammatikteile sind in erweiterter Backus-
Naur-Form nach ISO/IEC 14977:1996(E) [JTC96] verfasst.
letter
= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k"
| "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v"
| "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G"
| "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R"
| "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" ;
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 = "x" | "X" ;
y = "y" | "Y" ;
z = "z" | "Z" ;
83
B Grammatik der Formeln algebraischer Flächen
pi = "PI" ;
e = "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 ;
constant
= integer | float ;
parameter
= constant
| identifier
| "-" parameter
| parameter "+" parameter
| parameter "-" parameter
| parameter "*" parameter
| parameter "/" parameter
| parameter "^" integer
| "(" parameter ")"
| function1 "(" parameter ")"
| function2 "(" parameter "," parameter ")" ;
(* Startsymbol *)
polynomial
= x
| y
| z
| parameter
| "-" polynomial
| polynomial "+" polynomial
| polynomial "-" polynomial
| polynomial "*" polynomial
| polynomial "/" parameter
| polynomial "^" integer
| "(" polynomial ")" ;
84
C Inhalt der DVD
Die dieser Arbeit beliegende DVD enthält
• eine für Microsoft Windows (32 Bit) übersetzte Version des Programms
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 Ren-
dering. 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 Expan-
sion 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. http://flex.
sourceforge.net/, Abruf: 30. Mai 2007
87
Literaturverzeichnis
[Freb] Free Software Foundation: GNU Bison, the GNU parser generator. http://
www.gnu.org/software/bison/, Abruf: 30. Mai 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
[KBR04] Kessenich, J. ; Baldwin, D. ; Rost, R.: The OpenGL Shading Language ver-
sion 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 Archi-
tecture. In: GPU Gems 2. Addison Wesley, März 2005, Kapitel 30, S. 471–491
[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
[Mat78] Matthiessen, Ludwig: Grundzüge der antiken und modernen Algebra der litte-
ralen Gleichungen. 1. BG Teubner Verlag, 1878 http://historical.library.cornell.
edu/cgi-bin/cul.math/docviewer?did=01920001&seq=7
[Mul56] Muller, David E.: A Method for Solving Algebraic Equations Using an Auto-
matic Computer. In: Mathematical Tables and Other Aids to Computation 10
(1956), Nr. 56, S. 208–215
[PDC+ 03] Purcell, Timothy J. ; Donner, Craig ; Cammarano, Mike ; Jensen, Hen-
rik W. ; Hanrahan, Pat: Photon Mapping on Programmable Graphics Hard-
ware. 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
[SA04] Segal, Mark ; Akeley, Kurt: The OpenGL Graphics System: A Specificati-
on, 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
[Wat82] Watkins, David S.: Understanding the QR Algorithm. In: SIAM Review 24
(1982), Nr. 4, S. 427–440
[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
91
Literaturverzeichnis
92
Abbildungsverzeichnis
93
Abbildungsverzeichnis
94
Tabellenverzeichnis
95