Skip to content

2D SDF(3): Reatangle and Rounded

Bonsai edited this page Apr 4, 2024 · 1 revision

Rectangle

A rectangle can be defined by its center point, width and heigth. There are manly three cases for the distribution of a point's position with respect to the rectangle:

  1. P1 inside the reactangle: for points inside the reactangle, the signed distance to the nearest boundary is negative. where the absoulute value is the minimum distance to the nearest edge.
  2. P2, P3 Outside. Aligned Horizontally or vertically: if a Point lies on an extension of the rectangle's side(i.e, aligned vertically or horizontally), the nearest distance will be the straight-line distance to the nearest edge.
  3. P4 outside, Not Aligned with Any Edge: For points outside the rectangle that are neither parallel nor perpendicular to hte edges, the nearest distance is the Euclidean distance to the closest cornner of the rectangle.

Assume the rectangle's center $C(x_c, y_c)$ is at center of corrdinate $C(0, 0)$. with width $w$ and height $h$, and any point $P(x, y)$. Due to the symmetry of the coordinate axes, we only need to consider the four cases in the first quadrant; for other quadrant, the distances can be translated to the first quadrant through the absolute value.

  1. Compute the distance from $P$ along horizontal and vertical directions to the boundaries of the rectangle $dx = x - w/2$ , $dy = y - h/2$
  2. Depending on the signs of $dx$ and $dy$ , we can determine how to calculate the distance $d$
    1. dx<0 and dy<0: $d = max(dx, dy)$
    2. P2, P3 Outside. Aligned Horizontally or vertically, i.e.,oneOf(dx,dy) < 0 $d = max(dx, dy)$
    3. P4 outside, Not Aligned with Any Edge: i.e.,dx>0 and dy>0 $d = \sqrt{dx^2 + dy^2}$

this becomes very clear and straightforward in shader code

float sdf_rectangle1(vec2 pct, vec2 wh) {
  vec2 dxy = abs(pct) - wh;
  if (dxy.x > 0. && dxy.y >0.) {
    return length(dxy);
  } else {
    return max(dxy.x, dxy.y);
  }
}

However, it's important to note the shader prefer to avoidif elsestatements. Consider how to combine two conditions into a single statement. particularly for cases 2 and 3 $d = \sqrt{max(dx, 0)^2 + max(dy, 0)^2}$ In GLSL, vectors support the max function, which allows further simplification to max(dxy, vec2(0.)), However, this formula doesn't apply to case 1 as the result would be 0. Therefore, $max(min(dx, 0), min(dy,0))$ This formula results in 0 for cases 2 and 3. Consequently, to achieve the final result. we can simply add the two formulas together. The shader code follows as such:

float sdf_rectangle2(vec2 pct, vec2 wh) {
  vec2 dxy = abs(pct) - wh;
  return length(max(dxy, 0.0)) + max(min(dxy.x, 0.0), min(dxy.y, 0.0));
}

Then we got this :)

Rounded

Implementing rounded corners is quite straightforward. as illustrated above. The inner rectangle represetns the actual rectangle. and by increasing the inner rectangle's corners by the radius of the rounding, you can obtain the distance. Then, by subtracting the raidus. you can achieve the rounded corner effect. the specific code is as follows

float sdf_rounded_rectangle(vec2 pct, vec2 wh, float radius) {
  vec2 dxy = abs(pct) - wh + radius;
  return length(max(dxy, 0.0)) +  // one of (dx, dy) greater than 0 
          max(min(dxy.x, 0.0), min(dxy.y, 0.0))- // dx, dy lower than 0
          radius;
}

Then we got this :)