ForgedThoughts
A 3D modeling and rendering programming language utilizing SDFs.
Install / Use
/learn @markusmoenig/ForgedThoughtsREADME
ForgedThoughts
ForgedThoughts is a Rust workspace for a small scene language (.ft) and a CPU renderer focused on signed distance field scenes.
Current state:
- Forge parser, evaluator, and scene loading
- Fast depth preview rendering from
.ftfiles - Classical Whitted-style CPU rendering for lookdev
- Acceleration backends:
naive,bvh,bricks - Built-in lights:
PointLight,SphereLight,EnvLight - Built-in material backends:
Lambert,Metal,Dielectric - Forge-defined material hooks for:
color,roughness,ior,thin_walledemission_color,emission_strengthmedium,subsurfaceeval,pdf,sample
Workspace
crates/forgedthoughts: core language + renderer librarycrates/ftc: CLI frontendexamples/: sample.ftscenes and their rendered.pngoutputs
Quickstart
Build:
cargo build
Validate a scene:
ftc check --scene examples/mvp.ft
Trace renderer:
ftc --scene examples/glass.ft
Trace renderer with supersampling:
ftc --scene examples/glass.ft --aa 4
Depth preview:
ftc depth --scene examples/mvp.ft
Depth preview with smoother edges:
ftc depth --scene examples/mvp.ft --aa 4
Watch and re-render on save:
ftc depth --scene examples/mvp.ft --watch
Acceleration benchmark:
ftc bench --scene examples/mvp.ft --iterations 5 --warmup 1
Outputs default to the scene path with .png extension, so examples/glass.ft renders to examples/glass.png.
Renderers
Main renderer
- Classical Whitted-style CPU renderer for quick iteration
- Progressive tiled updates
- Supports
--aafor camera supersampling - Supports debug AOVs with
--debug-aov - Uses the shared material system, but still has some hardcoded reflection/refraction logic internally
depth
- Fast depth preview renderer
- Intended for quick shape iteration
- Supports
--aafor smoother depth edges - Supports
--watchfor iterative modeling loops
Language Snapshot
Forge is object-like, incremental, and scriptable:
var sphere = Sphere {
radius: 1.0
};
sphere.pos.y = 0.3;
let mat = Dielectric {
color: #f5fcff,
ior: 1.52,
roughness: 0.02,
thin_walled: 0.0
};
sphere.material = mat;
let scene = sphere;
Semantic assets can also expose part-oriented material assignment:
var table = Table {
width: 1.7,
depth: 0.9,
height: 0.78
};
table.top.material = Lambert { color: #7a4c35 };
table.legs.material = Metal { color: #2b3138, roughness: 0.22 };
var vase = Sphere { radius: 0.18 }
.attach(table.top, Top);
var lamp = Lamp {}
.attach(cupboard.body, Top, Bottom)
.face_to(table.top);
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);
Supported language pieces today include:
- top-level functions
- top-level imports
- top-level exports
let/var- nested property assignment like
pos.xandrot.z - object literals
- scalar and
vec3math - hex color literals like
#ff0000and#f00 - material definitions with local bindings and functions
- environment definitions with local bindings and functions
- custom SDF definitions with programmable hooks like
distance(p), optionaldomain(p), and optionaldistance_post(d, p) - hard booleans with
+,-, and& - named
hg_sdf-style boolean variants likeunion_round,diff_chamfer, andintersect_stairs
Example Forge material:
material SoftGold {
model: Metal;
color = vec3(0.92, 0.78, 0.34);
fn eval(ctx) {
let ndotl = max(dot(ctx.normal, ctx.wi), 0.0);
return mix(vec3(0.08, 0.06, 0.03), color, ndotl) * (1.0 / 3.14159265);
}
fn pdf(ctx) {
return max(dot(ctx.normal, ctx.wi), 0.0) / 3.14159265;
}
fn sample(ctx) {
return BsdfSample {
wi: ctx.normal,
f: color * (1.0 / 3.14159265),
pdf: 1.0,
delta: 0.0,
apply_cos: 1.0,
transmission: 0.0,
thin_walled: 0.0,
next_ior: ctx.current_ior
};
}
};
Example top-level helper functions:
fn accent() {
return #ebc757;
}
fn tint(base, amount) {
return mix(base, vec3(1.0), amount);
}
fn make_gold() {
return Metal {
color: tint(accent(), 0.12),
roughness: 0.18
};
}
See:
examples/lambert.ftexamples/metal.ftexamples/glass.ftexamples/ft_sdf.ftexamples/imports.ftexamples/ft_material.ftexamples/ft_material_glass.ftexamples/ft_material_wax.ftexamples/ft_bsdf.ft
Example custom SDF:
sdf SoftBlob {
let wave_scale = 0.16;
fn bounds() {
return vec3(1.2, 1.2, 1.1);
}
fn warp(p) {
return vec3(p.x, p.y + sin(p.x * 4.0) * wave_scale, p.z);
}
fn distance(p) {
let q = warp(p);
return length(q) - 1.0;
}
};
let scene = SoftBlob {};
fn bounds() is optional, but it matters for performance. Without it, custom SDFs fall back to a very conservative bound and acceleration quality drops sharply.
Custom SDFs can also use programmable modifier hooks:
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;
}
}
Ordinary objects can use the same idea directly:
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;
};
Example procedural environment:
environment Sky {
let zenith = #4d74c7;
let horizon = #d8e7ff;
fn color(dir) {
let t = clamp(dir.y * 0.5 + 0.5, 0.0, 1.0);
return mix(horizon, zenith, t);
}
};
color(dir) is used as the visible background on misses in the main renderer and in depth.
Imports
Forge supports top-level imports:
import "./shared/materials.ft";
import "Gold" as gold;
import "SoftBlob" as blob;
import "Studio";
Import rules:
./...and../...resolve relative to the current file on diskmaterials/...,objects/..., andscenes/...resolve from the embedded built-in library- bare built-in names like
Glass,SoftBlob, andStudioalso resolve from the embedded built-in library as namenamespaces the imported top-level symbols undername.- each import is loaded only once
- cyclic imports are rejected
Files can also declare explicit exports:
let private_color = #ebc757;
material Gold {
model: Metal;
color = private_color;
roughness = 0.18;
};
export { Gold };
List the built-in library from the CLI:
ftc list materials
ftc list objects
ftc list scenes
Each entry includes its path, a short description, and semantic tags.
Material Model
There are two layers right now:
- Built-in host BSDF backends:
Lambert,Metal,Dielectric - Forge-side overrides on top of those backends
A Forge material can:
- set static properties like
color = vec3(1.0) - compute dynamic properties from hit context with
fn color(ctx) { ... } - define custom
eval(ctx),pdf(ctx), andsample(ctx)hooks
Current hit/BSDF context includes values such as:
ctx.positionctx.local_positionctx.normalctx.view_dirctx.woctx.wictx.current_iorctx.u1,ctx.u2,ctx.u3forsample(ctx)
Current Limits
subsurfaceexists as material data, but true subsurface transport is not implemented yetmediumcurrently affects transmission through simple Beer-Lambert attenuation- Forge-defined
eval/pdf/sampleare currently most useful through the shared material system, but the renderer still has some backend-specific recursion logic - Forge material functions now use the VM/JIT path for the supported numeric and vec3 subset, with interpreter fallback for the rest
Development
Run checks:
cargo test
cargo clippy --all-targets --all-features -- -D warnings
Direction
The current direction is:
- keep CPU rendering practical for complex SDF scenes
- push Forge materials from parameter scripting toward self-contained shareable shading code
- widen VM/JIT coverage and only then decide where a broader compiler path is worth the added complexity
