// Graphics Programming

WebGL Animation

Hierarchical character animation with 360° camera flyaround, Blinn-Phong illumination, physics simulation, and custom time-based GLSL shaders. Textures derived from my own paintings.

WebGL GLSL JavaScript Hierarchical Modeling Blinn-Phong Physics Sim

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.

JavaScript
// 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
Transformation Composition
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.

Orbital Camera Position
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.

Blinn-Phong Model
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.
GLSL Fragment Shader
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;
}
Why Blinn-Phong?

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.

Euler Integration
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.

GLSL Fragment Shader
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);
}
The Color Formula

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.