Building a 3D Air Purifier Simulation with Three.js and WebGL
Creating immersive 3D visualizations for complex systems requires more than just pretty graphics—it demands a deep understanding of both the underlying technology and user experience principles. Today, we'll dissect a sophisticated air purifier simulation that combines Three.js, WebGL, and advanced animation techniques to create an educational and interactive experience.
This isn't your typical "spinning cube" Three.js tutorial. We're diving into production-level code that handles particle systems, procedural texture generation, complex lighting scenarios, and smooth user interactions. Whether you're a frontend developer looking to add 3D capabilities to your toolkit or a seasoned graphics programmer interested in web-based implementations, this breakdown will give you actionable insights.
Interactive demo of the 3D air purifier simulation showcasing particle flow and exploded view functionality
The Technical Architecture
The air purifier simulation is built on a foundation of modern web technologies, each serving a specific purpose in creating a seamless user experience:
Core Technology Stack
- Three.js v0.160.0 - The backbone for 3D rendering, scene management, and WebGL abstraction
- GSAP v3.12.5 - Handles complex animations, UI transitions, and the signature exploded view effect
- WebGL with PCF Soft Shadows - Provides realistic lighting and shadow rendering
- Custom Control System - Lightweight alternative to OrbitControls for specific interaction patterns
What makes this implementation particularly interesting is the decision to forgo heavy frameworks in favor of a lean, performance-focused approach. The entire application weighs in at under 200KB, yet delivers desktop-quality 3D graphics in the browser.
Scene Initialization and Rendering Pipeline
The application follows a structured initialization pattern that sets up the 3D environment with careful attention to performance and visual quality:
function init() {
// Scene setup with optimized renderer settings
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f8ff);
// Camera configuration for optimal viewing angles
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(8, 6, 8);
// WebGL renderer with shadow mapping
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(window.innerWidth, window.innerHeight);
}
The renderer configuration deserves special attention. PCFSoftShadowMap provides high-quality shadows with minimal performance impact, while the antialiasing ensures crisp edges on geometric shapes. The alpha channel support enables the glassmorphism UI effects we'll discuss later.
Advanced Lighting Setup
The lighting system creates a believable indoor environment that enhances the air purifier's visual appeal:
// Directional light simulating window sunlight
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
directionalLight.position.set(10, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
// Ambient lighting for realistic fill
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
// Point lights for accent lighting
const pointLight = new THREE.PointLight(0xffffff, 0.8, 100);
pointLight.position.set(-5, 5, -5);
This three-point lighting setup mimics professional photography techniques, ensuring the air purifier components are clearly visible while maintaining realistic shadows and depth perception.
Procedural Texture Generation
One of the most impressive aspects of this simulation is its use of procedural textures, eliminating the need for external image assets while maintaining visual fidelity:
Wood Floor Texture
function createWoodTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const ctx = canvas.getContext('2d');
// Base wood color
ctx.fillStyle = '#8B4513';
ctx.fillRect(0, 0, 512, 512);
// Wood grain lines
for (let i = 0; i < 50; i++) {
ctx.strokeStyle = `rgba(139, 69, 19, ${Math.random() * 0.3})`;
ctx.lineWidth = Math.random() * 3 + 1;
ctx.beginPath();
ctx.moveTo(0, Math.random() * 512);
ctx.lineTo(512, Math.random() * 512);
ctx.stroke();
}
return new THREE.CanvasTexture(canvas);
}
This approach offers several advantages: reduced loading times, infinite scalability, and the ability to generate variations programmatically. The wood texture uses randomized grain patterns that create a natural, organic appearance without repetition artifacts.
HEPA Filter Mesh Pattern
The filter visualization uses a similar procedural approach to create the characteristic mesh pattern of HEPA filters:
function createFilterTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
// Create mesh pattern
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, 256, 256);
ctx.strokeStyle = '#cccccc';
ctx.lineWidth = 1;
// Horizontal lines
for (let i = 0; i < 256; i += 8) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(256, i);
ctx.stroke();
}
return new THREE.CanvasTexture(canvas);
}
Particle System Architecture
The heart of the simulation lies in its sophisticated particle system that visualizes air flow through the purifier. This system manages 600 individual particles across multiple flow paths:
Particle Flow Paths
The system uses CatmullRomCurve3 to create smooth, natural-looking air flow patterns:
function setupAirFlow() {
// Window to purifier intake path
const intakePath = new THREE.CatmullRomCurve3([
new THREE.Vector3(-8, 3, 0), // Window
new THREE.Vector3(-4, 2.5, 0), // Mid-room
new THREE.Vector3(-1, 2, 0), // Near purifier
new THREE.Vector3(0, 1.5, 0) // Intake
]);
// Purifier exhaust to room circulation
const exhaustPath = new THREE.CatmullRomCurve3([
new THREE.Vector3(0, 3, 0), // Exhaust
new THREE.Vector3(2, 3.5, 0), // Rising air
new THREE.Vector3(4, 4, 2), // Room circulation
new THREE.Vector3(-2, 3, 4) // Return flow
]);
}
Each particle follows these predefined paths while incorporating subtle randomization to prevent mechanical-looking movement. The curves are mathematically smooth, ensuring particles don't exhibit jarring direction changes that would break the illusion of natural air flow.
Dynamic Particle Properties
Particles change properties as they move through the purification process:
function updateParticles(deltaTime) {
particles.forEach(particle => {
// Update position along curve
particle.t += particle.speed * deltaTime;
if (particle.t > 1) {
// Transition to next flow stage
particle.currentPath = getNextPath(particle.currentPath);
particle.t = 0;
}
// Color transition: dirty (gray) to clean (blue)
if (particle.currentPath === 'cleaning') {
const cleanProgress = particle.t;
particle.material.color.lerpColors(
dirtyColor, // RGB(128, 128, 128)
cleanColor, // RGB(64, 164, 223)
cleanProgress
);
}
// Scale variation for visual interest
const baseScale = 0.02;
const scaleVariation = Math.sin(particle.t * Math.PI * 2) * 0.01;
particle.scale.setScalar(baseScale + scaleVariation);
});
}
The Exploded View System
Perhaps the most technically impressive feature is the exploded view that separates the air purifier into its constituent components. This is achieved through careful object hierarchy management and GSAP animations:
Component Hierarchy
function createPurifier() {
const purifier = new THREE.Group();
// Create individual components
const parts = {
base: createBase(),
carbonFilter: createCarbonFilter(),
hepaFilter: createHEPAFilter(),
preFilter: createPreFilter(),
shell: createShell(),
fan: createFan(),
topRing: createTopRing()
};
// Set initial positions for exploded view
parts.base.userData.explodedY = 0;
parts.carbonFilter.userData.explodedY = 2;
parts.hepaFilter.userData.explodedY = 4;
parts.preFilter.userData.explodedY = 6;
parts.shell.userData.explodedY = 8;
parts.fan.userData.explodedY = 10;
parts.topRing.userData.explodedY = 12;
return purifier;
}
Smooth Animation Orchestration
The exploded view animation uses GSAP's timeline system to coordinate multiple simultaneous animations:
function toggleExplode() {
const timeline = gsap.timeline();
Object.values(purifierParts).forEach((part, index) => {
const targetY = isExploded ?
part.userData.explodedY :
part.userData.originalY;
timeline.to(part.position, {
y: targetY,
duration: 0.8,
ease: "power2.inOut"
}, index * 0.1); // Stagger animation
});
// Fade in component labels
if (isExploded) {
timeline.to('.component-labels', {
opacity: 1,
duration: 0.3
}, 0.5);
}
}
Glassmorphism UI Implementation
The user interface employs glassmorphism design principles, creating a modern, translucent overlay that doesn't interfere with the 3D scene:
.control-panel {
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 16px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
.stats-display {
background: linear-gradient(145deg,
rgba(255, 255, 255, 0.1),
rgba(255, 255, 255, 0.05));
backdrop-filter: blur(15px);
}
The backdrop-filter property creates the signature blur effect, while the subtle gradients and shadows provide depth without overwhelming the 3D content.
Performance Optimization Strategies
Running a complex 3D simulation at 60fps requires careful performance optimization:
Geometry Instancing
For the 600 particles, the system uses instanced geometry to minimize draw calls:
const particleGeometry = new THREE.SphereGeometry(0.02, 8, 6);
const particleCount = 600;
// Use InstancedMesh for better performance
const instancedParticles = new THREE.InstancedMesh(
particleGeometry,
particleMaterial,
particleCount
);
// Update matrices in batch
const matrix = new THREE.Matrix4();
particles.forEach((particle, index) => {
matrix.setPosition(particle.position);
instancedParticles.setMatrixAt(index, matrix);
});
instancedParticles.instanceMatrix.needsUpdate = true;
Selective Rendering Updates
The system only updates components that have actually changed:
function render() {
// Only update particles when purifier is running
if (isPurifierOn) {
updateParticles(clock.getDelta());
}
// Only rotate fan when active
if (fanSpeed > 0) {
fanMesh.rotation.y += fanSpeed * 0.1;
}
// Conditional shadow updates
if (lightingChanged) {
renderer.shadowMap.needsUpdate = true;
lightingChanged = false;
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
Real-World Applications and Extensions
This air purifier simulation demonstrates techniques applicable to numerous other domains:
- Industrial Training - Equipment operation and maintenance procedures
- Product Visualization - E-commerce and marketing applications
- Educational Content - Interactive learning experiences for complex systems
- IoT Dashboards - Real-time visualization of sensor data and device status
Potential Enhancements
The current implementation provides an excellent foundation for additional features:
- WebXR Integration - VR/AR support for immersive experiences
- Physics Simulation - More realistic particle behavior using libraries like Cannon.js
- Real-time Data - Integration with actual IoT sensors for live air quality monitoring
- Multi-device Synchronization - Shared experiences across multiple users
Conclusion
Building sophisticated 3D web applications requires balancing visual fidelity with performance constraints. This air purifier simulation succeeds by leveraging modern web technologies thoughtfully—using procedural generation to reduce asset loading, instanced rendering for performance, and careful animation orchestration for smooth user experiences.
The techniques demonstrated here—from particle system architecture to glassmorphism UI design—represent current best practices in web-based 3D development. As WebGL capabilities continue to expand and browser performance improves, we can expect even more impressive applications that blur the line between web and native experiences.
For developers looking to implement similar visualizations, start with the fundamentals: understand your rendering pipeline, optimize early and often, and never underestimate the impact of good UX design on technical implementations. The web platform has evolved into a capable 3D development environment—it's time we started treating it as such.