Steuerungstasten

nächste Folie (auch Enter oder Spacebar).
vorherige Folie
 d  schaltet das Zeichnen auf Folien ein/aus
 p  wechselt zwischen Druck- und Präsentationsansicht
CTRL  +  vergrößert die Folien
CTRL  -  verkleinert die Folien
CTRL  0  setzt die Größenänderung zurück

Das Weiterschalten der Folien kann ebenfalls durch das Klicken auf den rechten bzw. linken Folienrand erfolgen.

Notation

Typ Schriftart Beispiele
Variablen (Skalare) kursiv $a, b, x, y$
Funktionen aufrecht $\mathrm{f}, \mathrm{g}(x), \mathrm{max}(x)$
Vektoren fett, Elemente zeilenweise $\mathbf{a}, \mathbf{b}= \begin{pmatrix}x\\y\end{pmatrix} = (x, y)^\top,$ $\mathbf{B}=(x, y, z)^\top$
Matrizen Schreibmaschine $\mathtt{A}, \mathtt{B}= \begin{bmatrix}a & b\\c & d\end{bmatrix}$
Mengen kalligrafisch $\mathcal{A}, B=\{a, b\}, b \in \mathcal{B}$
Zahlenbereiche, Koordinatenräume doppelt gestrichen $\mathbb{N}, \mathbb{Z}, \mathbb{R}^2, \mathbb{R}^3$

Vulkan Ray Tracing Pipeline

  • Vulkan ist eine Programmierschnittstelle für Grafikanwendungen
  • Die Vulkan-Programmierschnittstelle wird durch die Khronos Group standardisiert
  • Ende 2020 wurde für GLSL 4.6 eine Erweiterung für Ray Tracing definiert: GLSL_EXT_ray_tracing
  • Diese soll im Rahmen dieser Vorlesung verwendet werden
  • Der Standard wird sowohl von Nvidia Geforce RTX Grafikkarten als auch von AMD Grafikkarten der RX 6000 Serie unterstützt

Vulkan GLSL Ray Tracing Emulator

  • Da nicht alle Studierenden Zugriff auf eine passende Grafikkarte mit Raytracing-Beschleuniger-Hardware haben, wird ein web-basierter Emulator bereitgestellt:
  • Der Emulator übersetzt den Raytracing-Shader-Code in einen Standard WebGL Fragment Shader, der im Browser ausgeführt wird
  • Der Emulator kann ebenfalls ein C++ Vulkan Projekt exportieren

raytracing_emulator

Ray Tracing Shader Pipeline

raytracing_pipeline

Die Raytracing-Pipeline besteht aus 5 Shadern:

  • Ray Generation
  • Closest-Hit
  • Miss
  • Intersection
  • Any-Hit

Ray Generation Shader

raytracing_pipeline
  • Der Ray Generation Shader erstellt Strahlen und sendet sie durch Aufrufen der Funktion traceRayEXT(...) zum "Acceleration Structure Traversal"-Block
  • Dieser Block dient zur hardwarebeschleunigten Strahlverfolgung und ist der nicht programmierbare Teil der Pipeline
  • Der wichtigste Parameter der traceRayEXT Funktion ist die payload Variable, die die gesammelten Informationen des Strahls enthält
  • Die payload Variable hat einen benutzerdefinierten Typ und kann während der Strahlverfolgung durch die unterschiedlichen Shader verändert werden
  • Sobald die Strahlverfolgung abgeschlossen ist, kehrt die Funktion traceRayEXT zum aufrufenden Shader zurück und die payload Variable kann vom Ray Generation-Shader ausgewertet werden, um ein Ausgabebild zu erstellen

Intersection Shader

raytracing_pipeline
  • Wenn die Strahlenverfolgung einen Schnittpunkt des Strahls mit einer benutzerdefinierten Bounding Box (oder Dreieck eines Dreiecksnetzes) erkennt, wird der Intersection Shader aufgerufen
  • Wenn der Intersection Shader berechnet, dass ein Strahl-Primitiv-Schnittpunkt innerhalb der Bounding Box aufgetreten ist, benachrichtigt er die Strahlverfolgung mit der Funktion reportIntersectionEXT(...)
  • Darüber hinaus kann der Intersection Shader Daten in die hitAttributeEXT Variable schreiben (die vom benutzerdefinierten Typ sein kann)
  • Für Dreiecke ist der Intersection Shader bereits in der Hardware vorimplementiert
  • Die eingebaute Schnittpunktberechnung für Dreiecke liefert baryzentrische Koordinaten des Schnittpunkts durch die "hitAttributeEXT vec2 baryCoord" Variable
  • Für andere Primitive (Würfel, Kugeln, usw.) wird ein eigener Shader benötigt

Any-Hit Shader

raytracing_pipeline
  • Wenn ein Strahl-Primitiv-Schnitt gemeldet wird und ein Any-Hit Shader vorhanden ist, wird dieser aufgerufen
  • Die Aufgabe des Any-Hit Shaders besteht darin, einen Treffer zu akzeptieren oder zu ignorieren
  • Eine typische Anwendung für einen Any-Hit Shader ist die Verarbeitung von teilweisen transparenten Oberflächen. Wenn der Treffer in einem transparenten Bereich auftritt, soll er ignoriert werden.
  • Treffer werden mit dem ignoreIntersectionEXT Befehl ignoriert
  • Es ist auch möglich, die weitere Strahlverfolgung im Any-Hit Shader durch den Befehl terminateRayEXT abzubrechen
  • Wenn kein Any-Hit Shader angegeben ist oder die Anweisung ignoreIntersectionEXT nicht aufgerufen wird, dann wird der Treffer an die Strahlverfolgung übermittelt

Closest-Hit und Miss Shader

raytracing_pipeline
  • Sobald die Strahlverfolgung alle möglichen Treffer entlang des Strahls festgestellt hat und mindestens ein Treffer aufgetreten ist, wird der Closest-Hit Shader mit dem nächsten der Treffer aufgerufen
  • Andernfalls, wenn kein Treffer aufgetreten ist, wird der Miss Shader aufgerufen
  • Beide Arten von Shadern können die payload Variable manipulieren
  • Zum Beispiel könnte der Miss Shader die Umgebungsfarbe setzen und der Closest-Hit Shader könnte die Farbberechnung für eine getroffene Oberfläche durchführen
  • Zu diesem Zweck kann der Closest-Hit Shader auf mehrere vorgegebene Variablen zugreifen, wie beispielsweise gl_PrimitiveID oder gl_InstanceID, die für jeden Treffer entsprechend gesetzt werden

Closest-Hit und Miss Shader

raytracing_pipeline
  • Der Closest-Hit und der Miss Shader können ebenfalls die traceRayEXT Funktion aufrufen, was einen weiteren Strahl in den Strahlverfolgungsblock sendet und eventuell eine Rekursion erzeugen kann
  • Eine typische Anwendung im Closest-Hit Shader ist das Versenden eines Schattenstrahls in die Richtung der Lichtquelle, um festzustellen, ob die Lichtquelle durch andere Objekte verdeckt wird
  • Da dieser möglicherweise einen weiteren Aufruf des Closest-Hit Shader auslöst, kann ein Rekursion entstehen
  • Die Anzahl der rekursiven Funktionsaufrufe sollte so gering wie möglich gehalten werden, um die optimale Leistung zu erzielen

Automatisch gesetzte Variablen (Built-In Variables)

Ray generation Closest-hit Miss Intersection Any-hit
uvec3 gl_LaunchIDEXT
uvec3 gl_LaunchSizeEXT
int gl_PrimitiveID
int gl_InstanceID
int gl_InstanceCustomIndexEXT
int gl_GeometryIndexEXT
vec3 gl_WorldRayOriginEXT
vec3 gl_WorldRayDirectionEXT
vec3 gl_ObjectRayOriginEXT
vec3 gl_ObjectRayDirectionEXT
float gl_RayTminEXT
float gl_RayTmaxEXT
uint gl_IncomingRayFlagsEXT
float gl_HitTEXT
uint gl_HitKindEXT
mat4x3 gl_ObjectToWorldEXT
mat4x3 gl_WorldToObjectEXT

Vordefinierte Konstanten

const uint gl_RayFlagsNoneEXT = 0u;
const uint gl_RayFlagsNoOpaqueEXT = 2u;
const uint gl_RayFlagsTerminateOnFirstHitEXT = 4u;
const uint gl_RayFlagsSkipClosestHitShaderEXT = 8u;
const uint gl_RayFlagsCullBackFacingTrianglesEXT = 16u;
const uint gl_RayFlagsCullFrontFacingTrianglesEXT = 32u;
const uint gl_RayFlagsCullOpaqueEXT = 64u;
const uint gl_RayFlagsCullNoOpaqueEXT = 128u;
const uint gl_HitKindFrontFacingTriangleEXT = 0xFEu;
const uint gl_HitKindBackFacingTriangleEXT = 0xFFu;

TLAS und BLAS

tlas_blas
TLAS
BLAS
gl_ObjectToWorldEXT
gl_WorldToObjectEXT
gl_InstanceID = 0
gl_InstanceID = 1
gl_InstanceID = 2
Triangle Mesh
Intersection Boxes
Intersection Boxes

TLAS und BLAS

  • Die 3D-Szene wird durch die Beschleunigungsstruktur repräsentiert, die im Strahlverfolgungsblock verwendet wird
  • Die Beschleunigungsstruktur besteht aus einer Top-Level Acceleration Structure (TLAS) und mehreren Bottom-Level Acceleration Structures (BLAS)
  • Jedes BLAS kann entweder ein Dreiecksnetz oder eine benutzerdefinierte Menge von Axis-Aligned Bounding Boxen (AABBs) sein
  • Ein BLAS kann im TLAS instanziiert werden und erhält eine eindeutige gl_InstanceID
  • Darüber hinaus erhält jedes Dreieck in einem Dreiecksnetz bzw. jede AABB in der Menge der Bounding Boxen eine aufeinanderfolgende gl_PrimitiveID
  • Jedes BLAS hat seine Transformation vom Objekt- zum Weltkoordinatensystem, die erstmals zugewiesen wird, wenn das BLAS zum TLAS hinzugefügt wird
  • Auf diese Transformation kann im Shader über die gl_ObjectToWorldEXT und gl_WorldToObjectEXT Variable zugegriffen werden
  • Wenn ein Treffer bei der Strahlverfolgung passiert und der Intersection, Any-Hit oder Closest-Hit Shader aufgerufen wird, werden die eben genannten Variablen entsprechend gesetzt

Einfache Beispiele für einen Ray Generation Shader

Strahlen im 3D Raum

ray
Ursprung
Richtung
$\mathbf{s}$
$\mathbf{b}$
$\mathbf{r}$
  • Ein Strahl im 3D Raum kann definiert werden durch seinen
    • Ursprung $\mathbf{s}=(s_x, s_y, s_z)^\top$ und
    • Richtungsvektor $\mathbf{r}=(r_x, r_y, r_z)^\top$
  • Dabei wird in der Regel die Länge des Richtungsvektors zu 1 normiert
    ray parameterization
    $\mathbf{s}$
    $\mathbf{p}(t)$
    $t$
    $\mathbf{r}$
  • Soll beispielsweise ein Strahl ausgehend vom Ursprung $\mathbf{s}$ in Richtung eines zweiten Punkts $\mathbf{b}$ berechnet werden, so gilt:
    $\begin{aligned} \mathbf{r} &= \frac{\mathbf{b} - \mathbf{s}}{|\mathbf{b} - \mathbf{s}|} \end{aligned}$
  • Jeder Punkt $\mathbf{p}$ auf dem Strahl kann durch einen Skalar $t$ parametrisiert werden:
    $\mathbf{p}(t) = \mathbf{s} + t \,\, \mathbf{r} \quad \forall\, \, t \ge\,\, 0$

Perspektivische Kamera

perspective_camera
-1
1
Projektionszentrum $\mathbf{c}$
Bildebene
Brennweite
$f$
$y$
$z$
$x$
$y$
$z$
$\mathbf{c}$
$\Theta$
$w$
$h$
$\mathbf{p}$
$\mathbf{p}$
  • Im Folgenden soll hergeleitet werden, wie ein Strahl durch eine Pixelkoordinate einer perspektivischen Kamera berechnet werden kann
  • Bei einer perspektivischen Kamera starten alle Strahlen im Projektionszentrum $\mathbf{c}$
  • Damit steht der Ursprung $\mathbf{s}$ der Kamerastrahlen schon einmal fest:
    $\mathbf{s}= \mathbf{c} = (0.0, 0.0, 0.0)^\top$

Perspektivische Kamera

perspective_camera
-1
1
Projektionszentrum $\mathbf{c}$
Bildebene
Brennweite
$f$
$y$
$z$
$x$
$y$
$z$
$\mathbf{c}$
$\Theta$
$w$
$h$
$\mathbf{p}$
$\mathbf{p}$
  • Äquivalent zur Definition in OpenGL, soll die Kamera in die negative $z$-Richtung ausgerichtet sein und die Bildebene in y-Richtung im Bereich $[-1.0, 1.0]$ liegen
  • Bei einem gegebenen Öffnungswinkel $\Theta$ in y-Richtung errechnet sich damit die Brennweite zu (siehe Zeichnung):

    $\frac{f}{1} = \frac{\cos( 0.5 \, \Theta)}{\sin( 0.5 \, \Theta)} \Leftrightarrow f = \mathrm{cotan}( 0.5 \, \Theta)$

Perspektivische Kamera

perspective_camera
-1
1
Projektionszentrum $\mathbf{c}$
Bildebene
Brennweite
$f$
$y$
$z$
$x$
$y$
$z$
$\mathbf{c}$
$\Theta$
$w$
$h$
$\mathbf{p}$
$\mathbf{p}$
  • Damit gilt für die 3D-Position eines Punktes auf der Bildebene
    $\mathbf{p} = (p_x, p_y, -f)^\top$
    und der Richtungsvektor des Kamerastrahls lautet:
    $\mathbf{r} = \frac{\mathbf{p} - \mathbf{c}}{|\mathbf{p} - \mathbf{c}|} = \frac{\mathbf{p}}{|\mathbf{p}|} $

Perspektivische Kamera

img_coord
Pixelraster
Pixelfläche
Außenkante der Rastergrafik
Pixelkoordinaten
Texturkoordinaten
Normalized Device Coordinates
$x$
$y$
  • Umrechnung zwischen Pixelkoordinaten $(x_p, y_p)$ und Texturkoordinaten $(x_t, y_t)$ bei einer Bildbreite $w$ und Bildhöhe $h$ in Pixeln:
    $\begin{pmatrix}x_t\\y_t\end{pmatrix} = \begin{pmatrix}\frac{x_p + 0.5}{w}\\ \frac{y_p + 0.5}{h}\end{pmatrix}$

Perspektivische Kamera

img_coord
Pixelraster
Pixelfläche
Außenkante der Rastergrafik
Pixelkoordinaten
Texturkoordinaten
Normalized Device Coordinates
$x$
$y$
  • Umrechnung zwischen Texturkoordinaten $(x_t, y_t)$ und Normalized Device Coordinates $(x_n, y_n)$:
    $\begin{pmatrix}x_n\\y_n\end{pmatrix} = \begin{pmatrix}2.0 \,x_t - 1.0\\ 2.0 \,y_t - 1.0\end{pmatrix}$

Perspektivische Kamera

img_coord
Pixelraster
Pixelfläche
Außenkante der Rastergrafik
Pixelkoordinaten
Texturkoordinaten
Normalized Device Coordinates
$x$
$y$
  • Umrechnung zwischen Normalized Device Coordinates $(x_n, y_n)$ und der 3D-Position des Punktes $\mathbf{p}$:
    $\mathbf{p} = \begin{pmatrix}p_x\\p_y\\p_z\end{pmatrix} = \begin{pmatrix}\frac{w}{h} \, x_n\\ y_n\\ -f\end{pmatrix}$

Perspektivische Kamera - Ray Generation Shader

// Returns a camera ray for a camera at the origin that is 
// looking in negative z-direction. "fieldOfViewY" must be given in degrees.
// "point" must be in range [0.0, 1.0] to cover the complete image plane.
//
vec3 getCameraRay(float fieldOfViewY, float aspectRatio, vec2 point) {
  // compute focal length from given field-of-view
  float focalLength = 1.0 / tan(0.5 * fieldOfViewY * 3.14159265359 / 180.0);
  // compute position in the camera's image plane in range [-1.0, 1.0]
  vec2 pos = 2.0 * (point - 0.5);
  return normalize(vec3(pos.x * aspectRatio, pos.y, -focalLength));
}

void main() { /**** RAY GENERATION SHADER ****/

  // compute the texture coordinate for the output image in range [0.0, 1.0]
  vec2 texCoord = (vec2(gl_LaunchIDEXT.xy) + 0.5) / vec2(gl_LaunchSizeEXT.xy);

  // camera's aspect ratio
  float aspect = float(gl_LaunchSizeEXT.x) / float(gl_LaunchSizeEXT.y);
  
  vec3 rayOrigin = vec3(0.0, 0.0, 0.0);
  vec3 rayDirection = getCameraRay(45.0, aspect, texCoord);
  ...
 

Perspektivische Kamera - Ray Generation Shader

example_cameraray

Rendern eines Dreiecksnetzes

trimesh raytracing_pipeline2
  • Um ein Dreiecksnetzes zu rendern, werden Closest-Hit und Miss Shader hinzugefügt
  • Die Kamerastrahlen aus dem vorhergehenden Beispiel werden durch den Aufruf der vordefinierten Funktion traceRayEXT(...) im Ray Generation Shader in den Strahlverfolgungsblock geschickt
  • Wenn ein Kamerastrahl das Dreiecksnetz triff, wird der Closest-Hit Shader aufgerufen und die payload.color Variable wird auf rot gesetzt. Anderenfalls, wenn kein Treffer stattfindet, setzt der Miss Shader payload.color auf schwarz.
  • Nach der Ausführung des Closest-Hit oder Miss Shader kehrt die Funktion traceRayEXT zum aufrufenden Ray Generation Shader zurück und die geänderte payload.color Variable wird dort in das Ausgabebild geschrieben
  • Example: Triangle Mesh

Zugriff auf die Vertex-Daten eines Dreiecksnetzes

vertexdata raytracing_pipeline2
  • Wenn der Closest-Hit Shader aufgerufen wird, werden die vordefinierten Variablen gl_InstanceID, gl_ObjectToWorldEXT und gl_PrimitiveID automatisch entsprechend gesetzt und erlauben so die BLAS-Instanz, seine Transformation und das Primitiv (d.h. in diesem Fall das Dreieck) des Treffers zu identifizieren
  • Der Emulator stellt drei Funktionen bereit die gl_InstanceID und gl_PrimitiveID als Eingabeparameter haben und die lokalen Vertex Positionen, lokalen Normalen und Texturkoordinaten der drei Stützpunkte des getroffenen Dreiecks liefern
  • Desweiteren werden baryzentrische Koordinaten (dazu später mehr) der Trefferposition durch den vorimplementierten Intersection Shader für Dreiecke berechnet und im Closest-Hit Shader durch die vec2 baryCoord Variable bereitgestellt
  • Example: Vertex Data

Wiederholung: Skalarprodukt

scalarproduct
$\mathbf{a}$
$\mathbf{b}$
$\alpha$
$\mathbf{a}_{\mathbf{b}}$
  • Das Skalarprodukt verknüpft zwei Vektoren $\mathbf{a}$ und
    $\mathbf{b} \in \mathbb{R}^N$. Ergebnis ist ein Skalar.
    Skalarprodukt: $\langle \mathbf{a} \cdot \mathbf{b}\rangle = \mathbf{a}^\top \mathbf{b} = \sum\limits_{i=1}^N a_i b_i$
    Skalarprodukt im $\mathbb{R}^3$: $\mathbf{a}^\top \mathbf{b} = (a_1, a_2, a_3) \begin{pmatrix}b_1\\b_2 \\ b_3 \end{pmatrix} = a_1 b_1 + a_2 b_2 + a_3 b_3$
  • Kommutativ: $\langle \mathbf{a} \cdot \mathbf{b}\rangle = \langle \mathbf{b} \cdot \mathbf{a}\rangle$
  • Kosinusgesetz: $\langle \mathbf{a} \cdot \mathbf{b}\rangle = |\mathbf{a}| |\mathbf{b}| \cos \alpha$
  • Orthogonale Vektoren: $\mathbf{a} \perp \mathbf{b} \rightarrow \langle \mathbf{a} \cdot \mathbf{b}\rangle = 0$
  • Senkrechte Projektion: $\mathbf{a}_{\mathbf{b}} = (\mathbf{a}^\top \frac{\mathbf{b}}{|\mathbf{b}| }) \frac{\mathbf{b}}{|\mathbf{b}| }$
  • Verbindung zur Matrixmultiplikation:
    $\begin{bmatrix}a_{11} & a_{12}\\a_{21} & a_{22}\end{bmatrix}\begin{bmatrix}b_{11} & b_{12}\\b_{21} & b_{22}\end{bmatrix} = \begin{bmatrix}\mathbf{a}_1^\top\\ \mathbf{a}_2^\top\end{bmatrix}\begin{bmatrix}\mathbf{b}_1 & \mathbf{b}_2\end{bmatrix} = \begin{bmatrix}\mathbf{a}_1^\top\mathbf{b}_1 & \mathbf{a}_1^\top\mathbf{b}_2\\ \mathbf{a}_2^\top\mathbf{b}_1 & \mathbf{a}_2^\top\mathbf{b}_2 \end{bmatrix}$

Wiederholung: Kreuzprodukt

scalarproduct
$\mathbf{a}$
$\mathbf{b}$
$\alpha$
$\mathbf{a} \times \mathbf{b}$
$|\mathbf{a} \times \mathbf{b}|$
  • Das Kreuzprodukt verknüpft zwei Vektoren $\mathbf{a}$ und $\mathbf{b} \in \mathbb{R}^3$. Ergebnis ist ein neuer Vektor $\mathbf{c} = \mathbf{a} \times \mathbf{b} \in \mathbb{R}^3$.
  • Kreuzprodukt:
    $\begin{pmatrix}a_1\\a_2 \\ a_3 \end{pmatrix} \times \begin{pmatrix}b_1\\b_2 \\ b_3 \end{pmatrix} = \begin{pmatrix}a_2 b_3 - a_3 b_2\\ a_3 b_1 - a_1 b_3 \\ a_1 b_2 - a_2 b_1 \end{pmatrix}$
  • Matrixschreibweise:
    $\mathbf{a} \times \mathbf{b} = \begin{bmatrix}0 & -a_3 & a_2 \\a_3 & 0 & -a_1 \\ -a_2 & a_1 & 0 \end{bmatrix} \mathbf{b} = [\mathbf{a}]_\times \,\mathbf{b}$
  • Antisymmetrie: $\mathbf{a} \times \mathbf{b} = -\mathbf{b} \times \mathbf{a}$
  • Sinusgesetz: $|\mathbf{a} \times \mathbf{b}| = |\mathbf{a}| |\mathbf{b}| \sin \alpha$
  • Orthogonale Vektoren: $\mathbf{a} \perp (\mathbf{a} \times \mathbf{b}) \perp \mathbf{b}$

Bewegen der Kamera

lookat
Augpunkt $\mathbf{c}_{\mathrm{\small eye}}$
Referenzpunkt $\mathbf{p}_{\mathrm{\small ref}}$
Up-Vektor $\mathbf{v}_{\mathrm{\small up}}$
$\tilde{\mathbf{c}}_x$
$\tilde{\mathbf{c}}_y$
$\tilde{\mathbf{c}}_z$
  • Durch Definition von
    • Augpunkt  $\mathbf{c}_{\mathrm{\small eye}}$
    • anvisiertem Referenzpunkt $\mathbf{p}_{\mathrm{\small ref}}$
    • Up-Vektor $\mathbf{v}_{\mathrm{\small up}}$ (der definiert, in welche Richtung die $y$-Koordinate der Kamera zeigt)
    ergeben sich die Basisvektoren des Kamerakoordinatensystems zu:
    $\begin{align} \mathbf{d} & = \mathbf{c}_{\mathrm{\small eye}} - \mathbf{p}_{\mathrm{\small ref}}\\ \tilde{\mathbf{c}}_z &= \frac{\mathbf{d}}{|\mathbf{d}|} \\ \mathbf{v}' &= \frac{\mathbf{v}_{\mathrm{\small up}}}{|\mathbf{v}_{\mathrm{\small up}}|} \\ \tilde{\mathbf{c}}_x &= \mathbf{v}'\times \tilde{\mathbf{c}}_z \\ \tilde{\mathbf{c}}_y &= \tilde{\mathbf{c}}_z \times \tilde{\mathbf{c}}_x\\ \end{align}$

Bewegen der Kamera

  • Für die Kamera im Ursprung berechnet sich der Kamerastrahl zu:
    $\begin{align}\mathbf{p} &= \begin{pmatrix}p_x\\p_y\\p_z\end{pmatrix} = \begin{pmatrix}\frac{w}{h} \, x_n\\ y_n\\ -f\end{pmatrix}\\ \mathbf{r} &= \frac{\mathbf{p}}{|\mathbf{p}|}\quad \quad \mathbf{s}= (0.0, 0.0, 0.0)^\top\end{align} $
  • Damit gilt für den Kamerastrahl im transformierten Koordinatensystem:
    $\begin{align}\tilde{\mathbf{p}} &= \tilde{\mathbf{c}}_x \, p_x + \tilde{\mathbf{c}}_y \, p_y + \tilde{\mathbf{c}}_z \, p_z\\ \tilde{\mathbf{r}} &= \frac{\tilde{\mathbf{p}}}{|\tilde{\mathbf{p}}|} \quad \quad \tilde{\mathbf{s}}= \mathbf{c}_{\mathrm{\small eye}}\end{align}$
  • Beispiel: Camera Orbit

Versenden eines Schattenstrahls

raytracing_pipeline3
  • Der Closest-Hit und der Miss Shader können ebenfalls die traceRayEXT Funktion aufrufen, um einen Strahl in den Strahlverfolgungsblock zu schicken
  • In folgendem Beispiel ruft der Closest-Hit Shader traceRayEXT auf und schickt einen Schattenstrahl in Richtung der Lichtquelle.
shadow_ray
Schattenstrahl
Primärstrahl

Versenden eines Schattenstrahls

shadow_ray
Schattenstrahl
Primärstrahl
  • Die Aufgabe des Schattenstrahls besteht darin, den emittierenden Closest-Hit Shader zu informieren, ob sich zwischen dem ersten Treffer und der Lichtquelle ein Objekt befindet oder nicht
  • Wenn ein Treffer auf dem Strahlengang in Richtung des Lichts auftritt, sind wir nicht daran interessiert den Closest-Hit Shader für diesen Treffer aufzurufen. Deshalb werden die Ray-Flags auf gl_RayFlagsSkipClosestHitShaderEXT gesetzt
  • Darüber hinaus kann Rechenzeit bei der Strahlverfolgung gespart werden, da nicht der nächste Treffer gefunden werden muss. Die Strahlenverfolgung kann bereits beim ersten Treffer abbrechen, weshalb zusätzlich das Flag gl_RayFlagsTerminateOnFirstHitEXT gesetzt wird
  • Wenn für den Schattenstrahl kein Treffer auftritt, wird der Miss Shader aufgerufen und setzt die payload.shadowRayMiss Variable von false auf true
  • Wenn die Funktion traceRayEXT Funktion zum emittierenden Closest-Hit Shader zurückkehrt, kann diese Variable überprüft werden, um festzustellen, ob der Oberflächenpunkt im Schatten liegt oder nicht

Beispiel: Versenden eines Schattenstrahls

example_shadowray

Beispiel: Versenden eines Schattenstrahls

struct RayPayloadType {
  vec3 color;
  bool shadowRayMiss;
}; // type of the "payload" variable

...

void  main() { /**** CLOSEST-HIT SHADER ****/
  
  // get mesh vertex data in object space
  vec3 p0, p1, p2;
  gsnGetPositions(gl_InstanceID, gl_PrimitiveID, p0, p1, p2);
  vec3 n0, n1, n2;
  gsnGetNormals(gl_InstanceID, gl_PrimitiveID, n0, n1, n2);
  vec2 t0, t1, t2;
  gsnGetTexCoords(gl_InstanceID, gl_PrimitiveID, t0, t1, t2);

  // interpolate with barycentric coordinate
  vec3 barys = vec3(1.0f - baryCoord.x - baryCoord.y, baryCoord.x, baryCoord.y);
  vec3 localNormal = normalize(n0 * barys.x + n1 * barys.y + n2 * barys.z);
  vec3 localPosition = p0 * barys.x + p1 * barys.y + p2 * barys.z;
  vec2 texCoords = t0 * barys.x + t1 * barys.y + t2 * barys.z;

  // transform to world space
  mat3 normalMat;
  gsnGetNormal3x3Matrix(gl_InstanceID, normalMat);
  vec3 normal = normalize(normalMat * localNormal);
  vec3 position = gl_ObjectToWorldEXT * vec4(localPosition, 1.0);
  
  // dynamic light location
  float t = float(frameID % 45)/float(45);
  vec3 lightPos = vec3(5.0 * sin(2.0*PI*t), 5.0 * cos(2.0*PI*t),  5.0);
  vec3 lightDir = normalize(lightPos - position);
  
  // prepare shadow ray
  uint rayFlags = gl_RayFlagsTerminateOnFirstHitEXT |
                                gl_RayFlagsSkipClosestHitShaderEXT;
  float rayMin     = 0.001;
  float rayMax     = length(lightPos - position);  
  float shadowBias = 0.001;
  uint cullMask = 0xFFu;
  float frontFacing = dot(-gl_WorldRayDirectionEXT, normal);
  vec3 shadowRayOrigin = position + sign(frontFacing) * shadowBias * normal;
  vec3 shadowRayDirection = lightDir;
  payload.shadowRayMiss = false;

  // shot shadow ray
  traceRayEXT(topLevelAS, rayFlags, cullMask, 0u, 0u, 0u, 
         shadowRayOrigin, rayMin, shadowRayDirection, rayMax, 0);
  
  // diffuse shading
  vec3 radiance = ambientColor; // ambient term
  if(payload.shadowRayMiss) { // if not in shadow
    float irradiance = max(dot(lightDir, normal), 0.0);
    if(irradiance > 0.0) { // if receives light
      radiance += baseColor * irradiance; // diffuse shading
    }
  }  
  
  payload.color = vec3(radiance);
}

void main() { /**** MISS SHADER ****/
  // set color to black
  payload.color = vec3(0.0, 0.0, 0.0);
  // shadow ray has not hit an object
  payload.shadowRayMiss = true;
}

Reflexionen

reflections
  • Bei einer reflektierenden Oberfläche, kann beim Ray Tracing zusätzlich der reflektierte Strahl berechnet und weiterverfolgt werden
  • In einem ersten einfachen Modell nehmen wir an, die Strahldichte an einem Punkt ist die Summe aus
    • dem direkten Licht der Lichtquelle
    • plus der Strahldichte, die durch den reflektierten Strahl bestimmt wird
  • Der Ray Tracer stoppt nur bei nicht-reflektierenden Oberflächen. Daher Wiederholungen beschränken, sonst gibt es eventuell eine Endlos-Schleife

Reflexionen

  • Laut Reflexionsgesetz hat die Reflexionsrichtung den gleichen Winkel zur Oberflächennormalen wie die einfallende Richtung
    Einfallswinkel = Ausfallwinkel
  • Die Reflexionsrichtung $\mathbf{r}_{\mathrm{\small out}}$ berechnet sich daher aus der Oberflächennormalen $\mathbf{n}$ und der einfallende Richtung $\mathbf{r}_{\mathrm{\small in}}$ durch:
    $\mathbf{r}_{\mathrm{\small out}} = \mathbf{r}_{\mathrm{\small in}} - 2 \, \langle \mathbf{r}_{\mathrm{\small in}} \cdot \mathbf{n}\rangle \,\mathbf{n}$
    reflect_angles
    $\mathbf{r}_{\mathrm{\small in}}$
    $\mathbf{r}_{\mathrm{\small out}}$
    $\mathbf{n}$
  • In GLSL steht zur Berechnung der Reflexionsrichtung die Funktion reflect zur Verfügung
    vec3 r_out = reflect(r_in, normal);

Reflexionen

  • Reflexionen lassen sich besonders einfach mit rekursiven Funktionsaufrufen implementieren
    trace(level, ray, &color) { // THIS IS PSEUDOCODE!!!
      if (intersect(ray, &hit)) { 
        shadow = testShadow(hit);
        directColor = getDirectLight(hit, shadow);
        if (reflectionFactor > 0.0 && level < maxLevel) {
          reflectedRay = reflect(ray, hit.normal);
          trace(level + 1, reflectedRay, &reflectionColor); // recursion
        }
        color = color + directColor + reflectionFactor * reflectionColor;
      } else {
        color = backgroundColor;
      }
    }
  • Bei der Implementierung mit Ray Tracing Shadern sollte die Anzahl der rekursiven Funktionsaufrufe jedoch so gering wie möglich gehalten werden

Reflexionen ohne Rekursion

  • Das gleiche Ergebnis kann auch ohne Rekursion berechnet werden
    trace(ray, &color) { // THIS IS PSEUDOCODE!!!
      nextRay = ray;  
      contribution = 1.0; level = 0;
      while (nextRay && level < maxLevel) {
        if (intersect(nextRay, &hit)) {
          shadow = testShadow(hit);
          directColor = getDirectLight(hit, shadow);
          if (reflectionFactor > 0.0) {
            reflectedRay = reflect(nextRay, hit.normal);
            nextRay = reflectedRay;
          } else {
            nextRay = false;
          }
        } else {
          directColor = backgroundColor;
          nextRay = false;
        }
        color = color + contribution * directColor;
        contribution = contribution * reflectionFactor;
        level = level + 1;
      }
    }

Beispiel: Reflexionen ohne Rekursion

example_reflections

Beispiel: Reflexionen ohne Rekursion

struct RayPayloadType {
  vec3 directLight;
  vec3 nextRayOrigin;
  vec3 nextRayDirection;
  float nextReflectionFactor;
  bool shadowRayMiss;
}; // type of the "payload" variable

...

void main() { /**** RAY GENERATION SHADER ****/

  // compute the texture coordinate for the output image in range [0.0, 1.0]
  vec2 texCoord = (vec2(gl_LaunchIDEXT.xy) + 0.5) / vec2(gl_LaunchSizeEXT.xy);

  // camera parameter
  float aspect = float(gl_LaunchSizeEXT.x) / float(gl_LaunchSizeEXT.y);
  vec3 rayOrigin = camPos;
  vec3 rayDirection = getCameraRayLookAt(20.0, aspect, camPos, 
                                                 camLookAt, camUp, texCoord);
  
  uint rayFlags = gl_RayFlagsNoneEXT; // no ray flags
  float rayMin = 0.001; // minimum ray distance for a hit
  float rayMax = 10000.0; // maximum ray distance for a hit  
  uint cullMask = 0xFFu; // no culling
  
  // init ray and payload
  payload.nextRayOrigin = rayOrigin;
  payload.nextRayDirection = rayDirection;
  payload.nextReflectionFactor = 1.0;
  float contribution = 1.0;
  vec3 color = vec3(0.0, 0.0, 0.0);
  int level = 0;
  const int maxLevel = 5;
  
  // shot rays
  while(length(payload.nextRayDirection) > 0.1 && 
              level < maxLevel && contribution > 0.001) {
    // Submitting the camera ray to the acceleration structure traversal.
    // The last parameter is the index of the "payload" variable (always 0)
    traceRayEXT(topLevelAS, rayFlags, cullMask, 0u, 0u, 0u, 
            payload.nextRayOrigin, rayMin, payload.nextRayDirection, rayMax, 0);
    color += contribution * payload.directLight;
    contribution *= payload.nextReflectionFactor;
    level++;
  }

  gsnSetPixel(vec4(color, 1.0));
}

void  main() { /**** CLOSEST-HIT SHADER ****/
  
  // get mesh vertex data in object space
  vec3 p0, p1, p2;
  gsnGetPositions(gl_InstanceID, gl_PrimitiveID, p0, p1, p2);
  vec3 n0, n1, n2;
  gsnGetNormals(gl_InstanceID, gl_PrimitiveID, n0, n1, n2);
  vec2 t0, t1, t2;
  gsnGetTexCoords(gl_InstanceID, gl_PrimitiveID, t0, t1, t2);

  // interpolate with barycentric coordinate
  vec3 barys = vec3(1.0f - baryCoord.x - baryCoord.y, baryCoord.x, baryCoord.y);
  vec3 localNormal = normalize(n0 * barys.x + n1 * barys.y + n2 * barys.z);
  vec3 localPosition = p0 * barys.x + p1 * barys.y + p2 * barys.z;
  vec2 texCoords = t0 * barys.x + t1 * barys.y + t2 * barys.z;

  // transform to world space
  mat3 normalMat;
  gsnGetNormal3x3Matrix(gl_InstanceID, normalMat);
  vec3 normal = normalize(normalMat * localNormal);
  vec3 position = gl_ObjectToWorldEXT * vec4(localPosition, 1.0);

  vec3 lightDir = normalize(lightPos - position);
  
  // prepare shadow ray
  uint rayFlags = gl_RayFlagsTerminateOnFirstHitEXT | 
                             gl_RayFlagsSkipClosestHitShaderEXT;
  float rayMin     = 0.001;
  float rayMax     = length(lightPos - position);  
  float shadowBias = 0.001;
  uint cullMask = 0xFFu;
  float frontFacing = dot(-gl_WorldRayDirectionEXT, normal);
  vec3 shadowRayOrigin = position + sign(frontFacing) * shadowBias * normal;
  vec3 shadowRayDirection = lightDir;
  payload.shadowRayMiss = false;

  // shot shadow ray
  traceRayEXT(topLevelAS, rayFlags, cullMask, 0u, 0u, 0u, 
         shadowRayOrigin, rayMin, shadowRayDirection, rayMax, 0);
  
  // diffuse shading (direct light)
  vec3 radiance = ambientColor; // ambient term
  if(payload.shadowRayMiss) { // if not in shadow
    float irradiance = max(dot(lightDir, normal), 0.0);
    if(irradiance > 0.0) { // if receives light
      radiance += baseColor * irradiance; // diffuse shading
    }
  }  
  payload.directLight = radiance;
  
  // compute reflected ray (prepare next traceRay)
  float reflectionFactor = 0.25;
  if(reflectionFactor > 0.0) {
    payload.nextRayOrigin = position;
    payload.nextRayDirection = reflect(gl_WorldRayDirectionEXT, normal);
    payload.nextReflectionFactor = reflectionFactor;
  } else {
    // no more reflections
    payload.nextRayOrigin = vec3(0.0, 0.0, 0.0);
    payload.nextRayDirection = vec3(0.0, 0.0, 0.0); 
  }
}

void main() { /**** MISS SHADER ****/
  // set color to black
  payload.directLight = vec3(0.0, 0.0, 0.0);
  // shadow ray has not hit an object
  payload.shadowRayMiss = true;
  // no more reflections
  payload.nextRayOrigin = vec3(0.0, 0.0, 0.0);
  payload.nextRayDirection = vec3(0.0, 0.0, 0.0);
}

Distributed Ray Tracing: Anti-Aliasing

example_antialiasing
  • Um Aliasing durch Unterabtastung zu verhindern, können mehrere Strahlen pro Pixel versendet werden
  • Dabei muss die zufällige Position des Strahls im Pixel eine Gleichverteilung besitzen
  • Durch anschließende Mittelung der Beiträge der Strahlen, kann der richtige Farbwert für den Pixel ermittelt werden
  • Wenn der bisherige Mittelwert im vorangegangenen Bild gespeichert wird, dann kann der neue Mittelwert wie folgt berechnet werden:
    vec4 previousAverage = gsnGetPreviousPixel();
    vec3 newAverage = (previousAverage.rgb * float(frameID) + payload.color) / float(frameID + 1);
    gsnSetPixel(vec4(newAverage, 1.0));

Halton-Sequenz

Abtastpositionen der 2D Halten-Sequenz
halten2d
$y = h_3$
$x = h_2$
  • Um wiederholte Abtastung an gleicher Position zu verhindern, werden in der Praxis gerne Pseudozufallssequenzen, wie beispielsweise die Halton-Sequenz verwendet
  • Halton-Sequenz (in mehreren Dimensionen)
    $\left(h_2(n), h_3(n), h_5(n), h_7(n), \dots, h_{p_i}(n)\right)^\top$
    Dabei ist $p_i$ die $i$-te Primzahl und $h_r(n)$ berechnet sich, indem der Zahlwert von $n$ (zur Basis $r$) am Dezimalpunkt gespiegelt wird
  • Die erzeugten Abtastwerte sind gleichverteilt
  • Beliebige Länge der Sequenz möglich
  • Beispiele:
    $h_2((26)_{10})= h_2((11010)_2) = (0.01011)_2 = 11/32$
    $h_3((19)_{10})= h_3((201)_3)= (0.102)_3=11/27$

Halton-Sequenz

Index $n$ Zahlwert (Basis 2) Gespiegelt $h_2(n)$
110.1 = 1/2 0.5
2100.01 = 1/4 0.25
3110.11 = 3/4 0.75
41000.001 = 1/8 0.125
51010.101 = 1/2 + 1/8 0.625
61100.011 = 1/4 + 1/8 0.375
71110.111 = 1/2 + 1/4 + 1/8 0.875
halten2

Halton-Sequenz

Index $n$ Zahlwert (Basis 3) Gespiegelt $h_3(n)$
110.1 = 1/3 0.333
220.2 = 2/3 0.666
3100.01 = 1/9 0.111
4110.11 = 1/3 + 1/9 0.444
5120.21 = 2/3 + 1/9 0.777
6200.02 = 2/9 0.222
7210.12 = 1/3 + 2/9 0.555
8220.22 = 2/3 + 2/9 0.888
halten2

Hammersley-Sequenz

  • Wenn die Anzahl $N$ der Abtastwert im Vorfeld feststeht, kann auch die Hammersley-Sequenz verwendet werden, bei der die erste Dimension schneller zu berechnen ist
  • Hammersley-Sequenz (in mehreren Dimensionen)
    $\left(\frac{n}{N}, h_2(n), h_3(n), h_5(n), h_7(n), \dots, h_{p_i}(n)\right)^\top$
hammersley2d
$y = h_2$
$x = \frac{n}{N}$

Distributed Ray Tracing: Anti-Aliasing

example_antialiasing

Distributed Ray Tracing: Soft Shadows

softshadow
Flächenlichtquelle
Kernschatten
(Umbra)
Halbschatten
(Penumbra)
Halbschatten
(Penumbra)
Kamera
$\frac{5}{8}$
  • Distributed Ray Tracing (Cook et al., Siggraph 1984) eignet sich nicht nur für Anti-Aliasing, auch weiche Schatten können damit erzeugt werden
  • Dazu wird die Position auf einer Flächenlichtquelle zufällig variiert (gleichverteilt)
  • Weitere Anwendungen: Glanz, Bewegungsunschärfe, Tiefenunschärfe

Distributed Ray Tracing: Soft Shadows

example_softshadows

Path Tracing

path_tracing
  • Würde Distributed Ray Tracing auch für indirektes Licht angewendet werden (z.B. für indirekte diffuse Reflexion) führt dies schnell zu Problemen, da die Anzahl der Strahlen exponentiell wächst
    • $N$ Strahlen für 1. Indirektion
    • $N^2$ Strahlen für 2.Indirektion
    • $N^3$ Strahlen für 3. Indirektion usw.
  • Die Indirektionen höhere Grades haben eigentlich einen geringeren Beitrag zum Bild, bekommen aber deutlich mehr Strahlen als die niedrigeren Indirektionen
  • Lösung: Path Tracing (Kajiya, Siggraph 1986)
    • Bei jedem Schnittpunkt aus alle möglichen Strahlen immer nur 1 Strahl auswählen und weiterverfolgen. Es ergibt sich 1 Pfad pro Primärstrahl
    • $N$ verschiedene Pfade pro Pixel auswerten und Mittelwert pro Pixel bilden
    • Vorteil: Gleicher Aufwand für alle Indirektionen

Path Tracing

path_tracing
  • In einem vereinfachtem Modell nehmen wir zunächst an, die Strahldichte an einem Punkt ist die Summe aus dem direkten Anteil und dem
    • Anteil für indirekte diffuse Reflexion (in alle Richtungen)
    • Anteil für ideale Reflexion (Spiegelung)
    • Anteil für ideale Refraktion (Brechung)
  • D.h. es wird entweder der Strahl für die indirekte diffuse Reflexion, ideale Reflexion oder ideale Refraktion weiterverfolgt
  • Später werden wir natürlich noch bessere physikalische Modelle behandeln (Rendering Gleichung, BRDF, Fresnel, ...)

Path Tracing: Cornell Box

example_pathtracing.png
Beispiel: Path Tracing
example_path_tracing_cycles_256
Referenz: Blender Cycles
  • Die Ray Tracing Shader Implementierung (links) und die Blender/Cycles Referenz (rechts) verwenden beide 256 Samples per Pixel
  • Beide Renderings stimmen einigermaßen überein. In der Cycles Version hat die Kugel zusätzlich einen reflektiven Anteil.

Gibt es Fragen?

questions

Anregungen oder Verbesserungsvorschläge können auch gerne per E-Mail an mich gesendet werden: Kontakt

Weitere Vorlesungsfolien

Folien auf Englisch (Slides in English)