guide·intermediate·updated 2026-09-05

Procedural 3D Generation in Three.js: Skip the Asset Files

Generate 3D models at runtime using Three.js instead of downloading files. Get infinite variety, zero storage costs, and 2-10x faster loads. Production patterns included.

Astra 63d models

TL;DR: Stop downloading 3D model files. Generate geometry on-the-fly using math functions. Get infinite variety, zero file storage, and 2-10x performance gains. Production-ready patterns included.


The Problem: Heavy 3D Assets

Your web app needs 3D models. Your options:

Option A (Traditional): Download .gltf/.fbx files (10–500 MB each). Wait for download. Store on S3 (costs money). Models are static.

Result: Slow page loads, high bandwidth costs, limited variety.

Option B (Procedural): Generate geometry at runtime from TypeScript code. Instant generation. Zero files. Infinite variations.

Result: Fast loads, zero storage costs, dynamic models.

This is why elite developers (Tom Krcha at Warp, Chiro Visuals) generate models instead of downloading them.


What Is Procedural 3D Generation?

Procedural generation = math functions that output 3D geometry.

Instead of:

Model file (downloaded) → render

You do:

JavaScript function → BufferGeometry → render

Real example:

// Download approach: Large file, download wait time
const model = await loadGLTF('train.gltf');
 
// Procedural approach: 0 KB file, instant generation
const trainGeometry = generateTrainGeometry({
  wheelRadius: 5,
  bodyLength: 20,
  carriages: 3
});

The train doesn't exist as a file. It's computed from parameters in real-time.


Why This Matters: Real Numbers

Scenario: You're building a SaaS product design tool. Users customize 3D objects (trains, buildings, furniture).

Old way (asset files):

  • Store 100 model variations: 5,000 MB on S3
  • Monthly S3 costs: ~$100+
  • Download latency: 2–5 seconds per model
  • Can't generate new combinations on-the-fly New way (procedural generation):
  • Generate any combination: 0 MB storage
  • Monthly S3 costs: ~$5 (just code)
  • Generation latency: milliseconds (instant to user)
  • Infinite variations from single code Performance metrics (verified, 2026):

On a 3D model visualization (like procedural train generation):

  • Asset-based: Large file download + rendering wait time
  • Procedural: 0 MB + instant runtime generation
  • Savings: No asset storage, instant generation For 1,000 concurrent users:
  • Asset approach: 45 GB bandwidth bill
  • Procedural approach: negligible network cost

How It Works: The Pattern

Step 1: Define Parameters

const boxParams = {
  width: 10,
  height: 20,
  depth: 5,
  color: 0xff0000
};

Step 2: Generate Geometry (Math Functions)

const geometry = new THREE.BoxGeometry(
  boxParams.width,
  boxParams.height,
  boxParams.depth
);

Step 3: Create Mesh & Render

const material = new THREE.MeshStandardMaterial({ 
  color: boxParams.color 
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

The key insight: The geometry is created by math functions, not downloaded files. Change parameters → different geometry, instantly.


Real-World Pattern: Procedural Wheels

Problem: A wheel is just a cylinder with specific parameters (radius, thickness, spoke count).

Why download a 5 MB wheel model when you can compute it?

function generateWheel(radius, thickness, spokes) {
  // Create cylinder for rim
  const rimGeometry = new THREE.CylinderGeometry(
    radius,      // outer radius
    radius,      // inner radius  
    thickness,   // thickness
    32           // segments
  );
 
  // Create spokes (repeated cylinders)
  const spokeGeometry = new THREE.CylinderGeometry(0.5, 0.5, radius, 8);
  
  // Create spokes array
  const spokes = [];
  for (let i = 0; i < spokes; i++) {
    const angle = (i / spokes) * Math.PI * 2;
    const spoke = new THREE.Mesh(spokeGeometry, material);
    spoke.position.set(
      Math.cos(angle) * radius / 2,
      0,
      Math.sin(angle) * radius / 2
    );
    spoke.rotation.z = angle;
    spokes.push(spoke);
  }
  
  // Merge geometries
  const merged = BufferGeometryUtils.mergeGeometries([
    rimGeometry,
    ...spokes.map(s => s.geometry)
  ]);
  
  return merged;
}
 
// Usage: Change parameters → different wheels
const smallWheel = generateWheel(5, 1, 6);   // 6-spoke wheel
const largeWheel = generateWheel(15, 2, 8);  // 8-spoke wheel

File size: 0 bytes for both wheels (just code)
Generation time: Instant (no file download needed)
Variations possible: Infinite (any radius, thickness, spoke count)


Advanced Pattern: Noise-Based Geometry

For organic shapes (terrain, plants, crystals), use Perlin noise or Simplex noise.

Concept: Noise generates random values that look natural (not random-looking).

import { Perlin } from 'three/examples/jsm/math/Perlin.js';
 
function generateTerrain(width, height, scale) {
  const geometry = new THREE.BufferGeometry();
  const perlin = new Perlin();
  
  // Create height values using Perlin noise
  const vertices = [];
  for (let x = 0; x < width; x++) {
    for (let z = 0; z < height; z++) {
      const y = perlin.noise(x / scale, z / scale) * 10; // Scale to 0-10 height
      vertices.push(x, y, z);
    }
  }
  
  // Create mesh
  geometry.setAttribute('position', new THREE.BufferAttribute(
    new Float32Array(vertices),
    3
  ));
  
  // Add faces (triangles connecting vertices)
  const indices = [];
  for (let x = 0; x < width - 1; x++) {
    for (let z = 0; z < height - 1; z++) {
      const a = x * height + z;
      const b = (x + 1) * height + z;
      const c = x * height + (z + 1);
      const d = (x + 1) * height + (z + 1);
      
      indices.push(a, c, b);
      indices.push(b, c, d);
    }
  }
  
  geometry.setIndex(new THREE.BufferAttribute(
    new Uint16Array(indices),
    1
  ));
  
  return geometry;
}
 
// Generate infinite terrain variations
const terrain1 = generateTerrain(100, 100, 10);   // Smooth rolling hills
const terrain2 = generateTerrain(100, 100, 3);    // Sharp rocky terrain

Real performance: Perlin noise terrain generation is fast enough for real-time applications, enabling procedural terrain to regenerate on-the-fly without lag.

File size: 0 bytes (pure code)
Variations: Infinite (adjust scale, octaves, persistence)


Performance: Procedural vs Assets

WebGPU Advantage (2026 Update)

With WebGPU, procedural generation gets dramatically faster:

Particle system comparison:

  • WebGL: 10,000 particles updates at 30ms per frame
  • WebGPU with compute shaders: 100,000 particles in 2ms
  • Improvement: 150x faster and 10x more particles Why? WebGPU compute shaders run directly on GPU, not CPU.

Instancing for Repeated Objects

If you need 1,000 identical wheels on a conveyor belt:

const wheelGeometry = generateWheel(5, 1, 6);
const material = new THREE.MeshStandardMaterial();
 
// Create 1,000 wheels with single draw call
const instancedMesh = new THREE.InstancedMesh(
  wheelGeometry,
  material,
  1000
);
 
for (let i = 0; i < 1000; i++) {
  const matrix = new THREE.Matrix4();
  matrix.setPosition(i * 10, 0, 0); // Space them out
  instancedMesh.setMatrixAt(i, matrix);
}
 
scene.add(instancedMesh);

Performance:

  • Naive approach (1,000 separate meshes): 1,000 draw calls, 60 FPS = impossible
  • Instanced approach: 1 draw call, 1,000 meshes, 60 FPS = easy This is the technique Tom Krcha used for his train demo.

When to Use Procedural vs Assets

Need Use Procedural Use Assets
Simple geometric shapes (boxes, wheels, cylinders) ✅ Yes ❌ No
Highly detailed faces or organic forms ❌ No ✅ Yes
User-customizable variations (adjust size, color, style) ✅ Yes ❌ No
Fixed, hand-crafted artwork ❌ No ✅ Yes
Performance-critical (1000+ objects) ✅ Yes ❌ No
Real-time generation (new on each frame) ✅ Yes ❌ No
Offline 3D art ❌ No ✅ Yes

Hybrid approach (best): Use procedural for structure, assets for detail.

Example: Procedural building outline + asset textures.


Common Mistakes

Mistake Impact Fix
Generate geometry every frame Huge CPU waste Generate once, reuse mesh
Too many vertices WebGL crashes Optimize with LOD (level of detail)
No BufferGeometry Terrible performance Always use BufferGeometry, never Geometry
Forgetting to compute normals Lighting looks wrong Call geometry.computeVertexNormals()
Creating 1,000 separate meshes Draw call hell Use InstancedMesh or BatchedMesh

Start This Week

import * as THREE from 'three';
 
// 1. Create basic box geometry
const geometry = new THREE.BoxGeometry(10, 20, 5);
 
// 2. Create material
const material = new THREE.MeshStandardMaterial({ color: 0x0088ff });
 
// 3. Create mesh
const mesh = new THREE.Mesh(geometry, material);
 
// 4. Add to scene
scene.add(mesh);
 
// 5. Customize: Just change parameters
const customBox = new THREE.BoxGeometry(5, 15, 10);  // Different dimensions

Next step: Write a function that generates geometry based on user input.

function generateCustomBox(width, height, depth, color) {
  const geometry = new THREE.BoxGeometry(width, height, depth);
  const material = new THREE.MeshStandardMaterial({ color });
  return new THREE.Mesh(geometry, material);
}
 
// User clicks "make it bigger" → regenerate
const box = generateCustomBox(10, 20, 5, 0xff0000);

Your Competitive Edge

Teams using asset files:

  • Download large model files
  • Limit variations to what they pre-modeled
  • Can't customize in real-time
  • High bandwidth bills Teams using procedural generation:
  • Generate models instantly
  • Infinite variations from code
  • Full runtime customization
  • Minimal bandwidth costs The difference in user experience is enormous.

Next Steps

  1. Learn Three.js: Follow the official docs (threejs.org)
  2. Start simple: Box, cylinder, plane geometries (built-in)
  3. Add parameters: Width, height, color inputs
  4. Scale up: Noise functions for organic shapes
  5. Optimize: Use InstancedMesh for thousands of objects
  6. WebGPU: Migrate to WebGPURenderer for 2-10x performance

Tools & Libraries

Built-in (no install):

  • Three.js BoxGeometry, CylinderGeometry, etc.
  • Perlin noise (three/examples/jsm/math/Perlin.js) Open-source:
  • THREE.Terrain (procedural terrain engine)
  • Babylon.js (similar to Three.js, great procedural support)
  • TresJS (TypeScript-first Three.js wrapper) Learn:
  • Three.js documentation (threejs.org)
  • Codrops procedural geometry tutorial (August 2026)
  • Chiro Visuals WebGPU experiments (GitHub)

Got stuck, or want this shipped end-to-end for you? bitroot.club builds custom products for founders. →