#define_import_path forky::sdf3 /* https://gist.github.com/munrocket/f247155fc22ecb8edf974d905c677de1 # WGSL 3D SDF Primitives #### How to use this gist: 1. Build a sphere tracer with WebGPU ([paper](https://www.researchgate.net/publication/2792108_Sphere_Tracing_A_Geometric_Method_for_the_Antialiased_Ray_Tracing_of_Implicit_Surfaces), [paper2](https://www.semanticscholar.org/paper/Enhanced-Sphere-Tracing-Keinert-Sch%C3%A4fer/4c9bd91bd044980f5746d623315be5285cc799c9), [youtube](https://www.youtube.com/watch?v=PGtv-dBi2wE)) 2. Create model with sdf functions from here 3. Add light and shadows 4. ??? 5. PROFIT This code tested in Chrome and Firefox, should work on PC too. Function `select` can work differently in browsers. # Primitives #### Sphere - exact */ fn sdSphere(p: vec3, r: f32) -> f32 { return length(p) - r; } /* #### Ellipsoid - bound (not exact) */ fn sdEllipsoid(p: vec3, r: vec3) -> f32 { let k0 = length(p / r); let k1 = length(p / (r * r)); return k0 * (k0 - 1.) / k1; } /* #### Box - exact */ fn sdBox(p: vec3, b: vec3) -> f32 { let q = abs(p) - b; return length(max(q, vec3(0.))) + min(max(q.x, max(q.y, q.z)), 0.); } /* #### Round Box - exact */ fn sdRoundBox(p: vec3, b: vec3, r: f32) -> f32 { let q = abs(p) - b; return length(max(q, vec3(0.))) + min(max(q.x,max(q.y, q.z)), 0.) - r; } /* #### Box Frame - exact */ fn sdBoxFrame(p: vec3, b: vec3, e: f32) -> f32 { let q = abs(p) - b; let w = abs(q + e) - e; return min(min( length(max(vec3(q.x, w.y, w.z), vec3(0.))) + min(max(q.x, max(w.y, w.z)), 0.), length(max(vec3(w.x, q.y, w.z), vec3(0.))) + min(max(w.x, max(q.y, w.z)), 0.)), length(max(vec3(w.x, w.y, q.z), vec3(0.))) + min(max(w.x, max(w.y, q.z)), 0.)); } /* #### Gyroid - bound */ fn sdGyroid(p: vec3, h: f32) -> f32 { return abs(dot(sin(p), cos(p.zxy))) - h; } /* #### Torus - exact */ fn sdTorus(p: vec3, R: f32, r: f32) -> f32 { let q = vec2(length(p.xz) - R, p.y); return length(q) - r; } /* #### Capped Torus - exact */ fn sdCappedTorus(p: vec3, R: f32, r: f32, sincos: vec2) -> f32 { let q = vec3(abs(p.x), p.y, p.z); let k = select(length(q.xy), dot(q.xy, sincos), sincos.y * q.x > sincos.x * q.y); return sqrt(dot(q, q) + R * R - 2. * R * k) - r; } /* #### Link - exact */ fn sdLink(p: vec3, R: f32, r: f32, le: f32) -> f32 { let q = vec3(p.x, max(abs(p.y) - le, 0.), p.z); return length(vec2(length(q.xy) - R, q.z)) - r; } /* #### Capsule / Line - exact */ fn sdCapsule(p: vec3, a: vec3, b: vec3, r: f32) -> f32 { let pa = p - a; let ba = b - a; let h = clamp(dot(pa, ba) / dot(ba, ba), 0., 1.); return length(pa - ba * h) - r; } /* #### Vertical Capsule / Line - exact */ fn sdVerticalCapsule(p: vec3, h: f32, r: f32) -> f32 { let q = vec3(p.x, p.y - clamp(p.y, 0., h), p.z); return length(q) - r; } /* #### Cylinder - exact */ fn sdCylinder(p: vec3, a: vec3, b: vec3, r: f32) -> f32 { let ba = b - a; let pa = p - a; let baba = dot(ba, ba); let paba = dot(pa, ba); let x = length(pa * baba - ba * paba) - r * baba; let y = abs(paba - baba * 0.5) - baba * 0.5; let x2 = x * x; let y2 = y * y * baba; let d = x2 * step(0., x) + y2 * step(0., y); let d2 = select(d, -min(x2, y2), max(x, y) < 0.); return sign(d2) * sqrt(abs(d2)) / baba; } /* #### Vertical Cylinder - exact */ fn sdVerticalCylinder(p: vec3, h: f32, r: f32) -> f32 { let d = abs(vec2(length(p.xz), p.y)) - vec2(r, h); return min(max(d.x, d.y), 0.) + length(max(d, vec2(0.))); } /* #### Rounded Cylinder - exact */ fn sdRoundedCylinder(p: vec3, h: f32, r: f32, re: f32) -> f32 { let d = vec2(length(p.xz) - 2. * r + re, abs(p.y) - h); return min(max(d.x, d.y), 0.) + length(max(d, vec2(0.))) - re; } /* #### Infinite Cylinder - exact */ fn sdInfiniteCylinder(p: vec3, c: vec3) -> f32 { return length(p.xz - c.xy) - c.z; } /* #### Cone - exact */ fn sdCone(p: vec3, h: f32, sincos: vec2) -> f32 { // Alternatively pass q instead of (sin(alpha), cos(alpha)) let q = h * vec2(sincos.x / sincos.y, -1.); let w = vec2(length(p.xz), p.y); let a = w - q * clamp(dot(w,q) / dot(q,q), 0., 1.); let b = w - q * vec2(clamp(w.x / q.x, 0., 1.), 1.); let k = sign(q.y); let d = min(dot(a, a), dot(b, b)); let s = max(k * (w.x * q.y - w.y * q.x), k * (w.y - q.y)); return sqrt(d) * sign(s); } /* #### Cone - bound (not exact) */ fn sdConeBound(p: vec3, h: f32, sincos: vec2) -> f32 { return max(dot(sincos.yx, vec2(length(p.xz), p.y)), -h - p.y); } /* #### Infinite Cone - exact */ fn sdInfiniteCone(p: vec3, sincos: vec2) -> f32 { let q = vec2(length(p.xz), -p.y); let d = length(q - sincos * max(dot(q, sincos), 0.)); return d * select(-1., 1., q.x * sincos.y - q.y * sincos.x > 0.0); } /* #### Capped Vertical Cone - exact */ fn sdCappedVerticalCone(p: vec3, h: f32, r1: f32, r2: f32) -> f32 { let q = vec2(length(p.xz), p.y); let k1 = vec2(r2, h); let k2 = vec2(r2 - r1, 2. * h); let ca = vec2(q.x - min(q.x, select(r2, r1, q.y < 0.)), abs(q.y) - h); let cb = q - k1 + k2 * clamp(dot(k1 - q, k2) / dot(k2, k2), 0., 1.); let s = select(1., -1., cb.x < 0. && ca.y < 0.); return s * sqrt(min(dot(ca, ca), dot(cb, cb))); } /* #### Capped Cone - exact */ fn sdCappedCone(p: vec3, a: vec3, b: vec3, ra: f32, rb: f32) -> f32 { let rba = rb - ra; let baba = dot(b - a, b - a); let papa = dot(p - a, p - a); let paba = dot(p - a, b - a) / baba; let x = sqrt(papa - paba * paba * baba); let cax = max(0.0, x - select(rb, ra, paba < 0.5)); let cay = abs(paba - 0.5) - 0.5; let k = rba * rba + baba; let f = clamp((rba * (x - ra) + paba * baba) / k, 0.0, 1.0); let cbx = x - ra - f * rba; let cby = paba - f; let s = select(1., -1., cbx < 0.0 && cay < 0.0); return s * sqrt(min(cax * cax + cay * cay * baba, cbx * cbx + cby * cby * baba)); } /* #### Round Vertical cone - exact */ fn sdRoundVerticalCone(p: vec3, h: f32, r1: f32, r2: f32) -> f32 { let q = vec2(length(p.xz), p.y); let b = (r1 - r2) / h; let a = sqrt(1. - b * b); let k = dot(q, vec2(-b, a)); if (k < 0.) { return length(q) - r1; } if (k > a * h) { return length(q - vec2(0., h)) - r2; } return dot(q, vec2(a, b)) - r1; } /* #### Round cone - exact */ fn sdRoundCone(p: vec3, a: vec3, b: vec3, r1: f32, r2: f32) -> f32 { let ba = b - a; let l2 = dot(ba, ba); let rr = r1 - r2; let a2 = l2 - rr * rr; let il2 = 1. / l2; let pa = p - a; let y = dot(pa, ba); let z = y - l2; let w = pa * l2 - ba * y; let x2 = dot(w, w); let y2 = y * y * l2; let z2 = z * z * l2; let k = sign(rr) * rr * rr * x2; if (sign(z) * a2 * z2 > k) { return sqrt(x2 + z2) * il2 - r2; } if (sign(y) * a2 * y2 < k) { return sqrt(x2 + y2) * il2 - r1; } return (sqrt(x2 * a2 * il2) + y * rr) * il2 - r1; } /* #### Solid Angle - exact */ fn sdSolidAngle(p: vec3, sincos: vec2, r: f32) -> f32 { let q = vec2(length(p.xz), p.y); let l = length(q) - r; let m = length(q - sincos * clamp(dot(q, sincos), 0., r)); return max(l, m * sign(sincos.y * q.x - sincos.x * q.y)); } /* #### Plane - exact */ fn sdPlane(p: vec3, n: vec3, h: f32) -> f32 { // n must be normalized return dot(p, n) + h; } /* #### Octahedron - exact */ fn sdOctahedron(p: vec3, s: f32) -> f32 { var q: vec3 = abs(p); let m = q.x + q.y + q.z - s; if (3. * q.x < m) {q = q.xyz;} else {if (3. * q.y < m) {q = q.yzx;} else {if (3. * q.z < m) {q = q.zxy;} else {return m * 0.57735027;}}} let k = clamp(0.5 * (q.z - q.y + s), 0., s); return length(vec3(q.x, q.y - s + k, q.z - k)); } /* #### Octahedron - bound (not exact) */ fn sdOctahedronBound(p: vec3, s: f32) -> f32 { let q = abs(p); return (q.x + q.y + q.z - s) * 0.57735027; } /* #### Pyramid - exact */ fn sdPyramid(p: vec3, h: f32) -> f32 { let m2 = h * h + 0.25; var xz: vec2 = abs(p.xz); xz = select(xz, xz.yx, xz[1] > xz[0]); xz = xz - vec2(0.5); let q = vec3(xz[1], h * p.y - 0.5 * xz[0], h * xz[0] + 0.5 * p.y); let s = max(-q.x, 0.); let t = clamp((q.y - 0.5 * xz[1]) / (m2 + 0.25), 0., 1.); let a = m2 * (q.x + s) * (q.x + s) + q.y * q.y; let b = m2 * (q.x + 0.5 * t) * (q.x + 0.5 * t) + (q.y - m2 * t) * (q.y - m2 * t); let d2 = min(a, b) * step(min(q.y, -q.x * m2 - q.y * 0.5), 0.); return sqrt((d2 + q.z * q.z) / m2) * sign(max(q.z, -p.y)); } /* #### Hexagonal Prism - exact */ fn sdHexPrism(p: vec3, h: vec2) -> f32 { let k = vec3(-0.8660254, 0.5, 0.57735); let a = abs(p); let v = a.xy - 2. * min(dot(k.xy, a.xy), 0.) * k.xy; let d1 = length(v - vec2(clamp(v.x, -k.z * h.x, k.z * h.x), h.x)) * sign(v.y - h.x); let d2 = a.z - h.y; return min(max(d1, d2), 0.) + length(max(vec2(d1, d2), vec2(0.))); } /* #### Triangular Prism - bound */ fn sdTriPrism(p: vec3, h: vec2) -> f32 { let q = abs(p); return max(q.z - h.y, max(q.x * 0.866025 + p.y * 0.5, -p.y) - h.x * 0.5); } /* #### Quadratic Bezier - exact */ fn sdBezier(p: vec3, A: vec3, B: vec3, C: vec3) -> vec2 { let a = B - A; let b = A - 2. * B + C; let c = a * 2.; let d = A - p; let kk = 1. / dot(b, b); let kx = kk * dot(a, b); let ky = kk * (2. * dot(a, a) + dot(d, b)) / 3.; let kz = kk * dot(d, a); let p1 = ky - kx * kx; let p3 = p1 * p1 * p1; let q = kx * (2.0 * kx * kx - 3.0 * ky) + kz; var h: f32 = q * q + 4. * p3; var res: vec2; if (h >= 0.) { h = sqrt(h); let x = (vec2(h, -h) - q) / 2.; let uv = sign(x) * pow(abs(x), vec2(1. / 3.)); let t = clamp(uv.x + uv.y - kx, 0., 1.); let f = d + (c + b * t) * t; res = vec2(dot(f, f), t); } else { let z = sqrt(-p1); let v = acos(q / (p1 * z * 2.)) / 3.; let m = cos(v); let n = sin(v) * 1.732050808; let t = clamp(vec2(m + m, -n - m) * z - kx, vec2(0.0), vec2(1.0)); let f = d + (c + b * t.x) * t.x; var dis: f32 = dot(f, f); res = vec2(dis, t.x); let g = d + (c + b * t.y) * t.y; dis = dot(g, g); res = select(res, vec2(dis, t.y), dis < res.x); } res.x = sqrt(res.x); return res; } /* #### Triangle - exact */ fn udTriangle(p: vec3, a: vec3, b: vec3, c: vec3) -> f32 { let ba = b - a; let pa = p - a; let cb = c - b; let pb = p - b; let ac = a - c; let pc = p - c; let nor = cross(ba, ac); let d1 = ba * clamp(dot(ba, pa) / dot(ba, ba), 0., 1.) - pa; let d2 = cb * clamp(dot(cb, pb) / dot(cb, cb), 0., 1.) - pb; let d3 = ac * clamp(dot(ac, pc) / dot(ac, ac), 0., 1.) - pc; let k0 = min(min(dot(d1, d1), dot(d2, d2)), dot(d3, d3)); let k1 = dot(nor, pa) * dot(nor, pa) / dot(nor, nor); let t = sign(dot(cross(ba, nor), pa)) + sign(dot(cross(cb, nor), pb)) + sign(dot(cross(ac, nor), pc)); return sqrt(select(k0, k1, t < 2.)); } /* #### Quad - exact */ fn udQuad(p: vec3, a: vec3, b: vec3, c: vec3, d: vec3) -> f32 { let ba = b - a; let pa = p - a; let cb = c - b; let pb = p - b; let dc = d - c; let pc = p - c; let ad = a - d; let pd = p - d; let nor = cross(ba, ad); let d1 = ba * clamp(dot(ba, pa) / dot(ba, ba), 0., 1.) - pa; let d2 = cb * clamp(dot(cb, pb) / dot(cb, cb), 0., 1.) - pb; let d3 = dc * clamp(dot(dc, pc) / dot(dc, dc), 0., 1.) - pc; let d4 = ad * clamp(dot(ad, pd) / dot(ad, ad), 0., 1.) - pd; let k0 = min(min(dot(d1, d1), dot(d2, d2)), min(dot(d3, d3), dot(d4, d4))); let k1 = dot(nor, pa) * dot(nor, pa) / dot(nor, nor); let t = sign(dot(cross(ba, nor), pa)) + sign(dot(cross(cb, nor), pb)) + sign(dot(cross(dc, nor), pc)) + sign(dot(cross(ad, nor), pd)); return sqrt(select(k0, k1, t < 3.)); } /* # Boolean operations with primitives #### Union, Subtraction, Intersection - exact (outside), bound, bound */ fn opUnion(d1: f32, d2: f32) -> f32 { return min(d1, d2); } fn opSubtraction(d1: f32, d2: f32) -> f32 { return max(d1, -d2); } fn opIntersection(d1: f32, d2: f32) -> f32 { return max(d1, d2); } /* #### Chamfer Union, Chamfer Subtraction, Chamfer Intersection - bound, bound, bound */ fn opUnionChamfer(d1: f32, d2: f32, r: f32) -> f32 { return min(min(d1, d2), (d1 - r + d2) * 0.5); } fn opSubtractionChamfer(d1: f32, d2: f32, r: f32) -> f32{ return max(max(d1, -d2), (d1 + r - d2) * 0.5); } fn opIntersectionChamfer(d1: f32, d2: f32, r: f32) -> f32 { return max(max(d1, d2), (d1 + r + d2) * 0.5); } /* #### Blend Union, Blend Subtraction, Blend Intersection - bound, bound, bound */ fn opUnionBlend(d1: f32, d2: f32, k: f32) -> f32 { let h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0., 1.); return mix(d2, d1, h) - k * h * (1. - h); } fn opSubtractionBlend(d1: f32, d2: f32, k: f32) -> f32 { let h = clamp(0.5 - 0.5 * (d1 + d2) / k, 0., 1.); return mix(d1, -d2, h) + k * h * (1. - h); } fn opIntersectionBlend(d1: f32, d2: f32, k: f32) -> f32 { let h = clamp(0.5 - 0.5 * (d2 - d1) / k, 0., 1.); return mix(d2, d1, h) + k * h * (1. - h); } /* # Displacement #### Displacement - bound (not exact) */ fn opDisplace(d1: f32, d2: f32) -> f32 { return d1 + d2; } //let d = opDisplace(sdfPrimitive3d(p), displacement3d(p)); /* #### Twist - bound */ fn opTwist(p: vec3, k: f32) -> vec3 { let s = sin(k * p.y); let c = cos(k * p.y); let m = mat2x2(vec2(c, s), vec2(-s, c)); return vec3(m * p.xz, p.y); } //let d = sdfPrimitive3d(opTwist(p, k)); /* #### Bend - bound */ fn opCheapBend(p: vec3, k: f32) -> vec3 { let s = sin(k * p.x); let c = cos(k * p.x); let m = mat2x2(vec2(c, s), vec2(-s, c)); return vec3(m * p.xy, p.z); } //let d = sdfPrimitive3d(opCheapBend(p, k)); /* # Positioning #### Translate - exact */ fn opTranslate(p: vec3, t: vec3) -> vec3 { return p - t; } //let d = sdfPrimitive3d(opTranslate(p, t)); /* #### 90 degree rotation - exact */ fn op90RotateX(p: vec3) -> vec3 { return vec3(p.x, p.z, -p.y); } fn op90RotateY(p: vec3) -> vec3 { return vec3(-p.z, p.y, p.x); } fn op90RotateZ(p: vec3) -> vec3 { return vec3(p.y, -p.x, p.z); } //let d = sdfPrimitive3d(op90RotateZ(p)); /* #### Rotation around axis - exact */ fn opRotateX(p: vec3, a: f32) -> vec3 { let s = sin(a); let c = cos(a); return vec3(p.x, c * p.y + s * p.z, -s * p.y + c * p.z); } fn opRotateY(p: vec3, a: f32) -> vec3 { let s = sin(a); let c = cos(a); return vec3(c * p.x - s * p.z, p.y, s * p.x + c * p.z); } fn opRotateZ(p: vec3, a: f32) -> vec3 { let s = sin(a); let c = cos(a); return vec3(c * p.x + s * p.y, -s * p.x + c * p.y, p.z); } //let d = sdfPrimitive3d(opRotateY(p, a)); /* #### Rotation around free axis - exact */ fn opRotateE(p: vec3, e: vec3, a: f32) -> vec3 { let c = cos(a); return dot(e, p) * (1. - c) * e - cross(e, p) * sin(a) + c * p; } //let d = sdfPrimitive3d(opRotateE(p, normalize(vec3(1.,0.,.5)), a)); /* #### Scale - exact */ fn opScale(p: vec3, s: f32) -> vec3 { return p / s; } //let d = sdfPrimitive3d(opScale(p, s)) * s; /* #### Free transformation - exact */ fn opTransform(p: vec3, transform: mat4x4) -> vec3 { let q = inverse(transform) * vec4(p, 1.); } //let d = sdfPrimitive3d(opTransform(p, transform)) * determinant(transform); // OR //let d = sdfPrimitive3d(opScale(opRotateE(opTranslate(p, t), e, a), s)) * s; /* #### Symmetry - exact */ fn opSymmetryX(p: vec3) -> vec3 { return vec3(abs(p.x), p.y, p.z); } fn opSymmetryY(p: vec3) -> vec3 { return vec3(p.x, abs(p.y), p.z); } fn opSymmetryZ(p: vec3) -> vec3 { return vec3(p.x, p.y, abs(p.z)); } //let d = sdfPrimitive3d(opSymmetryX(p)); /* #### Infinite Repetition - exact */ fn opInfArray(p: vec3, c: vec3) -> vec3 { return p - c * round(p / c); } //let d = sdfPrimitive3d(opInfArray(p, c)); /* #### Finite Repetition - exact */ fn opLimArray(p: vec3, c: f32, lim: vec3) -> vec3 { return p - c * clamp(round(p / c), -lim, lim); } //let d = sdfPrimitive3d(opLimArray(p, c)); /* # Primitive alterations #### Elongation - exact */ fn opElongate(p: vec3, h: vec3) -> vec3 { return p - clamp(p, -h, h); } //let d = sdfPrimitive3d(opElongateFast(p, h)); fn opElongateCorrect(p: vec3, h: vec3) -> vec4 { let q = abs(p) - h; let sgn = 2. * step(vec3(0.), p) - vec3(1.); return vec4(sgn * max(q, vec3(0.)), min(max(q.x, max(q.y, q.z)), 0.)); } //let p2 = opElongateCorrect(p, h); //let d = p2.w + sdfPrimitive3d(p2.xyz); /* #### Rounding - exact */ fn opRound(p: vec3, r: f32) -> f32 { return sdfPrimitive3d(p) - r; } /* #### Onion - exact */ fn opOnion(d: f32, thickness: f32) -> f32 { return abs(d) - thickness; } //let d = opOnion(sdfPrimitive3d(p), thickness); /* #### Extrusion from 2D SDF - exact */ fn opExtrusion(d: f32, z: f32, h: f32) -> f32 { let w = vec2(d, abs(z) - h); return min(max(w.x, w.y), 0.) + length(max(w, vec2(0.))); } //let d = opExtrusion(sdfPrimitive2d(p.xy), p.z, h)); /* #### Revolution from 2D SDF - exact */ fn opRevolution(p: vec3, o: f32) -> vec2 { return vec2(length(p.xz) - o, p.y); } //let d = sdfPrimitive2d(opRevolution(p, h)); /* #### Change metric - bound */ fn length4(p: vec3) -> f32 { var q: vec3 = p * p; q = q * q; return sqrt(sqrt(q.x + q.y + q.z)); } fn length6(p: vec3) -> f32 { var q: vec3 = p * p * p; q = q * q; return pow(q.x + q.y + q.z, 1. / 6.); } fn length8(p: vec3) -> f32 { var q: vec3 = p * p; q = q * q; q = q * q; return pow(q.x + q.y + q.z, 1. / 8.); } /* MIT License. © 2020 Inigo Quilez, Johann Korndörfer, Martijn Steinrucken, Munrocket */