Image Synthesis
Vulkan Ray Tracing Pipeline
Thorsten Thormählen
April 16, 2024
Part 2, Chapter 1
Thorsten Thormählen
April 16, 2024
Part 2, Chapter 1
This is the print version of the slides.
Advance slides with the → key or
by clicking on the right border of the slide
Slides can also be advanced by clicking on the left or right border of the slide.
| Type | Font | Examples |
|---|---|---|
| Variables (scalars) | italics | $a, b, x, y$ |
| Functions | upright | $\mathrm{f}, \mathrm{g}(x), \mathrm{max}(x)$ |
| Vectors | bold, elements row-wise | $\mathbf{a}, \mathbf{b}= \begin{pmatrix}x\\y\end{pmatrix} = (x, y)^\top,$ $\mathbf{B}=(x, y, z)^\top$ |
| Matrices | Typewriter | $\mathtt{A}, \mathtt{B}= \begin{bmatrix}a & b\\c & d\end{bmatrix}$ |
| Sets | calligraphic | $\mathcal{A}, B=\{a, b\}, b \in \mathcal{B}$ |
| Number systems, Coordinate spaces | double-struck | $\mathbb{N}, \mathbb{Z}, \mathbb{R}^2, \mathbb{R}^3$ |
The ray tracing pipeline consists of 5 different shaders:
traceRayEXT(...)traceRayEXT function is the payload variable that
contains the collected information of the raypayload variable is of user-defined type and can be modified in the shaders stages that are called for a particular
ray during its traversaltraceRayEXT function returns to the caller and the
payload variable can be evaluated in the ray generation shader to produce an output imagereportIntersectionEXT(...)hitAttributeEXT variable (which can be of user-defined type).hitAttributeEXT vec2 baryCoord" variableignoreIntersectionEXT statementterminateRayEXT statementignoreIntersectionEXT statement
is not called in the shader, the hit is reported to the ray traversaltraceRayEXT function, which submits another ray into the
ray traversal block and might create a recursion | 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 |
✓ | ✓ | ✓ |
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; |
gl_ObjectToWorldEXTgl_WorldToObjectEXTgl_InstanceID = 0gl_InstanceID = 1gl_InstanceID = 2gl_InstanceIDgl_PrimitiveIDgl_ObjectToWorldEXT and
gl_WorldToObjectEXT variables
$\frac{f}{1} = \frac{\cos( 0.5 \, \Theta)}{\sin( 0.5 \, \Theta)} \Leftrightarrow f = \mathrm{cotan}( 0.5 \, \Theta)$
// 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);
...
traceRayEXT(...) function in the ray generation shaderpayload.color variable is set to red.
Otherwise, if no hits occur, the miss shader is called and the payload.color variable is set to black.traceRayEXT function
returns to the calling ray generation shader and the modified payload.color variable
is written to the output image
gl_InstanceID,
gl_ObjectToWorldEXT, and gl_PrimitiveID
are set accordingly and allow to identify the BLAS instance, its transformation, and the primitive (i.e., in this case, the triangle) of the closest hit location
gl_InstanceID and gl_PrimitiveID
variables as input parameters and return the local vertex positions, local normals, and texture coordinates for
the three vertices of the triangle that were hitvec2 baryCoord variable
traceRayEXT function to submit a ray into the
acceleration structure traversal traceRayEXT to send a shadow ray in the direction of the light source.
gl_RayFlagsSkipClosestHitShaderEXTgl_RayFlagsTerminateOnFirstHitEXT flag.payload.shadowRayMiss variable from false to truetraceRayEXT function returns to the emitting closest-hit shader,
this variable can be checked to determine if the surface point is in shadow or not
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;
}
reflect to calculate the reflection direction:
vec3 r_out = reflect(r_in, normal);
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; } }
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;
}
}
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);
}
vec4 previousAverage = gsnGetPreviousPixel(); vec3 newAverage = (previousAverage.rgb * float(frameID) + payload.color) / float(frameID + 1); gsnSetPixel(vec4(newAverage, 1.0));
| Index $n$ | Numerical value (Base 2) | Mirrored | $h_2(n)$ |
|---|---|---|---|
| 1 | 1 | 0.1 = 1/2 | 0.5 |
| 2 | 10 | 0.01 = 1/4 | 0.25 |
| 3 | 11 | 0.11 = 3/4 | 0.75 |
| 4 | 100 | 0.001 = 1/8 | 0.125 |
| 5 | 101 | 0.101 = 1/2 + 1/8 | 0.625 |
| 6 | 110 | 0.011 = 1/4 + 1/8 | 0.375 |
| 7 | 111 | 0.111 = 1/2 + 1/4 + 1/8 | 0.875 |
| Index $n$ | Numerical value (Base 3) | Mirrored | $h_3(n)$ |
|---|---|---|---|
| 1 | 1 | 0.1 = 1/3 | 0.333 |
| 2 | 2 | 0.2 = 2/3 | 0.666 |
| 3 | 10 | 0.01 = 1/9 | 0.111 |
| 4 | 11 | 0.11 = 1/3 + 1/9 | 0.444 |
| 5 | 12 | 0.21 = 2/3 + 1/9 | 0.777 |
| 6 | 20 | 0.02 = 2/9 | 0.222 |
| 7 | 21 | 0.12 = 1/3 + 2/9 | 0.555 |
| 8 | 22 | 0.22 = 2/3 + 2/9 | 0.888 |
Please notify me by e-mail if you have questions, suggestions for improvement, or found typos: Contact