Signed Distance Fields – Part 1

It’s been too long! Here’s an effect I was taking a look at a few months ago.

So there is this cool technique that had gained significant popularity in the demoscene called “Signed Distance Fields”. There’s a truly excellent presentation by iq of rgba (Iñigo Quilez) posted on his website which he presented at nvscene back in 2008 called “Rendering Worlds with Two Triangles”. I wanted to play around with some GLSL and thought this would be a really interesting algorithm to take a look at. You can see some of the power of these types of functions in a presentation that smash of fairlight (Matt Swaboda) gave at GDC earlier this year

So here’s an extremely basic GLSL shader I made to learn exactly how the distance fields work. A frequent question when working with signed distance fields, is why march through the scene if you already know the exact distance from your viewpoint to the surface of an object? Well, if you consider the path of a ray fired into a scene, in order to determine the intersection point of that that ray with the implicitly defined surface of one out of many objects, a great deal of math would be involved to determine the exact intersection point; too much for a real time application. The signed distance fields are a way to describe geometry by providing a distance from a given point in 3d space for the entire scene. Combined with ray marching, we can start at the camera and step at least the distance to the nearest surface, but in the direction of the ray. If the ray pointed directly into that nearest surface, the ray would return the intersection point in 3D space. Otherwise, we can run the equation again given a point in 3D space that is that distance along the ray to find a value for the nearest surface. March again, test for intersection, return if we intersected, otherwise continue marching.

To really solidify the algorithm in my brain, I wrote up this little shader. The only input I use is the window dimensions, which are only used for coloring. I hope to soon add shadow computations to provide a true 3D look.


// Distance functions from:

uniform vec2 resolution;

vec3 translate(in vec3 pos, in vec3 translate)
    mat4 transform = mat4(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);

    transform[3][0] = translate.x;
    transform[3][1] = translate.y;
    transform[3][2] = translate.z;

    return vec3(inverse(transform) * vec4(pos,1));

vec3 scale(in vec3 pos, in float scale)
    mat4 transform = mat4(scale,0,0,0,0,scale,0,0,0,0,scale,0,0,0,0,scale);

    return vec3(inverse(transform) * vec4(pos,1));

vec3 repeat(in vec3 pos, in vec3 repeat)
    return mod(pos, repeat) - 0.5 * repeat;

float sdSphere(in vec3 pos, in float scale)
    return length(pos) - scale;

float udBox(in vec3 pos, in vec3 corner)
    return length(max(abs(pos) - corner, 0.0));

float raymarch(in vec3 ro, in vec3 rd)
    float epsilon = 0.0001;
    float farplane = 30;
    vec3 raypos = 0;
    vec3 boxCorner = vec3(1.0, 0.2, 0.7);
    float sphereScale = 0.7;
    vec3 repeatDelta = vec3(5, 5, 5);
    for (float t = 0; t < farplane;)
        raypos = ro + t*rd;
        float h = min(  udBox(repeat(translate(raypos, vec3(-0.5, 0.5, 0)), repeatDelta), boxCorner),
                        sdSphere(repeat(raypos, repeatDelta), sphereScale)); // distance to nearest object
        t += h;
        if (h < epsilon)
            return t;
    return -1.0;

void main()
    // normalize pixel position
    vec2 uv = gl_FragCoord.xy/resolution.xy;

    // generate a ray with origin ro and direction rd
    vec3 ro = vec3(0.0, 0.0, 2.0);
    float aspect = 1.6; // resolution.x/resolution.y
    vec3 rd = normalize(vec3((-1.0+2.0*uv)*vec2(aspect,1.0), -1.0));

    // fire the ray and intersect with the scene
    float t = raymarch(ro, rd);

    // draw black by default
    vec3 col = vec3(0.0);
    if (t > 0.0)
        // if intersection, draw color
        col = vec3(uv.x, 0, uv.y);

    gl_FragColor = vec4(col, 1.0);

Leave a Comment

Tags: ,

November 16, 2012 Graphics