Skip to main content

opSubtraction: Boolean Subtraction Operation

Geometric Carving for Distance Field Composition

The opSubtraction operations perform boolean subtraction by removing one SDF shape from another. This creates carved or cut-out effects, enabling complex geometric compositions through subtractive modeling.

Mathematical Foundation

Basic subtraction uses the maximum of the negated first distance and the second distance:

dsubtraction=max(d1,d2)d_{\text{subtraction}} = \max(-d_1, d_2)

Smooth subtraction blends the transition using a smoothstep interpolation:

h=clamp(12d2+d12k,0,1)h = \text{clamp}\left(\frac{1}{2} - \frac{d_2 + d_1}{2k}, 0, 1\right) dsmooth=mix(d2,d1,h)+kh(1h)d_{\text{smooth}} = \text{mix}(d_2, -d_1, h) + kh(1-h)

Function Variants

FunctionParametersDescription
opSubtractiond1, d2Sharp boolean subtraction
opSubtractionVec4d1, d2Vec4 distance with material info
opSubtractionSmoothd1, d2, kSmoothed subtraction with blending factor
opSubtractionSmoothVec4d1, d2, kSmooth vec4 subtraction

Implementation Demonstration

Live Editor
const fragment = () => {
      const up = vec3(0, 1, 0)
      const eps = vec3(0.01, 0, 0)
      const eye = rotate3dY(iTime).mul(vec3(4))
      const sdf = Fn(([p]: [Vec3]) => {
              const sphere = sphereSDFRadius(p, 1)
              const cylinder = cylinderSDFHeightRadius(p, 0.5, 2)
              return opSubtraction(cylinder, sphere)
      })
      const march = Fn(([eye, dir]: [Vec3, Vec3]) => {
              const p = eye.toVar()
              const d = sdf(p).toVar()
              Loop(16, ({ i }) => {
                      If(d.lessThanEqual(eps.x), () => {
                              const dx = sdf(p.add(eps.xyy)).sub(d)
                              const dy = sdf(p.add(eps.yxy)).sub(d)
                              const dz = sdf(p.add(eps.yyx)).sub(d)
                              const normal = vec3(dx, dy, dz).normalize()
                              const light = vec3(2, 4, 3).sub(p).normalize()
                              const diffuse = normal.dot(light).max(0.1)
                              return vec4(vec3(diffuse.mul(0.8).add(0.2)), 1)
                      })
                      p.addAssign(d.mul(dir))
                      d.assign(sdf(p))
              })
              return vec4(0)
      })
      const z = eye.negate().normalize()
      const x = z.cross(up)
      const y = x.cross(z)
      const scr = vec3(uv.sub(0.5), 2)
      const dir = mat3(x, y, z).mul(scr).normalize()
      return march(eye, dir)
}