|
@@ -0,0 +1,664 @@
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
+<html lang="en">
|
|
|
|
+ <head>
|
|
|
|
+ <title>three.js webgl - gpgpu - flocking</title>
|
|
|
|
+ <meta charset="utf-8">
|
|
|
|
+ <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
|
|
+ <link type="text/css" rel="stylesheet" href="main.css">
|
|
|
|
+ <style>
|
|
|
|
+ body {
|
|
|
|
+ background-color: #fff;
|
|
|
|
+ color: #444;
|
|
|
|
+ }
|
|
|
|
+ a {
|
|
|
|
+ color:#08f;
|
|
|
|
+ }
|
|
|
|
+ </style>
|
|
|
|
+ </head>
|
|
|
|
+ <body>
|
|
|
|
+
|
|
|
|
+ <div id="info">
|
|
|
|
+ <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl gpgpu birds + GLTF mesh<br/>
|
|
|
|
+ Flamingo by <a href="http://mirada.com/">mirada</a> from <a href="http://ro.me">rome</a><br/>
|
|
|
|
+ Move mouse to disturb birds.
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- shader for bird's position -->
|
|
|
|
+ <script id="fragmentShaderPosition" type="x-shader/x-fragment">
|
|
|
|
+
|
|
|
|
+ uniform float time;
|
|
|
|
+ uniform float delta;
|
|
|
|
+
|
|
|
|
+ void main() {
|
|
|
|
+
|
|
|
|
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
|
|
|
|
+ vec4 tmpPos = texture2D( texturePosition, uv );
|
|
|
|
+ vec3 position = tmpPos.xyz;
|
|
|
|
+ vec3 velocity = texture2D( textureVelocity, uv ).xyz;
|
|
|
|
+
|
|
|
|
+ float phase = tmpPos.w;
|
|
|
|
+
|
|
|
|
+ phase = mod( ( phase + delta +
|
|
|
|
+ length( velocity.xz ) * delta * 3. +
|
|
|
|
+ max( velocity.y, 0.0 ) * delta * 6. ), 62.83 );
|
|
|
|
+
|
|
|
|
+ gl_FragColor = vec4( position + velocity * delta * 15. , phase );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ </script>
|
|
|
|
+
|
|
|
|
+ <!-- shader for bird's velocity -->
|
|
|
|
+ <script id="fragmentShaderVelocity" type="x-shader/x-fragment">
|
|
|
|
+
|
|
|
|
+ uniform float time;
|
|
|
|
+ uniform float testing;
|
|
|
|
+ uniform float delta; // about 0.016
|
|
|
|
+ uniform float separationDistance; // 20
|
|
|
|
+ uniform float alignmentDistance; // 40
|
|
|
|
+ uniform float cohesionDistance; //
|
|
|
|
+ uniform float freedomFactor;
|
|
|
|
+ uniform vec3 predator;
|
|
|
|
+
|
|
|
|
+ const float width = resolution.x;
|
|
|
|
+ const float height = resolution.y;
|
|
|
|
+
|
|
|
|
+ const float PI = 3.141592653589793;
|
|
|
|
+ const float PI_2 = PI * 2.0;
|
|
|
|
+ // const float VISION = PI * 0.55;
|
|
|
|
+
|
|
|
|
+ float zoneRadius = 40.0;
|
|
|
|
+ float zoneRadiusSquared = 1600.0;
|
|
|
|
+
|
|
|
|
+ float separationThresh = 0.45;
|
|
|
|
+ float alignmentThresh = 0.65;
|
|
|
|
+
|
|
|
|
+ const float UPPER_BOUNDS = BOUNDS;
|
|
|
|
+ const float LOWER_BOUNDS = -UPPER_BOUNDS;
|
|
|
|
+
|
|
|
|
+ const float SPEED_LIMIT = 9.0;
|
|
|
|
+
|
|
|
|
+ float rand( vec2 co ){
|
|
|
|
+ return fract( sin( dot( co.xy, vec2(12.9898,78.233) ) ) * 43758.5453 );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void main() {
|
|
|
|
+
|
|
|
|
+ zoneRadius = separationDistance + alignmentDistance + cohesionDistance;
|
|
|
|
+ separationThresh = separationDistance / zoneRadius;
|
|
|
|
+ alignmentThresh = ( separationDistance + alignmentDistance ) / zoneRadius;
|
|
|
|
+ zoneRadiusSquared = zoneRadius * zoneRadius;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
|
|
|
|
+ vec3 birdPosition, birdVelocity;
|
|
|
|
+
|
|
|
|
+ vec3 selfPosition = texture2D( texturePosition, uv ).xyz;
|
|
|
|
+ vec3 selfVelocity = texture2D( textureVelocity, uv ).xyz;
|
|
|
|
+
|
|
|
|
+ float dist;
|
|
|
|
+ vec3 dir; // direction
|
|
|
|
+ float distSquared;
|
|
|
|
+
|
|
|
|
+ float separationSquared = separationDistance * separationDistance;
|
|
|
|
+ float cohesionSquared = cohesionDistance * cohesionDistance;
|
|
|
|
+
|
|
|
|
+ float f;
|
|
|
|
+ float percent;
|
|
|
|
+
|
|
|
|
+ vec3 velocity = selfVelocity;
|
|
|
|
+
|
|
|
|
+ float limit = SPEED_LIMIT;
|
|
|
|
+
|
|
|
|
+ dir = predator * UPPER_BOUNDS - selfPosition;
|
|
|
|
+ dir.z = 0.;
|
|
|
|
+ // dir.z *= 0.6;
|
|
|
|
+ dist = length( dir );
|
|
|
|
+ distSquared = dist * dist;
|
|
|
|
+
|
|
|
|
+ float preyRadius = 150.0;
|
|
|
|
+ float preyRadiusSq = preyRadius * preyRadius;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // move birds away from predator
|
|
|
|
+ if ( dist < preyRadius ) {
|
|
|
|
+
|
|
|
|
+ f = ( distSquared / preyRadiusSq - 1.0 ) * delta * 100.;
|
|
|
|
+ velocity += normalize( dir ) * f;
|
|
|
|
+ limit += 5.0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // if (testing == 0.0) {}
|
|
|
|
+ // if ( rand( uv + time ) < freedomFactor ) {}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Attract flocks to the center
|
|
|
|
+ vec3 central = vec3( 0., 0., 0. );
|
|
|
|
+ dir = selfPosition - central;
|
|
|
|
+ dist = length( dir );
|
|
|
|
+
|
|
|
|
+ dir.y *= 2.5;
|
|
|
|
+ velocity -= normalize( dir ) * delta * 5.;
|
|
|
|
+
|
|
|
|
+ for ( float y = 0.0; y < height; y++ ) {
|
|
|
|
+ for ( float x = 0.0; x < width; x++ ) {
|
|
|
|
+
|
|
|
|
+ vec2 ref = vec2( x + 0.5, y + 0.5 ) / resolution.xy;
|
|
|
|
+ birdPosition = texture2D( texturePosition, ref ).xyz;
|
|
|
|
+
|
|
|
|
+ dir = birdPosition - selfPosition;
|
|
|
|
+ dist = length( dir );
|
|
|
|
+
|
|
|
|
+ if ( dist < 0.0001 ) continue;
|
|
|
|
+
|
|
|
|
+ distSquared = dist * dist;
|
|
|
|
+
|
|
|
|
+ if ( distSquared > zoneRadiusSquared ) continue;
|
|
|
|
+
|
|
|
|
+ percent = distSquared / zoneRadiusSquared;
|
|
|
|
+
|
|
|
|
+ if ( percent < separationThresh ) { // low
|
|
|
|
+
|
|
|
|
+ // Separation - Move apart for comfort
|
|
|
|
+ f = ( separationThresh / percent - 1.0 ) * delta;
|
|
|
|
+ velocity -= normalize( dir ) * f;
|
|
|
|
+
|
|
|
|
+ } else if ( percent < alignmentThresh ) { // high
|
|
|
|
+
|
|
|
|
+ // Alignment - fly the same direction
|
|
|
|
+ float threshDelta = alignmentThresh - separationThresh;
|
|
|
|
+ float adjustedPercent = ( percent - separationThresh ) / threshDelta;
|
|
|
|
+
|
|
|
|
+ birdVelocity = texture2D( textureVelocity, ref ).xyz;
|
|
|
|
+
|
|
|
|
+ f = ( 0.5 - cos( adjustedPercent * PI_2 ) * 0.5 + 0.5 ) * delta;
|
|
|
|
+ velocity += normalize( birdVelocity ) * f;
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ // Attraction / Cohesion - move closer
|
|
|
|
+ float threshDelta = 1.0 - alignmentThresh;
|
|
|
|
+ float adjustedPercent = ( percent - alignmentThresh ) / threshDelta;
|
|
|
|
+
|
|
|
|
+ f = ( 0.5 - ( cos( adjustedPercent * PI_2 ) * -0.5 + 0.5 ) ) * delta;
|
|
|
|
+
|
|
|
|
+ velocity += normalize( dir ) * f;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // this make tends to fly around than down or up
|
|
|
|
+ // if (velocity.y > 0.) velocity.y *= (1. - 0.2 * delta);
|
|
|
|
+
|
|
|
|
+ // Speed Limits
|
|
|
|
+ if ( length( velocity ) > limit ) {
|
|
|
|
+ velocity = normalize( velocity ) * limit;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ gl_FragColor = vec4( velocity, 1.0 );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ </script>
|
|
|
|
+
|
|
|
|
+ <script type="module">
|
|
|
|
+
|
|
|
|
+ import * as THREE from '../build/three.module.js';
|
|
|
|
+ import Stats from './jsm/libs/stats.module.js';
|
|
|
|
+ import { GUI } from './jsm/libs/dat.gui.module.js';
|
|
|
|
+ import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
|
|
|
|
+ import { GPUComputationRenderer } from './jsm/misc/GPUComputationRenderer.js';
|
|
|
|
+
|
|
|
|
+ /* TEXTURE WIDTH FOR SIMULATION */
|
|
|
|
+ var WIDTH = 64
|
|
|
|
+ var BIRDS = WIDTH * WIDTH;
|
|
|
|
+
|
|
|
|
+ /* BAKE ANIMATION INTO TEXTURE and CREATE GEOMETRY FROM BASE MODEL */
|
|
|
|
+ var BirdGeometry = new THREE.BufferGeometry();
|
|
|
|
+ var textureAnimation, durationAnimation, originalMaterial,birdMesh, materialShader, vertexPerBird;
|
|
|
|
+ function nextPowerOf2( n ) { return Math.pow( 2, Math.ceil( Math.log( n ) / Math.log( 2 ) ) ) };
|
|
|
|
+
|
|
|
|
+ Math.lerp = function (value1, value2, amount) {
|
|
|
|
+
|
|
|
|
+ amount = Math.max( Math.min( amount, 1 ), 0 );
|
|
|
|
+ return value1 + (value2 - value1) * amount;
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ var gltfs = [ 'models/gltf/Parrot.glb', 'models/gltf/Flamingo.glb' ]
|
|
|
|
+ var colors = [ 0xccFFFF, 0xffdeff ]
|
|
|
|
+ var sizes = [ 0.2, 0.1 ]
|
|
|
|
+ var selectModel = Math.floor( Math.random() * gltfs.length )
|
|
|
|
+ new GLTFLoader().load( gltfs[ selectModel ], function ( gltf ) {
|
|
|
|
+
|
|
|
|
+ var animations = gltf.animations;
|
|
|
|
+ durationAnimation = Math.round( animations[ 0 ].duration * 60 );
|
|
|
|
+ var birdGeo = gltf.scene.children[ 0 ].geometry;
|
|
|
|
+ originalMaterial = gltf.scene.children[ 0 ].material;
|
|
|
|
+ var morphAttributes = birdGeo.morphAttributes.position;
|
|
|
|
+ var tHeight = nextPowerOf2( durationAnimation );
|
|
|
|
+ var tWidth = nextPowerOf2( birdGeo.getAttribute( 'position' ).count );
|
|
|
|
+ vertexPerBird = birdGeo.getAttribute( 'position' ).count;
|
|
|
|
+ var tData = new Float32Array( 3 * tWidth * tHeight );
|
|
|
|
+
|
|
|
|
+ for( var i = 0 ; i < tWidth ; i++ ){
|
|
|
|
+
|
|
|
|
+ for( var j = 0 ; j < tHeight ; j++ ){
|
|
|
|
+
|
|
|
|
+ var offset = j * tWidth * 3;
|
|
|
|
+
|
|
|
|
+ var curFrame = j;
|
|
|
|
+ var curMorph = Math.floor( j / durationAnimation * morphAttributes.length );
|
|
|
|
+ var nextMorph = ( Math.floor( j / durationAnimation * morphAttributes.length ) + 1 ) % morphAttributes.length;
|
|
|
|
+ var lerpAmount = j / durationAnimation * morphAttributes.length % 1;
|
|
|
|
+
|
|
|
|
+ if( j < durationAnimation ){
|
|
|
|
+
|
|
|
|
+ tData[ offset + i * 3 ] = Math.lerp( morphAttributes[ curMorph ].array[ i * 3 ], morphAttributes[ nextMorph ].array[ i * 3 ], lerpAmount );
|
|
|
|
+ tData[ offset + i * 3 + 1 ] = Math.lerp( morphAttributes[ curMorph ].array[ i * 3 + 1 ], morphAttributes[ nextMorph ].array[ i * 3 + 1 ], lerpAmount );
|
|
|
|
+ tData[ offset + i * 3 + 2 ] = Math.lerp( morphAttributes[ curMorph ].array[ i * 3 + 2 ], morphAttributes[ nextMorph ].array[ i * 3 + 2 ], lerpAmount );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ textureAnimation = new THREE.DataTexture( tData, tWidth, tHeight, THREE.RGBFormat, THREE.FloatType );
|
|
|
|
+ textureAnimation.needsUpdate = true;
|
|
|
|
+
|
|
|
|
+ var vertices = [], color = [], reference = [], seeds = [], indices = [];
|
|
|
|
+ var totalVertices = birdGeo.getAttribute( 'position' ).count * 3 * BIRDS;
|
|
|
|
+ for( var i = 0 ; i < totalVertices ; i++ ){
|
|
|
|
+
|
|
|
|
+ var bIndex = i % ( birdGeo.getAttribute( 'position' ).count * 3 );
|
|
|
|
+ vertices.push( birdGeo.getAttribute( 'position' ).array[ bIndex ] );
|
|
|
|
+ color.push( birdGeo.getAttribute( 'color' ).array[ bIndex ] );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var r = Math.random();
|
|
|
|
+ for( var i = 0 ; i < birdGeo.getAttribute( 'position' ).count * BIRDS ; i++ ){
|
|
|
|
+
|
|
|
|
+ var bIndex = i % ( birdGeo.getAttribute( 'position' ).count );
|
|
|
|
+ var bird = Math.floor( i / birdGeo.getAttribute( 'position' ).count );
|
|
|
|
+ if( bIndex == 0 ) r = Math.random();
|
|
|
|
+ var j = ~ ~ bird;
|
|
|
|
+ var x = ( j % WIDTH ) / WIDTH;
|
|
|
|
+ var y = ~ ~ ( j / WIDTH ) / WIDTH;
|
|
|
|
+ reference.push( x, y, bIndex / tWidth, durationAnimation / tHeight );
|
|
|
|
+ seeds.push( bird, r, Math.random(), Math.random() );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for( var i = 0 ; i < birdGeo.index.array.length * BIRDS ; i++ ){
|
|
|
|
+
|
|
|
|
+ var offset = Math.floor( i / birdGeo.index.array.length ) * ( birdGeo.getAttribute( 'position' ).count );
|
|
|
|
+ indices.push( birdGeo.index.array[ i % birdGeo.index.array.length ] + offset );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ BirdGeometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertices ), 3 ) );
|
|
|
|
+ BirdGeometry.setAttribute( 'birdColor', new THREE.BufferAttribute( new Float32Array( color ), 3 ) );
|
|
|
|
+ BirdGeometry.setAttribute( 'color', new THREE.BufferAttribute( new Float32Array( color ), 3 ) );
|
|
|
|
+ BirdGeometry.setAttribute( 'reference', new THREE.BufferAttribute( new Float32Array( reference ), 4 ) );
|
|
|
|
+ BirdGeometry.setAttribute( 'seeds', new THREE.BufferAttribute( new Float32Array( seeds ), 4 ) );
|
|
|
|
+
|
|
|
|
+ BirdGeometry.setIndex( indices );
|
|
|
|
+
|
|
|
|
+ init();
|
|
|
|
+ animate();
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ var container, stats;
|
|
|
|
+ var camera, scene, renderer;
|
|
|
|
+ var mouseX = 0, mouseY = 0;
|
|
|
|
+
|
|
|
|
+ var windowHalfX = window.innerWidth / 2;
|
|
|
|
+ var windowHalfY = window.innerHeight / 2;
|
|
|
|
+
|
|
|
|
+ var BOUNDS = 800, BOUNDS_HALF = BOUNDS / 2;
|
|
|
|
+
|
|
|
|
+ var last = performance.now();
|
|
|
|
+
|
|
|
|
+ var gpuCompute;
|
|
|
|
+ var velocityVariable;
|
|
|
|
+ var positionVariable;
|
|
|
|
+ var positionUniforms;
|
|
|
|
+ var velocityUniforms;
|
|
|
|
+ var birdUniforms;
|
|
|
|
+
|
|
|
|
+ function init() {
|
|
|
|
+
|
|
|
|
+ container = document.createElement( 'div' );
|
|
|
|
+ document.body.appendChild( container );
|
|
|
|
+
|
|
|
|
+ camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 3000 );
|
|
|
|
+ camera.position.z = 350;
|
|
|
|
+
|
|
|
|
+ scene = new THREE.Scene();
|
|
|
|
+ scene.background = new THREE.Color( colors[ selectModel ] );
|
|
|
|
+ scene.fog = new THREE.Fog( colors[ selectModel ], 100, 1000 );
|
|
|
|
+
|
|
|
|
+ // LIGHTS
|
|
|
|
+
|
|
|
|
+ var hemiLight = new THREE.HemisphereLight( colors[ selectModel ], 0xffffff, 1.6 );
|
|
|
|
+ hemiLight.color.setHSL( 0.6, 1, 0.6 );
|
|
|
|
+ hemiLight.groundColor.setHSL( 0.095, 1, 0.75 );
|
|
|
|
+ hemiLight.position.set( 0, 50, 0 );
|
|
|
|
+ scene.add( hemiLight );
|
|
|
|
+
|
|
|
|
+ var dirLight = new THREE.DirectionalLight( 0x00CED1, 0.6 );
|
|
|
|
+ dirLight.color.setHSL( 0.1, 1, 0.95 );
|
|
|
|
+ dirLight.position.set( - 1, 1.75, 1 );
|
|
|
|
+ dirLight.position.multiplyScalar( 30 );
|
|
|
|
+ scene.add( dirLight );
|
|
|
|
+
|
|
|
|
+ renderer = new THREE.WebGLRenderer( { antialias : true, alpha : true } );
|
|
|
|
+ renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
+ container.appendChild( renderer.domElement );
|
|
|
|
+
|
|
|
|
+ initComputeRenderer();
|
|
|
|
+
|
|
|
|
+ stats = new Stats();
|
|
|
|
+ container.appendChild( stats.dom );
|
|
|
|
+
|
|
|
|
+ document.addEventListener( 'mousemove', onDocumentMouseMove, false );
|
|
|
|
+ document.addEventListener( 'touchstart', onDocumentTouchStart, false );
|
|
|
|
+ document.addEventListener( 'touchmove', onDocumentTouchMove, false );
|
|
|
|
+
|
|
|
|
+ window.addEventListener( 'resize', onWindowResize, false );
|
|
|
|
+
|
|
|
|
+ var gui = new GUI();
|
|
|
|
+
|
|
|
|
+ var effectController = {
|
|
|
|
+
|
|
|
|
+ separation: 20.0,
|
|
|
|
+ alignment: 20.0,
|
|
|
|
+ cohesion: 20.0,
|
|
|
|
+ freedom: 0.75,
|
|
|
|
+ size: sizes[ selectModel ],
|
|
|
|
+ count: BIRDS
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ var valuesChanger = function () {
|
|
|
|
+
|
|
|
|
+ velocityUniforms[ "separationDistance" ].value = effectController.separation;
|
|
|
|
+ velocityUniforms[ "alignmentDistance" ].value = effectController.alignment;
|
|
|
|
+ velocityUniforms[ "cohesionDistance" ].value = effectController.cohesion;
|
|
|
|
+ velocityUniforms[ "freedomFactor" ].value = effectController.freedom;
|
|
|
|
+ if( materialShader ) materialShader.uniforms[ "size" ].value = effectController.size;
|
|
|
|
+ BirdGeometry.setDrawRange ( 0, vertexPerBird * effectController.count )
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ valuesChanger();
|
|
|
|
+
|
|
|
|
+ gui.add( effectController, "separation", 0.0, 100.0, 1.0 ).onChange( valuesChanger );
|
|
|
|
+ gui.add( effectController, "alignment", 0.0, 100, 0.001 ).onChange( valuesChanger );
|
|
|
|
+ gui.add( effectController, "cohesion", 0.0, 100, 0.025 ).onChange( valuesChanger );
|
|
|
|
+ gui.add( effectController, "size", 0, 1, 0.01 ).onChange( valuesChanger );
|
|
|
|
+ gui.add( effectController, "count", 0, BIRDS, 1 ).onChange( valuesChanger );
|
|
|
|
+ gui.close();
|
|
|
|
+
|
|
|
|
+ initBirds();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function initComputeRenderer() {
|
|
|
|
+
|
|
|
|
+ gpuCompute = new GPUComputationRenderer( WIDTH, WIDTH, renderer );
|
|
|
|
+
|
|
|
|
+ var dtPosition = gpuCompute.createTexture();
|
|
|
|
+ var dtVelocity = gpuCompute.createTexture();
|
|
|
|
+ fillPositionTexture( dtPosition );
|
|
|
|
+ fillVelocityTexture( dtVelocity );
|
|
|
|
+
|
|
|
|
+ velocityVariable = gpuCompute.addVariable( "textureVelocity", document.getElementById( 'fragmentShaderVelocity' ).textContent, dtVelocity );
|
|
|
|
+ positionVariable = gpuCompute.addVariable( "texturePosition", document.getElementById( 'fragmentShaderPosition' ).textContent, dtPosition );
|
|
|
|
+
|
|
|
|
+ gpuCompute.setVariableDependencies( velocityVariable, [ positionVariable, velocityVariable ] );
|
|
|
|
+ gpuCompute.setVariableDependencies( positionVariable, [ positionVariable, velocityVariable ] );
|
|
|
|
+
|
|
|
|
+ positionUniforms = positionVariable.material.uniforms;
|
|
|
|
+ velocityUniforms = velocityVariable.material.uniforms;
|
|
|
|
+
|
|
|
|
+ positionUniforms[ "time" ] = { value: 0.0 };
|
|
|
|
+ positionUniforms[ "delta" ] = { value: 0.0 };
|
|
|
|
+ velocityUniforms[ "time" ] = { value: 1.0 };
|
|
|
|
+ velocityUniforms[ "delta" ] = { value: 0.0 };
|
|
|
|
+ velocityUniforms[ "testing" ] = { value: 1.0 };
|
|
|
|
+ velocityUniforms[ "separationDistance" ] = { value: 1.0 };
|
|
|
|
+ velocityUniforms[ "alignmentDistance" ] = { value: 1.0 };
|
|
|
|
+ velocityUniforms[ "cohesionDistance" ] = { value: 1.0 };
|
|
|
|
+ velocityUniforms[ "freedomFactor" ] = { value: 1.0 };
|
|
|
|
+ velocityUniforms[ "predator" ] = { value: new THREE.Vector3() };
|
|
|
|
+ velocityVariable.material.defines.BOUNDS = BOUNDS.toFixed( 2 );
|
|
|
|
+
|
|
|
|
+ velocityVariable.wrapS = THREE.RepeatWrapping;
|
|
|
|
+ velocityVariable.wrapT = THREE.RepeatWrapping;
|
|
|
|
+ positionVariable.wrapS = THREE.RepeatWrapping;
|
|
|
|
+ positionVariable.wrapT = THREE.RepeatWrapping;
|
|
|
|
+
|
|
|
|
+ var error = gpuCompute.init();
|
|
|
|
+ if ( error !== null ) {
|
|
|
|
+ console.error( error );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function initBirds() {
|
|
|
|
+
|
|
|
|
+ var geometry = BirdGeometry;
|
|
|
|
+
|
|
|
|
+ var m = new THREE.MeshStandardMaterial({
|
|
|
|
+ vertexColors : true,
|
|
|
|
+ flatShading : true,
|
|
|
|
+ roughness : 1,
|
|
|
|
+ metalness : 0
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ m.onBeforeCompile = ( shader ) => {
|
|
|
|
+
|
|
|
|
+ shader.uniforms.texturePosition = { value : null };
|
|
|
|
+ shader.uniforms.textureVelocity = { value : null };
|
|
|
|
+ shader.uniforms.textureAnimation = { value : textureAnimation };
|
|
|
|
+ shader.uniforms.time = { value : 1.0 };
|
|
|
|
+ shader.uniforms.size = { value : 0.1 };
|
|
|
|
+ shader.uniforms.delta = { value : 0.0 };
|
|
|
|
+
|
|
|
|
+ var token = '#define STANDARD';
|
|
|
|
+
|
|
|
|
+ var insert = /* glsl */`
|
|
|
|
+ attribute vec4 reference;
|
|
|
|
+ attribute vec4 seeds;
|
|
|
|
+ attribute vec3 birdColor;
|
|
|
|
+ uniform sampler2D texturePosition;
|
|
|
|
+ uniform sampler2D textureVelocity;
|
|
|
|
+ uniform sampler2D textureAnimation;
|
|
|
|
+ uniform float size;
|
|
|
|
+ uniform float time;
|
|
|
|
+ `;
|
|
|
|
+
|
|
|
|
+ shader.vertexShader = shader.vertexShader.replace( token, token + insert );
|
|
|
|
+
|
|
|
|
+ var token = '#include <begin_vertex>';
|
|
|
|
+
|
|
|
|
+ var insert = /* glsl */`
|
|
|
|
+ vec4 tmpPos = texture2D( texturePosition, reference.xy );
|
|
|
|
+
|
|
|
|
+ vec3 pos = tmpPos.xyz;
|
|
|
|
+ vec3 velocity = normalize(texture2D( textureVelocity, reference.xy ).xyz);
|
|
|
|
+ vec3 aniPos = texture2D( textureAnimation, vec2( reference.z, mod( ( time + seeds.x ) * ( ( 0.0004 + seeds.y / 10000.0) + normalize( velocity ) / 20000.0 ), reference.w ) ) ).xyz;
|
|
|
|
+ vec3 newPosition = position;
|
|
|
|
+
|
|
|
|
+ newPosition = mat3( modelMatrix ) * ( newPosition + aniPos );
|
|
|
|
+ newPosition *= size + seeds.y * size * 0.2;
|
|
|
|
+
|
|
|
|
+ velocity.z *= -1.;
|
|
|
|
+ float xz = length( velocity.xz );
|
|
|
|
+ float xyz = 1.;
|
|
|
|
+ float x = sqrt( 1. - velocity.y * velocity.y );
|
|
|
|
+
|
|
|
|
+ float cosry = velocity.x / xz;
|
|
|
|
+ float sinry = velocity.z / xz;
|
|
|
|
+
|
|
|
|
+ float cosrz = x / xyz;
|
|
|
|
+ float sinrz = velocity.y / xyz;
|
|
|
|
+
|
|
|
|
+ mat3 maty = mat3( cosry, 0, -sinry, 0 , 1, 0 , sinry, 0, cosry );
|
|
|
|
+ mat3 matz = mat3( cosrz , sinrz, 0, -sinrz, cosrz, 0, 0 , 0 , 1 );
|
|
|
|
+
|
|
|
|
+ newPosition = maty * matz * newPosition;
|
|
|
|
+ newPosition += pos;
|
|
|
|
+
|
|
|
|
+ vec3 transformed = vec3( newPosition );
|
|
|
|
+ `;
|
|
|
|
+
|
|
|
|
+ shader.vertexShader = shader.vertexShader.replace( token, insert );
|
|
|
|
+
|
|
|
|
+ materialShader = shader;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ birdMesh = new THREE.Mesh( geometry, m );
|
|
|
|
+ birdMesh.rotation.y = Math.PI / 2;
|
|
|
|
+
|
|
|
|
+ birdMesh.castShadow = true;
|
|
|
|
+ birdMesh.receiveShadow = true;
|
|
|
|
+
|
|
|
|
+ scene.add( birdMesh );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function fillPositionTexture( texture ) {
|
|
|
|
+
|
|
|
|
+ var theArray = texture.image.data;
|
|
|
|
+
|
|
|
|
+ for ( var k = 0, kl = theArray.length; k < kl; k += 4 ) {
|
|
|
|
+
|
|
|
|
+ var x = Math.random() * BOUNDS - BOUNDS_HALF;
|
|
|
|
+ var y = Math.random() * BOUNDS - BOUNDS_HALF;
|
|
|
|
+ var z = Math.random() * BOUNDS - BOUNDS_HALF;
|
|
|
|
+
|
|
|
|
+ theArray[ k + 0 ] = x;
|
|
|
|
+ theArray[ k + 1 ] = y;
|
|
|
|
+ theArray[ k + 2 ] = z;
|
|
|
|
+ theArray[ k + 3 ] = 1;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function fillVelocityTexture( texture ) {
|
|
|
|
+
|
|
|
|
+ var theArray = texture.image.data;
|
|
|
|
+
|
|
|
|
+ for ( var k = 0, kl = theArray.length; k < kl; k += 4 ) {
|
|
|
|
+
|
|
|
|
+ var x = Math.random() - 0.5;
|
|
|
|
+ var y = Math.random() - 0.5;
|
|
|
|
+ var z = Math.random() - 0.5;
|
|
|
|
+
|
|
|
|
+ theArray[ k + 0 ] = x * 10;
|
|
|
|
+ theArray[ k + 1 ] = y * 10;
|
|
|
|
+ theArray[ k + 2 ] = z * 10;
|
|
|
|
+ theArray[ k + 3 ] = 1;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ function onWindowResize() {
|
|
|
|
+
|
|
|
|
+ windowHalfX = window.innerWidth / 2;
|
|
|
|
+ windowHalfY = window.innerHeight / 2;
|
|
|
|
+
|
|
|
|
+ camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
|
+
|
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function onDocumentMouseMove( event ) {
|
|
|
|
+
|
|
|
|
+ mouseX = event.clientX - windowHalfX;
|
|
|
|
+ mouseY = event.clientY - windowHalfY;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function onDocumentTouchStart( event ) {
|
|
|
|
+
|
|
|
|
+ if ( event.touches.length === 1 ) {
|
|
|
|
+
|
|
|
|
+ event.preventDefault();
|
|
|
|
+
|
|
|
|
+ mouseX = event.touches[ 0 ].pageX - windowHalfX;
|
|
|
|
+ mouseY = event.touches[ 0 ].pageY - windowHalfY;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function onDocumentTouchMove( event ) {
|
|
|
|
+
|
|
|
|
+ if ( event.touches.length === 1 ) {
|
|
|
|
+
|
|
|
|
+ event.preventDefault();
|
|
|
|
+
|
|
|
|
+ mouseX = event.touches[ 0 ].pageX - windowHalfX;
|
|
|
|
+ mouseY = event.touches[ 0 ].pageY - windowHalfY;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //
|
|
|
|
+
|
|
|
|
+ function animate() {
|
|
|
|
+
|
|
|
|
+ requestAnimationFrame( animate );
|
|
|
|
+
|
|
|
|
+ render();
|
|
|
|
+ stats.update();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function render() {
|
|
|
|
+
|
|
|
|
+ var now = performance.now();
|
|
|
|
+ var delta = ( now - last ) / 1000;
|
|
|
|
+
|
|
|
|
+ if ( delta > 1 ) delta = 1; // safety cap on large deltas
|
|
|
|
+ last = now;
|
|
|
|
+
|
|
|
|
+ positionUniforms[ "time" ].value = now;
|
|
|
|
+ positionUniforms[ "delta" ].value = delta;
|
|
|
|
+ velocityUniforms[ "time" ].value = now;
|
|
|
|
+ velocityUniforms[ "delta" ].value = delta;
|
|
|
|
+ if( materialShader ) materialShader.uniforms[ "time" ].value = now;
|
|
|
|
+ if( materialShader ) materialShader.uniforms[ "delta" ].value = delta;
|
|
|
|
+
|
|
|
|
+ velocityUniforms[ "predator" ].value.set( 0.5 * mouseX / windowHalfX, - 0.5 * mouseY / windowHalfY, 0 );
|
|
|
|
+
|
|
|
|
+ mouseX = 10000;
|
|
|
|
+ mouseY = 10000;
|
|
|
|
+
|
|
|
|
+ gpuCompute.compute();
|
|
|
|
+
|
|
|
|
+ if( materialShader ) materialShader.uniforms[ "texturePosition" ].value = gpuCompute.getCurrentRenderTarget( positionVariable ).texture;
|
|
|
|
+ if( materialShader ) materialShader.uniforms[ "textureVelocity" ].value = gpuCompute.getCurrentRenderTarget( velocityVariable ).texture;
|
|
|
|
+
|
|
|
|
+ renderer.render( scene, camera );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ </script>
|
|
|
|
+ </body>
|
|
|
|
+</html>
|