Personal project · 2026

Voxel Game Engine 2

The second version of a C++ voxel engine, focused on making a larger procedurally generated world run smoothly in real time.

Version 2 replaces the original single-noise terrain generator with layered terrain and climate-driven biomes, while rebuilding chunk streaming, rendering, and memory management around measured performance.

3.9x
Faster chunk loading
16x
Less memory per chunk
79%
Fewer draw calls

Performance

In-game performance overlay showing frame time and chunk statistics
Custom profiling overlay tracking frame time, chunk generation, meshing, and rendering.

A custom F3 performance overlay tracks frame time, chunk generation and meshing time, and the number of pending, loaded, visible, and rendered chunks.

The measurements drove the main optimization work instead of relying on visual or subjective performance improvements.

Chunk Streaming

Parallel Generation

Terrain generation and mesh construction were moved off the OpenGL thread and distributed across a persistent std::thread worker pool.

Because terrain is deterministic from world position and seed, chunks can be generated independently without shared world state.

147 ms → 37 ms per chunk batch

A separate 3 ms per-frame build budget prevents large streaming batches from causing frame-time spikes when crossing chunk boundaries.

Chunk Unloading

Chunks outside a configurable keep distance are unloaded and their GPU buffers released, preventing memory usage from growing indefinitely as the player explores.

Edited blocks are replayed when a chunk is regenerated, preserving player changes across streaming.

Rendering

Frustum Culling

Chunks outside the camera's view frustum are rejected using bounding-box tests against the six planes of the view-projection matrix.

At a render distance of 9:

361 chunks → 76 chunks drawn

This reduced draw calls by 79% while preserving the visible scene. The same frustum system is also used for shadow rendering with the light's view-projection matrix.

player, facing up
Every chunk in render distance is tested against the six frustum planes. Only the filled ones reach a draw call: 361 down to 76.

Greedy Meshing

Visible block faces are merged into larger quads using a 2D greedy-meshing pass.

Across 60 generated chunks:

BeforeAfter
Quads33,16814,088
Vertices132,67256,352
Indices199,00884,528

The result is 2.35x fewer quads and 57% fewer vertices and indices.

Wireframe view showing large merged quads across flat terrain
Greedy meshing replaces individual block faces with larger merged surfaces.

Memory

Each voxel originally stored its own world position alongside its block type.

Since a block's position can be derived from its index within a chunk, the redundant position data was removed and block types were reduced to a byte-sized representation.

405 KB → 25 KB per chunk

That is a 16x reduction in chunk memory.

Combined with chunk unloading, memory usage now remains bounded during long-distance exploration.

Procedural World Generation

Layered Terrain

The original single Perlin height map was replaced with layered noise fields for:

  • Continents
  • Mountain ranges
  • Hills
  • Peaks
  • Rivers and lakes
  • Fine terrain detail
  • Temperature and moisture

Domain warping adds larger-scale variation while keeping generation deterministic from (x, z) and the world seed.

Voxel landscape showing mountains, rivers and coastline
Layered noise generates continents, mountains, rivers, and coastlines from a single seed.

Climate-Driven Biomes

Biome selection is based on temperature, moisture, elevation, and a variation field.

12 biomes are defined through data rather than hard-coded generation logic, controlling surface blocks, vegetation density, and terrain tint.

Voxel landscape showing different biome regions
Climate-driven biome generation with distinct terrain and vegetation.

Result

Version 2 turned the engine from a functional voxel renderer into a more scalable real-time system.

3.9x faster chunk loading · 16x lower chunk memory · 79% fewer draw calls · 2.35x fewer mesh quads

The main theme throughout the rebuild was profiling-driven optimization: identify the expensive stage, change the system structure, and measure the result.

Stack

  • C++
  • OpenGL
  • GLM
  • FastNoiseLite
  • std::thread
  • CMake