<!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"> <style> body { background-color: #ffffff; margin: 0px; overflow: hidden; font-family:Monospace; font-size:13px; text-align:center; text-align:center; cursor: pointer; } a { color:#0078ff; } #info { color: #000; position: absolute; top: 10px; width: 100%; } </style> </head> <body> <div id="info"> <a href="http://threejs.org" target="_blank">three.js</a> - <span id="birds"></span> webgl gpgpu birds<br/> Select <span id="options"></span> birds<br/> Move mouse to disturb birds. </div> <script src="../build/three.min.js"></script> <script src="js/Detector.js"></script> <script src="js/libs/stats.min.js"></script> <script src="js/libs/dat.gui.min.js"></script> <script src="js/SimulationRenderer.js"></script> <!-- TODO: If you're reading this, you may wish to improve this example by - Create a better shading for the birds? - Refactoring the SimulationRenderer to a more generic TextureRenderer / making the GPGPU workflow easier? --> <!-- pass through vertex shader --> <script id="vertexShader" type="x-shader/x-vertex"> void main() { gl_Position = vec4( position, 1.0 ); } </script> <!-- pass through fragment shader --> <script id="fragmentShader" type="x-shader/x-fragment"> uniform vec2 resolution; uniform float time; uniform sampler2D texture; void main() { vec2 uv = gl_FragCoord.xy / resolution.xy; vec3 color = texture2D( texture, uv ).xyz; gl_FragColor = vec4( color, 1.0 ); } </script> <!-- end pass through shaders --> <!-- shader for bird's position --> <script id="fragmentShaderPosition" type="x-shader/x-fragment"> uniform vec2 resolution; uniform float time; uniform float delta; uniform sampler2D textureVelocity; uniform sampler2D texturePosition; 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 vec2 resolution; uniform float time; uniform float testing; uniform float delta; // about 0.016 uniform float seperationDistance; // 20 uniform float alignmentDistance; // 40 uniform float cohesionDistance; // uniform float freedomFactor; uniform vec3 predator; uniform sampler2D textureVelocity; uniform sampler2D texturePosition; const float width = WIDTH; const float height = WIDTH; const float PI = 3.141592653589793; const float PI_2 = PI * 2.0; // const float VISION = PI * 0.55; float zoneRadius = 40.0; float zoneRadiusSquared = zoneRadius * zoneRadius; float separationThresh = 0.45; float alignmentThresh = 0.65; const float UPPER_BOUNDS = 400.0; 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 = seperationDistance + alignmentDistance + cohesionDistance; separationThresh = seperationDistance / zoneRadius; alignmentThresh = ( seperationDistance + 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 seperationSquared = seperationDistance * seperationDistance; 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="x-shader/x-vertex" id="birdVS"> attribute vec2 reference; attribute float birdVertex; attribute vec3 birdColor; uniform sampler2D texturePosition; uniform sampler2D textureVelocity; varying vec4 vColor; varying float z; uniform float time; void main() { vec4 tmpPos = texture2D( texturePosition, reference ); vec3 pos = tmpPos.xyz; vec3 velocity = normalize(texture2D( textureVelocity, reference ).xyz); vec3 newPosition = position; if ( birdVertex == 4.0 || birdVertex == 7.0 ) { // flap wings newPosition.y = sin( tmpPos.w ) * 5.; } newPosition = mat3( modelMatrix ) * newPosition; 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; z = newPosition.z; vColor = vec4( birdColor, 1.0 ); gl_Position = projectionMatrix * viewMatrix * vec4( newPosition, 1.0 ); } </script> <!-- bird geometry shader --> <script type="x-shader/x-fragment" id="birdFS"> varying vec4 vColor; varying float z; uniform vec3 color; void main() { // Fake colors for now float z2 = 0.2 + ( 1000. - z ) / 1000. * vColor.x; gl_FragColor = vec4( z2, z2, z2, 1. ); } </script> <script> if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); var hash = document.location.hash.substr( 1 ); if (hash) hash = parseInt(hash, 0); /* TEXTURE WIDTH FOR SIMULATION */ var WIDTH = hash || 32; var BIRDS = WIDTH * WIDTH; // Custom Geometry - using 3 triangles each. No UVs, no normals currently. THREE.BirdGeometry = function () { var triangles = BIRDS * 3; var points = triangles * 3; THREE.BufferGeometry.call( this ); var vertices = new THREE.BufferAttribute( new Float32Array( points * 3 ), 3 ); var birdColors = new THREE.BufferAttribute( new Float32Array( points * 3 ), 3 ); var references = new THREE.BufferAttribute( new Float32Array( points * 2 ), 2 ); var birdVertex = new THREE.BufferAttribute( new Float32Array( points ), 1 ); this.addAttribute( 'position', vertices ); this.addAttribute( 'birdColor', birdColors ); this.addAttribute( 'reference', references ); this.addAttribute( 'birdVertex', birdVertex ); // this.addAttribute( 'normal', new Float32Array( points * 3 ), 3 ); var v = 0; function verts_push() { for (var i=0; i < arguments.length; i++) { vertices.array[v++] = arguments[i]; } } var wingsSpan = 20; for (var f = 0; f<BIRDS; f++ ) { // Body verts_push( 0, -0, -20, 0, 4, -20, 0, 0, 30 ); // Left Wing verts_push( 0, 0, -15, -wingsSpan, 0, 0, 0, 0, 15 ); // Right Wing verts_push( 0, 0, 15, wingsSpan, 0, 0, 0, 0, -15 ); } for( var v = 0; v < triangles * 3; v++ ) { var i = ~~(v / 3); var x = (i % WIDTH) / WIDTH; var y = ~~(i / WIDTH) / WIDTH; var c = new THREE.Color( 0x444444 + ~~(v / 9) / BIRDS * 0x666666 ); birdColors.array[ v * 3 + 0 ] = c.r; birdColors.array[ v * 3 + 1 ] = c.g; birdColors.array[ v * 3 + 2 ] = c.b; references.array[ v * 2 ] = x; references.array[ v * 2 + 1 ] = y; birdVertex.array[ v ] = v % 9; } this.applyMatrix( new THREE.Matrix4().makeScale( 0.2, 0.2, 0.2 ) ); } THREE.BirdGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); var container, stats; var camera, scene, renderer, geometry, i, h, color; var mouseX = 0, mouseY = 0; var windowHalfX = window.innerWidth / 2; var windowHalfY = window.innerHeight / 2; var PARTICLES = WIDTH * WIDTH; var BOUNDS = 800, BOUNDS_HALF = BOUNDS / 2; document.getElementById('birds').innerText = PARTICLES; function change(n) { location.hash = n; location.reload(); return false; } var options = ''; for (i=1; i<7; i++) { var j = Math.pow(2, i); options += '<a href="#" onclick="return change(' + j + ')">' + (j * j) + '</a> '; } document.getElementById('options').innerHTML = options; var last = performance.now(); var simulator; init(); animate(); 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.fog = new THREE.Fog( 0xffffff, 100, 1000 ); renderer = new THREE.WebGLRenderer(); renderer.setClearColor( scene.fog.color ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); container.appendChild( renderer.domElement ); simulator = new SimulationRenderer(WIDTH, renderer); simulator.init(); stats = new Stats(); stats.domElement.style.position = 'absolute'; stats.domElement.style.top = '0px'; container.appendChild( stats.domElement ); document.addEventListener( 'mousemove', onDocumentMouseMove, false ); document.addEventListener( 'touchstart', onDocumentTouchStart, false ); document.addEventListener( 'touchmove', onDocumentTouchMove, false ); // window.addEventListener( 'resize', onWindowResize, false ); var gui = new dat.GUI(); var effectController = { seperation: 20.0, alignment: 20.0, cohesion: 20.0, freedom: 0.75 }; var valuesChanger = function() { simulator.velocityUniforms.seperationDistance.value = effectController.seperation; simulator.velocityUniforms.alignmentDistance.value = effectController.alignment; simulator.velocityUniforms.cohesionDistance.value = effectController.cohesion; simulator.velocityUniforms.freedomFactor.value = effectController.freedom; }; valuesChanger(); gui.add( effectController, "seperation", 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.close(); initBirds(); } function initBirds() { var geometry = new THREE.BirdGeometry(); // For Vertex Shaders birdAttributes = { // index: { type: 'i', value: [] }, birdColor: { type: 'c', value: null }, reference: { type: 'v2', value: null }, birdVertex: { type: 'f', value: null } }; // For Vertex and Fragment birdUniforms = { color: { type: "c", value: new THREE.Color( 0xff2200 ) }, texturePosition: { type: "t", value: null }, textureVelocity: { type: "t", value: null }, time: { type: "f", value: 1.0 }, delta: { type: "f", value: 0.0 }, }; // ShaderMaterial var shaderMaterial = new THREE.ShaderMaterial( { uniforms: birdUniforms, attributes: birdAttributes, vertexShader: document.getElementById( 'birdVS' ).textContent, fragmentShader: document.getElementById( 'birdFS' ).textContent, side: THREE.DoubleSide }); // var birdMesh = new THREE.Mesh( geometry, shaderMaterial ); birdMesh.rotation.y = Math.PI / 2; birdMesh.matrixAutoUpdate = false; birdMesh.updateMatrix(); scene.add(birdMesh); } 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; birdUniforms.time.value = now; birdUniforms.delta.value = delta; simulator.simulate( delta ); simulator.velocityUniforms.predator.value.set( mouseX / windowHalfX, -mouseY / windowHalfY, 0 ); mouseX = 10000; mouseY = 10000; renderer.render( scene, camera ); } </script> </body> </html>