Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 year ago by CelestialEngineer226

How can I create and animate instanced particle morphs in THREE.js?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm working on a particle system in THREE.js using an InstancedMesh to create a morphing animation effect. I create 8000 instances of a small sphere and use two different GLTF models ('/earth.glb' and '/land.glb') to sample points on their surfaces. I then translate and color the particles based on these samples. The code below shows how I'm setting up the particles and sampling the models:

JAVASCRIPT
const color = new THREE.Color(); const sphereGeometry = new THREE.SphereGeometry(0.008, 16, 16); const particles = new THREE.InstancedMesh( sphereGeometry, new THREE.MeshStandardMaterial(), 8000 ); particles.instanceMatrix.setUsage(THREE.DynamicDrawUsage); const matrix = new THREE.Matrix4(); const secondMatrix = new THREE.Matrix4(); const group = new THREE.Group(); scene.add(group); const globeRadius = 1.3; let sampler; let model; loader.load('/earth.glb', (gltf) => { model = gltf.scene; gltf.scene.traverse((obj) => { if (obj.isMesh) { obj.scale.set(0.4, 0.4, 0.4); sampler = new MeshSurfaceSampler(obj).build(); for (let i = 0; i < 30000; i++) { const sample = new THREE.Vector3(); sampler.sample(sample); sample.normalize(); const phi = Math.atan2(sample.z, sample.x); const gradientPosition = (phi + Math.PI) / (2 * Math.PI); if (gradientPosition < 0.5) { color.lerpColors(new THREE.Color(0x006dff), new THREE.Color(0xfc0001), gradientPosition * 2); } else { color.lerpColors(new THREE.Color(0xfc0001), new THREE.Color(0xf2e300), (gradientPosition - 0.5) * 2); } sample.multiplyScalar(globeRadius); matrix.makeTranslation(sample.x, sample.y, sample.z); particles.setMatrixAt(i, matrix); particles.setColorAt(i, color); } group.add(particles); group.scale.set(0.36, 0.36, 0.36); } }); }); let secondModel; loader.load('/land.glb', (gltf) => { secondModel = gltf.scene; gltf.scene.traverse((obj) => { if (obj.isMesh) { obj.scale.set(0.4, 0.4, 0.4); sampler = new MeshSurfaceSampler(obj).build(); for (let i = 0; i < 30000; i++) { const sample = new THREE.Vector3(); sampler.sample(sample); secondMatrix.makeTranslation(sample.x, sample.y, sample.z); // particles.setMatrixAt(i, secondMatrix); particles.setColorAt(i, color); } group.scale.set(0.36, 0.36, 0.36); } }); });

I'm also exploring how to animate these particles. I have an example (commented out) using GSAP's timeline and ScrollTrigger to animate properties using a tween:

JAVASCRIPT
// tl.to(?, { // x:?, // y:?, // z:?, // scrollTrigger: { // trigger: "#page1", // start: "bottom bottom", // end: "bottom top", // scrub: true, // markers: true, // } // })

My desired outcome is to morph the particle positions and colors smoothly between the two sampled models, perhaps in response to scroll or other interactions. What is the best approach to animate these particles while maintaining performance and visual consistency? Any guidance or examples would be appreciated.

7

Comments 0

Answers 1

Views 1.9k

1 Answer

Answer by StackOverflowTychoAI Agent 9 days ago

0

Animating Particle Morphing with InstancedMesh

To create a smooth morphing animation between two different particle configurations using InstancedMesh, you'll need to store both position sets and interpolate between them during animation. Here's a recommended approach:

1. Store Both Position Sets

First, modify your code to store both position sets:

JAVASCRIPT
// Arrays to store positions and colors const earthPositions = []; const earthColors = []; const landPositions = []; const landColors = []; // Load first model (earth) loader.load('/earth.glb', (gltf) => { model = gltf.scene; gltf.scene.traverse((obj) => { if (obj.isMesh) { obj.scale.set(0.4, 0.4, 0.4); sampler = new MeshSurfaceSampler(obj).build(); // Sample positions for earth model for (let i = 0; i < 8000; i++) { const sample = new THREE.Vector3(); sampler.sample(sample); sample.normalize(); // Calculate color const phi = Math.atan2(sample.z, sample.x); const gradientPosition = (phi + Math.PI) / (2 * Math.PI); const earthColor = new THREE.Color(); if (gradientPosition < 0.5) { earthColor.lerpColors(new THREE.Color(0x006dff), new THREE.Color(0xfc0001), gradientPosition * 2); } else { earthColor.lerpColors(new THREE.Color(0xfc0001), new THREE.Color(0xf2e300), (gradientPosition - 0.5) * 2); } // Store position and color sample.multiplyScalar(globeRadius); earthPositions.push(sample.clone()); earthColors.push(earthColor.clone()); // Set initial position and color matrix.makeTranslation(sample.x, sample.y, sample.z); particles.setMatrixAt(i, matrix); particles.setColorAt(i, earthColor); } particles.instanceMatrix.needsUpdate = true; particles.instanceColor.needsUpdate = true; group.add(particles); group.scale.set(0.36, 0.36, 0.36); } }); }); // Load second model (land) loader.load('/land.glb', (gltf) => { secondModel = gltf.scene; gltf.scene.traverse((obj) => { if (obj.isMesh) { obj.scale.set(0.4, 0.4, 0.4); sampler = new MeshSurfaceSampler(obj).build(); // Sample positions for land model for (let i = 0; i < 8000; i++) { const sample = new THREE.Vector3(); sampler.sample(sample); // Store land position landPositions.push(sample.clone()); // You can define different colors for land model const landColor = new THREE.Color(0x00ff00); // Example color landColors.push(landColor); } } }); });

2. Create Animation Function

Now, create a function to animate between the two states:

JAVASCRIPT
// Temporary variables for animation const tempMatrix = new THREE.Matrix4(); const tempPosition = new THREE.Vector3(); const tempColor = new THREE.Color(); // Animation progress (0 to 1) let morphProgress = 0; function updateParticles() { // Only proceed if both position arrays are populated if (earthPositions.length === 0 || landPositions.length === 0) return; for (let i = 0; i < 8000; i++) { // Interpolate position tempPosition.lerpVectors( earthPositions[i], landPositions[i], morphProgress ); // Interpolate color tempColor.lerpColors( earthColors[i], landColors[i], morphProgress ); // Update matrix and color tempMatrix.makeTranslation(tempPosition.x, tempPosition.y, tempPosition.z); particles.setMatrixAt(i, tempMatrix); particles.setColorAt(i, tempColor); } // Mark instance attributes for update particles.instanceMatrix.needsUpdate = true; particles.instanceColor.needsUpdate = true; }

3. Animate with GSAP and ScrollTrigger

Now you can use GSAP to animate the morphProgress variable:

JAVASCRIPT
// Add this after both models are loaded gsap.registerPlugin(ScrollTrigger); const tl = gsap.timeline({ scrollTrigger: { trigger: "#page1", start: "bottom bottom", end: "bottom top", scrub: true, markers: true, onUpdate: (self) => { // Update morphProgress based on scroll position morphProgress = self.progress; updateParticles(); } } });

4. Alternative: Animation in the Render Loop

If you prefer to animate independently of scrolling, you can update in your render loop:

JAVASCRIPT
function animate() { requestAnimationFrame(animate); // Update morphProgress based on time or other factors // morphProgress = ... (some value between 0 and 1) updateParticles(); renderer.render(scene, camera); } animate();

Performance Considerations

  1. Limit particle count: 8,000 is a reasonable number, but test performance on target devices.

  2. Use BufferGeometry attributes: For even better performance, consider using a custom shader with attribute-based animation instead of updating matrices.

  3. Optimize sampling: Sample both models only once at initialization.

  4. LOD (Level of Detail): Consider reducing particle count when animating for smoother performance.

  5. Batch updates: If performance is an issue, you could update only a subset of particles each frame.

This approach should give you a smooth morphing effect between your two models while maintaining good performance.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Discussion

No comments yet.