Macrae Smith
Key Features
Multi-Threading
3D Procedural Terrain Generation using Perlin Noise and Cube Marching Algorithms
Gerstner Water Simulation
Boid Fish Simulation
Realistic Lighting with Fog, Caustics, Underwater Color Attenuation, and Light Rays
Dynamic Vegetation
Tri-planar Texture Splatting
Compute Shader Simulation
Thesis
Description
For my master's thesis at SMU Guildhall, I researched, architected, and developed a technical showcase of procedural generation and rendering techniques for creating dynamic, game-optimized ocean environments.
Duration: June 2025 - Present
Technologies:
My custom C++ engine
D3D12
HLSL
ImGui
Multi-Threaded Chunk Generation


To simulate an endless ocean, I architected a fully streaming world system built around 3D chunk partitioning. The world is divided into fixed-size volumetric chunks that are generated and destroyed dynamically based on player position.
Chunk generation runs on a multi-threaded job system with explicit state management to ensure thread safety. Each chunk progresses through defined lifecycle states, preventing race conditions between worker threads and the main thread.
A flood fill traversal begins from the player’s current chunk to determine the working set within a configurable radius. Chunks are scored and prioritized based on distance and camera frustum visibility, ensuring that visible terrain is generated first while off-screen work is deferred.
This approach maintains the illusion of infinite space while keeping memory usage and frame time stable under continuous world expansion.
Each chunk is sent to a separate thread for generation and then ultimately re-claimed by the main thread for activation.

To simulate an endless ocean, I architected a fully streaming world system built around 3D chunk partitioning. The world is divided into fixed-size volumetric chunks that are generated and destroyed dynamically based on player position.
Chunk generation runs on a multi-threaded job system with explicit state management to ensure thread safety. Each chunk progresses through defined lifecycle states, preventing race conditions between worker threads and the main thread.
A flood fill traversal begins from the player’s current chunk to determine the working set within a configurable radius. Chunks are scored and prioritized based on distance and camera frustum visibility, ensuring that visible terrain is generated first while off-screen work is deferred.
This approach maintains the illusion of infinite space while keeping memory usage and frame time stable under continuous world expansion.
Each chunk is sent to a separate thread for generation and then ultimately re-claimed by the main thread for activation.
Density Gradient Field


Terrain generation is driven by a signed density field. Positive density represents solid volume, negative density represents air, and the zero-crossing defines the terrain surface. This scalar field becomes the foundation for both mesh extraction and collision queries.
The density field is constructed through layered combinations of 2D and 3D Perlin, Fractal, and domain-warped noise, each responsible for a distinct structural feature.
Base 3D Terrain Layer
A primary 3D Perlin field establishes the volumetric foundation. Density is biased from solid at ocean depth to air near sea level, producing overhangs, outcroppings, and non-heightmap topology. All higher-level terrain modulation builds on this base volume.
Continentalness
A large-scale 2D noise pass applies smooth elevation offsets to the density field, shaping broad valleys and large island masses that intersect the ocean surface.
Erosion
A heavily warped 2D noise layer subtracts from the density field to simulate canyon formation and water-carved structures, increasing geological complexity without disrupting large-scale landmasses.
Peaks and Valleys
Higher-frequency 2D Perlin noise introduces rapid elevation variation, accentuating sharp ridges, deep pits, and localized underwater rock formations.
Once finalized, the density field is used for surface extraction during mesh generation and for volumetric collision checks against dynamic entities such as the player and flocking fish.

Terrain generation is driven by a signed density field. Positive density represents solid volume, negative density represents air, and the zero-crossing defines the terrain surface. This scalar field becomes the foundation for both mesh extraction and collision queries.
The density field is constructed through layered combinations of 2D and 3D Perlin, Fractal, and domain-warped noise, each responsible for a distinct structural feature.
Base 3D Terrain Layer
A primary 3D Perlin field establishes the volumetric foundation. Density is biased from solid at ocean depth to air near sea level, producing overhangs, outcroppings, and non-heightmap topology. All higher-level terrain modulation builds on this base volume.
Continentalness
A large-scale 2D noise pass applies smooth elevation offsets to the density field, shaping broad valleys and large island masses that intersect the ocean surface.
Erosion
A heavily warped 2D noise layer subtracts from the density field to simulate canyon formation and water-carved structures, increasing geological complexity without disrupting large-scale landmasses.
Peaks and Valleys
Higher-frequency 2D Perlin noise introduces rapid elevation variation, accentuating sharp ridges, deep pits, and localized underwater rock formations.
Once finalized, the density field is used for surface extraction during mesh generation and for volumetric collision checks against dynamic entities such as the player and flocking fish.
Cube Marching


Once the signed density field is generated, terrain surfaces are extracted using a Marching Cubes algorithm.
For each voxel cell, density is sampled at its eight corner positions. The sign of each sample determines whether the corner lies inside or outside the surface. These eight sign bits are combined into a case index, which is used to query a precomputed lookup table.

The lookup table defines the triangle topology for that configuration and specifies which edges of the cube contain surface intersections. Vertex positions are computed along those edges by interpolating between density samples at the zero-crossing.
This approach generates geometry only where the density field transitions from negative to positive, efficiently extracting the isosurface while avoiding unnecessary mesh data in fully solid or fully empty regions.
Once the signed density field is generated, terrain surfaces are extracted using a Marching Cubes algorithm.
For each voxel cell, density is sampled at its eight corner positions. The sign of each sample determines whether the corner lies inside or outside the surface. These eight sign bits are combined into a case index, which is used to query a precomputed lookup table.
Fish


To simulate marine life, I implemented a boid-based flocking system for schooling fish. Each entity computes a steering vector as a weighted combination of Cohesion, Alignment, and Separation forces. This produces emergent flock behavior while maintaining local avoidance against terrain and the player.
Behavior variation is data-driven. Each fish type defines its own weighting coefficients, allowing species-level differences in grouping tightness, directional stability, and separation bias without altering core logic.
Swimming motion is handled entirely on the GPU. A vertex shader applies sinusoidal displacement along the length of the mesh, producing a traveling wave that simulates body undulation. This approach avoids skeletal animation overhead while maintaining lightweight, scalable motion across large schools.

To simulate marine life, I implemented a boid-based flocking system for schooling fish. Each entity computes a steering vector as a weighted combination of Cohesion, Alignment, and Separation forces. This produces emergent flock behavior while maintaining local avoidance against terrain and the player.
Behavior variation is data-driven. Each fish type defines its own weighting coefficients, allowing species-level differences in grouping tightness, directional stability, and separation bias without altering core logic.
Swimming motion is handled entirely on the GPU. A vertex shader applies sinusoidal displacement along the length of the mesh, producing a traveling wave that simulates body undulation. This approach avoids skeletal animation overhead while maintaining lightweight, scalable motion across large schools.
Vegetation


Dense underwater vegetation required thousands of instances per scene, so I built a compute-shader-driven instancing pipeline to manage placement, variation, and animation data on the GPU.
Biome-level 2D noise determines where vegetation is allowed to grow, while type-specific noise fields generate cluster masks to produce natural groupings. Additional noise sampling drives per-instance width and height variation within defined ranges, ensuring large-scale diversity without manual placement.
Underwater Currents
Vegetation motion is driven by a time-evolving, warped noise field representing ocean currents. Three frequency bands control large-scale sway, mid-range drift, and fine wobble. An additional swirl field introduces subtle vertical motion.
In the vertex shader, displacement is applied along the instance’s longitudinal axis with strength increasing toward the tip. The base remains anchored while the upper sections bend in the sampled current direction. Dot products against the instance normal, combined with an editable rigidity parameter, modulate deformation strength per vegetation type, preserving structural identity while maintaining dynamic motion.
Sea Grass
Sea grass is composed of dense, segmented triangle strips. Curved normals are generated to simulate volumetric lighting on otherwise flat geometry, enhancing perceived thickness and depth.
Sea Anemone
Anemones are constructed from thin cylindrical segments arranged radially. Noise-driven deformation along the length of each cylinder breaks uniformity and introduces organic curvature. Additional high-frequency motion layered over the current simulation creates subtle, lifelike behavior.

Dense underwater vegetation required thousands of instances per scene, so I built a compute-shader-driven instancing pipeline to manage placement, variation, and animation data on the GPU.
Biome-level 2D noise determines where vegetation is allowed to grow, while type-specific noise fields generate cluster masks to produce natural groupings. Additional noise sampling drives per-instance width and height variation within defined ranges, ensuring large-scale diversity without manual placement.
Underwater Currents
Vegetation motion is driven by a time-evolving, warped noise field representing ocean currents. Three frequency bands control large-scale sway, mid-range drift, and fine wobble. An additional swirl field introduces subtle vertical motion.
In the vertex shader, displacement is applied along the instance’s longitudinal axis with strength increasing toward the tip. The base remains anchored while the upper sections bend in the sampled current direction. Dot products against the instance normal, combined with an editable rigidity parameter, modulate deformation strength per vegetation type, preserving structural identity while maintaining dynamic motion.
Sea Grass
Sea grass is composed of dense, segmented triangle strips. Curved normals are generated to simulate volumetric lighting on otherwise flat geometry, enhancing perceived thickness and depth.
Sea Anemone
Anemones are constructed from thin cylindrical segments arranged radially. Noise-driven deformation along the length of each cylinder breaks uniformity and introduces organic curvature. Additional high-frequency motion layered over the current simulation creates subtle, lifelike behavior.
Underwater Lighting


Underwater lighting was a major visual pillar of the environment. I focused on simulating the optical behaviors that make submerged scenes feel distinct from standard atmospheric rendering.
Caustics
In real life, surface waves refract incoming light and project dynamic patterns onto the terrain below. I simulated this effect using animated 2D noise masked by Voronoi edge patterns to produce shifting, high-contrast caustic bands. The result is a time-varying light modulation that blends seemlessly across chunk boundaries.
Underwater Fog
A post-process pass samples the depth buffer to compute per-pixel distance from the camera. Based on this depth, scene color is blended toward a defined ocean absorption color, producing volumetric fog that increases with depth and distance from the surface.
Color Attenuation
Water selectively absorbs longer wavelengths first, reducing red and green light as distance increases. In the pixel shader, I applied distance-based attenuation to individual color channels, approximating wavelength absorption relative to depth and light source distance. This produces the characteristic blue shift and enhances depth perception in the scene.

Underwater lighting was a major visual pillar of the environment. I focused on simulating the optical behaviors that make submerged scenes feel distinct from standard atmospheric rendering.
Caustics
In real life, surface waves refract incoming light and project dynamic patterns onto the terrain below. I simulated this effect using animated 2D noise masked by Voronoi edge patterns to produce shifting, high-contrast caustic bands. The result is a time-varying light modulation that blends seemlessly across chunk boundaries.
Underwater Fog
A post-process pass samples the depth buffer to compute per-pixel distance from the camera. Based on this depth, scene color is blended toward a defined ocean absorption color, producing volumetric fog that increases with depth and distance from the surface.
Color Attenuation
Water selectively absorbs longer wavelengths first, reducing red and green light as distance increases. In the pixel shader, I applied distance-based attenuation to individual color channels, approximating wavelength absorption relative to depth and light source distance. This produces the characteristic blue shift and enhances depth perception in the scene.
Performance Optimization


As world scale increased and the scene filled with dense vegetation and flocking entities, memory pressure and frame time instability became primary constraints. I introduced several structural optimizations to keep the engine within real-time performance budgets.
Density Gradient Field Optimization
Most generated chunks were either entirely solid or entirely empty. Storing full volumetric density data for those regions was wasteful. I added a density crossing test during generation and discarded chunks without a surface intersection. This eliminated unnecessary mesh builds and significantly reduced memory usage.
Voxel Scale Tuning
Without a full LOD system, terrain density resolution directly dictated mesh complexity. Increasing voxel scale from 1m to 5m reduced density samples and triangle counts dramatically with minimal visual degradation. The voxel scale remains runtime-configurable through ImGui, allowing controlled tradeoffs between fidelity and performance.
Frustum Culling
As terrain detail increased, draw calls became a bottleneck. I implemented per-chunk frustum culling to exclude non-visible geometry from submission. This reduced triangle throughput and stabilized render time under heavy world loads.
Boid System Optimization
Flocking behavior introduced quadratic neighbor checks that degraded rapidly as fish grouped together. Early distance checks were insufficient due to natural clustering. I introduced spatial partitioning aligned with the chunk system to localize neighbor queries. To prevent worst-case clustering costs, each boid evaluates only a capped subset of nearby entities, selected via a frame-dependent hash combined with a unique fish ID. This maintained emergent flock behavior while producing stable and predictable frame times.

As world scale increased and the scene filled with dense vegetation and flocking entities, memory pressure and frame time instability became primary constraints. I introduced several structural optimizations to keep the engine within real-time performance budgets.
Density Gradient Field Optimization
Most generated chunks were either entirely solid or entirely empty. Storing full volumetric density data for those regions was wasteful. I added a density crossing test during generation and discarded chunks without a surface intersection. This eliminated unnecessary mesh builds and significantly reduced memory usage.
Voxel Scale Tuning
Without a full LOD system, terrain density resolution directly dictated mesh complexity. Increasing voxel scale from 1m to 5m reduced density samples and triangle counts dramatically with minimal visual degradation. The voxel scale remains runtime-configurable through ImGui, allowing controlled tradeoffs between fidelity and performance.
Frustum Culling
As terrain detail increased, draw calls became a bottleneck. I implemented per-chunk frustum culling to exclude non-visible geometry from submission. This reduced triangle throughput and stabilized render time under heavy world loads.
Boid System Optimization
Flocking behavior introduced quadratic neighbor checks that degraded rapidly as fish grouped together. Early distance checks were insufficient due to natural clustering. I introduced spatial partitioning aligned with the chunk system to localize neighbor queries. To prevent worst-case clustering costs, each boid evaluates only a capped subset of nearby entities, selected via a frame-dependent hash combined with a unique fish ID. This maintained emergent flock behavior while producing stable and predictable frame times.
Stay tuned for more additions coming soon!...


Underwater light shafts
Water surface rendering with refraction and reflection
More vegetation types

Underwater light shafts
Water surface rendering with refraction and reflection
More vegetation types