webgl_gpgpu_birds.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - gpgpu - flocking</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <style>
  8. body {
  9. background-color: #ffffff;
  10. margin: 0px;
  11. overflow: hidden;
  12. font-family:Monospace;
  13. font-size:13px;
  14. text-align:center;
  15. text-align:center;
  16. cursor: pointer;
  17. }
  18. a {
  19. color:#0078ff;
  20. }
  21. #info {
  22. color: #000;
  23. position: absolute;
  24. top: 10px;
  25. width: 100%;
  26. }
  27. </style>
  28. </head>
  29. <body>
  30. <div id="info">
  31. <a href="http://threejs.org" target="_blank">three.js</a> - <span id="birds"></span> webgl gpgpu birds<br/>
  32. Select <span id="options"></span> birds<br/>
  33. Move mouse to disturb birds.
  34. </div>
  35. <script src="../build/three.min.js"></script>
  36. <script src="js/Detector.js"></script>
  37. <script src="js/libs/stats.min.js"></script>
  38. <script src="js/libs/dat.gui.min.js"></script>
  39. <script src="js/SimulationRenderer.js"></script>
  40. <!--
  41. TODO: If you're reading this, you may wish to improve this example by
  42. - Create a better shading for the birds?
  43. - Refactoring the SimulationRenderer to a more generic TextureRenderer / making the GPGPU workflow easier?
  44. -->
  45. <!-- pass through vertex shader -->
  46. <script id="vertexShader" type="x-shader/x-vertex">
  47. void main() {
  48. gl_Position = vec4( position, 1.0 );
  49. }
  50. </script>
  51. <!-- pass through fragment shader -->
  52. <script id="fragmentShader" type="x-shader/x-fragment">
  53. uniform vec2 resolution;
  54. uniform float time;
  55. uniform sampler2D texture;
  56. void main() {
  57. vec2 uv = gl_FragCoord.xy / resolution.xy;
  58. vec3 color = texture2D( texture, uv ).xyz;
  59. gl_FragColor = vec4( color, 1.0 );
  60. }
  61. </script>
  62. <!-- end pass through shaders -->
  63. <!-- shader for bird's position -->
  64. <script id="fragmentShaderPosition" type="x-shader/x-fragment">
  65. uniform vec2 resolution;
  66. uniform float time;
  67. uniform float delta;
  68. uniform sampler2D textureVelocity;
  69. uniform sampler2D texturePosition;
  70. void main() {
  71. vec2 uv = gl_FragCoord.xy / resolution.xy;
  72. vec4 tmpPos = texture2D( texturePosition, uv );
  73. vec3 position = tmpPos.xyz;
  74. vec3 velocity = texture2D( textureVelocity, uv ).xyz;
  75. float phase = tmpPos.w;
  76. phase = mod( ( phase + delta +
  77. length( velocity.xz ) * delta * 3. +
  78. max( velocity.y, 0.0 ) * delta * 6. ), 62.83 );
  79. gl_FragColor = vec4( position + velocity * delta * 15. , phase );
  80. }
  81. </script>
  82. <!-- shader for bird's velocity -->
  83. <script id="fragmentShaderVelocity" type="x-shader/x-fragment">
  84. uniform vec2 resolution;
  85. uniform float time;
  86. uniform float testing;
  87. uniform float delta; // about 0.016
  88. uniform float seperationDistance; // 20
  89. uniform float alignmentDistance; // 40
  90. uniform float cohesionDistance; //
  91. uniform float freedomFactor;
  92. uniform vec3 predator;
  93. uniform sampler2D textureVelocity;
  94. uniform sampler2D texturePosition;
  95. const float width = WIDTH;
  96. const float height = WIDTH;
  97. const float PI = 3.141592653589793;
  98. const float PI_2 = PI * 2.0;
  99. // const float VISION = PI * 0.55;
  100. float zoneRadius = 40.0;
  101. float zoneRadiusSquared = zoneRadius * zoneRadius;
  102. float separationThresh = 0.45;
  103. float alignmentThresh = 0.65;
  104. const float UPPER_BOUNDS = 400.0;
  105. const float LOWER_BOUNDS = -UPPER_BOUNDS;
  106. const float SPEED_LIMIT = 9.0;
  107. float rand(vec2 co){
  108. return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
  109. }
  110. void main() {
  111. zoneRadius = seperationDistance + alignmentDistance + cohesionDistance;
  112. separationThresh = seperationDistance / zoneRadius;
  113. alignmentThresh = ( seperationDistance + alignmentDistance ) / zoneRadius;
  114. zoneRadiusSquared = zoneRadius * zoneRadius;
  115. vec2 uv = gl_FragCoord.xy / resolution.xy;
  116. vec3 birdPosition, birdVelocity;
  117. vec3 selfPosition = texture2D( texturePosition, uv ).xyz;
  118. vec3 selfVelocity = texture2D( textureVelocity, uv ).xyz;
  119. float dist;
  120. vec3 dir; // direction
  121. float distSquared;
  122. float seperationSquared = seperationDistance * seperationDistance;
  123. float cohesionSquared = cohesionDistance * cohesionDistance;
  124. float f;
  125. float percent;
  126. vec3 velocity = selfVelocity;
  127. float limit = SPEED_LIMIT;
  128. dir = predator * UPPER_BOUNDS - selfPosition;
  129. dir.z = 0.;
  130. // dir.z *= 0.6;
  131. dist = length( dir );
  132. distSquared = dist * dist;
  133. float preyRadius = 150.0;
  134. float preyRadiusSq = preyRadius * preyRadius;
  135. // move birds away from predator
  136. if (dist < preyRadius) {
  137. f = ( distSquared / preyRadiusSq - 1.0 ) * delta * 100.;
  138. velocity += normalize( dir ) * f;
  139. limit += 5.0;
  140. }
  141. // if (testing == 0.0) {}
  142. // if ( rand( uv + time ) < freedomFactor ) {}
  143. // Attract flocks to the center
  144. vec3 central = vec3( 0., 0., 0. );
  145. dir = selfPosition - central;
  146. dist = length( dir );
  147. dir.y *= 2.5;
  148. velocity -= normalize( dir ) * delta * 5.;
  149. for (float y=0.0;y<height;y++) {
  150. for (float x=0.0;x<width;x++) {
  151. vec2 ref = vec2( x + 0.5, y + 0.5 ) / resolution.xy;
  152. birdPosition = texture2D( texturePosition, ref ).xyz;
  153. dir = birdPosition - selfPosition;
  154. dist = length(dir);
  155. if (dist < 0.0001) continue;
  156. distSquared = dist * dist;
  157. if (distSquared > zoneRadiusSquared ) continue;
  158. percent = distSquared / zoneRadiusSquared;
  159. if ( percent < separationThresh ) { // low
  160. // Separation - Move apart for comfort
  161. f = (separationThresh / percent - 1.0) * delta;
  162. velocity -= normalize(dir) * f;
  163. } else if ( percent < alignmentThresh ) { // high
  164. // Alignment - fly the same direction
  165. float threshDelta = alignmentThresh - separationThresh;
  166. float adjustedPercent = ( percent - separationThresh ) / threshDelta;
  167. birdVelocity = texture2D( textureVelocity, ref ).xyz;
  168. f = ( 0.5 - cos( adjustedPercent * PI_2 ) * 0.5 + 0.5 ) * delta;
  169. velocity += normalize(birdVelocity) * f;
  170. } else {
  171. // Attraction / Cohesion - move closer
  172. float threshDelta = 1.0 - alignmentThresh;
  173. float adjustedPercent = ( percent - alignmentThresh ) / threshDelta;
  174. f = ( 0.5 - ( cos( adjustedPercent * PI_2 ) * -0.5 + 0.5 ) ) * delta;
  175. velocity += normalize(dir) * f;
  176. }
  177. }
  178. }
  179. // this make tends to fly around than down or up
  180. // if (velocity.y > 0.) velocity.y *= (1. - 0.2 * delta);
  181. // Speed Limits
  182. if ( length( velocity ) > limit ) {
  183. velocity = normalize( velocity ) * limit;
  184. }
  185. gl_FragColor = vec4( velocity, 1.0 );
  186. }
  187. </script>
  188. <script type="x-shader/x-vertex" id="birdVS">
  189. attribute vec2 reference;
  190. attribute float birdVertex;
  191. attribute vec3 birdColor;
  192. uniform sampler2D texturePosition;
  193. uniform sampler2D textureVelocity;
  194. varying vec4 vColor;
  195. varying float z;
  196. uniform float time;
  197. void main() {
  198. vec4 tmpPos = texture2D( texturePosition, reference );
  199. vec3 pos = tmpPos.xyz;
  200. vec3 velocity = normalize(texture2D( textureVelocity, reference ).xyz);
  201. vec3 newPosition = position;
  202. if ( birdVertex == 4.0 || birdVertex == 7.0 ) {
  203. // flap wings
  204. newPosition.y = sin( tmpPos.w ) * 5.;
  205. }
  206. newPosition = mat3( modelMatrix ) * newPosition;
  207. velocity.z *= -1.;
  208. float xz = length( velocity.xz );
  209. float xyz = 1.;
  210. float x = sqrt( 1. - velocity.y * velocity.y );
  211. float cosry = velocity.x / xz;
  212. float sinry = velocity.z / xz;
  213. float cosrz = x / xyz;
  214. float sinrz = velocity.y / xyz;
  215. mat3 maty = mat3(
  216. cosry, 0, -sinry,
  217. 0 , 1, 0 ,
  218. sinry, 0, cosry
  219. );
  220. mat3 matz = mat3(
  221. cosrz , sinrz, 0,
  222. -sinrz, cosrz, 0,
  223. 0 , 0 , 1
  224. );
  225. newPosition = maty * matz * newPosition;
  226. newPosition += pos;
  227. z = newPosition.z;
  228. vColor = vec4( birdColor, 1.0 );
  229. gl_Position = projectionMatrix * viewMatrix * vec4( newPosition, 1.0 );
  230. }
  231. </script>
  232. <!-- bird geometry shader -->
  233. <script type="x-shader/x-fragment" id="birdFS">
  234. varying vec4 vColor;
  235. varying float z;
  236. uniform vec3 color;
  237. void main() {
  238. // Fake colors for now
  239. float z2 = 0.2 + ( 1000. - z ) / 1000. * vColor.x;
  240. gl_FragColor = vec4( z2, z2, z2, 1. );
  241. }
  242. </script>
  243. <script>
  244. if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
  245. var hash = document.location.hash.substr( 1 );
  246. if (hash) hash = parseInt(hash, 0);
  247. /* TEXTURE WIDTH FOR SIMULATION */
  248. var WIDTH = hash || 32;
  249. var BIRDS = WIDTH * WIDTH;
  250. // Custom Geometry - using 3 triangles each. No UVs, no normals currently.
  251. THREE.BirdGeometry = function () {
  252. var triangles = BIRDS * 3;
  253. var points = triangles * 3;
  254. THREE.BufferGeometry.call( this );
  255. var vertices = new THREE.BufferAttribute( new Float32Array( points * 3 ), 3 );
  256. var birdColors = new THREE.BufferAttribute( new Float32Array( points * 3 ), 3 );
  257. var references = new THREE.BufferAttribute( new Float32Array( points * 2 ), 2 );
  258. var birdVertex = new THREE.BufferAttribute( new Float32Array( points ), 1 );
  259. this.addAttribute( 'position', vertices );
  260. this.addAttribute( 'birdColor', birdColors );
  261. this.addAttribute( 'reference', references );
  262. this.addAttribute( 'birdVertex', birdVertex );
  263. // this.addAttribute( 'normal', new Float32Array( points * 3 ), 3 );
  264. var v = 0;
  265. function verts_push() {
  266. for (var i=0; i < arguments.length; i++) {
  267. vertices.array[v++] = arguments[i];
  268. }
  269. }
  270. var wingsSpan = 20;
  271. for (var f = 0; f<BIRDS; f++ ) {
  272. // Body
  273. verts_push(
  274. 0, -0, -20,
  275. 0, 4, -20,
  276. 0, 0, 30
  277. );
  278. // Left Wing
  279. verts_push(
  280. 0, 0, -15,
  281. -wingsSpan, 0, 0,
  282. 0, 0, 15
  283. );
  284. // Right Wing
  285. verts_push(
  286. 0, 0, 15,
  287. wingsSpan, 0, 0,
  288. 0, 0, -15
  289. );
  290. }
  291. for( var v = 0; v < triangles * 3; v++ ) {
  292. var i = ~~(v / 3);
  293. var x = (i % WIDTH) / WIDTH;
  294. var y = ~~(i / WIDTH) / WIDTH;
  295. var c = new THREE.Color(
  296. 0x444444 +
  297. ~~(v / 9) / BIRDS * 0x666666
  298. );
  299. birdColors.array[ v * 3 + 0 ] = c.r;
  300. birdColors.array[ v * 3 + 1 ] = c.g;
  301. birdColors.array[ v * 3 + 2 ] = c.b;
  302. references.array[ v * 2 ] = x;
  303. references.array[ v * 2 + 1 ] = y;
  304. birdVertex.array[ v ] = v % 9;
  305. }
  306. this.applyMatrix( new THREE.Matrix4().makeScale( 0.2, 0.2, 0.2 ) );
  307. };
  308. THREE.BirdGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
  309. var container, stats;
  310. var camera, scene, renderer, geometry, i, h, color;
  311. var mouseX = 0, mouseY = 0;
  312. var windowHalfX = window.innerWidth / 2;
  313. var windowHalfY = window.innerHeight / 2;
  314. var PARTICLES = WIDTH * WIDTH;
  315. var BOUNDS = 800, BOUNDS_HALF = BOUNDS / 2;
  316. document.getElementById('birds').innerText = PARTICLES;
  317. function change(n) {
  318. location.hash = n;
  319. location.reload();
  320. return false;
  321. }
  322. var options = '';
  323. for (i=1; i<7; i++) {
  324. var j = Math.pow(2, i);
  325. options += '<a href="#" onclick="return change(' + j + ')">' + (j * j) + '</a> ';
  326. }
  327. document.getElementById('options').innerHTML = options;
  328. var last = performance.now();
  329. var simulator;
  330. init();
  331. animate();
  332. function init() {
  333. container = document.createElement( 'div' );
  334. document.body.appendChild( container );
  335. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 3000 );
  336. camera.position.z = 350;
  337. scene = new THREE.Scene();
  338. scene.fog = new THREE.Fog( 0xffffff, 100, 1000 );
  339. renderer = new THREE.WebGLRenderer();
  340. renderer.setClearColor( scene.fog.color );
  341. renderer.setPixelRatio( window.devicePixelRatio );
  342. renderer.setSize( window.innerWidth, window.innerHeight );
  343. container.appendChild( renderer.domElement );
  344. simulator = new SimulationRenderer(WIDTH, renderer);
  345. simulator.init();
  346. stats = new Stats();
  347. stats.domElement.style.position = 'absolute';
  348. stats.domElement.style.top = '0px';
  349. container.appendChild( stats.domElement );
  350. document.addEventListener( 'mousemove', onDocumentMouseMove, false );
  351. document.addEventListener( 'touchstart', onDocumentTouchStart, false );
  352. document.addEventListener( 'touchmove', onDocumentTouchMove, false );
  353. //
  354. window.addEventListener( 'resize', onWindowResize, false );
  355. var gui = new dat.GUI();
  356. var effectController = {
  357. seperation: 20.0,
  358. alignment: 20.0,
  359. cohesion: 20.0,
  360. freedom: 0.75
  361. };
  362. var valuesChanger = function() {
  363. simulator.velocityUniforms.seperationDistance.value = effectController.seperation;
  364. simulator.velocityUniforms.alignmentDistance.value = effectController.alignment;
  365. simulator.velocityUniforms.cohesionDistance.value = effectController.cohesion;
  366. simulator.velocityUniforms.freedomFactor.value = effectController.freedom;
  367. };
  368. valuesChanger();
  369. gui.add( effectController, "seperation", 0.0, 100.0, 1.0 ).onChange( valuesChanger );
  370. gui.add( effectController, "alignment", 0.0, 100, 0.001 ).onChange( valuesChanger );
  371. gui.add( effectController, "cohesion", 0.0, 100, 0.025 ).onChange( valuesChanger );
  372. gui.close();
  373. initBirds();
  374. }
  375. function initBirds() {
  376. var geometry = new THREE.BirdGeometry();
  377. // For Vertex Shaders
  378. birdAttributes = {
  379. // index: { type: 'i', value: [] },
  380. birdColor: { type: 'c', value: null },
  381. reference: { type: 'v2', value: null },
  382. birdVertex: { type: 'f', value: null }
  383. };
  384. // For Vertex and Fragment
  385. birdUniforms = {
  386. color: { type: "c", value: new THREE.Color( 0xff2200 ) },
  387. texturePosition: { type: "t", value: null },
  388. textureVelocity: { type: "t", value: null },
  389. time: { type: "f", value: 1.0 },
  390. delta: { type: "f", value: 0.0 },
  391. };
  392. // ShaderMaterial
  393. var shaderMaterial = new THREE.ShaderMaterial( {
  394. uniforms: birdUniforms,
  395. attributes: birdAttributes,
  396. vertexShader: document.getElementById( 'birdVS' ).textContent,
  397. fragmentShader: document.getElementById( 'birdFS' ).textContent,
  398. side: THREE.DoubleSide
  399. });
  400. // var
  401. birdMesh = new THREE.Mesh( geometry, shaderMaterial );
  402. birdMesh.rotation.y = Math.PI / 2;
  403. birdMesh.matrixAutoUpdate = false;
  404. birdMesh.updateMatrix();
  405. scene.add(birdMesh);
  406. }
  407. function onWindowResize() {
  408. windowHalfX = window.innerWidth / 2;
  409. windowHalfY = window.innerHeight / 2;
  410. camera.aspect = window.innerWidth / window.innerHeight;
  411. camera.updateProjectionMatrix();
  412. renderer.setSize( window.innerWidth, window.innerHeight );
  413. }
  414. function onDocumentMouseMove( event ) {
  415. mouseX = event.clientX - windowHalfX;
  416. mouseY = event.clientY - windowHalfY;
  417. }
  418. function onDocumentTouchStart( event ) {
  419. if ( event.touches.length === 1 ) {
  420. event.preventDefault();
  421. mouseX = event.touches[ 0 ].pageX - windowHalfX;
  422. mouseY = event.touches[ 0 ].pageY - windowHalfY;
  423. }
  424. }
  425. function onDocumentTouchMove( event ) {
  426. if ( event.touches.length === 1 ) {
  427. event.preventDefault();
  428. mouseX = event.touches[ 0 ].pageX - windowHalfX;
  429. mouseY = event.touches[ 0 ].pageY - windowHalfY;
  430. }
  431. }
  432. //
  433. function animate() {
  434. requestAnimationFrame( animate );
  435. render();
  436. stats.update();
  437. }
  438. function render() {
  439. var now = performance.now();
  440. var delta = (now - last) / 1000;
  441. if (delta > 1) delta = 1; // safety cap on large deltas
  442. last = now;
  443. birdUniforms.time.value = now;
  444. birdUniforms.delta.value = delta;
  445. simulator.simulate( delta );
  446. simulator.velocityUniforms.predator.value.set( mouseX / windowHalfX, -mouseY / windowHalfY, 0 );
  447. mouseX = 10000;
  448. mouseY = 10000;
  449. renderer.render( scene, camera );
  450. }
  451. </script>
  452. </body>
  453. </html>