webgl_gpgpu_protoplanet.html 14 KB

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