Modeling
Forge supports a growing set of modeling helpers for building custom objects without dropping fully into low-level SDF math every time.
The current model has three layers:
- object-level modeling helpers like mirrors, repeats, slices, and carved noise
- native primitive distance intrinsics you can call from Forge
- fully programmable custom SDF hooks such as
domain(p)anddistance_post(d, p)
Object Helpers
Forge exposes a first object-level modeling-helper slice:
mirror_x(),mirror_y(),mirror_z(): Mirrors an object across its local X, Y, or Z axis.repeat_x(spacing, count),repeat_y(...),repeat_z(...): Repeats an object along one axis with fixed spacing and finite count.slice_x(min, max),slice_y(...),slice_z(...): Clips an object to a local-space range on one axis.noise(octaves[, scale[, lacunarity]]): Applies recursive subtractive FBM-style breakup to the object surface.hole_line_x_sdf(p, radius, half_len, spacing, count),hole_line_y_sdf(...),hole_line_z_sdf(...): Builds a repeated line of cylindrical holes along localZ, useful for perforated parts and vents in custom assets.
let rib = Box { size: vec3(0.2, 1.0, 0.4) };
let columns = rib.repeat_x(0.6, 5.0);
let mirrored = columns.mirror_z();
let clipped = mirrored.slice_y(-0.4, 0.4);
noise(...) is useful for carved rock and terrain-like breakup on otherwise simple shapes:
let stone = Box {
size: vec3(1.0, 1.0, 1.0),
round: 0.08
}
.noise(7.0, 1.6, 1.2);
These helpers are lowered into native renderer structures before marching, so they do not depend on the slow interpreted hot path.
Math Helpers
Forge’s general scalar, vector, 3D noise, and native primitive distance helpers now live on the dedicated Math page.
Programmable SDF Hooks
For geometry that goes beyond the built-ins, define a Forge SDF with a single distance contract:
sdf SoftBlob {
fn bounds() {
return vec3(1.2, 1.2, 1.1);
}
fn warp(p) {
return vec3(p.x, p.y + sin(p.x * 4.0) * 0.16, p.z);
}
fn distance(p) {
let q = warp(p);
return length(q) - 1.0;
}
};
let scene = SoftBlob {};
Rules:
fn distance(p)is requiredpis evaluated in local/object space- helper functions can be reused inside the SDF block
fn bounds()is optional but strongly recommended- optional
fn domain(p)can transform point space beforedistance(p) - optional
fn distance_post(d, p)can modify the computed distance afterward
Example programmable modifier pattern:
sdf TwistStatue {
fn bounds() {
return vec3(0.4, 0.9, 0.4);
}
fn domain(p) {
return rotate_y(p, p.y * 18.0);
}
fn distance(p) {
return length(p) - 0.5;
}
fn distance_post(d, p) {
return abs(d + sin(p.y * 120.0) * 0.004) - 0.03;
}
}
The same programmable hooks can also be attached directly to ordinary objects:
var statue = Box { size: vec3(0.55, 1.5, 0.42) };
statue.domain = fn(p) {
return rotate_y(p, p.y * 18.0);
};
statue.distance_post = fn(d, p) {
return abs(d + sin((p.y + 0.75) * 115.0) * 0.0045) - 0.028;
};
Bounds
Without bounds(), custom SDFs fall back to a conservative bound. That keeps rendering correct, but acceleration gets much worse and scenes can become noticeably slower.
Current bounds() behavior:
- it returns a local half-extent as
vec3(...), or a numeric radius - the renderer caches that and uses it for pruning
- approximate bounds are still much better than none
So for most custom shapes, add bounds() early even if it is only approximate.