فهرست منبع

Merge pull request #19318 from santi-grau/dev

GPGPU birds example with GLTF loader and MeshStandardMaterial
Mr.doob 5 سال پیش
والد
کامیت
89438bb39c
2فایلهای تغییر یافته به همراه665 افزوده شده و 0 حذف شده
  1. 1 0
      examples/files.js
  2. 664 0
      examples/webgl_gpgpu_birds_gltf.html

+ 1 - 0
examples/files.js

@@ -298,6 +298,7 @@ var files = {
 		"webgl_custom_attributes_points3",
 		"webgl_custom_attributes_points3",
 		"webgl_fire",
 		"webgl_fire",
 		"webgl_gpgpu_birds",
 		"webgl_gpgpu_birds",
+		"webgl_gpgpu_birds_gltf",
 		"webgl_gpgpu_water",
 		"webgl_gpgpu_water",
 		"webgl_gpgpu_protoplanet",
 		"webgl_gpgpu_protoplanet",
 		"webgl_instancing_modified",
 		"webgl_instancing_modified",

+ 664 - 0
examples/webgl_gpgpu_birds_gltf.html

@@ -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>