01 Hierarchical Modeling
The characters are built as scene graphs - tree structures where each body part is a child of its parent. When the torso moves, the arms, legs, and head move with it automatically. This is achieved through a matrix stack.
Each transformation (translate, rotate, scale) post-multiplies the current model matrix.
gPush() saves the current state, gPop() restores it.
This lets child nodes inherit parent transformations while keeping their own.
// Draw arm attached to shoulder gPush(); // Save torso transform gTranslate(0.8, 0.5, 0); // Move to shoulder joint gRotate(armAngle, 1,0,0); // Swing arm gPush(); gScale(0.15, 0.4, 0.15); // Shape the upper arm drawCylinder(); gPop(); // Forearm inherits shoulder + upper arm transforms gTranslate(0, -0.5, 0); gRotate(elbowAngle, 1,0,0); drawCylinder(); gPop(); // Restore to torso
M_final = M_torso × T_shoulder × R_arm × S_arm Each primitive is drawn with: gl_Position = P × V × M_final × vertex where: P = projection matrix (perspective) V = view matrix (camera) M = accumulated model matrix
02 360° Camera Flyaround
The camera orbits the scene using parametric circular motion. The eye position traces a circle in the XZ plane while simultaneously spiraling inward, creating the dramatic zoom effect.
eyeX = r(t) × sin(θ(t)) eyeZ = r(t) × cos(θ(t)) eyeY = constant where: θ(t) = angular position, incremented each frame r(t) = radius, decreasing over time (spiral inward) The lookAt matrix constructs the view transform: V = lookAt(eye, target, up)
The lookAt function builds an orthonormal basis from three vectors:
the forward direction (eye to target), the right vector (cross product with up),
and the true up (cross of forward and right). These form the rotation part of the view matrix.
03 Blinn-Phong Illumination
The shading uses Blinn-Phong, a modification of Phong that replaces the reflection vector with a half-vector. It's computationally cheaper and often looks more realistic for certain materials.
H = normalize(L + V) // Half-vector Color = Ambient + Diffuse + Specular Ambient = kₐ × Iₐ Diffuse = kd × I × max(0, N·L) Specular = ks × I × max(0, N·H)ⁿ // Note: N·H not R·V The half-vector H bisects the angle between the light direction L and view direction V.
vec4 ads(vec3 pos, vec3 Lpos, vec3 fN) { vec3 N = normalize(fN); vec3 L = normalize(Lpos - pos); vec3 V = normalize(-pos); vec3 H = normalize(V + L); // Blinn-Phong half vector vec4 ambient = ambientProduct; vec4 diffuse = max(dot(L,N), 0.0) * diffuseProduct; vec4 specular = pow(max(dot(N,H), 0.0), 30.0) * specularProduct; return ambient + diffuse + specular; }
Computing the reflection vector R requires a dot product and subtraction. The half-vector H is just a normalized sum - cheaper. Plus, Blinn-Phong handles edge cases better when the light is at grazing angles.
04 Physics Simulation
The bouncing character uses Euler integration to simulate gravity. Each frame, velocity is updated by acceleration, then position is updated by velocity. On collision with the ground, velocity is reflected and reduced by an energy loss factor.
Each timestep dt: v(t+dt) = v(t) + a × dt // Update velocity p(t+dt) = p(t) + v(t+dt) × dt // Update position On collision (p.y < ground): v.y = -energyLoss × v.y // Reflect and dampen p.y = ground // Correct penetration The twist: gravity decreases over time, so the character jumps higher and higher until he "flies away".
The variable gravity creates the narrative arc - the character's jumps grow until he escapes entirely, then returns out of control. Simple physics, dramatic effect.
05 Time-Based Fragment Shaders
The psychedelic color effects come from sinusoidal functions of time passed to the fragment shader. Combined with depth-based attenuation, this creates the day/night cycle inside the cone.
void funkyColorShaded(out vec4 fragColor, in vec2 uv, in vec3 pos) { // Brightness varies with time (simulates sun rising/setting) float brightness = sin((3.14/32.0) * (iTime - 13.0)); // Color cycles through RGB based on time and UV position vec3 col = brightness + 0.5 * sin(iTime + uv.xyx + vec3(0,2,4)); // Depth-based shading: farther = darker float depth = pos.z / 28.0; col += vec3(depth); fragColor = vec4(col, 1.0); }
sin(iTime + uv.xyx + vec3(0,2,4)) creates three phase-shifted sine waves
for R, G, and B. As time advances, the colors cycle smoothly through the spectrum.
The UV coordinates add spatial variation so the effect isn't uniform.
06 Source Code
The complete implementation includes hierarchical character construction, texture mapping with my own paintings, and the full narrative animation sequence.