For milestone 3, Estelle implemented a custom sky and fluid surface waves, Saahil implemented additional biomes and procedurally placed assets, and Jesse completed shadows and underater distortion post-processing.
-
Custom sky shader with day-night cycle, animated sun, and procedurally generated stars
-
Animated cloud rendering using Worley noise and fractal Brownian motion to generate repeating cloud-like forms
-
Water waves inside the lambert vertex shader using sine/cosine waves on position vectors and calculating normals based on the slope of the waves for accurate reflectionss
I implemented multiple biomes using a complex series of noise functions and maps, such that they blend neatly and have varying height fields. I also added procedurally placed trees and cacti throughout their respective biomes. I also implemented a procedural river system based on distance-to-edge voronoi.
First, I implemented shadow mapping. This involves creating a depth texture, and computing the orthographic depth of the terrain from the light perspective. Then, using this texture, I computed whether or not areas are in shadow by comparing their depth from the light vs the texture's closest depth. To make this look better, I worked it into lambert shading to still have ambient light, and implemented a slope-adjusted bias that adjusts the level of bias to face slopes in order to minimize artifacts and shadow acne. I modified the proposed ideas in the shared tutorial to create a custom biased strategy similar to lambert shading, making use of ambient bias and dynamic bias to capture different directions of lighting the best I could. Lastly, I made it so that water and lava, due to their transparency, do not cast shadows. This isn't 100% perfect (still artifacts as shadow mapping has flaws), but captures the right effect. The light was also modified to follow the player around, so there are worldwide shadows.
The second thing I did was implement additional post processing when the player is underwater. I combined a wave-based uv distortion to emulate fluid distortion, as well as a caustic effect that emulates the distortion of light beams by the surface scattering into the player's eyes (think the light on the bottom of a pool). The same was applied with lava, but with different color, distortion properties (to emulate a thicker viscosity) and lower overall opacity/harder to see.
For milestone 2, Estelle implemented textures, Saahil completed multithreading, and Jesse completed caves.
I first loaded the textures image using code from HW5, and then used my interleaved VBO creation logic from last milestone to split each chunk into a pair of opaque and transparent interleaved VBOs based on their block type and process / render them separately in each part of the pipeline. I indexed the location of each texture in the collective png file and assigned them to vertices/faces using the orientation of each face (for example, grass had different texture slots on its top facem side faces, and bottom). I then implemented animations for water and lava by making a time variable and incrementing it every time the frame updated (60fps). It was difficult to create a function that would create a smoothly looping animation; I experimented with trig and mod functions and ended up with a pretty good function. I also had trouble loading the image and had to debug my texture loading process + ensuring that the vector processing logic in the new system of two VBOs was accurate since some of the numbers ended up off.
The Perlin Noise modification was fairly simple, just factoring in the additional vertices (8 total) necessary. For caves, I toyed around with the constants a bit to make sure that they looked nice and things worked well; lava and bedrock were also fairly simple. For the lava/water physics, I decreased player acceleration when they are in the water/lava, and implemented an up velocity on the space button as well. Lastly, for post-processing, I redirected the frame buffer to point to a separate texture, then redrew with post processing effects. I used a uniform variable specifically to handle how much to tint by, before redrawing onto the default frame buffer.
For this milestone, I implemented multithreading to prevent gameplay slowdown during terrain generation by creating two types of worker threads: BlockTypeWorkers and VBOWorkers. Every game tick, the system examines a 5x5 grid of terrain generation zones centered on the player's current position, spawning BlockTypeWorker threads to generate procedural height field BlockType data for ungenerated zones and VBOWorker threads to compute VBO data for Chunks lacking buffer information. I managed shared memory by using collections in the Terrain class, such as std::unordered_set for Chunk*s and a std::vector of a custom struct holding vertex and index data for VBOs. To ensure thread safety, I implemented mutexes to protect shared memory structures, locking them before reading or writing and unlocking immediately after operations. The VBOWorkers were designed to compute buffer data without attempting GPU communication, with the main thread responsible for sending completed VBO data to the GPU during each game tick, thereby maintaining smooth gameplay performance while efficiently expanding the game world through concurrent terrain generation.
For milestone 1, Estelle implemented chunking, Saahil completed terrain, and Jesse completed physics.
The implementation was pretty cookie-cutter (implementing Chunks as Drawables, assessing whether each face has a neighbor and populating + creating and processing a new interleaved VBO to ultimately render only the faces of each chunk that are touching air, I also implemented the expandTerrainIfNeeded function that renders additional chunks based on the player's position) but I had a lot of bugs: I created the interleaved buffer with alternating positions and colors, which was a problem because the given lambert shader required normals to render (added dummy vectors to represent normals). I had to debug the finer pointer/buffer inputs with teammates and TAs using RenderDoc due to inconsistencies categorizing positions, normals, and colors from the interleaved VBO and correctly apply the model matrix before rendering.
I had a number of interesting challenges when implementing player physics. First was drag--I implemented my drag in a delta-time sensitive manner which simulates floating on ice when in the air and having a much higher drag constant on the ground then when jumping. Additionally, I chose to give teh player less aerial control of their velocity while jumping instead of giving them no control (which would be physically accurate), for a smoother feeling control system. Had to add a few parameters to the inputbundle to handle other inputs. I tuned the accelerations the player can apply such that it feels relatively smooth without allowing too high of a speed. The actual delta-time itself was relatively uninteresting. For collision, I used a ray/volume casting technique. I had to add unique logic to check whether the corner is top/bottom, left/right, etc before applying a modification buffer. This is because without bumping the coordinate values, a floor or ceiling operation isn't necessarily super consistent to what I want, but a decrease in value on a higher-x value corner would work fine but break on the lower-x value corner, while an increase in value would break the higher-x value corner (that's to say, the corners had opposite buffering direections). The result works well in terms of being smooth to walk into a wall/not getting stuck, and the floor generally working as well. As for the ray cast from the camera in order to handle placing/destruction, I simply started at the camera and iterated to the next point at which x, y, or z is a whole number (intersection with object) and did necessary calculations there.
For terrain, I followed the general pattern of creating a height field by sampling multiple perlin noise textures. However, I also experimented with creating a cell automata type system to create overhangs and more complex geometry. It didn't work super well, so I have dialed it down for this iteration, but I hope to expand on it in milestone 2. I want to create more overhangs and complex mountain shapes. Other than that, it was a simple application of generating stacked perlin noise. The most difficult part was figuring out the sizing of the noise, because there was no efficient way to test different scales other than trial and error. I didn't use any assistance for this assignment.