Joe Subbiani Exploring Code and Four Dimensions

4D SDFs: Building 4D Shapes with Ray Marching


Ray Marching

Ray marching is the process of casting a ray in the direction of a pixel, calculating the distance to the nearest surface, and marching forward. By calculating the distance to the nearest surface, and using some extra logic, each pixel of an image can be coloured according to the scene defined by a signed distance function.

ray-march-algorithm ray-march-render

For more information on ray marching, check out The Art of Code on youtube, and Inigo Quilez's blog covering a huge range of SDFs and other functions used in ray marching.


Signed Distance Functions (SDFs) for 4D Shapes

Check out my 4D SDFs written in HLSL here.

Sphere

A sphere is defined purely by a radius subtracted from its origin point

float sdSphere(float4 p, float r)
{
    float d = length(p) - r;
    return d;
}

Box

The box is defined by its hight/width along each of the 4D axes. This SDF does not utilise an interior distance, and therefore can occasionally produce a glitchy surface. This can be avoided by giving the box thickness by subtracting a small value. Note: subtracting larger values will produce rounded edges.

float sdBox(float4 p, float4 s)
{
    float d = length(max(abs(p) - s, 0));
    return d;
}

Torus (2 Radii)

A torus with with two radii, r1r1 and r2r2, defines a circular path with radius r1r1, with a thickness in all dimensions defined by radius r2r2. The cross section of this may appear as two spheres

float sdTorus(float4 p, float r1, float r2)
{
    float d = length(
                float2(
                    length( 
                    float2(length(p.zx) - r1, p.y)
                            ), p.w
                    )) - r2;
    return d;
}

Torus (3 Radii)

A torus with with three radii r1r1, r2r2 and r3r3, defines a major circular path with radius r1r1. A second minor circular path defined by r2r2 follows the major path. This minor path is then given a thickness defined by radius r3r3. The cross section of this may appear as two tori.

float sdTorus(float4 p, float r1, float r2, float r3)
{
    float d = length(
                float2(
                    length( 
                    float2(length(p.zx) - r1, p.y)
                            ) - r2, p.w
                    )) - r3;
    return d;
}

Cone (Extended along w)

The surface of the cone is calculated as a function of its height and radius at the base of the cone.

float sdConeW(float4 p, float r, float h)
{
    float2 q = float2(length(p.xyz), p.w);
    float2 tip = q - float2(0, h);
    float2 mantleDir = normalize(float2(h, r));
    float mantle = dot(tip, mantleDir);
    float d = max(mantle, -q.y);
    float projected = dot(tip, float2(mantleDir.y, -mantleDir.x));

    // distance to tip
    if ((q.y > h) && (projected < 0)) {
        d = max(d, length(tip));
    }

    // distance to base ring
    if ((q.x > r) && (projected > length(float2(h, r)))) {
        d = max(d, length(q - float2(r, 0)));
    }
    return d;
}

Capsule

A capsule is defined as the path between two points; aa and bb. This path is then given thickness. The capsule can be plotted manually, or a helper function can be defined for simpler use in code.

float sdCapsule( float4 p, float4 a, float4 b, float r )
{
    float4 pa = p - a, ba = b - a;
    float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
    return length( pa - ba*h ) - r;
}

float sdCapsuleW(float4 p, float length, float r)
{
    float l = length/2;

    return sdCapsule(p, 
                     float4(0, 0, 0,-l),
                     float4(0, 0, 0, l),
                     r
                    );
}

Pentachoron (Hyper Tetrahedron)

A plane is defined at each vertex of the pentachoron, and the boolean intersection of all these planes is accumulated. This produces a hyper tetrahedron which can be scaled using the parameter ss, in the same way a sphere is defined by subtracting the radius.

float sdPentachoron(float4 p, float s){
    float a = +p.x +p.y -p.z -p.w;
    float b = -p.x -p.y -p.z -p.w;
    float c = +p.x -p.y +p.z -p.w;
    float d = -p.x +p.y +p.z -p.w;
    float e = p.w;
    return (max(max(max(a,b),max(c,d)), e)-s)/sqrt(5.);
}