CUSTOM VOXEL ENGINE & WORLD GENERATOR


UE5 | Solo | 2025 | After finishing my year 1 university objectives early, I took on this project to enhance my PCG and Unreal C++ skillset.

Optimised PCG

This multi-threading project required that I iteratively optimise systems on multiple levels to achieve high performance.

Prototype Iterations:
- Utilized mesh instancing to generate a chunk of voxels using the same draw calls, lightening GPU load.
- Designed an algorithm to spawn only the surface of a chunk, removing 2774 unnecessary instances per 16x16.
- Improved said algorithm to function above the individual chunk level.
- Moved PCG calculations to a custom thread and distributed instancing across ticks.
- Implemented culling, saving, and loading.

Subterranean World

Inspired by the art of Blame, this generated world is fully destructible, customisable, and buildable.

Below rewritten in psuedo(ish)code is the game's world generation thread; here the main cycle for calculating what should be loaded is executed.


	worldGenThread::Run() {

		validateGenerator();

		//reset this and generator obj for cycling
		lastQueuedChunks = preloadChunks(generator->getLocationChunk(player->getCurrentLocation()));

		//thread lifetime
		while (!bShutdown) {

			playerChunk = generator->getLocationChunk(player->getCurrentLocation());

			if (playerChunk != previousPlayerChunk) {

				//safe queuing of world changes to Main Game Thread

				if (not generator->getIsChunkQueueInUse()) {
					generator->setIsChunkQueueInUse(true);

					chunksToLoad = getUnloadedCloseChunks(playerChunk);
					queueUnloadedChunks(chunksToLoad, playerChunk);
					lastQueuedChunks = chunksToLoad;

					generator->setIsChunkQueueInUse(false);
				}

				if (not generator->getIsChunkUnloadQueueInUse()) {
					generator->setIsChunkUnloadQueueInUse(true);

					chunksToUnload = getChunksToUnload(playerChunk);
					queueLoadedFarChunks(chunksToUnload);

					generator->setIsChunkUnloadQueueInUse(false);
				}

				previousPlayerChunk = playerChunk;
			}
			Sleep(cyclePeriod);
		}
	}