As the Fall 2025 semester wraps up, I’ve completed the final project for my Advanced Computer Graphics course.

Live Demo: https://comp5411.vercel.app/
I’ve always been a fan of Vercel, and I was particularly impressed by their 2025 digital badge. For my final project, I decided to replicate that aesthetic but significantly elevate the technical complexity. My goal was to move beyond shader fluid and implement a realtime physical fluid simulation where the fluid reacts dynamically to user interaction, gravity, and the swinging motion of the badge.
The Challenge: Coupling 2 Physics Systems
The core challenge of this project was coupling two distinct physical systems:
- Rigid Body Dynamics: handling the lanyard constraints, user drag interaction, and the badge’s rotation / bouncing.
- Fluid Particle Simulation: managing liquid behavior, viscosity, and sloshing effects.
My initial approach was to simulate everything in a single “World Frame”, updating particles based on the badge’s absolute position. However, this proved to be too complex. The high-velocity movements caused tunneling issues, where particles would frequently escape the container and fall through the geometry.
The Solution: World Frame vs. Local Frame
After some research and prototyping, I reframed the problem. Instead of simulating the fluid in the world space (where the badge rotates), I simulate the fluid in the badge’s local space.
In this local frame, the badge boundaries are static, axis-aligned planes. This makes collision detection trivial (e.g. checking ). It improves performance and stability dramatically.
To simulate the movement, I transform the forces rather than geometry. When the badge rotates or accelerates in the world, the fluid inside feels an inertial force in the opposite direction.
Imagine you are driving a car and turn to the left. From the world’s perspective, the car is turning left. However, from your perspective inside the car, you feel pulled to the right. That’s exactly the concept applied here.
I calculate a local gravity vector:
where
- is the standard gravity .
- is the inverse of the badge’s rotation quaternion.
- approximates the inertial forces derived from the swinging motion.
Once the particle positions are updated, I use the three.js shading language to map the particleBuffer directly to instance positions in the Vertex Shader. This creates a “Zero-Copy” pipeline where particle data never leaves the GPU memory.
Generative AI Integration
To enhance user customization, I integrate an LLM (Moonshot AI via an API proxy). This allows users to generate custom styles using natural language descriptions (e.g., “Cyberpunk Neon”). The backend prompts the LLM to output a JSON configuration conforming to a Zod schema:
{
"fluidColor": "#ff0055",
"glass": {
"transmission": 0.9,
"roughness": 0.1,
"ior": 1.45
}
}This feature was heavily inspired by what Guillermo’s recommendation of tweakcn, a tool that leverages AI to generate shadcn styling configuration.