Case Study

Particle Morphing Shader (Three.js + GLSL + Blender)

A GPU-driven particle morphing system that blends between multiple 3D shapes using custom GLSL vertex logic. Models were created in Blender, exported as GLB, loaded with GLTFLoader + DRACOLoader, and converted into normalized position buffers so every morph runs smoothly at real-time frame rates. Deployed as a static Vite build to GitHub Pages.

The Problem

Build a high-performance particle system that can morph between multiple complex 3D shapes without updating large position attributes on the CPU each frame, while supporting real-world GLTF scenes with multiple meshes per model and mismatched vertex counts between morph targets.

Solution

  • Authored multiple morph-target models in Blender and exported them as a GLB asset for consistent import into the Three.js pipeline.
  • Loaded the GLB with GLTFLoader and DRACOLoader to keep the demo lightweight while still using high-vertex geometry.
  • Handled real-world GLTF structure by traversing each model, cloning each mesh geometry, applying matrixWorld transforms, and merging multi-mesh targets into a single geometry buffer.
  • Extracted position attributes for each target and computed the maximum vertex count across all shapes to establish a shared particle budget.
  • Normalized every target into a Float32Array of maxCount by filling missing vertices via randomized sampling from the original vertex set to avoid a visible clump at the origin.
  • Uploaded two position sets to the GPU, position and aPositionTarget, then performed morphing entirely in the vertex shader using mix() and a uProgress uniform.
  • Improved motion feel by driving per-particle delays with simplex noise sampled from both origin and target positions, then applying smoothstep-based timing windows for staggered starts and eased arrivals.
  • Added per-particle randomness via an aSize attribute and used additive blending with depthWrite disabled for a glowing, layered particle look.
  • Prevented morph targets from disappearing due to incorrect bounds by disabling frustum culling on the Points object.

Results

  • Smooth, GPU-driven morph transitions between multiple Blender-authored shapes without CPU-side attribute reuploads during animation.
  • Stable real-time rendering with large particle counts and visually pleasing staggered motion driven by simplex noise.
  • Supports complex GLTF targets composed of multiple meshes by merging them into a single particle buffer per shape.
  • Self-contained demo deployed to GitHub Pages and linked directly from the portfolio.

Next

  • Move morph state to a GPGPU approach using ping-pong framebuffers to enable continuous flow-field motion and persistent particle velocity.
  • Add post-processing bloom and subtle chromatic aberration for higher perceived energy while keeping particle counts stable.
  • Improve target quality by generating consistent vertex sampling across shapes in Blender or during import to reduce over-sampling hotspots.
  • Add mobile-friendly interaction by mapping tap events to target cycling and offering a simplified UI for touch devices.
← Back Home