Engineering · Graphics/3DApr 2026
Mini Minecraft.
A voxel world that reads as a place rather than noise, with every system underneath it built from first principles in C++ and OpenGL.
This was a university class project, so the repo is kept private, but I'm happy to walk through the code on request.
Overview
A faithful Minecraft engine: no game engine, no framework
Mini Minecraft is a tight, roughly month-long group project for CIS 4600 (GPU Programming) at Penn, built through April. The goal was to rebuild Minecraft's core engine (procedural world generation, real-time rendering, player physics, interactive terrain) using only C++17, OpenGL 3.3, and GLSL, with no game engine or rendering framework. The final codebase spans 26 C++ source files and 13 GLSL shaders.
It ran across three milestones, each teammate owning distinct systems. My contributions spanned 7-biome procedural terrain, 3D cave generation with post-process fluid overlays, and a full render-pipeline upgrade: PCF shadow mapping, screen-space reflections, vertex ambient occlusion, distance fog, Blinn-Phong specular, and a day-night cycle.
Terrain
Terrain that reads as a place, not as noise
Raw noise makes convincing hills and unconvincing worlds: everything undulates the same way everywhere. The terrain here works in two layers. Per-biome height fields give each region its own character (a Voronoi F2−F1 hill field for soft cellular grassland, cubed ridge noise for sharp rocky peaks, 4-octave FBM for texture), and a large-scale selector field decides which biomes own a given column.
Each biome claims a lobe of the selector axis, a smoothstep tent centered at its own position, modulated by independent relief and ridge masks and normalized so the weights always sum to one. Where lobes overlap, biomes blend; where one dominates, the terrain commits to it.
Terrain
Caves
Caves from a negative threshold
Underground, a hand-rolled 3D Perlin (eight surflet contributions with a quintic falloff) is sampled at two anisotropic scales, with Y compressed twice as hard as X and Z, blended 85/15. That compression is why the caves read as winding tunnels instead of spherical bubbles.
Every block below Y=128 where the blended noise goes negative is carved. Below Y=25 the void fills with LAVA, Y=0 stays BEDROCK, and under lakes the carve ceiling drops so a cave can never breach a lake floor. Ore spawns only on carved cave surfaces: a stone block must have an exposed face before a hash decides whether it becomes coal, iron, gold, or (below Y=22, at odds of 0.24%) diamond.
Caves
Shadows
Shadow mapping is a war on three artifacts
The sun renders opaque terrain into a 4096² depth map over a 320-block orthographic volume. Naive depth comparison then produces the classic trio: acne (self-shadow stripes), Peter-Panning (shadows detaching from their casters), and shimmer (edges crawling as the player walks). Each gets its own counter-measure.
Acne dies to a slope-scaled bias, 0.0015 · tan(acos(N·L)), clamped so it can never grow into Peter-Panning. Shimmer dies to texel snapping: the light frustum's center moves in whole shadow-map texels (about 0.078 blocks) in a fixed-orientation light space, so walking never sub-pixel-shifts the map. Hard edges die to a 7×7 PCF kernel, 49 depth tests per fragment; shadow depth itself follows the day, shallow at night and deep at noon.
SSR
Making water reflect what is actually on screen
The first water shader faked reflections with a fresnel sky tint, and it read as gray plastic. The fix was real screen-space reflections: a separate pass writes view-space positions into an RGBA32F buffer, and every water fragment marches its reflected ray against that buffer.
The march runs as a DDA in pixel space, one pixel per step along the dominant screen axis for up to 384 iterations, then a 5-step binary refinement pins the hit. Four fade terms (hit quality, travel distance, screen edge, grazing-angle fresnel) suppress the artifacts SSR is infamous for, and masks restrict the whole effect to up-facing water surfaces.
Light & air
The cheap tricks that sell the frame
Vertex ambient occlusion is computed on the CPU at mesh build: three neighbor tests per vertex (two edges, one corner), the both-edges case short-circuiting straight to the darkest value, packed into the spare .w of the UV attribute. Soft contact shadows, free at render time.
The 240-second day-night cycle keeps two sun vectors: the visual sun, which sets, and a lighting sun whose elevation never drops below 0.08, so night shading stays plausible instead of lighting the world from underground. Sky, fog, sun color, and shadow depth all derive from the same daylight scalar; swimming into water or lava adds an animated UV wobble and tint in the post-process pass (stronger for lava).
Rendering
Reflection
What building a graphics engine teaches you
Building a renderer from first principles forces you to understand every stage — there's nowhere to hide when the shadow acne is yours. The biggest lesson was systems discipline across a team: clean interfaces between terrain, streaming, physics, and rendering were what let three people move fast without stepping on each other.
Outcome
Credits
- Brian LeeGraphics & systems: procedural terrain & biomes, 3D caves + post-process, and the render pipeline (PCF shadows, SSR, vertex AO, day-night, fog, Blinn-Phong).
- Angelina HuTerrain chunking (interleaved VBOs + face culling), multithreaded chunk streaming, view-frustum culling, day/night visuals, procedural assets, and audio.
- Seth ThorPlayer physics & collision, texture-atlas UVs, animated water/lava shaders, A* pathfinding on a thread pool, and a predator-prey NPC ecosystem.