If You Know the Distance, You Can Draw It
In one line
If you have a function that tells you the shortest distance from a point to the surface, you can find the surface by advancing a ray safely by that distance each time. You need neither triangles nor a vertex buffer.
Why this was needed
Rasterization presupposes triangles. To change a shape you have to rebuild the vertices, and merging or subtracting two shapes is a geometric operation and therefore tricky. To express two drops melting together smoothly with triangles, you would have to rebuild the mesh every frame.
A distance function expresses a shape as a formula. A sphere is the single line length(p) - r, and the union of two shapes is the single line min(a, b). Joining them smoothly takes only a few lines too. There is no data, so it hardly uses any memory, and changing a shape is simply changing a formula.
How it works
A signed distance function (SDF) takes a point and returns the shortest distance from that point to the surface. It is negative inside, positive outside, and 0 on the surface.
구 length(p) - r
평면 p.y - h
상자 q = abs(p) - b
length(max(q, 0)) + min(max(q.x, q.y, q.z), 0)
The key is that this value is a safe distance to advance. If the nearest surface from the current position is d away, then in whatever direction you go, you will meet nothing for d. So even if you jump the ray ahead by d, you cannot pass the surface. Repeating this is sphere tracing, often called ray marching.
t = 0
반복:
d = scene(ro + rd * t)
if d < eps: 맞았다
t += d
if t > 최대거리: 빗나갔다
In empty space it jumps far, and near a surface it approaches a little at a time. That is why a ray that grazes the surface is the most expensive — the distance stays small, so the steps are cut fine. If you paint the iteration count as a color, you get a picture where the edge of the silhouette glows brightly, which is a good way to see a performance problem with your own eyes.
Combining shapes is also done with functions.
합집합 min(a, b)
교집합 max(a, b)
차집합 max(a, -b) # a 에서 b 를 파낸다
A smooth union blends the two distances.
h = clamp(0.5 + 0.5 * (b - a) / k, 0, 1)
mix(b, a, h) - k * h * (1 - h)
The last term swells the joined part to make a smooth connection. The larger k is, the more widely they melt together.
The normal is not stored separately but found from the gradient of the distance function. You move a tiny bit along each axis and look at the difference in distance. By definition, the gradient of a distance function is the direction of steepest increase in distance, that is, the outward direction of the surface.
Shadows also take just one ray. From the hit point you march again toward the light, and if it hits something, it is in shade. All the problems you run into with rasterization, baking a shadow map and transforming coordinates, are absent here. In exchange, you shoot two rays per pixel on the screen.
The nature of the performance is also different from rasterization. The cost of rasterization attaches to the number of triangles and the number of pixels covered, but the cost of ray marching attaches to the number of steps per pixel. So if the screen resolution stays the same, the cost does not grow much however complex you make the shape, whereas doubling the resolution quadruples it. When the shape gets more complex, it slows down only when the distance function itself becomes heavy.
There is also an interesting property called domain repetition. If you apply a modulo operation to the point's coordinates and pass it to the distance function, a scene where the same shape repeats infinitely is made with a single line of function. It uses no extra memory at all. However, the distance function can then become larger than the real distance, so a correction that accounts for both the repetition period and the size of the shape is needed.
What it looks like in the field
This technique grew up in demoscene shaders that draw a whole scene with a single shader and in Shadertoy culture, and it is now used in practice as well. Examples are volume effects like clouds and smoke, effects of objects melting together, and SDF textures that draw fonts and icons sharply regardless of resolution.
The thing to watch out for is that the distance function must not be larger than the real distance. If you make it larger, the ray jumps over the surface and holes appear. So when stretching a shape non-uniformly or repeating it, you need a correction that shrinks the distance conservatively.
Finally, let me write down how it mixes with rasterization. In practice it is rare to draw a whole scene with ray marching; usually you rasterize one quadrilateral that covers the screen and then run the marching loop inside its fragment shader. Then you can write the result into the depth buffer and blend naturally with objects drawn with triangles. Volume effects like clouds and smoke are made this way — the shape with a distance function, the rest of the scene with triangles, and the two combined by depth.
What you will do in the next lab
You build a few distance functions and combination operations, and find surfaces with sphere tracing. After drawing only the silhouette, you add shading with gradient normals, and draw a shape made by combining intersection, subtraction and smooth union. Finally, you put in a floor plane and shoot shadow rays to produce a picture of a ball casting a shadow on the floor.