Project Details

  • Weekend project
  • Our own engine
  • C++ and DirectX 11

Purpose

The Demoscene is something that I’ve been interested in for a long time. That’s why I wanted to write my own sphere tracer to render environments using signed distance fields, because I think it’s cool and really fun! I made an implementation in WebGL a couple of years ago, but I wasn’t happy with the results cause it couldn’t perform as well as a native application, so that’s also a reason for why I wanted to redo it.

Deferred Pipeline

Usually, when people create these kinds of things, they bake the shading and lighting into the same pass as the ray marching. But in my case I simply output data to a GBuffer, since I already had a functioning Deferred Renderer with PBR. This was great cause it makes my Sphere Tracer simple and easy to work with, all it has to do is output data, and not worry about shading.

What’s great about this is that the things that I render with my Sphere Tracer will have the exact same shading and lighting as models created by artists.

Depth

Since the ray marching takes place on a full screen quad, it cannot really be rendered in a scene with normal models. To solve this, I wait until the ray marching stage is finished, then I manually output the depth value to the depth stencil using the system-value semantic SV_DEPTH. This let me render ray marched scenes within scenes containing models, which is pretty awesome!

Signed Distance Fields

When our ray is marching through space, it needs to know how close to a surface it is, the ray will then march forward with that same distance. This makes sure the ray never goes through a surface.

The distance to a surface is calculated with so called Signed Distance Functions, which are functions that take a point in space and determine the distance to the surface.

Mercury is a very popular group in the demoscene and they’ve made some of my favourite demos. They’ve posted all their Signed Distance Functions here with very well written comments.

Distance Functions

Distance Functions are all of the functions used to calculate distances to primitive types such as:

  • Sphere
  • Box
  • Capsule
  • Torus
  • And more…

You can then create your own Distance Functions that are combinations of these to create new shapes.

Domain Manipulation

You can manipulate the domain in many different ways, some operations you can do are:

  • Rotation
  • Translation
  • Repetition
    • Using the Modulo operation, you can repeat your domain a fixed number of times or infinitely. This is one of the coolest things about ray marching, in my opinion, cause you can easily create infinite worlds with variation.

Boolean Operations

There are all of the default boolean operations such as Union, Intersection and Difference.

But you can also create some really cool boolean operations if you manipulate the distance field in 2D space. For example, you can create smooth edges, chamfered edges, or edges built out of columns, they can all be seen in the gif on this page.

Ray Marching

The way the ray marching is implemented is very simple, I have a starting point which is the camera position, and a different direction per pixel for the ray direction.

The ray then marches along this direction with a variable step size until it hits something.

This is where the name Sphere Tracing comes from. When the ray is marching, it samples the distance field and gets a distance to the closest surface from its current point in space. This can be seen as a sphere with the center point at the current point of ray marching and the radius being the distance to the surface. The closest surface in space will touch the surface of this imaginary sphere.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float t = 0.0f;

for(int i = 0; i < globalNumSteps; ++i)
{
	float3 p = startPosition + rayDirection * t;

	float d = SceneDistance(p);

	if(abs(d) < globalEpsilon * (t * 0.125f + 1.f) || t >= globalFar)
	{
		return;
	}

	t += d * 0.75f;
}

Further Improvements

In the future, I would like to handle materials in a better way, to make it easier to define materials for different objects, perhaps with some nice interface. This would put more focus on design and creativity instead of having to write tedious code.

Screenshot