fairy forest glade
Table of Contents
- overview
- demos
- what i built
- tech stack
- premise and world building
- what got left out
- links
- references and inspirations
overview
fairy forest glade was my individual project for CS7GV6 Computer Graphics. the basic requirement was to animate fireflies and a hovering fairy with soft night lighting. this was a good base to work with and add to, and i had always wanted to tackle a procedurally generated terrain. this project looked like the perfect opportunity to implement this!
the final result was an OpenGL scene created from scratch in C++, with a procedurally generated terrain, instanced foliage (300k+ grass blades), billboarded trees, a cel-shaded fairy with hierarchical wing animation, fireflies that orbit her and act as point light sources, and a procedural night sky with an animated moon. everything runs at 60 FPS through a combination of GPU instancing, view frustum culling, and a distance-based LOD system.
demos
final result:
halfway point (control + hierarchical animation):
what i built
terrain
the terrain is a 50×50 mesh generated on the CPU using Fractional Brownian Motion (FBM). Multiple octaves of Perlin noise are summed together, where each octave doubles the frequency and halves the amplitude. the formula:
normals are computed from neighboring vertex heights for smooth per-fragment lighting. height-based colour blending gives the terrain dark valleys, green mid-slopes, and lighter peaks. inspired by Inigo Quilez's FBM tutorial and Acerola's terrain generation video. the equation for the finite difference normal is as follows:
foliage: grass, flowers, trees
all vegetation uses GPU instancing, with one draw call per foliage type, and instance data (position, texture index) packed into a buffer. this is what lets ~300,000 grass instances render at 60 FPS.
grass uses a cross-quad geometry (two quads rotated 90° from each other) for a 3D appearance from any angle, with alpha-masked textures and wind animation driven by layered noise in the vertex shader. flowers are procedurally generated: petal patterns using polar coordinates (sin(angle * 5.0) for a 5-petal design), with pink-purple colour ranges and a separately rendered stem.
trees have two variants, normal and thick, with OBJ branch models loaded via Assimp and billboarded leaf clusters generated around branch vertices. leaves are quad instances in spherical distributions around attachment points, each with a random texture from a set of 4 alpha-masked variants.
// leaf cluster generation, spherical distribution
float theta = dist01(rng) * 2.0f * glm::pi<float>();
float phi = acos(2.0f * dist01(rng) - 1.0f);
float r = cluster.radius * pow(dist01(rng), 1.0f / 3.0f); // uniform sphere
leaf.offset = r * glm::vec3(sin(phi)*cos(theta), sin(phi)*sin(theta), cos(phi));
LOD and frustum culling
a three-zone LOD system scales foliage density by distance:
| zone | distance | density |
|---|---|---|
| near | < 15 units | 100% |
| mid | 15–35 units | 50% |
| far | 35–60 units | 20% |
| beyond | > 60 units | culled |
grass gets a special ultra-dense carpet within 8 units of the camera before the LOD kicks in. sphere-based view frustum culling (6-plane test on a bounding sphere per foliage instance) filters visible instances on the CPU before uploading to the GPU each frame. the Frustum struct lives in camera.h and gets recomputed per frame from the camera's Euler angles.
As per the sphere frustum culling test, a point is culled if for any plane :
where is the plane normal, is the sphere centre, is the plane offset, and is the bounding radius.
fairy character and animation
the fairy model was built in Blender, with a body, two upper wings, two lower wings as separate OBJs. it was then loaded with Assimp. the wing hierarchy uses forward kinematics: body is the root, upper wings are children of the body, lower wings are children of their respective upper wings.
// hierarchy setup
leftUpperWing.parent = &body;
leftLowerWing.parent = &leftUpperWing;
rightUpperWing.parent = &body;
rightLowerWing.parent = &rightUpperWing;
wing flapping is driven by sine waves with phase offsets. upper and lower wings flap slightly out of sync to look more organic. a separate hover animation bobs the body up and down using sin(currentTime * hoverSpeed) * hoverAmount. the fairy is fully controllable: arrow keys for 6DOF movement, I/K for vertical flight, J/L for rotation.
the hover bob:
the wing flap with phase offset between upper and lower wings:
where is the phase delay between upper and lower wings.
lighting
three light sources interact in the scene:
- moonlight: a directional light with cool blue-white colour (
vec3(0.6, 0.7, 0.9)), low intensity, simulating low-angle night atmosphere. the moon direction slowly animates over time. - fairy light: a warm point light (
vec3(1.0, 0.9, 0.6)) centered on the fairy's position with a 1.5-unit offset upward. - firefly lights: up to 8 of the 50 closest fireflies contribute as point lights, each with individual colours and positions updated per-frame.
all lighting uses the Blinn-Phong model in fragment shaders (ambient + diffuse + specular per source, with quadratic attenuation for point lights). lighting is computed in world space.
cel shading
all surfaces, i.e., the terrain, fairy, trees, grass, flowers, go through a cel-shading pass that quantises the continuous Blinn-Phong diffuse value into discrete bands:
// cel shading quantisation
float diffuse = max(dot(normal, lightDir), 0.0);
int numBands = 4;
float celDiffuse = floor(diffuse * numBands) / numBands;
// sharp specular highlight
float spec = pow(max(dot(normal, halfVector), 0.0), shininess);
if (spec > 0.5) finalColor = vec3(1.0); // full white highlight
fireflies are excluded from this. their glow stays smooth for the magical effect.
a shared colours.glsl library defines the fairy-themed pastel palette (pinks, lavenders, mints, peaches) and utility functions (adjustBrightness, blendColors) included across all shaders.
the quantisation step more formally uses the following equation:
where is the number of shading bands.
skybox and procedural sky
the skybox uses a 4K night HDRI (satara_night_no_lamps_4k.exr) from PolyHaven, loaded with OpenEXR. on top of it, a procedural sky shader renders animated twinkling stars and a smooth moon that tracks the moonDir uniform, which is the same direction vector used for the main directional light, so the moon matches the actual light source.
fireflies
50 fireflies orbit the fairy's position within a 2.5-unit radius. each has an individual position, velocity, colour, pulse phase, and size. they use billboard rendering (always face the camera via view matrix right/up vectors) with a radial alpha gradient and additive blending (GL_SRC_ALPHA, GL_ONE) for the glow effect. pulse animation: sin(time * 2.0 + phase) * 0.5 + 0.5.
for the fireflies, the pulse animation:
point light attenuation:
where , , are the constant, linear, and quadratic terms.
tech stack
| tool | detail |
|---|---|
| language | C++17 |
| graphics API | OpenGL 3.3 Core |
| build | CMake 3.16 + vcpkg |
| windowing | GLFW 3.4 |
| GL extensions | GLEW 2.1 |
| math | GLM |
| model loading | Assimp |
| texture loading | stb_image |
| HDRI | OpenEXR + IMath |
premise and world building
the aesthetic references i leaned on were Frieren: Beyond Journey's End for the fairy's character and lonely-but-not-sad vibe, Winx Club for the general fairy world-building and cute critter companions, the NewJeans ASAP music video for the forest lighting and dreamy soft colour palette, and Love and Deepspace for the glowy shader inspiration.

![]() |
![]() |
![]() |
![]() |
|---|---|---|---|
| Lighting and aesthetic inspiration | Home inspiration | PBR + NPR in harmony | Water simulation and interaction inspiration |
![]() |
![]() |
|---|---|
| Rafayel's pre-attack battle animation | Rafayel's attack scene battle animation |
![]() |
![]() |
![]() |
|---|---|---|
| MC's solo battle animation | Xavier's combination pre-attack battle animation | Xavier's combination attack scene battle animation |
i gave the world a narrative that i could not really end up sticking to. while it acted as a good starting point, a lot of its aspects were things i didn't get enough time to implement, and later, it made no sense to add them to the scene. here's some concept sketches i did for the scene, the main character and the general visual aesthetics i was going for:


what got left out
a lot did. the biggest gap was the fairy's home. i planned to have an abandoned metro car with an apothecary and a massive glowing desktop setup. i had Blender assets from years ago that i'd abandoned that were almost perfect for this, but December had other plans. the summoning circle, the second playable character with a switchable POV, and the other bioluminescent creatures all stayed on the cutting room floor too.
i also had a full character sheet and outfit design for the fairy that didn't make it into the demo video (my TA told me to add them in during the demo, so they're in the final report but not the video). the cel shading system also has a lot of room to grow. i'd like to explore more painterly approaches, potentially procedural normal maps or outline passes.

i plan to keep working on this in the future.
links
references and inspirations
- Inigo Quilez - Painting a Landscape with Mathematics FBM terrain technique
- Acerola - How Do Games Render So Much Grass? billboard grass + GPU instancing
- Acerola - What I Did To Optimize My Game's Grass LOD and instancing optimisations
- KodiakWhale - How I made better foliage than 99% of games stylised foliage approach
- Trung Duy Nguyen - Anime Tree Tutorial in Blender camera-facing billboards + cel shading aesthetic
- LearnOpenGL - Camera camera system base
- Poly Haven - Satara Night No Lamps HDRI skybox texture








