Posts mit dem Label Displacment werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Displacment werden angezeigt. Alle Posts anzeigen

3/17/2017

Tornado Simulation

Die Tornado Simulation ist ein Experiment das ich schon seit langem starten wollte. Und mit meiner 3D Particle-Engine, die jetzt schon in so zahlreichen anderen Experimenten zum Einsatz kam, und etwas Zeit ließ sich diese Simulation jetzt endlich mal umsetzen.
Für die Simulation selbst kommen keine weiteren Frameworks wie z.B. Three.js zum Einsatz. Einfach pures JavaScript. Dadurch konnte ich auch in diesem Experiment die Dateigröße mit 13KB recht klein halten.
Zum Rendern nutze ich das Canvas-Element und das ImageData Interface um Zugriff auf die Pixel zu bekommen.
Wer sich für dieses Thema interessiert, ich hatte dazu mal ein Tutorial geschrieben.
Aber zurück zum Tornado. Ziel war es im weitesten Sinn eine möglichst realistische Simulation eines Tornados mit den zur Verfügung stehenden Mitteln zu realisieren.
Die Höhe des Tornados ergibt sich aus der Anzahl der „Radial Segments“. Diese sind in einer sich drehenden Spirale angeordnet, die nach oben hin exponentiell größer wird. Auf die Animation der Verschiebung der Spirale könnt ihr mit „Spiral Disp.“ Einfluss nehmen. Um jedes der „Radial Segments“ kreisen die „Tubular Segments“. Deren Anzahl kann nur auf ein Maximum festgelegt werden, da hier der Zufall mitbestimmt wie viele „Tubular Segments“ pro „Radial Segment“ generiert werden. Jedes einzelne „Tubular Segment“, also Partikel, erhält einen per Zufall generierten Graustufen-Wert und kann eine Spur hinter sich herziehen, deren maximale Länge ihr in den Einstellungen per „Max Trail Length“ beeinflussen könnt. Auch auf die Geschwindigkeit und Drehrichtung der Partikel könnt ihr mit „Max Speed“ Einfluss nehmen. Mit „Min Radius“ könnt ihr zudem noch festlegen wie groß/klein der Radius des Tornados unten am Boden ist.
Geht einfach mal in die Einstellungen mit „Open Controls“ und spielt mit den Werten. Aber bitte etwas vorsichtig. Langsame Rechner könnten hierbei schnell an ihre Grenzen gebracht werden!
Ihr könnt die Anwendung direkt hier unterhalb starten (einfach mit der Maus über das untenstehende Fenster fahren), oder ihr öffnet die Simulation in einem neuen Browserfenster. Vor allem User mit mobilen Endgeräten und älteren Rechnern sollten sich die Simulation in einem extra Fenster anschauen.
Viel Spaß und haltet eure Hüte fest.

 

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;