A node-graph wireframe processing suite for the web. Load any 3D model. Chain visual effects. Generate infinite procedural variations.
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.
![]() |
![]() |
![]() |
![]() |
![]() |
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.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
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*.exeThen double-click dist/Wireframed*.exe. Done.
Requires Node.js on your machine to build. The resulting
.exerequires nothing from the end user.
Windows
launch.bat ← double-click this
Mac / Linux
chmod +x launch.sh
./launch.shBoth 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| 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. |
| 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 | 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
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.
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>{
"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.
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 passeswireframedC/
├── 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)
- 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.
- 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.













