3/11/2016

Up to 1.572.864 Particles with custom Displacement Shader

Bei diesem Experiment habe ich seit längerer Pause mal wieder mit ThreeJS gearbeitet.
Nach den ganzen Canvas Experimenten zuletzt war es mal wieder an der Zeit auf die volle Leistung von WebGL Zugriff zu haben.
Ich habe dazu mein „Distorted Sphere“ Canvas Experiment für ThreeJS portiert.
Wo bei den Canvas Experimenten bei 50.000 bis maximal 100.000 Partikeln Schluss war, geht es nun auch mit bis zu 1.572.864 Partikeln, und dass bei 60FPS (auf einer flotten Maschine).
Ich habe dazu meine ersten (Displacement Particle) Shader geschrieben, damit die ganzen Berechnungen nicht auf der CPU, sondern der GPU berechnet werden. Die beiden Shader (vertex und fragment) sind bestimmt noch nicht perfekt, aber ich stell die euch hier weiter unten zur freien Verfügung. 


Die Partikel habe ich mit Hilfe der „Points“-Klasse, dem ShaderMaterial und der BufferGeometry von ThreeJS realisiert. Auch hier gibt es die entscheidenen Code-Schnipsel weiter unten mit dazu. Die Berechnung, bzw. Anordnung, der Partikel wird hier nicht näher erklärt, aber dafür seht ihr wie ihr Partikel adden könnt und was nötig ist um diese mit den beiden Shadern zu animieren.
 

Wie immer könnt ihr über das Menü diverse Einstellungen vornehmen und so das Experiment aktiv beeinflussen. Die Steuerung erfolgt per Maus (Klick + Drag und Srollrad) oder Touch-Gesten.

Hier geht es direkt zum Experiment. Besonders zu empfehlen für Mobile User/Touch Devices!
Der Fullscreen-Modus funktioniert im Moment leider nur im Chrome Browser fehlerfrei.


Hier die beiden Shader (vertex und fragment):
uniform float size;

varying float distance;
uniform float distanceMax;

uniform float amplitude;
uniform float d;
uniform float pi;

varying vec3 v;

void main() {

    float x = position.x;
    float y = position.y;
    float z = position.z;

    distance = cos( x * d * pi ) * cos( y * d * pi ) * cos( z * d * pi ) * amplitude;

    v = vec3( x, y, z );
    v = normalize( v );
    v *= distance;

    vec3 pos = position + v;

    gl_PointSize = size;
    gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );

}
varying float distance;
uniform float distanceMax;

uniform int colorScheme;

void main() {

    float percent = ( distance + distanceMax ) / ( distanceMax * 2.0 );

    if ( colorScheme == 0 ) {

        gl_FragColor = vec4( 1.0, percent, 0.0, 1.0 );

    } else if ( colorScheme == 1 ) { 

        gl_FragColor = vec4( 1.0 - percent, 1.0 - percent, 1.0 - percent, 1.0 );

    } else if ( colorScheme == 2 ) { 

        gl_FragColor = vec4( 0.0, percent, 1.0 - percent, 1.0 );

    } else if ( colorScheme == 3 ) { 

        gl_FragColor = vec4( percent, 0.0, 0.0, 1.0 );

    } else if ( colorScheme == 4 ) { 

        gl_FragColor = vec4( 0.0, percent, 0.0, 1.0 );

    } else if ( colorScheme == 5 ) { 

        gl_FragColor = vec4( 0.0, 0.0, percent, 1.0 );

    } else if ( colorScheme == 6 ) { 

        gl_FragColor = vec4( percent, 0.0, 1.0 - percent, 1.0 );

    } else if ( colorScheme == 7 ) { 

        gl_FragColor = vec4( 1.0 - percent, percent, percent, 1.0 );

    }

}
Und hier die entscheidene Code Passage zum erstellen der Partikel:
var geometry = new THREE.BufferGeometry();

var positions = new Float32Array( particles * 3 );

var index = 0;

for ( var i = 0, l = positions.length; i < l; i += 3 ) {

    // positions

    var phi = Math.acos( -1 + ( 2 * index ) / particles );
    var theta = Math.sqrt( particles * Math.PI ) * phi;

    var x = radius * Math.cos( theta ) * Math.sin( phi );
    var y = radius * Math.sin( theta ) * Math.sin( phi );
    var z = radius * Math.cos( phi );

    positions[ i     ] = x;
    positions[ i + 1 ] = y;
    positions[ i + 2 ] = z;

    index++;

}

geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );

uniforms = {

    amplitude: { type: 'f', value: 0 },
    d: { type: 'f', value: d },
    pi: { type: 'f', value: Math.MATHPI180 },
    distanceMax: { type: 'f', value: sinusMaxAmplitude },
    size: { type: 'f', value: particleSize },
    colorScheme: { type: 'i', value: colorScheme }

}

var shaderMaterial = new THREE.ShaderMaterial( {

    uniforms: uniforms,
    vertexShader: document.getElementById( 'vertexshader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentshader' ).textContent

} );

points = new THREE.Points( geometry, shaderMaterial );

scene.add( points );
An dieser Stelle wird der Wert für die Amplitude in der render Function an die Shader übergeben:
uniforms.amplitude.value = sinusAmplitude;

1 Kommentar:

Frank hat gesagt…

if else within shaders... bad idea to be optimized! :)