webgl_gpgpu_protoplanet.html 14 KB

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