webgl_gpgpu_protoplanet.html 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - gpgpu - protoplanet</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: #000000;
  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: #ffffff;
  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="protoplanets"></span> webgl gpgpu debris<br/>
  32. Select <span id="options"></span> debris<br/>
  33. </div>
  34. <script src="../build/three.js"></script>
  35. <script src="js/Detector.js"></script>
  36. <script src="js/libs/stats.min.js"></script>
  37. <script src="js/libs/dat.gui.min.js"></script>
  38. <script src="js/controls/OrbitControls.js"></script>
  39. <script src="js/GPUComputationRenderer.js"></script>
  40. <!-- Fragment shader for protoplanet's position -->
  41. <script id="computeShaderPosition" type="x-shader/x-fragment">
  42. #define delta ( 1.0 / 60.0 )
  43. void main() {
  44. vec2 uv = gl_FragCoord.xy / resolution.xy;
  45. vec4 tmpPos = texture2D( texturePosition, uv );
  46. vec3 pos = tmpPos.xyz;
  47. vec4 tmpVel = texture2D( textureVelocity, uv );
  48. vec3 vel = tmpVel.xyz;
  49. float mass = tmpVel.w;
  50. if ( mass == 0.0 ) {
  51. vel = vec3( 0.0 );
  52. }
  53. // Dynamics
  54. pos += vel * delta;
  55. gl_FragColor = vec4( pos, 1.0 );
  56. }
  57. </script>
  58. <!-- Fragment shader for protoplanet's velocity -->
  59. <script id="computeShaderVelocity" type="x-shader/x-fragment">
  60. // For PI declaration:
  61. #include <common>
  62. #define delta ( 1.0 / 60.0 )
  63. uniform float gravityConstant;
  64. uniform float density;
  65. const float width = resolution.x;
  66. const float height = resolution.y;
  67. float radiusFromMass( float mass ) {
  68. // Calculate radius of a sphere from mass and density
  69. return pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
  70. }
  71. void main() {
  72. vec2 uv = gl_FragCoord.xy / resolution.xy;
  73. float idParticle = uv.y * resolution.x + uv.x;
  74. vec4 tmpPos = texture2D( texturePosition, uv );
  75. vec3 pos = tmpPos.xyz;
  76. vec4 tmpVel = texture2D( textureVelocity, uv );
  77. vec3 vel = tmpVel.xyz;
  78. float mass = tmpVel.w;
  79. if ( mass > 0.0 ) {
  80. float radius = radiusFromMass( mass );
  81. vec3 acceleration = vec3( 0.0 );
  82. // Gravity interaction
  83. for ( float y = 0.0; y < height; y++ ) {
  84. for ( float x = 0.0; x < width; x++ ) {
  85. vec2 secondParticleCoords = vec2( x + 0.5, y + 0.5 ) / resolution.xy;
  86. vec3 pos2 = texture2D( texturePosition, secondParticleCoords ).xyz;
  87. vec4 velTemp2 = texture2D( textureVelocity, secondParticleCoords );
  88. vec3 vel2 = velTemp2.xyz;
  89. float mass2 = velTemp2.w;
  90. float idParticle2 = secondParticleCoords.y * resolution.x + secondParticleCoords.x;
  91. if ( idParticle == idParticle2 ) {
  92. continue;
  93. }
  94. if ( mass2 == 0.0 ) {
  95. continue;
  96. }
  97. vec3 dPos = pos2 - pos;
  98. float distance = length( dPos );
  99. float radius2 = radiusFromMass( mass2 );
  100. if ( distance == 0.0 ) {
  101. continue;
  102. }
  103. // Checks collision
  104. if ( distance < radius + radius2 ) {
  105. if ( idParticle < idParticle2 ) {
  106. // This particle is aggregated by the other
  107. vel = ( vel * mass + vel2 * mass2 ) / ( mass + mass2 );
  108. mass += mass2;
  109. radius = radiusFromMass( mass );
  110. }
  111. else {
  112. // This particle dies
  113. mass = 0.0;
  114. radius = 0.0;
  115. vel = vec3( 0.0 );
  116. break;
  117. }
  118. }
  119. float distanceSq = distance * distance;
  120. float gravityField = gravityConstant * mass2 / distanceSq;
  121. gravityField = min( gravityField, 1000.0 );
  122. acceleration += gravityField * normalize( dPos );
  123. }
  124. if ( mass == 0.0 ) {
  125. break;
  126. }
  127. }
  128. // Dynamics
  129. vel += delta * acceleration;
  130. }
  131. gl_FragColor = vec4( vel, mass );
  132. }
  133. </script>
  134. <!-- Particles vertex shader -->
  135. <script type="x-shader/x-vertex" id="particleVertexShader">
  136. // For PI declaration:
  137. #include <common>
  138. uniform sampler2D texturePosition;
  139. uniform sampler2D textureVelocity;
  140. uniform float cameraConstant;
  141. uniform float density;
  142. varying vec4 vColor;
  143. float radiusFromMass( float mass ) {
  144. // Calculate radius of a sphere from mass and density
  145. return pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
  146. }
  147. void main() {
  148. vec4 posTemp = texture2D( texturePosition, uv );
  149. vec3 pos = posTemp.xyz;
  150. vec4 velTemp = texture2D( textureVelocity, uv );
  151. vec3 vel = velTemp.xyz;
  152. float mass = velTemp.w;
  153. vColor = vec4( 1.0, mass / 250.0, 0.0, 1.0 );
  154. vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );
  155. // Calculate radius of a sphere from mass and density
  156. //float radius = pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
  157. float radius = radiusFromMass( mass );
  158. // Apparent size in pixels
  159. if ( mass == 0.0 ) {
  160. gl_PointSize = 0.0;
  161. }
  162. else {
  163. gl_PointSize = radius * cameraConstant / ( - mvPosition.z );
  164. }
  165. gl_Position = projectionMatrix * mvPosition;
  166. }
  167. </script>
  168. <!-- Particles fragment shader -->
  169. <script type="x-shader/x-fragment" id="particleFragmentShader">
  170. varying vec4 vColor;
  171. void main() {
  172. float f = length( gl_PointCoord - vec2( 0.5, 0.5 ) );
  173. if ( f > 0.5 ) {
  174. discard;
  175. }
  176. gl_FragColor = vColor;
  177. }
  178. </script>
  179. <script>
  180. if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
  181. var hash = document.location.hash.substr( 1 );
  182. if ( hash ) hash = parseInt( hash, 0 );
  183. // Texture width for simulation (each texel is a debris particle)
  184. var WIDTH = hash || 64;
  185. var container, stats;
  186. var camera, scene, renderer, geometry, controls;
  187. var PARTICLES = WIDTH * WIDTH;
  188. document.getElementById( 'protoplanets' ).innerText = PARTICLES;
  189. function change(n) {
  190. location.hash = n;
  191. location.reload();
  192. return false;
  193. }
  194. var options = '';
  195. for ( var i = 1; i < 8; i++ ) {
  196. var j = Math.pow( 2, i );
  197. options += '<a href="#" onclick="return change(' + j + ')">' + ( j * j ) + '</a> ';
  198. }
  199. document.getElementById( 'options' ).innerHTML = options;
  200. var last = performance.now();
  201. var gpuCompute;
  202. var velocityVariable;
  203. var positionVariable;
  204. var positionUniforms;
  205. var velocityUniforms;
  206. var particleUniforms;
  207. var effectController;
  208. init();
  209. animate();
  210. function init() {
  211. container = document.createElement( 'div' );
  212. document.body.appendChild( container );
  213. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 5, 15000 );
  214. camera.position.y = 120;
  215. camera.position.z = 400;
  216. scene = new THREE.Scene();
  217. renderer = new THREE.WebGLRenderer();
  218. renderer.setClearColor( 0x000000 );
  219. renderer.setPixelRatio( window.devicePixelRatio );
  220. renderer.setSize( window.innerWidth, window.innerHeight );
  221. container.appendChild( renderer.domElement );
  222. controls = new THREE.OrbitControls( camera, renderer.domElement );
  223. effectController = {
  224. // Can be changed dynamically
  225. gravityConstant: 100.0,
  226. density: 0.45,
  227. // Must restart simulation
  228. radius: 300,
  229. height: 8,
  230. exponent: 0.4,
  231. maxMass: 15.0,
  232. velocity: 70,
  233. velocityExponent: 0.2,
  234. randVelocity: 0.001
  235. };
  236. initComputeRenderer();
  237. stats = new Stats();
  238. container.appendChild( stats.dom );
  239. window.addEventListener( 'resize', onWindowResize, false );
  240. initGUI();
  241. initProtoplanets();
  242. dynamicValuesChanger();
  243. }
  244. function initComputeRenderer() {
  245. gpuCompute = new GPUComputationRenderer( WIDTH, WIDTH, renderer );
  246. var dtPosition = gpuCompute.createTexture();
  247. var dtVelocity = gpuCompute.createTexture();
  248. fillTextures( dtPosition, dtVelocity );
  249. velocityVariable = gpuCompute.addVariable( "textureVelocity", document.getElementById( 'computeShaderVelocity' ).textContent, dtVelocity );
  250. positionVariable = gpuCompute.addVariable( "texturePosition", document.getElementById( 'computeShaderPosition' ).textContent, dtPosition );
  251. gpuCompute.setVariableDependencies( velocityVariable, [ positionVariable, velocityVariable ] );
  252. gpuCompute.setVariableDependencies( positionVariable, [ positionVariable, velocityVariable ] );
  253. positionUniforms = positionVariable.material.uniforms;
  254. velocityUniforms = velocityVariable.material.uniforms;
  255. velocityUniforms.gravityConstant = { value: 0.0 };
  256. velocityUniforms.density = { value: 0.0 };
  257. var error = gpuCompute.init();
  258. if ( error !== null ) {
  259. console.error( error );
  260. }
  261. }
  262. function restartSimulation() {
  263. var dtPosition = gpuCompute.createTexture();
  264. var dtVelocity = gpuCompute.createTexture();
  265. fillTextures( dtPosition, dtVelocity );
  266. gpuCompute.renderTexture( dtPosition, positionVariable.renderTargets[ 0 ] );
  267. gpuCompute.renderTexture( dtPosition, positionVariable.renderTargets[ 1 ] );
  268. gpuCompute.renderTexture( dtVelocity, velocityVariable.renderTargets[ 0 ] );
  269. gpuCompute.renderTexture( dtVelocity, velocityVariable.renderTargets[ 1 ] );
  270. }
  271. function initProtoplanets() {
  272. geometry = new THREE.BufferGeometry();
  273. var positions = new Float32Array( PARTICLES * 3 );
  274. var p = 0;
  275. for ( var i = 0; i < PARTICLES; i++ ) {
  276. positions[ p++ ] = ( Math.random() * 2 - 1 ) * effectController.radius;
  277. positions[ p++ ] = 0;//( Math.random() * 2 - 1 ) * effectController.radius;
  278. positions[ p++ ] = ( Math.random() * 2 - 1 ) * effectController.radius;
  279. }
  280. var uvs = new Float32Array( PARTICLES * 2 );
  281. p = 0;
  282. for ( var j = 0; j < WIDTH; j++ ) {
  283. for ( var i = 0; i < WIDTH; i++ ) {
  284. uvs[ p++ ] = i / ( WIDTH - 1 );
  285. uvs[ p++ ] = j / ( WIDTH - 1 );
  286. }
  287. }
  288. geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
  289. geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
  290. particleUniforms = {
  291. texturePosition: { value: null },
  292. textureVelocity: { value: null },
  293. cameraConstant: { value: getCameraConstant( camera ) },
  294. density: { value: 0.0 }
  295. };
  296. // ShaderMaterial
  297. var material = new THREE.ShaderMaterial( {
  298. uniforms: particleUniforms,
  299. vertexShader: document.getElementById( 'particleVertexShader' ).textContent,
  300. fragmentShader: document.getElementById( 'particleFragmentShader' ).textContent
  301. } );
  302. material.extensions.drawBuffers = true;
  303. var particles = new THREE.Points( geometry, material );
  304. particles.matrixAutoUpdate = false;
  305. particles.updateMatrix();
  306. scene.add( particles );
  307. }
  308. function fillTextures( texturePosition, textureVelocity ) {
  309. var posArray = texturePosition.image.data;
  310. var velArray = textureVelocity.image.data;
  311. var radius = effectController.radius;
  312. var height = effectController.height;
  313. var exponent = effectController.exponent;
  314. var maxMass = effectController.maxMass * 1024 / PARTICLES;
  315. var maxVel = effectController.velocity;
  316. var velExponent = effectController.velocityExponent;
  317. var randVel = effectController.randVelocity;
  318. for ( var k = 0, kl = posArray.length; k < kl; k += 4 ) {
  319. // Position
  320. var x, y, z, rr;
  321. do {
  322. x = ( Math.random() * 2 - 1 );
  323. z = ( Math.random() * 2 - 1 );
  324. rr = x * x + z * z;
  325. } while ( rr > 1 );
  326. rr = Math.sqrt( rr );
  327. var rExp = radius * Math.pow( rr, exponent );
  328. // Velocity
  329. var vel = maxVel * Math.pow( rr, velExponent );
  330. var vx = vel * z + ( Math.random() * 2 - 1 ) * randVel;
  331. var vy = ( Math.random() * 2 - 1 ) * randVel * 0.05;
  332. var vz = - vel * x + ( Math.random() * 2 - 1 ) * randVel;
  333. x *= rExp;
  334. z *= rExp;
  335. y = ( Math.random() * 2 - 1 ) * height;
  336. var mass = Math.random() * maxMass + 1;
  337. // Fill in texture values
  338. posArray[ k + 0 ] = x;
  339. posArray[ k + 1 ] = y;
  340. posArray[ k + 2 ] = z;
  341. posArray[ k + 3 ] = 1;
  342. velArray[ k + 0 ] = vx;
  343. velArray[ k + 1 ] = vy;
  344. velArray[ k + 2 ] = vz;
  345. velArray[ k + 3 ] = mass;
  346. }
  347. }
  348. function onWindowResize() {
  349. camera.aspect = window.innerWidth / window.innerHeight;
  350. camera.updateProjectionMatrix();
  351. renderer.setSize( window.innerWidth, window.innerHeight );
  352. particleUniforms.cameraConstant.value = getCameraConstant( camera );
  353. }
  354. function dynamicValuesChanger() {
  355. velocityUniforms.gravityConstant.value = effectController.gravityConstant;
  356. velocityUniforms.density.value = effectController.density;
  357. particleUniforms.density.value = effectController.density;
  358. }
  359. function initGUI() {
  360. var gui = new dat.GUI();
  361. var folder1 = gui.addFolder( 'Dynamic parameters' );
  362. folder1.add( effectController, "gravityConstant", 0.0, 1000.0, 0.05 ).onChange( dynamicValuesChanger );
  363. folder1.add( effectController, "density", 0.0, 10.0, 0.001 ).onChange( dynamicValuesChanger );
  364. var folder2 = gui.addFolder( 'Static parameters - press restartSimulation' );
  365. folder2.add( effectController, "radius", 10.0, 1000.0, 1.0 );
  366. folder2.add( effectController, "height", 0.0, 50.0, 0.01 );
  367. folder2.add( effectController, "exponent", 0.0, 2.0, 0.001 );
  368. folder2.add( effectController, "maxMass", 1.0, 50.0, 0.1 );
  369. folder2.add( effectController, "velocity", 0.0, 150.0, 0.1 );
  370. folder2.add( effectController, "velocityExponent", 0.0, 1.0, 0.01 );
  371. folder2.add( effectController, "randVelocity", 0.0, 50.0, 0.1 );
  372. var buttonRestart = {
  373. restartSimulation: function() {
  374. restartSimulation();
  375. }
  376. };
  377. folder2.add( buttonRestart, 'restartSimulation' );
  378. folder1.open();
  379. folder2.open();
  380. }
  381. function getCameraConstant( camera ) {
  382. return window.innerHeight / ( Math.tan( THREE.Math.DEG2RAD * 0.5 * camera.fov ) / camera.zoom );
  383. }
  384. function animate() {
  385. requestAnimationFrame( animate );
  386. render();
  387. stats.update();
  388. }
  389. function render() {
  390. var now = performance.now();
  391. var delta = (now - last) / 1000;
  392. if (delta > 1) delta = 1; // safety cap on large deltas
  393. last = now;
  394. gpuCompute.compute();
  395. particleUniforms.texturePosition.value = gpuCompute.getCurrentRenderTarget( positionVariable ).texture;
  396. particleUniforms.textureVelocity.value = gpuCompute.getCurrentRenderTarget( velocityVariable ).texture;
  397. renderer.render( scene, camera );
  398. }
  399. </script>
  400. </body>
  401. </html>