Beschreibung

Das zweite Beispiel dreht sich um das Visualization Mapping, d.h. um die Abbildung der vorliegenden Daten auf visuell leicht erfassbare Größen. In der vorigen Aufgabe wurden aus gemessenen Volumendaten durch Filterung neue Daten gewonnen. Zu jeder Stelle im 3D-Volumen gibt es nun nicht mehr nur einen skalaren Dichtewert (Intensity), sondern auch einen Gradienten, also einen dreidimensionalen Vektor, der die lokale Änderung des Dichtewerts nach den 3 Hauptrichtungen (X,Y,Z) beschreibt. Somit muss eine geeignete Visualisierung für diese mehrdimensionalen Daten gefunden werden.

Exercise 2

Für diese Aufgabe soll die Visualisierung mithilfe von Glyphen durchgeführt werden, also Symbole, deren visuelle Eigenschaften die gegebenen Daten repräsentieren. Konkret sollen an regelmäßigen Stellen im Volumen Super-Ellipsoide angezeigt werden. Die Gestalt, Orientierung und Farbe dieser geometrischen Objekte soll dem Betrachter dann Rückschlüsse auf die Dichte sowie auf die Stärke und Richtung des Änderungsfeldes im Volumen geben. Das folgende Bild zeigt, wie das für einen Schädel-Datensatz etwa aussehen könnte:

Glyph Rendering Example   Glyph Rendering Example

Je länglicher und spitzer die Glyphen, desto größer der Gradientenbetrag. Die Glyphen zeigen in die Richtung der größten Änderung. Die Farbpalette reicht von grün (geringe Dichte) über gelb bis rot (hohe Dichte).

Die Übungsaufgabe besteht nun darin, die mittlerweile vierdimensionalen Volumendaten (3 Kanäle Gradient, 1 Kanal Dichtewert) auf die oben erwähnten Parameter von Superellipsoiden abzubilden. Wie in der ersten Aufgabe gibt es bereits ein Glyph-Renderer-Plugin für Volumeshop, in das von den Studierenden kleine Codestücke eingefügt werden sollen, um es zu vervollständigen und die beschriebene Funktionalität zu erreichen.

Zur Architektur des Plugins:

Im C++ Code (RendererVislabGlyphExercise.cpp) werden an regelmäßigen Stellen im Volumen Positionen für Glyphen erzeugt. Diese Positionen der Glyphen werden dann an die Grafikkarte geschickt, an die Rendering-Pipeline gereicht und in Shaderprogrammen weiterverarbeitet. Das folgende Diagramm zeigt Details zur Pipeline, die aus einer Glyphposition einen fertigen, dem Volumendatensatz entsprechenden Glyphen erstellt:

Jeder Glyph durchläuft daher folgende Schritte: Zunächst wird der Vertex-Shader einmal für jeden Glyphen ausgeführt, danach der Geometry-Shader ebenfalls einmal pro Glyph. Letzterer erzeugt jedoch an der Glyph-Position ein Billboard-Viereck, das durch den Rasterizer dann in viele Fragmente zerlegt wird. Der Fragment-Shader wird dann einmal für jedes Fragment (Pixel) eines Billboard-Vierecks ausgeführt. Dabei wird für jedes Fragment der entsprechende Blickstrahl mit dem Superellipsoid geschnitten und aufgrund des erhaltenen Schnittpunkts und der Oberflächennormale die Beleuchtung pro Pixel durchgeführt. Fast alle Operationen werden für diese Übung in Weltkoordinaten durchgeführt, einzig das Erstellen des Billboard-Vierecks geschieht im Viewspace.

Der gesamte Code, der von den Studierenden ergänzt werden soll, betrifft die Datei plugin_renderer_vislabglyphexercise.glsl, welche die Shaderprogramme enthält, die die Grafikkarte für jeden Glyphen ausführt. Nach Fertigstellung des Programms, also nach der Lösung der folgenden 7 Aufgaben, sollte es möglich sein, eine Glyphendarstellung der Volumensdatensatze wie in den Beispielscreenshots zu erhalten, wobei einige Parameter über das grafische User-Interface steuerbar sein sollen.

Achtung: Die im nächsten Abschnitt präsentierten Beispielbilder wurden mit sehr hoher Auflösung gerendert und können sich daher von der eigenen Lösung unterscheiden. Es wird daher empfohlen, sich beim Lösen der Beispiele an der Referenzlösung im Volumeshop zu orientieren.

Programmieraufgaben

TODO Beschreibung
1
Evaluating the Superellipsoid Equation
In der ersten Aufgabe ist die mathematische Grundlage des Glyph-Renderers zu schaffen. Der Schnitt eines Blickstrahls mit dem Superellipsoid erfolgt nicht analytisch, sondern numerisch iterativ. Zunächst wird der Strahl mit der Bounding-Box des Ellipsoids geschnitten, ausgehend davon wird der genaue Schnittpunkt mit der Oberfläche iterativ gesucht. Dafür müssen Kandidatenlösungen (Punkte in 3D-Weltkoordinaten) immer wieder in die Gleichung des Superellipsoids eingesetzt werden. Die analytische Gleichung der für unsere Übung verwendeten Superellipsoide ist:
Cxyz ist der Mittelpunkt des Ellipsoids in Weltkoordinaten, xs ist einer der drei Radii des Super-Ellipsoids (Radius in x-Richtung). Die anderen beiden Radii sind 1.0, da wir nur Streckungen nach einer Richtung erlauben möchten. Somit liegen alle Punkte xyz auf dem Super-Ellipsoid, für die die obige implizite Darstellungsgleichung erfüllt ist. Setzt man Punkte ein, die innerhalb des Superellipsoids liegen, so wird die ausgewertete linke Seite der Gleichung kleiner sein als die rechte, für Punkte außerhalb des Ellipsoids ist die linke Seite größer.
Der Exponent exp definiert die Form des Superellipsoids. Die Klasse der mit dieser Gleichung darstellbaren Superellipsoide reicht von Kugeln (exp = 2, xs = 1) und Ellipsoiden (exp = 2, xs beliebig) bis hin zu spitzeren rautenförmigen Körpern (exp < 2) oder quaderartigen (exp > 2). Ein paar anschauliche Beispiele für den 2D-Fall (Superellipsen) gibt es hier.
Die Aufgabe der Studierenden ist nun, für die Funktion evaluateSuperellipsoid() die Auswertung der linken Seite der obigen Gleichung für ein gegebenes Superellipsoid und einen gegebenen Punkt in 3D zu implementieren, und das Ergebnis als float-Wert zurückzugeben. Außerhalb der Funktion kann dann festgestellt werden, ob der Kandidatenpunkt der Iteration schon gut genug ist, um diesen zu akzeptieren. Nach der erfolgreichen Implementierung dieser Funktion sollten rote Glyphen als Kugeln dargestellt werden können:
  
Die relevante Stelle befindet sich relativ weit unten im Code, in der Nähe des Fragment-Programms und ist mit TODO 1 gekennzeichnet. Der Grund, warum diese Aufgabe zuerst gelöst werden sollte, ist, dass man dann bereits Glyphen sieht und für die weiteren sechs Aufgaben wesentlich anschaulicheres Feedback bekommt.
2
Calculate Texture Coordinates
Um an den Glyph-Positionen die zugrundeliegenden Daten des Volumens abfragen zu können, wird das Volumen in einer 3D-Textur gespeichert. Dabei handelt es sich um eine in Hardware interpolierbare Datenstruktur, in der in einem regelmäßigen Gitter mit normalisierten Koordinaten sämtliche vorhandenen Volumendaten abgelegt sind. Man kann sich die 3D-Textur als Funktion 3D -> 4D vorstellen, da jedem dreidimensionalen Punkt aus [0,1]x[0,1]x[0,1] ein vierdimensionales Tupel (GradientX, GradientY, GradientZ, Dichtewert) zugeordnet ist. Diese 3D-Textur befindet sich zum Zeitpunkt der Ausführung im Speicher der Grafikkarte und steht in Shaderprogrammen zur Verfügung.
Die Aufgabe der Studierenden ist es nun, für jede Glyph-Position, die in den Vertex-Shader der Render-Pipeline gelangt, die 3D-Texturkoordinaten (je zwischen 0 und 1) dieser Position zu berechnen und für den späteren Lookup zu speichern. Dazu stehen folgende Parameter im Vertex-Shader zur Verfügung: fStartX, fStartY und fStartZ geben die minimalen Weltkoordinaten der Volumens-Boundingbox an, während fRangeX, fRangeY, fRangeZ die Ausdehnungen der Volumens-Boundingbox beschreiben. Mithilfe dieser Parameter soll nun ein direktes, lineares Mapping der Weltkoordinaten auf Texturkoordinaten durchgeführt werden. (Man kann davon ausgehen, dass die Bounding Box axis-aligned im Raum steht)
Ob die Texturkoordinaten richtig berechnet wurden, wird erst nach der Lösung der nächsten Aufgaben sichtbar.
Die relevante Stelle befindet sich fast ganz oben im Code, im Vertex-Shader und ist mit TODO 2 gekennzeichnet. Ab dieser Aufgabe befinden sich die zu ergänzenden Codestücke fortlaufend von oben nach unten im Shader-Sourcefile.
3
Sample the Volume
Nachdem im vorigen Task Texturkoordinaten für jeden Glyph berechnet wurden, können im nächsten Schritt für jeden Glyph die Daten aus dem Volumen gelesen werden. Die ersten drei Kanäle des Volumens (RGB) enthalten den Gradienten, der vierte Kanal (Alpha) den Dichtewert. Dazu ist der Befehl "texture3D(Volumensresource, Texturkoordinaten)" zu verwenden, der als vierdimensionalen Vektor die Werte des Volumens (3D-Gradient, Dichte) an der gefragten Stelle zurückliefert. Die erhaltenen Dichtewerte liegen zwischen 0 (minimale Dichte) und 1 (maximale Dichte), die Gradienten sind zum Auslesezeitpunkt nicht normalisiert und liegen koordinatenweise zwischen -1 und 1.
Die relevante Stelle befindet sich im Geometry Shader und ist mit TODO 3 gekennzeichnet. Ob die Implementierung des Lookups korrekt durchgeführt wurde, sieht man spätestens nach Fertigstellung von TODO 4.
4
Glyph Threshold
Bislang wurden die Glyphen regelmäßig in einer gewissen Auflösung anzahlX*anzahlY*anzahlZ gezeichnet. Somit hätte man aber nie die Möglichkeit, innere Schichten zu betrachten. Daher gibt es im grafischen User-Interface einen Parameter "Glyph Threshold", der einen Schwellwert zwischen 0 und 1 angibt, den die Dichte des Volumens an einer Glyph-Position übersteigen muss, damit der Glyph angezeigt wird. Je höher der Threshold, desto mehr Glyphen verschwinden. Im Schädeldatensatz sind beispielsweise die äußeren Schichten eher wenig dicht (Luft, Haut, Gewebe), während der Schädelknochen oder die Zähne eine hohe Dichte aufweisen. Somit kann man durch Erhöhen des Schwellwerts die äußeren Schichten schrittweise wegblenden.
Momentan bewirken Veränderungen des GUI-Parameters "Glyph Threshold" noch nichts. Die Aufgabe der Studierenden ist nun, diese Funktionalität im Geometry Shader zu implementieren. Der Schwellwert ist im Shader unter dem Namen fGlyphThreshold verfügbar, der Dichtewert des Glyphen steht in fVolumeIntensity. Sollte der Dichtewert nicht groß genug sein, so soll die Pipeline für diesen Glyphen frühzeitig mittels "return;" beendet werden, wodurch an dieser Stelle kein Glyph angezeigt wird.
Die relevante Stelle befindet sich im Geometry Shader und ist mit TODO 4 gekennzeichnet. Wenn die Implementierung korrekt durchgeführt wurde, sollten Veränderungen des "Glyph Threshold" sich auf die Anzahl der dargestellten Glyphen auswirken.
5
Visual Mapping Part 1
Zum jetztigen Zeitpunkt sehen alle Glyphen gleich aus, da die Superellipsoid-Parameter (Länglichkeit, Gestalt) standardmäßig auf "xs" = 1.0 (isotrop), "exp" = 2.0 (kugelförmig) gesetzt werden. In den kommenden Tasks ist nun das Visual Mapping zu implementieren, d.h. die Zuordnung der vorliegenden Daten auf eben diese visuell erfassbaren Größen.
Zunächst soll von den Studierenden das Mapping der Gradient Magnitude, also des Gradientenbetrags auf die Länglichkeit des Glyphen durchgeführt werden. Homogene Regionen mit kleinem Gradienten sollen nahezu isotrope Glyphen erhalten, während mit steigendem Gradientenbetrag auch die Länglichkeit steigen soll. Für die Länglichkeit ist der Parameter "xs" verantwortlich.
Die Studierenden sollen nun ein lineares Mapping des Gradientenbetrags, gespeichert in fMagnitude, auf den Parameter "xs" wie folgt vornehmen:
fMagnitude = 0.0 --> xs = 1.0, also isotrop, nicht länglich
Je größer fMagnitude --> desto größer xs, also desto länglicher der Glyph
Da die Gradientenbeträge so klein sind, dass man sie mit diesem Mapping ohne Weiteres kaum unterscheiden könnte, gibt es einen zusätzlichen Multiplikations-Faktor, der für das Mapping an die Gradient Magnitude multipliziert werden soll, damit die Unterschiede besser erkennbar sind. Dieser Parameter heißt im Shader fGradientScale und kann im GUI unter dem Namen "Gradient Elongation Scale" manipuliert werden.
Die relevante Stelle befindet sich im Geometry Shader und ist mit TODO 5 gekennzeichnet. Wenn die Implementierung korrekt durchgeführt wurde, sollten Glyphen in Regionen mit großem Gradienten je nach Wahl des GUI-Parameters "Gradient Elongation Scale" länglicher werden.
6
Visual Mapping Part 2
Im nächsten Schritt soll die Form der Glyphen durch zusätzliches Mapping der Gradient Magnitude auf den Exponenten des Superellipsoids beeinflusst werden. Standardmäßig waren bislang alle Glyphen rund, da der Exponent immer auf den Default-Wert 2.0 gesetzt wurde (vgl. zB. Gleichung der Einheitskugel x² + y² + z² = 1).
Die Studierenden sollen ein lineares Mapping des Gradientenbetrags, gespeichert in fMagnitude auf den Parameter "exp" wie folgt vornehmen:
fMagnitude = 0.0 --> exp = 2.0, also rund
Je größer fMagnitude --> desto kleiner exp, also desto spitzer der Glyph
Als untere Grenze soll jedoch exp = 1.0 gelten, da wir für diese Übung nur konvexe Formen darstellen möchten. Werte kleiner als 1.0 sollen also auf 1.0 "ge-clamped" werden.
Auch hier soll der zusätzliche Multiplikationsfaktor fGradientScale an den Gradientenbetrag multipliziert werden, damit kleinere Unterschiede besser erkennbar gemacht werden können.
Anmerkung: Die korrekte Rotation der Glyphen, sodass sie in die Richtung des größten Anstiegs zeigen, ist übrigens schon implementiert und muss von den Studenten nicht bearbeitet werden.
Die relevante Stelle befindet sich im Geometry Shader und ist mit TODO 6 gekennzeichnet. Wenn die Implementierung korrekt durchgeführt wurde, sollten Glyphen in Regionen mit großem Gradienten je nach Wahl des GUI-Parameters "Gradient Elongation Scale" mehr oder weniger spitz werden.
7
Visual Mapping Part 3 - Color Mapping
Im letzten Schritt soll nun der Dichtewert des Volumens auf die Farbe des Glyphen abgebildet werden. Hierzu sollen die Studierenden exemplarisch bitte einfach folgendes Schema implementieren:
fVolumeIntensity = 0.0 --> Farbe rot RGB(1,0,0)
fVolumeIntensity = 1.0 --> Farbe grün RGB(0,1,0)
Dazwischen soll linear interpoliert werden, sodass zB eine Dichte von 0.5 auf gelb abgebildet wird.
Die relevante Stelle befindet sich im Geometry Shader und ist mit "TODO 7" gekennzeichnet. Wenn die Implementierung korrekt durchgeführt wurde, sollte die Farbe des Glyphen Aufschluss über den Dichtewert geben.

User Interface

Folgende GUI-Parameter stehen zur Verfügung um das Glyph-Rendering zu beeinflussen:
Name Effekt Zeigt Auswirkungen
Glyph Density Die Anzahl der Glyphen, die gerendert werden soll. Je größer dieser Wert, desto kleiner werden die Glyphen, damit es keine Überlappung gibt immer
Glyph Threshold Der Schwellwert, den die Dichte übersteigen muss, damit ein Glyph angezeigt wird ab TODO 4
Gradient Elongation Scale Dieser Multiplikator gibt an, wie stark die Glyphen aufgrund des Gradientenbetrags in die Länge gezogen werden und ihre Form verändern ab TODO 5
Shading Intensity Wie stark die Beleuchtungssituation das Erscheinungsbild der Glpyhen beeinflusst (0 = nur Farben, 1 = maximales Shading) immer
Diffuse Shading Diffuse Komponente der Glyph-Oberflächenreflexion
(Details siehe hier)
immer
Specular Shading Spiegelnde Komponente der Glyph-Oberflächenreflexion
(Details siehe hier)
immer
Specular Exponent Glattheit des Glyph-Materials, beeinflusst die spiegelnde Reflexion
(Details siehe hier)
immer

Hinweis

Falls Probleme mit der Performance auftreten:
  • Glyph Density eher gering halten und den Threshold eher hoch.
  • Weiter Hinauszoomen, sodass im Endeffekt wesentlich weniger Pixel berechnet werden müssen.
  • Sollte beim Nachladen des Shaders der Grafikkartentreiber abstürzen (evtl. auf älteren Laptops), so deaktiviert man vor dem Nachladen das Renderingplugin kurz, lädt nach und aktiviert dann das Plugin wieder.