understanding SDF[Signed Distance Functions] for a torus

This topic would contain a lot of sub questions because I have one for each shape but for today I just want to understand the function for a torus and a capped torus

This is the article for all shapes and I am trying to understand the code for torus

float sdTorus( vec3 p, vec2 t )
{
vec2 q = vec2(length(p.xz)-
t.x,p.y);
return length(q)-t.y;
}

and capped torus

float sdCappedTorus(in vec3 p,
in vec2 sc, in float ra, in
float rb)
{
p.x = abs(p.x);

float k = (sc.y*p.x>sc.x*p.y) ?
dot(p.xy,sc) : length(p.xy);

return sqrt( dot(p,p) + ra*ra
- 2.0*ra*k ) - rb;
}

I have so many questions. I know parameter P is the point for testing but what is t? what is ra and rb? what calculations are they doing what are the mathematics?

Just a theoretical explanation or some images explaining the general formula would be greatly appreciated.

This turned into a rabbit hole for me hopefully it helps answer your question, I recommend trying out the various functions on the ShaderToy website. Just drop them in and change the call to "sdCircle". I used the values R=0.75 and r=0.35 to test them out.

Part 1: Defining what an SDF means

Signed Distance Field or SDF, in this context, is a function that returns the signed distance from a point to a primitive. The returned value is always a float that can take on 3 context specific meanings.

• zero: This is on the surface, while it is rare to hit exactly on the surface of the primitive we can approximate a hit with a simple inequality like abs(d)<sigma where d is the signed distance and sigma is some small value like 0.001.
• negative: In this context negative values represent hits inside the primitive but choosing the meaning of a negative return value is context specific. Smaller (more negative) values represent values that are deeper in the interior of the primitive.
• positive: Again this is context specific, but in this case this is a hit outside the primitive and larger values mean it is farther away from the primitive.

Stop for a second and take another look at the first case, "zero". This may seem unimportant at first glance but it turns out that by defining a generic base primitive and then changing the value of sigma we can easily compute a primitive of any size. In the case of a torus we could have a very thin torus or one that is so big it envelopes itself. Then by adding one more variable representing the large Radius of the torus, the torus can now be changed to fit our needs as desired just by controlling two variables.

Part 2: Defining the primitive

Take a look at IQ's 2D function for a circle.

float sdCircle( vec2 p, float r ){ return length(p) - r; }

All this function is doing is computing the "distance" using the length function of our point "p" from our circle (which is sitting at the origin) then subtracting our radius "r". And easy as that any point greater then radius is positive, any point less then radius is negative.

This is almost a torus already, we are just missing the signed distance for the inside diameter of the 2D torus. But we already know that is really just some sigma value from our original circle, so all we have to do is add a second radius and compute the sigma. So:

float sdTorus2D( vec2 p, float R, float r )
{
float q = length(p)-R;
return abs(q)-r;
}

No need to recalculate the distance, so just figure out sigma to our second circle with abs(q)-r.

Part 3: Going 3D

If you stop and think about it for a second (ok...maybe more then a second) then it is easy to realize that our 2D torus is just a slice of a 3D torus. When you think about it like that then we can take another slice of our torus in 2D on its edge just by rotating our torus 90 degrees. This should result in two SDF circles side by side.

But turning the torus on its side is really just a matter of computing our signed distances in the opposite order...lets try:

float sdTorus2DSide( vec2 p, float R, float r )
{
vec2 q = vec2(abs(p.x)-R,p.y);
return length(q)-r;
}

The two different views essentially compute the distance in two different ways, so by combining them we should be able to view our torus in 3D from any angle just by adding an extra component "z" so that both computations can be independent of each other. Then combine our two radii into a vec2 and we end up with a function that looks just like IQ's.

float sdTorus( vec3 p, vec2 t )
{
vec2 q = vec2(length(p.xz)-t.x,p.y);
return length(q)-t.y;
}

This would still look two dimensional in the "circle" shadertoy, but now we can vary z to look at the torus slices. Or rotate our view point and see it emerge from the 2D world. Neat stuff!

BTW you can go through this same process with many primitives and get many different shapes as IQ has demonstrated on his website.

• I'm a bit confused about your sqTorus2D function. Let's say the R=5 & r=3 [Torus is at origin] and let's say there is a point (6,0,0) then the function returns abs(6-5)-3 => 1-3 => -2 but this does not make sense. The point is clearly outside the torus so it should return a positive value[+1 I think]? Have I misunderstood something? Mar 6 '21 at 4:16
• It computes the length of the input vector first. That gives us the distance to R, that value is a float not a vector. It then computes the inner radius in terms of the outer radius. Plug it into shaderToy and try it. (I gave a link to a super simple shaderToy at the top) Mar 6 '21 at 11:22

The following video discusses the SDFs of various primitives (linked to torus section):

https://youtu.be/Ff0jJyyiVyw?t=491

It discusses a torus but not the capped variant. The rest of the channel has useful content as well.

Additionally, iq has videos on detailed derivations of some primitives and techniques, sometimes hidden in the long livestreams, which are well worth a watch if you want to learn more about graphics methods and demoscene tricks: