0% found this document useful (0 votes)
17 views23 pages

GPU Ray Marching

Uploaded by

huwkigane
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views23 pages

GPU Ray Marching

Uploaded by

huwkigane
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 23

Advanced Graphics

GPU Ray Marching


Alex Benton, University of Cambridge – [email protected]
1
Supported in part by Google UK, Ltd
GPU Ray-tracing
Ray tracing 101: “Choose the color of
the pixel by firing a ray through and
seeing what it hits.”

Ray tracing 102:


“Let the pixel make up
its own mind.”

2
GPU Ray-tracing
1. Use a minimal fragment shader
(no transforms)
2. Set up OpenGL with minimal
geometry, a single quad
3. Bind a vec2 to each vertex
specifying ‘texture’ coordinates
4. Implement raytracing in GLSL
per pixel:
a. For each pixel, compute the ray
from the eye through the pixel,
using the interpolated texture
coordinate to identify the pixel
b. Run the ray tracing algorithm
for every ray

3
GPU Ray-tracing
// Window dimensions vec3 getRayDir(
uniform vec2 iResolution; vec3 camDir,
vec3 camUp,
// Camera position vec2 texCoord) {
uniform vec3 iRayOrigin; vec3 camSide = normalize(
cross(camDir, camUp));
// Camera facing direction vec2 p = 2.0 * texCoord - 1.0;
uniform vec3 iRayDir; p.x *= iResolution.x
/ iResolution.y;
// Camera up direction return normalize(
uniform vec3 iRayUp; p.x * camSide
+ p.y * camUp
// Distance to viewing plane + iPlaneDist * camDir);
uniform float iPlaneDist; }

// ‘Texture’ coordinate of each


// vertex, interpolated across
// fragments (0,0) → (1,1)
in vec2 texCoord;

4
GPU Ray-tracing
Hit traceSphere(vec3 rayorig, vec3 raydir, vec3 pos, float radius) {
float OdotD = dot(rayorig - pos, raydir);
float OdotO = dot(rayorig - pos, rayorig - pos);
float base = OdotD * OdotD - OdotO + radius * radius;

if (base >= 0) {
float root = sqrt(base);
float t1 = -OdotD + root;
float t2 = -OdotD - root;
if (t1 >= 0 || t2 >= 0) {
float t = (t1 < t2 && t1 >= 0) ? t1 : t2;
vec3 pt = rayorig + raydir * t;
vec3 normal = normalize(pt - pos);
return Hit(pt, normal, t);
}
}
return Hit(vec3(0), vec3(0), -1);
}

5
An alternative to raytracing:
Ray-marching
An alternative to classic ray-tracing is
ray-marching, in which we take a
series of finite steps along the ray until
we strike an object or exceed the
number of permitted steps.
● Also sometimes called ray casting
● Scene objects only need to answer,
“has this ray hit you? y/n”
● Great solution for data like height fields
● Unfortunately…
○ often involves many steps
○ too large a step size can lead to lost
intersections (step over the object)
○ an if() test in the heart of a for() loop
is very hard for the GPU to optimize

6
GPU Ray-marching:
Signed Distance Fields
Ray-marching can be dramatically
improved, to impressive realtime
GPU performance, using signed
distance fields:
1. Fire ray into scene
2. At each step, measure distance field
function: d(p) = [distance to nearest
object in scene]
3. Advance ray along ray heading by
distance d, because the nearest
intersection can be no closer than d

This is also sometimes called ‘sphere tracing’. Early paper:


http://graphics.cs.illinois.edu/sites/default/files/rtqjs.pdf

7
Signed distance functions
An SDF returns the minimum possible float sphere(vec3 p, float r) {
distance from point p to the surface return length(p) - r;
}
it describes.
The sphere, for instance, is the distance float cube(vec3 p, vec3 dim) {
from p to the center of the sphere, vec3 d = abs(p) - dim;
minus the radius. return min(max(d.x,
max(d.y, d.z)), 0.0)
Negative values indicate a sample + length(max(d, 0.0));
inside the surface, and still express }
absolute distance to the surface.
float cylinder(vec3 p, vec3 dim)
{
return length(p.xz - dim.xy)
- dim.z;
}

float torus(vec3 p, vec2 t) {


vec2 q = vec2(
length(p.xz) - t.x, p.y);
return length(q) - t.y;
}
https://www.scratchapixel.com/lessons/advanced-rendering/rendering-distance-fields 8
Raymarching signed distance fields
vec3 raymarch(vec3 pos, vec3 raydir) {
int step = 0;
float d = getSdf(pos);

while (abs(d) > 0.001 && step < 50) {


pos = pos + raydir * d;
d = getSdf(pos); // Return sphere(pos) or any other
step++;
}

return
(step < 50) ? illuminate(pos, rayorig) : background;
}

9
Visualizing step count
Final image Distance field

Brighter = more steps, up to 50

10
Combining SDFs
We combine SDF models by choosing
which is closer to the sampled point.
● Take the union of two SDFs by
taking the min() of their
functions.
● Take the intersection of two
SDFs by taking the max() of their
functions.
● The max() of function A and the
negative of function B will return
the difference of A - B.
By combining these binary operations
we can create functions which describe
very complex primitives.

11
Combining SDFs
min(A, B)
(union)

max(-A, B)
(difference)

max(A, B)
(intersection)

12
Blending SDFs
Taking the min(), max(), etc of two SDFs yields a
sharp discontinuity. Interpolating the two SDFs with
a smooth polynomial yields a smooth distance curve,
blending the models:

Sample blending function (Quilez)


float blend(float a, float b, float k) {
a = pow(a, k);
b = pow(b, k);
return pow((a * b) / (a + b), 1.0 / k);
}

13
Transforming SDF geometry
To rotate, translate or scale an SDF model, apply the inverse transform to the
input point within your distance function.
Ex:
float sphere(vec3 pt, float radius) {
return length(pt) - radius;
}

float f(vec3 pt) {


return sphere(pt - vec3(0, 3, 0));
}

This renders a sphere centered at (0, 3, 0).


More prosaically, assemble your local-to-world transform as usual, but apply its
inverse to the pt within your distance function.

14
Transforming SDF geometry
float fScene(vec3 pt) {

// Scale 2x along X
mat4 S = mat4(
vec4(2, 0, 0, 0),
vec4(0, 1, 0, 0),
vec4(0, 0, 1, 0),
vec4(0, 0, 0, 1));

// Rotation in XY
float t = sin(time) * PI / 4;
mat4 R = mat4(
vec4(cos(t), sin(t), 0, 0),
vec4(-sin(t), cos(t), 0, 0),
vec4(0, 0, 1, 0),
vec4(0, 0, 0, 1));

// Translate to (3, 3, 3)
mat4 T = mat4(
vec4(1, 0, 0, 3),
vec4(0, 1, 0, 3),
vec4(0, 0, 1, 3),
vec4(0, 0, 0, 1));

pt = (vec4(pt, 1) * inverse(S * R * T)).xyz;

return sdSphere(pt, 1);


}

15
Transforming SDF geometry
The previous example modified ‘all
of space’ with the same transform,
so its distance functions retain
their local linearity.
We can also apply non-uniform
spatial distortion, such as by
choosing how much we’ll modify
space as a function of where in
space we are.

float fScene(vec3 pt) {


pt.y -= 1;
float t = (pt.y + 2.5) * sin(time);
return sdCube(vec3(
pt.x * cos(t) - pt.z * sin(t),
pt.y / 2,
pt.x * sin(t) + pt.z * cos(t)), vec3(1));
}

16
Find the normal to an SDF
Finding the normal: local gradient
float d = getSdf(pt);
vec3 normal = normalize(vec3(
getSdf(vec3(pt.x + 0.0001, pt.y, pt.z)) - d,
getSdf(vec3(pt.x, pt.y + 0.0001, pt.z)) - d,
getSdf(vec3(pt.x, pt.y, pt.z + 0.0001)) - d));

The distance function is locally linear and


changes most as the sample moves directly
away from the surface. At the surface, the
direction of greatest change is therefore
equivalent to the normal to the surface.
Thus the local gradient (the normal) can be
approximated from the distance function.

17
SDF shadows
Ray-marched shadows are
straightforward: march a ray
towards each light source, don’t
illuminate if the SDF ever drops
too close to zero.
Unlike ray-tracing, soft shadows are
almost free with SDFs: attenuate
illumination by a linear function of
the ray marching near to another
object.

18
Soft SDF shadows
float shadow(vec3 pt) {
vec3 lightDir = normalize(lightPos - pt);
float kd = 1;
int step = 0;

for (float t = 0.1;


t < length(lightPos - pt)
&& step < renderDepth && kd > 0.001; ) {
float d = abs(getSDF(pt + t * lightDir));
if (d < 0.001) {
kd = 0;
} else {
kd = min(kd, 16 * d / t);
}
t += d;
By dividing d by t, we
step++;
attenuate the strength
}
of the shadow as its
return kd;
source is further from
}
the illuminated point.

19
Repeating SDF geometry
If we take the modulus of a point’s
position along one or more axes
before computing its signed
distance, then we segment space
into infinite parallel regions of
repeated distance. Space near the
origin ‘repeats’.
With SDFs we get infinite repetition
of geometry for no extra cost.

float fScene(vec3 pt) {


vec3 pos;
pos = vec3( mod(pt.x + 2, 4) - 2, pt.y, mod(pt.z + 2, 4) - 2);
return sdCube(pos, vec3(1));
}

20
Repeating SDF geometry
float sphere(vec3 pt, float radius) {
return length(pt) - radius;
}

● sdSphere(4, 4)
= √(4*4+4*4) - 1
= ~4.5
● sdSphere(
((4 + 2) % 4) - 2, 4)
= √(0*0+4*4) - 1
= 3
● sdSphere(
((4 + 2) % 4) - 2,
((4 + 2) % 4) - 2)
= √(0*0+0*0) - 1
= -1 // Inside surface

21
SDF - Live demo

22
Recommended reading
Seminal papers:
● John C. Hart et al., “Ray Tracing Deterministic 3-D Fractals”,
http://graphics.cs.illinois.edu/sites/default/files/rtqjs.pdf
● John C. Hart, “Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit
Surfaces”, http://graphics.cs.illinois.edu/papers/zeno

Special kudos to Inigo Quilez and his amazing blog:


● http://iquilezles.org/www/articles/smin/smin.htm
● http://iquilezles.org/www/articles/distfunctions/distfunctions.htm

Other useful sources:


● Johann Korndorfer, “How to Create Content with Signed Distance Functions”,
https://www.youtube.com/watch?v=s8nFqwOho-s
● Daniel Wright, “Dynamic Occlusion with Signed Distance Fields”,
http://advances.realtimerendering.com/s2015/DynamicOcclusionWithSignedDistanceFields.pdf
● 9bit Science, “Raymarching Distance Fields”,
http://9bitscience.blogspot.co.uk/2013/07/raymarching-distance-fields_14.html

23

You might also like