Skip to content
Sample of Voxel Field Cross-Section

nervous_system_hansmeyer · DillenburgerVoxelField

Voxel Field Cross-Section

Benjamin Dillenburger's voxel-printing research (Digital Building Technologies, ETH Zurich, ~2016) replaced the smooth isosurface extraction of conventional architectural geometry with a literal 3D voxel grid: every cell is either solid or empty, and the boundary of the field is what gets fabricated. This gives architects a building block whose resolution matches the printer's voxel size and whose interior porosity is naturally expressible as a 3D scalar field — usually Perlin/simplex FBM with a tuned threshold. The output 'looks like' a stone cross-section but is, in fact, a flat 2D slice through a 3D field. We render a single slice (or a blend of N adjacent slices) as a top-down cross-section: filled voxels become colored squares with darker per-cell edges, the overall field is masked to a circular boundary (the architectural mass), and distance-from-center fades brightness so the structure reads as illuminated from above.

Original: https://benjamin-dillenburger.com/voxel-printing/

▶ Try the live demo (JS port) ▶ Run reference.py via Pyodide 10 formulas have instant JS ports. The Pyodide path runs the actual Python in your browser — first load is ~6 MB, then instant.

Family
nervous_system_hansmeyer
Source
nervous system × hansmeyer
Evolution
geometry
Render
voxel_grid
Complexity
high
Agents
884736
Year
2016
Author
Benjamin Dillenburger (with Michael Hansmeyer)

Math essence

A 3D grid of size N^3 is filled with FBM noise: f(x,y,z) = sum_k persistence^k * noise(lacunarity^k * (x,y,z)), where noise is a value-noise gradient lookup (Perlin's improved-noise interpolant: 6t^5 - 15t^4 + 10t^3 fade on trilinear lattice values). Cells are kept iff f > threshold. A 2D cross-section is taken along axis ∈ {x,y,z} at fractional position t; optionally N adjacent slices are OR-blended to simulate slice thickness. Each filled voxel paints a square of size dim/N pixels, with a thin darker edge, then distance-from-center fade brightens center and darkens periphery.

Parameters

Name Type Default Range Description
dim int 600 200–1400 Square canvas side length
grid_size int 96 16–192 Voxels per axis (N^3 total cells)
noise_scale float 2.6 0.5–8 Lattice coordinate range for noise sampling
threshold float 0.05 -0.6–0.6 FBM cutoff — higher = sparser structure
octaves int 4 1–8 FBM octave count
persistence float 0.55 0.1–0.95 FBM amplitude decay per octave
lacunarity float 2 1.2–4 FBM frequency multiplier per octave
cross_section_axis str z null–null Slice axis (x|y|z)
cross_section_t float 0.5 0–1 Slice position as fraction along axis
radius float 0.46 0.15–0.5 Architectural mass radius as fraction of dim
n_slices_blend int 1 1–7 Number of adjacent slices to OR-blend
voxel_color tuple 220,215,230 null–null Voxel interior RGB
bg_color tuple 8,10,16 null–null Background RGB
accent_color tuple 255,170,110 null–null Periphery fade accent RGB
seed int null 0–999999 RNG seed (drives Perlin permutation)

Reference

First 60 lines of reference.py. Full source: library_external/nervous_system_hansmeyer/DillenburgerVoxelField/reference.py

from __future__ import annotations

from typing import ClassVar

import imageio.v2 as imageio
import numpy as np


def _hash3(seed: int) -> np.ndarray:
    s = np.arange(256 * 256 * 256, dtype=np.uint32)
    s = (s ^ (s >> 16)) * np.uint32(0x85ebca6b)
    s = (s ^ (s >> 13)) * np.uint32(0xc2b2ae35)
    s = s ^ np.uint32(seed & 0xFFFFFFFF) ^ (s >> 16)
    return (s & np.uint32(0xFF)).astype(np.float32) / 255.0


def _fade(t: np.ndarray) -> np.ndarray:
    return t * t * t * (t * (t * 6 - 15) + 10)


def _gradient_lookup_table(seed: int) -> np.ndarray:
    rng = np.random.default_rng(seed)
    grads = rng.normal(0.0, 1.0, size=(16, 3))
    norms = np.linalg.norm(grads, axis=1, keepdims=True)
    norms = np.where(norms < 1e-9, 1.0, norms)
    return grads / norms


class _Perlin3D:
    def __init__(self, seed: int = 0) -> None:
        self.grads = _gradient_lookup_table(seed)
        self.perm = np.arange(256, dtype=np.int32)
        rng = np.random.default_rng(seed * 7919 + 1)
        rng.shuffle(self.perm)
        self.perm = np.concatenate([self.perm, self.perm])

    def noise(self, x: np.ndarray, y: np.ndarray, z: np.ndarray) -> np.ndarray:
        xi = np.floor(x).astype(np.int64) & 255
        yi = np.floor(y).astype(np.int64) & 255
        zi = np.floor(z).astype(np.int64) & 255
        xf = x - np.floor(x)
        yf = y - np.floor(y)
        zf = z - np.floor(z)
        u = _fade(xf)
        v = _fade(yf)
        w = _fade(zf)
        aaa = self.perm[self.perm[self.perm[xi] + yi] + zi] & 15
        aba = self.perm[self.perm[self.perm[xi] + yi + 1] + zi] & 15
        aab = self.perm[self.perm[self.perm[xi] + yi] + zi + 1] & 15
        abb = self.perm[self.perm[self.perm[xi] + yi + 1] + zi + 1] & 15
        baa = self.perm[self.perm[self.perm[xi + 1] + yi] + zi] & 15
        bba = self.perm[self.perm[self.perm[xi + 1] + yi + 1] + zi] & 15
        bab = self.perm[self.perm[self.perm[xi + 1] + yi] + zi + 1] & 15
        bbb = self.perm[self.perm[self.perm[xi + 1] + yi + 1] + zi + 1] & 15

        def g(idx: np.ndarray, dx: np.ndarray, dy: np.ndarray, dz: np.ndarray) -> np.ndarray:
            return np.sum(self.grads[idx] * np.stack([dx, dy, dz], axis=-1), axis=-1)

        g000 = g(aaa, xf, yf, zf)
        g100 = g(baa, xf - 1, yf, zf)

Try running it locally

The reference implementation is self-contained Python with numpy. Install the package and run:

python library_external/nervous_system_hansmeyer/DillenburgerVoxelField/reference.py

Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.

More from nervous system × hansmeyer

See all nervous_system_hansmeyer artworks →