Skip to content

Repository files navigation

Interflect

A renderer that doesn't sample. It solves.

CI Release crates.io License Rust

No Monte Carlo. No denoiser. No GPU. No model weights. 0.5 MB. Three dependencies.

Built on work by Goral, Torrance, Greenberg & Battaile (1984) · Hart (1996) · Nusselt (1928) · Lambert (1760) · Heitz · Quilezsee everyone


Both of these took the same time

Path tracer — 1642 ms Interflect — 1499 ms
noisy clean
8 samples per pixel 1 evaluation per pixel

The left image is grainy because a path tracer guesses, then averages millions of guesses. Reaching the quality on the right takes it 28 seconds — or a neural denoiser.

Interflect has no grain to remove. It computes the answer directly, so there is no sample count, nothing to converge, and nothing to clean up afterwards.

Alcove: white spheres tinted red and blue purely by bounced light Product shot: chrome sphere, drilled bowl, torus and capsule
Left: the light never touches the red or blue walls directly. Every trace of colour on the white spheres arrived by bounce.

Measured, not claimed

Against a converged path trace of the identical scene. Reproduce with ./bench.sh.

Scene Interflect Path tracer Speedup SSIM Energy
sphere_only 1381 ms 26136 ms 18.9× 0.850 1.090
box_only 1757 ms 39115 ms 22.3× 0.839 1.066
high_albedo 1395 ms 31190 ms 22.4× 0.820 1.049
cornell 1613 ms 28364 ms 17.6× 0.806 1.050

Read SSIM against a ceiling of 0.892, not 1.0. The reference is stochastic, and SSIM penalises its own noise even against a perfect image — two references of the same scene differing only in sample count score 0.892 against each other. Total energy lands within 4.9–9.0% of ground truth.

6-core / 12-thread AMD Ryzen 5 4600H, 260×260, reference at 384 spp.

Output is bit-identical. Always.

$ interflect render scenes/cornell.rad -o a.png -t 1
$ interflect render scenes/cornell.rad -o b.png -t 12
$ md5sum a.png b.png
5846eb0cbdcf970b6679d666a01112f7  a.png
5846eb0cbdcf970b6679d666a01112f7  b.png

The two hashes match each other — that is the guarantee. On any thread count, on any run, the same binary and the same scene produce the same bytes. CI enforces it on every commit.

The literal value above is not itself a promise: it is specific to this build, and any change that alters inlining shifts a handful of pixels at silhouette edges. Determinism means reproducible, not frozen across versions.


Install

No Rust needed. Downloads a 0.5 MB binary:

curl -fsSL https://raw.githubusercontent.com/Cherie05/interflect/main/install.sh | sh
irm https://raw.githubusercontent.com/Cherie05/interflect/main/install.ps1 | iex

Rather not pipe a script to a shell? Plain archives and SHA256SUMS are on the Releases page.

Other ways to install
cargo binstall interflect   # prebuilt, no compile
cargo install interflect    # builds from source

From source:

git clone https://github.com/Cherie05/interflect
cd interflect
cargo build --release       # ~15 s

First render

interflect render scenes/product.rad -o out.png
interflect 0.1.3
  scene      7 objects, 2 lights, 6 materials, 3 bvh nodes
  film       640x480
  surfels    12270 placed of 20000 requested   (364 ms)
  transfer   311 clusters, 110026 links, row-sum 0.109, 10.4M sdf evals   (90 ms)
  solve      12 bounces, residual 5.37e-5, 0.3M sdf evals   (6 ms)
  shade      148 ms   15.3M sdf evals   50 evals/px
  total      609 ms

The idea

Radiosity died in the 1990s because it needed meshing. Signed distance fields don't have meshes. So it deserves a second look — and with no GPU and no denoiser allowed, it wins.

Classical radiosity (Goral et al., 1984) produced beautiful, completely noise-free, view-independent global illumination. It lost to path tracing for one reason: it needed every surface subdivided into well-conditioned patches, and automatic meshing was fragile, slow and artefact-prone.

An SDF has no mesh to subdivide. Its surface is the zero level set, and any point in space projects onto it by Newton iteration along the gradient — p ← p − f(p)·∇f(p) — converging in a handful of steps. Patches generate directly, at any density, with no topology, no seams and no failure cases.

From 1995 onward the field moved to GPUs, where Monte Carlo plus a neural denoiser is unbeatable. Refuse the GPU and refuse the denoiser, and the calculus inverts.

This is not unexplored ground, and it would be dishonest to imply otherwise. SDFs have been used for global illumination before — SDFDDGI (2020) and Zhang et al. (2021) both do real-time diffuse GI with distance fields, using irradiance probes with the SDF for visibility. Surfel radiosity is also live research: Gaussian Surfels via Adapted Radiosity (2025) builds a differentiable light-transport framework on exactly the classical radiosity theory, and for the same reason — it is view-independent and free of Monte Carlo noise.

What is different here is the combination and the constraints: patches placed directly on an SDF by Newton projection rather than probes in a volume, a classical form-factor solve rather than a learned or sampled one, and the whole thing on a CPU with bit-identical output in a 0.5 MB binary. Whether that combination is genuinely new, or merely uncommon, is a question for someone who knows the literature better than I do.

How the pipeline works
Stage What happens
sdf.rs / bvh.rs Analytic primitives with CSG. A binned-SAH BVH turns the nearest-distance query from O(n) to O(log n).
surfel.rs New. A Halton point set is projected onto the SDF surface by Newton iteration, then Poisson-disc thinned. No mesh, no RNG.
ltc.rs / shade.rs Area lights integrated in closed form — Lambert's polygon formula, the identity case of Linearly Transformed Cosines. Shadows are cone-traced from the distance field. Both noise-free.
formfactor.rs New. Nusselt-analog disc-to-disc form factors between surfels and normal-bucketed clusters, with cone-traced visibility, built once into a sparse CSR matrix.
solve.rs New. Jacobi iteration on the cached matrix. Each extra bounce is one sparse mat-vec.

Because the solve is view-independent, a new camera costs only the gather pass. --turntable 12 renders twelve orbit frames from one solve — 390 ms to solve, then 80 ms per frame.


Standing on

Almost nothing in this renderer is original. The one novel piece is the combination — meshless surfel placement on an SDF feeding a classical radiosity solve. Every component it is built from is someone else's work, and several of these people solved their part decades before there was a reason to render anything with it.

Whose work Year What it does here
Cindy M. Goral, Kenneth E. Torrance, Donald P. Greenberg, Bennett BattaileModeling the Interaction of Light between Diffuse Surfaces 1984 Radiosity itself. This renderer is an argument about their paper.
John C. HartSphere Tracing 1996 Every ray. Needs only a bound on the derivative, which is why it survives creased surfaces.
Wilhelm NusseltGraphische Bestimmung des Winkelverhältnisses bei der Wärmestrahlung 1928 The disc-to-disc form factor at the heart of the transfer matrix. A heat-transfer physicist, working on thermal radiation.
Johann Heinrich LambertPhotometria 1760 The closed form for a polygon's cosine-weighted solid angle. A path tracer spends hundreds of shadow rays approximating what he solved analytically 265 years ago.
Eric Heitz, Jonathan Dupuy, Stephen Hill, David Neubelt — Linearly Transformed Cosines 2016 The modern generalisation of Lambert's result.
Bruce Walter, Stephen Marschner, Hongsong Li, Kenneth Torrance — Microfacet Models 2007 The GGX distribution for glossy surfaces.
Christophe Schlick 1994 The Fresnel approximation.
John Halton 1960 The low-discrepancy sequence that places every surfel. Choosing this over an RNG is why the output is bit-identical.
Robert BridsonFast Poisson Disk Sampling 2007 Keeps surfel spacing even.
Inigo Quileziquilezles.org 2001– Every distance function, the soft shadows, the tone-mapped look. Published freely for two decades, and the reason SDF rendering is accessible to anyone at all.
Cornell University Program of Computer Graphics — the Cornell Box 1984 Still the measuring stick. Still the first scene worth testing against.

Bennett Battaile is the least-cited of the 1984 four. Wilhelm Nusselt never saw a computer. Lambert did the integral by hand.

CREDITS.md has the full list — 17 entries with citations, including Karis, Duff et al., Narkowicz, Pharr/Jakob/Humphreys and the Smith-visibility work — and the source files name them again at the point of use.

Making your own scene

Scenes are plain text. You never touch Rust.

The easy way

Open tools/scene-builder.html in any browser. Drag shapes in a front and top view, set sizes and colours with sliders, and it writes the scene file for you. Single offline HTML file — no server, no build, no 3D experience needed.

The direct way

cp scenes/TEMPLATE.rad scenes/mine.rad
interflect render scenes/mine.rad -o mine.png -w 300 -h 220   # ~0.2 s

Two rules cover most of it: the floor is at Y = 0, and to rest something on the floor, set its Y to its radius.

render   { width: 800, height: 600, surfels: 20000, bounces: 16 }
camera   { pos: [0, 1.8, 6.0], look: [0, 0.7, 0], fov: 38 }
material "red" { albedo: [0.70, 0.15, 0.12], roughness: 0.8 }

sphere   { center: [0, 0.6, 0], radius: 0.6, mat: "red" }
box      { center: [1.6, 0.5, 0], size: [1, 1, 1], round: 0.08, mat: "red" }
capsule  { a: [0,0,0], b: [0,1.3,0], radius: 0.25, mat: "red" }   # rounded ends
cylinder { a: [0,0,0], b: [0,1.0,0], radius: 0.3, mat: "red" }    # flat ends
cone     { a: [0,0,0], b: [0,1.1,0], radius: 0.4, mat: "red" }    # to a point
torus    { center: [0, 0.12, 0], major: 0.45, minor: 0.12, mat: "red" }

# drill a hole -- no boolean rebuild, no re-tessellation
sphere { center: [0,1,0], radius: 0.5, mat: "red", subtract_sphere: [[0,1.2,0], 0.4] }

# vertices counter-clockwise as seen from the emitting side
light { verts: [[-1.5,4,-1.5],[1.5,4,-1.5],[1.5,4,1.5],[-1.5,4,1.5]], emit: [13,12.5,11.5] }

Commands

interflect render <scene.rad> [OPTIONS]

  -o, --output <FILE>    output PNG                  [default: out.png]
  -t, --threads <N>      worker threads              [default: all cores]
  -w, --width  <N>       override scene width
  -h, --height <N>       override scene height
      --mode <MODE>      beauty | direct | indirect | reference
                         | normals | depth | steps
      --surfels <N>      override surfel count
      --bounces <N>      override bounce count
      --clusters <N>     transfer cluster resolution [default: 8]
      --spp <N>          samples/pixel for --mode reference
      --turntable <N>    N orbit frames from one GI solve
      --no-bvh           linear scan; verifies BVH correctness

--mode direct renders without the solve. The difference against beauty is exactly what the radiosity contributes.


FAQ

Is this faster than Blender Cycles or PBRT?

For the scenes it targets, against a converged render, yes — roughly 20×. But it is not a Cycles replacement. It has no caustics, no refraction, no participating media and no triangle meshes. It renders analytic primitives with correct diffuse global illumination, very fast, with no noise.

Why no denoiser?

There is nothing to denoise. Denoisers exist to clean up Monte Carlo variance, and this renderer has no Monte Carlo in it. Every modern denoiser is also a neural network, which would break the "no model weights" property.

Why does determinism matter?

Reproducibility. A render from this repo today produces a byte-identical file next year, on any thread count. That constrains the whole design — no RNG, no parallel float reductions, Jacobi instead of Gauss-Seidel — and CI enforces it. See CONTRIBUTING.md.

Can it render a photorealistic person, animal, or product photo?

No. There is no fur, no cloth, no skin, and no way to import a model. The vocabulary is spheres, boxes, capsules, cylinders, cones and tori. You can build a stylised figure from those — scenes/dog.rad does — but it will read as a toy, not a photograph.

What is it actually good for?

Product and packaging shots, architectural interiors, abstract and generative compositions, motion-graphics stills, turntables, and CI pipelines that need deterministic images without a GPU.

Why "Interflect"?

From interreflection — the term for light bouncing between surfaces, which is exactly what the solver computes. The difference between --mode direct and --mode beauty is the interreflected light.


What it will not do

Honest limits, and most of them are what make it fast:

  • No caustics. Light focused through glass needs bidirectional transport.
  • No refraction. Dielectrics are not implemented.
  • Blurry reflections are attenuated, not blurred. The specular indirect path traces the exact mirror direction; a roughness-driven cone trace is next.
  • Diffuse-dominant GI. Full glossy interreflection is out of scope.
  • No participating media. No fog, smoke or subsurface scattering.
  • Analytic primitives only. No triangle meshes.
  • Known artefact: faint contour banding in penumbrae where a shadow ray runs nearly parallel to a large surface.

Contributing

CONTRIBUTING.md has the two rules specific to this project — a bug fix ships with a test that fails first, and determinism is not negotiable — plus a list of good first issues.

TESTING.md covers four testing tiers, what CI gates, and a catalogue of what each failure mode looks like on screen.

25 regression tests. Every test in bvh, ltc, trace, formfactor and solve reproduces the failure signature of a real bug found during development. They are not coverage padding.

License

MIT — free to use, modify and redistribute, with no warranty.

NOTICE.md records the published algorithms this renderer implements and its dependency licences. DISCLAIMER.md restates the warranty position in plain language and gives the context the benchmark figures should be read with.

About

A renderer that doesn't sample. It solves. Noise-free CPU global illumination — no Monte Carlo, no denoiser, no GPU.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages