How to tween 10,000+ particles in Three.js?

1.5k views Asked by At

I have a THREE.Points object consisting of many (10,000+) vertices (a.k.a. particles).

However, I run into performance problems when I try to tween the location of the individual particles. This is expected given that I am using the following code which loops through all the particles and assigns each a tween.

var duration = 500;

for( var i = 0; i < particles.geometry.vertices.length; i++ ){

    // http://threejs.org/examples/css3d_sprites.html

    var currentVertex = particles.geometry.vertices[i];

    new TWEEN.Tween( currentVertex )
        .to( 
            {
                x: newVertices[i].x,
                y: newVertices[i].y,
                z: newVertices[i].z,
            },
            duration * ( Math.random() + 1 ) 
        )
        .easing( TWEEN.Easing.Exponential.InOut )
        .onUpdate( function(){

            particles.geometry.verticesNeedUpdate = true;
        })
        .start();
}

Is there a better way to approach this?
I do not mind if all the particles are updated in one draw call to their new inbetween positions.

2

There are 2 answers

1
Jet Blue On BEST ANSWER

Got it running after chewing on it for a while.

Solution was a combination of using Buffer Geometry (as seen here) and using shaders as suggested by @2pha.

The tweening function was moved to the vertex shader where it was possible to fake per pixel tweening. The various data needed by the tween function was stored in the ShaderMaterial uniforms and BufferGeometry attributes.

Some pseudo code,

// Buffer Geometry

var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( bPositions, 3 ) );
geometry.addAttribute( 'color', new THREE.BufferAttribute( bColors, 3 ) );
geometry.addAttribute( 'targetPosition', new THREE.BufferAttribute( bPositions2, 3 ) );


// Shader Material

var material = new THREE.ShaderMaterial({
    uniforms: {
        elapsedTime : {
            type: "f",
            value: 0.0
        },
        duration : {
            type: "f",
            value: 0.0
        }
    },
    vertexShader: document.getElementById( 'vertexShader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentShader' ).textContent
});

// Vertex Shader

uniform float elapsedTime;
uniform float duration;
attribute vec3 targetPosition;

float exponentialInOut( float k ){
    // https://github.com/tweenjs/tween.js/blob/master/src/Tween.js
    if( k <= 0.0 ){
        return 0.0;
    }
    else if( k >= 1.0 ){
        return 1.0;
    }
    else if( ( k *= 2.0 ) < 1.0 ){
        return 0.5 * pow( 1024.0, k - 1.0 );
    }
    return 0.5 * ( - pow( 2.0, - 10.0 * ( k - 1.0 ) ) + 2.0 );
}

void main(){

    // calculate time value (also vary duration of each particle)
    float t = elapsedTime / ( duration * ( 1.0 + randomNum.x ) );

    // calculate progress
    float progress = exponentialInOut( t );

    // calculate new position (simple linear interpolation)
    vec3 delta = targetPosition - position;
    vec3 newPosition = position + delta * progress;

    // something
    gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
}
1
2pha On

You are probably never going to get the performance you want animating that many particles in javascript alone.
Your best bet is probably moving your animation code to a shader so it is handled by the GPU which should easily be able to give you the performance you want.

There is a blog post of how to do this with code examples at : Animating a Million Letters Using Three.js