2D SDF Operation(1)_Some Transformations

Julito(盆栽)
6 min readMay 26, 2024

--

Rotation and Translation

Transformations of shapes in a coordinate system can be accomplished by the inverse transformation of the coordinate system. However, there’s an issue that I haven’t figured out — after combining rotation and translation, an infinite value appears. I’m not sure if it’s an issue with the built-in inverse function or a logical problem. This is reflected in the image by the disappearance of the inner circle. Let’s review here the translation and rotation matrices (linear transformations).

float dx = sin(iTime/3.0);
float dy = cos(iTime/3.0);
mat3 translationMatrix = mat3(
1, 0, dx,
0, 1, dy,
0, 0, 0
);

float theta = PI * (abs(sin(iTime/3.0)) + 0.1);
mat3 rotationMatrix = mat3(
cos(theta), -sin(theta), 0,
sin(theta), cos(theta), 0,
0, 0, 0
);

Scaling

Scaling is done by compressing/expanding space, so the final distance needs to be expanded/compressed back inversely. The calculation for uniform scaling is very simple, but it is impossible to do general non-uniform scaling and still obtain a correct SDF, because the information about the dimensions is lost. Here is IQ’s code directly.

float opScale( in vec3 p, in float s, in sdf3d primitive )
{
return primitive(p/s)*s;
}

Rounded

Rounding means pulling the outer arcs of corners inwards. The operation to perform is to stretch the coordinate system outwards along the direction of the corner (similar to the normal direction of a 3D surface), and then subtract the corresponding stretched amount from the final distance obtained. In terms of code, there will be an addition operation followed by a subtraction operation.

Star

For rounded star, the radius of the polar coordinates that are pulled outward.

Compared to the previous general star SDF function, the changes are

float roundR = 0.3;
//+ polar_P = vec2(length(P) + roundR, theta2);
//- polar_P = vec2(length(P) , theta2);
//+ return sign(cross2(v2, v1)) * length(v1-h*v2) - roundR;
//- return sign(cross2(v2, v1)) * length(v1-h*v2) ;

Polygon

The code was obtained through continuous debugging and observation of the image. I can’t clearly explain it now, I can only write it based on feeling. The basic idea is to scale the radius in polar coordinates as well. After completing the derivation of the previous SDF, readers should be able to get a feel for it too.

In the following code ,why does line 44 subtract

? My guess is that it is to scale to he edge and this value was also determined experimentally…

float rounded_polygon(vec2 P, float radius, int sides) {
// get polar angle
float angle = atan(P.y, P.x);
// make angle to range [0, 2*PI]
angle = P.y > 0. ? angle: angle + PI * 2.;
    // get each piece angle
float delta = 2. * PI / float(sides);
// how many pieces?
float areaNumber = floor(angle / delta);

// start angle of current piece
float theta1 = delta * areaNumber;
// end angle of current piece
float theta2 = delta * (areaNumber + 1.0);

float roundR = -abs(sin(iTime / 2.)) * 1.5 ;
float radius2 = -radius - roundR;
radius2 = -abs(radius2);
// start point on current piece
vec2 POINT_A = vec2(radius2 * cos(theta1), radius2 * sin(theta1) );
// end point on current piece
vec2 POINT_A_Prime = vec2(radius2 * cos(theta2), radius2 * sin(theta2) );
// the middle of startPoint and endPoint
vec2 POINT_D = (POINT_A + POINT_A_Prime)/2.0;
// corrdinate center
vec2 POINT_O = vec2(0.0);
// start axis of current piece
vec2 iAxis1 = vec2(-POINT_O+POINT_A);
for (int i=0; i<2; i++) {
vec2 PP = P;
if (i==1) { // symmetrical for area2
PP = symmetrical_point(P, POINT_D);
}
vec2 vector1 = vec2(PP - POINT_A);
float a1 = vector_angle(iAxis1, vector1);
if (a1 < (delta / 2.0)) {
return length(vector1)-roundR * abs(cos(delta/2.)) ;
}
}

// area 3
float theta = mod(angle, delta) - delta / 2.0;
float l1 = (length(P) + roundR) * cos(theta) - length(POINT_D) - roundR;
return l1;
}

Annular

Understanding the SDF of 2D graphics as the distance from the edge, where the interior of the shape is negative and the exterior is positive, an Annular actually fits very directly. The function is represented as follows:

float op_annular_circle( in vec2 p, in float r1 , float r2)
{
return abs(length(p) - r1 ) - r2;
}

Elongation

If a circle is to be elongated along the X-axis, assuming point A1 has coordinates (d, 0), from the perspective of the coordinate axis, it involves mapping all points within lines

and

to their projection points on

. That is, the x-values of all points within

and

become 0. Meanwhile, the x-values of all points outside

are reduced by

, then we got code.

float sdf_elongate_circle1(vec2 p,  float r, float xv ) {
p.x = p.x > 0.0 ? max(p.x - xv, 0.0 ) : p.x;
return length(p) - r;
}

If we want to elongate towards the negative direction of the x-axis as well,

float sdf_elongate_circle1(vec2 p,  float r, float xv ) {
p.x = p.x > 0.0 ? max(p.x - xv, 0.0 ) : p.x;
p.x = p.x < 0.0 ? min(p.x + xv, 0.0 ) : p.x;
return length(p) - r;
}

the processing for the y-axis is no different from that for the x-axis, so we have

float sdf_elongate_circle1(vec2 p,  float r, float xv, float yv ) {
p.x = p.x > 0.0 ? max(p.x - xv, 0.0 ) : p.x;
p.x = p.x < 0.0 ? min(p.x + xv, 0.0 ) : p.x;
p.y = p.y > 0.0 ? max(p.y - yv, 0.0 ) : p.y;
p.y = p.y < 0.0 ? min(p.y + yv, 0.0 ) : p.y;
return length(p) - r;
}

Four conditional statements that can be combined into the clamp function.

float sdf_elongate_circle3(vec2 p,  float r, vec2 h ) {
p = p - clamp(p, -h, h);
return length(p) - r;
}

Finaly we got this :)

Change of Metric

The most crucial function in distance fields is Length. This distance is the Euclidean distance. The method for calculating the distance is

If the function for calculating this distance becomes

Different values of (n) will bring some interesting changes to the shape, making the visual effect rounder… However, IQ, the great master of this domain, does not recommend this transformation, because it will affect the results of raymarching.

--

--

Julito(盆栽)
0 Followers

31 years old, transitioning to a digital artist in the first year with no prior experience. Hobbies include cooking, skateboarding, coding