A node-graph wireframe processing suite for the web. Load any 3D model. Chain visual effects. Generate infinite procedural variations.
The full node-graph interface — drag nodes, connect ports, tweak parameters in real time.
What is it?
Wireframed.js is a visual node-graph engine built entirely in the browser with Three.js and vanilla JavaScript. Think Houdini or Blender's geometry nodes — but zero installation, zero build step, and tuned specifically for generating stunning wireframe art from any 3D model.
You connect nodes in a directed graph. The pipeline re-evaluates automatically whenever anything changes, streaming the result into a live Three.js scene. Every node exposes real-time controls. Save and load the whole graph as a compact JSON preset.
Key properties:
- Open
index.html— that's it. Nonpm install, no bundler, no server required. - Import any GLTF / GLB / OBJ / FBX file directly from your disk.
- 30+ processing nodes across 6 categories.
wireframed-runtime.jslets you embed any saved preset into your own Three.js project in ~15 lines of code.
Gallery
Deep Randomize — one model, infinite looks
Hit Deep Randomize and the engine swaps to a new graph topology drawn from 20 curated layouts, then randomizes every parameter. Each click is a completely different aesthetic.
Quick Start
Option A — Windows portable .exe (recommended, no server needed)
Build once, distribute anywhere. The .exe bundles everything — no install, no browser, no server.
npm install # downloads Electron (~200 MB, one time only) npm run dist # builds dist/Wireframed*.exe
Then double-click dist/Wireframed*.exe. Done.
Requires Node.js on your machine to build. The resulting
.exerequires nothing from the end user.
Option B — Local server (no npm required)
Windows
launch.bat ← double-click this
Mac / Linux
chmod +x launch.sh ./launch.sh
Both launchers try Node.js first (node server.js), then fall back to Python's built-in server. Either way, your default browser opens at http://localhost:8080 automatically.
Once the browser is open:
- Use Load Model in the top bar to import a
.glb,.gltf,.obj, or.fbxfile. - Drag nodes from the left panel onto the canvas and connect their ports.
- Select a node to edit its parameters in the right panel.
- Hit Save Preset to export your graph as a
.jsonfile.
Manual alternatives (if you prefer):
node server.js # Node.js — opens browser automatically python -m http.server 8080 # Python 3 — then open http://localhost:8080 npx serve . # if you have npm available
Interface Overview
| Area | Description |
|---|---|
| Top bar | Load Model · Save/Load Preset · Render HD · Superficial Randomize · Deep Randomize · GLB path input |
| Left panel | Node library, organised by category. Drag any node onto the canvas. |
| Centre canvas | Node graph editor. Drag nodes, draw connections by clicking ports, orbit the 3D viewport with the mouse. |
| Right panel | Parameters for the selected node — sliders, colour pickers, dropdowns, live-updating. |
| Preset pills | One-click built-in presets: CyberCrystal, OrganicNeural, FractalVoid, ElegantMinimal. |
Randomize buttons
| Button | Effect |
|---|---|
| Superficial Randomize | Randomizes all numeric and colour parameters on the current graph, keeping the node topology unchanged. |
| Deep Randomize | Picks a new graph topology from 20 curated layouts, rebuilds the graph, then randomizes all parameters. Your loaded model is preserved and re-injected automatically. |
Node Library
Base
| Node | Type key | Description |
|---|---|---|
| Model Loader | base/ModelLoader |
Load GLTF/GLB/OBJ/FBX from file or URL. Auto-centers and scales. |
| Wireframe | base/Wireframe |
Extracts edges + vertices into a glowing line mesh. Custom gradient colours, noise displacement, vertex dots. |
| Merge | base/Merge |
Combines two geometry streams into one. |
| Transform | base/Transform |
Translate, rotate, scale, with optional animation. |
| Output | base/Output |
Sink node — writes geometry and post-passes into the Three.js scene. Every graph must end here. |
Fractal
| Node | Type key | Description |
|---|---|---|
| L-System | fractal/LSystem |
Parametric L-system turtle-graphics tree. Axiom, rules, depth, angle, step all editable. |
| Mandelbulb | fractal/Mandelbulb |
3D Mandelbulb raymarched to a triangle mesh. |
| Sierpinski | fractal/Sierpinski |
Recursive Sierpinski tetrahedron. |
| Koch | fractal/Koch |
Koch snowflake, lifted into 3D. |
| Dragon Curve | fractal/DragonCurve |
Folded paper-dragon iterated fractal as a line geometry. |
Mesh Generation
| Node | Type key | Description |
|---|---|---|
| Tube | meshgen/Tube |
Sweeps input edges into tapered organic tubes. |
| Ribbon | meshgen/Ribbon |
Extrudes edges into flat ribbon strips with twist. |
| Voronoi | meshgen/Voronoi |
Voronoi cell mesh seeded from input geometry vertices. |
| Metaballs | meshgen/Metaballs |
Isosurface metaballs positioned at input vertices. |
| Particle Swarm | meshgen/ParticleSwarm |
GPU-instanced particles swarming around input geometry. |
| Instanced Geo | meshgen/InstancedGeo |
Places a primitive instance at every input vertex. |
Aesthetic
| Node | Type key | Description |
|---|---|---|
| Glow | aesthetic/Glow |
Unreal Bloom post-processing pass. Strength, radius, threshold. |
| Chromatic Aberration | aesthetic/ChromaticAber |
RGB channel split with optional radial vignette. |
| Noise | aesthetic/Noise |
Perlin / Simplex / Worley animated vertex displacement. |
| Distortion | aesthetic/Distortion |
Screen-space ripple, wave, or swirl distortion. |
| Gradient | aesthetic/Gradient |
Maps a 3-stop colour gradient onto geometry by axis. |
| Line Bevel | aesthetic/LineBevel |
Thickens line geometry into bevelled tubes. |
Animation
| Node | Type key | Description |
|---|---|---|
| Verlet Physics | animation/VerletPhysics |
Cloth-like Verlet simulation on input edges. Gravity, wind, damping, stiffness. |
| Growth | animation/Growth |
Animated branch-growth algorithm that progressively reveals geometry. |
| Subdivision | animation/Subdivision |
Adaptive mesh subdivision with noise jitter. |
| Attractor | animation/Attractor |
Strange attractor trail (Lorenz, Halvorsen, Thomas, etc.). |
| Camera Path | animation/CameraPath |
Procedural orbital camera animation with configurable radius and duration. |
Export
| Node | Type key | Description |
|---|---|---|
| Image Export | export/Image |
Captures the current frame at any resolution and downloads as PNG. |
| GLTF Export | export/GLTF |
Exports the processed scene as a GLTF file. |
Built-in Presets
| Preset | Description |
|---|---|
| CyberCrystal | Electric cyan wireframe with Unreal Bloom glow and RGB chromatic split. |
| OrganicNeural | Pulsing neural mesh — Verlet physics on tube-wrapped edges, warm amber tones. |
| FractalVoid | Recursive cosmic horror in deep violet — Mandelbulb merged with an L-system, maximum chaos. |
| ElegantMinimal | Museum-quality monochrome wireframe with slow orbital camera. Clean, restrained, hypnotic. |
Embedding in Your Own Project
Wireframed.js ships with wireframed-runtime.js — a self-contained headless engine that lets you load any saved preset into an existing Three.js scene. No UI, no dependencies beyond Three.js itself.
The sampleProject/ boilerplate
sampleProject/index.html is the minimal reference integration. Below is the full file with every line explained:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Wireframed Sample</title> <!-- Keep the canvas flush with the window edges and set a dark background that matches the wireframe aesthetic. --> <style>body { margin: 0; overflow: hidden; background: #05080f }</style> <!-- Import map — tells the browser where to resolve bare specifiers like 'three' and 'three/addons/'. This is how Wireframed.js loads Three.js without a bundler. Pin the version to r168 to match the engine. --> <script type="importmap"> { "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.168.0/build/three.module.js", "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/jsm/" } } </script> </head> <body> <script type="module"> // ── Imports ─────────────────────────────────────────────────────────────────── // Standard Three.js scene primitives. import * as THREE from 'three' // OrbitControls — lets the user rotate/zoom/pan with the mouse. import { OrbitControls } from 'three/addons/controls/OrbitControls.js' // GLTFLoader — loads .glb / .gltf files. Swap for OBJLoader or FBXLoader // if your model uses a different format. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js' // EffectComposer pipeline — required for Wireframed's post-processing nodes // (Glow, ChromaticAber, Distortion) to work. The composer wraps the renderer // and runs the registered passes in sequence. import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js' import { RenderPass } from 'three/addons/postprocessing/RenderPass.js' import { OutputPass } from 'three/addons/postprocessing/OutputPass.js' // WireframedRuntime — the headless Wireframed engine. No UI, just the node // graph execution pipeline. Import path is relative to this HTML file. import { WireframedRuntime } from '../wireframed-runtime.js' // ── Renderer ────────────────────────────────────────────────────────────────── const renderer = new THREE.WebGLRenderer({ antialias: true }) renderer.setSize(innerWidth, innerHeight) renderer.setPixelRatio(devicePixelRatio) // ACESFilmic tone mapping gives bloom and HDR colours a cinematic look — // strongly recommended when using the Glow node. renderer.toneMapping = THREE.ACESFilmicToneMapping document.body.appendChild(renderer.domElement) // ── Scene & Camera ──────────────────────────────────────────────────────────── const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.01, 1000) camera.position.set(0, 0.5, 2) // pull back slightly so the model is in frame const controls = new OrbitControls(camera, renderer.domElement) controls.enableDamping = true // smooth inertia on mouse release // ── Post-processing composer ────────────────────────────────────────────────── // Build the composer with exactly two passes before handing it to Wireframed: // 1. RenderPass — renders the scene normally // 2. OutputPass — tone-maps and writes to the screen // // WireframedRuntime will inject its own passes (Bloom, ChromaticAber, etc.) // between these two whenever the graph contains post-processing nodes. const composer = new EffectComposer(renderer) composer.addPass(new RenderPass(scene, camera)) composer.addPass(new OutputPass()) // ← Wireframed inserts its passes before this // ── Responsive resize ───────────────────────────────────────────────────────── window.addEventListener('resize', () => { camera.aspect = innerWidth / innerHeight camera.updateProjectionMatrix() renderer.setSize(innerWidth, innerHeight) composer.setSize(innerWidth, innerHeight) }) // ── Wireframed runtime ──────────────────────────────────────────────────────── // Create a runtime instance, passing your renderer/scene/camera/composer. // Each instance owns its own independent node graph — you can create multiple // runtimes in the same page for different objects. const wf = new WireframedRuntime({ renderer, scene, camera, composer }) // Load a preset JSON file (the graph topology + all node parameters). // You can save presets from the Wireframed.js editor via Save Preset. // loadPreset() accepts a URL string or a plain JSON object. await wf.loadPreset('./sampleProject/testPreset.wf.json') // Load your model, then tell the runtime which object to process. // applyToModel() finds the ModelLoader node in the preset and feeds it // the geometry extracted from the GLTF scene. The node graph re-evaluates // immediately and the result appears in the scene. const gltf = await new GLTFLoader().loadAsync('./sampleProject/testModel.glb') wf.applyToModel(gltf.scene) // Other apply helpers: // wf.applyToScene() — processes everything currently in the scene // wf.applyToModels([a, b]) — maps an array of models to multiple ModelLoader nodes // ── Animation loop ──────────────────────────────────────────────────────────── renderer.setAnimationLoop(() => { controls.update() // required for damping to work // wf.tick() advances all time-based nodes: Verlet physics, Growth algorithm, // Attractor trails, Camera Path animation, animated noise, etc. // Call it every frame. Optionally pass a delta time in seconds: wf.tick(dt) wf.tick() // Render through the composer, not renderer.render(), so that all // Wireframed post-processing passes are executed. composer.render() }) </script> </body> </html>
testPreset.wf.json — the preset format
{
"name": "testPreset",
"version": "0.1.0",
"nodes": [
{ "id": "n1", "type": "base/ModelLoader", "position": { "x": 60, "y": 200 }, "params": {} },
{ "id": "n2", "type": "base/Wireframe", "position": { "x": 340, "y": 200 },
"params": { "colorA": "#00d4ff", "colorB": "#7b2fff", "opacity": 1.0, "glowWidth": 0.6 } },
{ "id": "n3", "type": "aesthetic/Glow", "position": { "x": 340, "y": 380 },
"params": { "strength": 1.5, "radius": 0.4, "threshold": 0.05 } },
{ "id": "n4", "type": "base/Output", "position": { "x": 620, "y": 280 }, "params": {} }
],
"connections": [
{ "id": "c1", "fromNodeId": "n1", "fromPort": "geometry", "toNodeId": "n2", "toPort": "geometry" },
{ "id": "c2", "fromNodeId": "n2", "fromPort": "geometry", "toNodeId": "n4", "toPort": "geometry" },
{ "id": "c3", "fromNodeId": "n3", "fromPort": "postpass", "toNodeId": "n4", "toPort": "postpass" }
]
}A preset is a plain JSON file. Every node has an id, a type (matching the registry key), a position for the graph canvas, and a params object with its settings. Connections link fromNodeId/fromPort → toNodeId/toPort. The graph is evaluated in topological order — no cycles allowed.
Runtime API reference
const wf = new WireframedRuntime({ renderer, scene, camera, composer }) // Load a preset await wf.loadPreset(urlOrJsonObject) // from URL string or parsed JSON wf.loadPresetFromString(jsonString) // from a raw JSON string // Inject geometry wf.applyToModel(object3D) // first ModelLoader node gets this model wf.applyToModels([obj1, obj2]) // multiple models → multiple ModelLoader nodes in order wf.applyToScene() // every mesh in the scene // Animation wf.tick(dt?) // advance by dt seconds (defaults to clock delta) // Live parameter editing wf.setParam('base/Wireframe', 'colorA', '#ff0000') // by node type wf.setParam('My Label', 'strength', 2.0) // by node label // Cleanup wf.dispose() // removes scene objects, clears composer passes
Architecture
wireframedC/
├── index.html Entry point — importmap + UI mount
├── wireframed.js App bootstrap: creates Engine, mounts UI panels
├── wireframed-runtime.js Headless runtime for embedding in external projects
│
├── core/
│ ├── Engine.js Three.js scene manager, render loop, post-pass chain
│ ├── NodeGraph.js DAG: add/remove nodes, connect ports, dirty propagation
│ ├── Pipeline.js Topological sort (Kahn's algorithm), node execution
│ ├── NodeRegistry.js Central node type registry — maps type keys to classes
│ ├── PresetManager.js Serialize / deserialize / download graph JSON
│ └── Store.js Global pub/sub event bus + key-value state
│
├── nodes/
│ ├── BaseNode.js Base class: paramDefs, inputPorts, outputPorts, execute()
│ ├── base/ ModelLoader, Wireframe, Merge, Transform, Output
│ ├── fractal/ LSystem, Mandelbulb, Sierpinski, Koch, DragonCurve
│ ├── meshgen/ Tube, Ribbon, Voronoi, Metaballs, ParticleSwarm, InstancedGeo
│ ├── aesthetic/ Glow, ChromaticAber, Noise, Distortion, Gradient, LineBevel
│ ├── animation/ VerletPhysics, Growth, Subdivision, Attractor, CameraPath
│ └── export/ Image, GLTF
│
├── ui/
│ ├── TopBar.js Top toolbar, Deep/Superficial Randomize, preset pills
│ ├── LeftPanel.js Categorised node library with drag-to-canvas
│ ├── NodeEditorCanvas.js Canvas node editor, arc drawing, port hit-testing
│ ├── RightPanel.js Property inspector for selected node
│ └── PropertyWidgets.js Slider, colour picker, dropdown, text, action widgets
│
├── utils/
│ ├── LoaderUtils.js Unified GLTF/OBJ/FBX loader with format detection
│ ├── GeometryUtils.js Edge extraction, geometry merging, centering helpers
│ ├── MathUtils.js seededRandom, smoothstep, lerp, noise functions
│ └── ColorUtils.js Hex ↔ RGB, gradient sampling, palette generation
│
├── presets/
│ ├── CyberCrystal.json
│ ├── OrganicNeural.json
│ ├── FractalVoid.json
│ └── ElegantMinimal.json
│
└── sampleProject/
├── index.html Minimal Three.js integration boilerplate
├── testPreset.wf.json Example preset (ModelLoader → Wireframe → Glow)
└── testModel.glb Placeholder model (replace with your own)
How the pipeline works
- Dirty propagation — when a node's parameter changes,
NodeGraph.markDirty(id)cascades forward through the DAG using BFS, marking every downstream node dirty. - Execution —
Pipeline.run()performs a topological sort (Kahn's algorithm) and callsnode.execute(inputs, engine, dt)only on dirty nodes, in dependency order. - Scene management —
OutputNodereceives the final geometry and callsengine.addToScene(). The engine maintains anodeId → Object3Dmap; adding the same object reference twice is a no-op. - Post-processing — nodes that produce a post-pass (Glow, ChromaticAber, Distortion) return
Output.postpass(pass).OutputNodecollects them andengine.setPostPassChain()splices them into the EffectComposer beforeOutputPass.
Ideas for Future Extensions
- GLSL shader node — an in-browser shader editor node (Monaco or CodeMirror) that lets you write custom vertex/fragment code and wire it into the pipeline.
- Audio-reactive parameter binding — map Web Audio API frequency bands to any node parameter in real time, turning the engine into a music visualiser.
- MP4 / image-sequence export — use the MediaRecorder API to capture the animation loop and export a video directly from the browser.
- Collaborative presets — a lightweight backend (Cloudflare Worker + KV) where users can share, browse, and remix community presets by URL.
- WebGPU compute path — offload heavy geometry generation (Mandelbulb, Voronoi, particle swarms) to WebGPU compute shaders for 10–100× faster evaluation on capable devices.
