Skip to main content
WYZUK
web-graphics··7 min read

Optimizing Procedural 3D Voxel Engines for 60 FPS in WebGL

Techniques used in YourCraft: chunk partitioning, greedy face culling, and camera frustum checks in Three.js.

Related Project: YourCraftView Project →

The Voxel Rendering Bottleneck

In a 3D voxel sandbox like YourCraft, a visible terrain grid of just 8x8 chunks (each 16x16x16 blocks) contains over 260,000 individual cubes. If rendered naively as separate Three.js Mesh instances, the browser GPU pipeline collapses within seconds due to tens of thousands of draw calls and redundant geometry calculation.

To achieve a locked 60 FPS in standard modern browsers, the engine must merge block faces into consolidated chunk buffers and discard every polygon that cannot be seen by the player.

Chunk Face Culling Algorithm

A voxel face needs to be drawn if and only if its adjacent neighboring cell is empty (air) or transparent (water/glass). By checking the 6 cardinal neighbor coordinates (x±1, y±1, z±1) prior to vertex buffer generation, YourCraft discards roughly 85% to 92% of all potential block faces.

JAVASCRIPTNeighbor adjacency test preventing unexposed internal geometry from reaching WebGL.
function isFaceVisible(world, x, y, z, faceDir) {
  const nx = x + faceDir[0];
  const ny = y + faceDir[1];
  const nz = z + faceDir[2];

  // If outside bounds, render boundary face
  if (ny < 0 || ny >= WORLD_HEIGHT) return true;
  const neighborBlock = world.getBlock(nx, ny, nz);
  
  // Only render face if neighbor is non-opaque air or transparent
  return !neighborBlock || neighborBlock.isTransparent;
}

Texture Atlas Mapping & Pointer Lock

Rather than binding multiple material shaders, YourCraft binds a single 1024x1024 texture atlas. Vertex UV coordinates are assigned dynamically depending on whether a block is dirt, stone, or grass. Paired with the HTML5 Pointer Lock API, users enjoy responsive first-person immersion.