GPUParticleSystem.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /*
  2. * GPU Particle System
  3. * @author flimshaw - Charlie Hoey - http://charliehoey.com
  4. *
  5. * A simple to use, general purpose GPU system. Particles are spawn-and-forget with
  6. * several options available, and do not require monitoring or cleanup after spawning.
  7. * Because the paths of all particles are completely deterministic once spawned, the scale
  8. * and direction of time is also variable.
  9. *
  10. * Currently uses a static wrapping perlin noise texture for turbulence, and a small png texture for
  11. * particles, but adding support for a particle texture atlas or changing to a different type of turbulence
  12. * would be a fairly light day's work.
  13. *
  14. * Shader and javascript packing code derrived from several Stack Overflow examples.
  15. *
  16. */
  17. THREE.GPUParticleSystem = function( options ) {
  18. var self = this;
  19. var options = options || {};
  20. // parse options and use defaults
  21. self.PARTICLE_COUNT = options.maxParticles || 1000000;
  22. self.PARTICLE_CONTAINERS = options.containerCount || 1;
  23. self.PARTICLES_PER_CONTAINER = Math.ceil( self.PARTICLE_COUNT / self.PARTICLE_CONTAINERS );
  24. self.PARTICLE_CURSOR = 0;
  25. self.time = 0;
  26. // Custom vertex and fragement shader
  27. var GPUParticleShader = {
  28. vertexShader: [
  29. 'precision highp float;',
  30. 'const vec4 bitSh = vec4(256. * 256. * 256., 256. * 256., 256., 1.);',
  31. 'const vec4 bitMsk = vec4(0.,vec3(1./256.0));',
  32. 'const vec4 bitShifts = vec4(1.) / bitSh;',
  33. '#define FLOAT_MAX 1.70141184e38',
  34. '#define FLOAT_MIN 1.17549435e-38',
  35. 'lowp vec4 encode_float(highp float v) {',
  36. 'highp float av = abs(v);',
  37. '//Handle special cases',
  38. 'if(av < FLOAT_MIN) {',
  39. 'return vec4(0.0, 0.0, 0.0, 0.0);',
  40. '} else if(v > FLOAT_MAX) {',
  41. 'return vec4(127.0, 128.0, 0.0, 0.0) / 255.0;',
  42. '} else if(v < -FLOAT_MAX) {',
  43. 'return vec4(255.0, 128.0, 0.0, 0.0) / 255.0;',
  44. '}',
  45. 'highp vec4 c = vec4(0,0,0,0);',
  46. '//Compute exponent and mantissa',
  47. 'highp float e = floor(log2(av));',
  48. 'highp float m = av * pow(2.0, -e) - 1.0;',
  49. //Unpack mantissa
  50. 'c[1] = floor(128.0 * m);',
  51. 'm -= c[1] / 128.0;',
  52. 'c[2] = floor(32768.0 * m);',
  53. 'm -= c[2] / 32768.0;',
  54. 'c[3] = floor(8388608.0 * m);',
  55. '//Unpack exponent',
  56. 'highp float ebias = e + 127.0;',
  57. 'c[0] = floor(ebias / 2.0);',
  58. 'ebias -= c[0] * 2.0;',
  59. 'c[1] += floor(ebias) * 128.0;',
  60. '//Unpack sign bit',
  61. 'c[0] += 128.0 * step(0.0, -v);',
  62. '//Scale back to range',
  63. 'return c / 255.0;',
  64. '}',
  65. 'vec4 pack(const in float depth)',
  66. '{',
  67. 'const vec4 bit_shift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);',
  68. 'const vec4 bit_mask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);',
  69. 'vec4 res = fract(depth * bit_shift);',
  70. 'res -= res.xxyz * bit_mask;',
  71. 'return res;',
  72. '}',
  73. 'float unpack(const in vec4 rgba_depth)',
  74. '{',
  75. 'const vec4 bit_shift = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0);',
  76. 'float depth = dot(rgba_depth, bit_shift);',
  77. 'return depth;',
  78. '}',
  79. 'uniform float uTime;',
  80. 'uniform float uScale;',
  81. 'uniform sampler2D tNoise;',
  82. 'attribute vec4 particlePositionsStartTime;',
  83. 'attribute vec4 particleVelColSizeLife;',
  84. 'varying vec4 vColor;',
  85. 'varying float lifeLeft;',
  86. 'void main() {',
  87. '// unpack things from our attributes',
  88. 'vColor = encode_float( particleVelColSizeLife.y );',
  89. '// convert our velocity back into a value we can use',
  90. 'vec4 velTurb = encode_float( particleVelColSizeLife.x );',
  91. 'vec3 velocity = vec3( velTurb.xyz );',
  92. 'float turbulence = velTurb.w;',
  93. 'vec3 newPosition;',
  94. 'float timeElapsed = uTime - particlePositionsStartTime.a;',
  95. 'lifeLeft = 1. - (timeElapsed / particleVelColSizeLife.w);',
  96. 'gl_PointSize = ( uScale * particleVelColSizeLife.z ) * lifeLeft;',
  97. 'velocity.x = ( velocity.x - .5 ) * 3.;',
  98. 'velocity.y = ( velocity.y - .5 ) * 3.;',
  99. 'velocity.z = ( velocity.z - .5 ) * 3.;',
  100. 'newPosition = particlePositionsStartTime.xyz + ( velocity * 10. ) * ( uTime - particlePositionsStartTime.a );',
  101. 'vec3 noise = texture2D( tNoise, vec2( newPosition.x * .015 + (uTime * .05), newPosition.y * .02 + (uTime * .015) )).rgb;',
  102. 'vec3 noiseVel = ( noise.rgb - .5 ) * 30.;',
  103. 'newPosition = mix(newPosition, newPosition + vec3(noiseVel * ( turbulence * 5. ) ), (timeElapsed / particleVelColSizeLife.a) );',
  104. 'if( velocity.y > 0. && velocity.y < .05 ) {',
  105. 'lifeLeft = 0.;',
  106. '}',
  107. 'if( velocity.x < -1.45 ) {',
  108. 'lifeLeft = 0.;',
  109. '}',
  110. 'if( timeElapsed > 0. ) {',
  111. 'gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );',
  112. '} else {',
  113. 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
  114. 'lifeLeft = 0.;',
  115. 'gl_PointSize = 0.;',
  116. '}',
  117. '}'
  118. ].join("\n"),
  119. fragmentShader: [
  120. 'float scaleLinear(float value, vec2 valueDomain) {',
  121. 'return (value - valueDomain.x) / (valueDomain.y - valueDomain.x);',
  122. '}',
  123. 'float scaleLinear(float value, vec2 valueDomain, vec2 valueRange) {',
  124. 'return mix(valueRange.x, valueRange.y, scaleLinear(value, valueDomain));',
  125. '}',
  126. 'varying vec4 vColor;',
  127. 'varying float lifeLeft;',
  128. 'uniform sampler2D tSprite;',
  129. 'void main() {',
  130. 'float alpha = 0.;',
  131. 'if( lifeLeft > .995 ) {',
  132. 'alpha = scaleLinear( lifeLeft, vec2(1., .995), vec2(0., 1.));//mix( 0., 1., ( lifeLeft - .95 ) * 100. ) * .75;',
  133. '} else {',
  134. 'alpha = lifeLeft * .75;',
  135. '}',
  136. 'vec4 tex = texture2D( tSprite, gl_PointCoord );',
  137. 'gl_FragColor = vec4( vColor.rgb * tex.a, alpha * tex.a );',
  138. '}'
  139. ].join("\n")
  140. };
  141. // preload a million random numbers
  142. self.rand = [];
  143. for(var i=1e5; i > 0; i--) {
  144. self.rand.push( Math.random() - .5 );
  145. }
  146. self.random = function() {
  147. return ++i >= self.rand.length ? self.rand[i=1] : self.rand[i];
  148. }
  149. self.particleNoiseTex = THREE.ImageUtils.loadTexture("textures/perlin-512.png");
  150. self.particleNoiseTex.wrapS = self.particleNoiseTex.wrapT = THREE.RepeatWrapping;
  151. self.particleSpriteTex = THREE.ImageUtils.loadTexture("textures/particle2.png");
  152. self.particleSpriteTex.wrapS = self.particleSpriteTex.wrapT = THREE.RepeatWrapping;
  153. self.particleShaderMat = new THREE.ShaderMaterial( {
  154. transparent: true,
  155. depthWrite: false,
  156. uniforms: {
  157. "uTime": { type: "f", value: 0.0 },
  158. "uScale": { type: "f", value: 1.0 },
  159. "tNoise": { type: "t", value: self.particleNoiseTex },
  160. "tSprite": { type: "t", value: self.particleSpriteTex }
  161. },
  162. attributes: {
  163. "particlePositionsStartTime": { type: "v4", value: [] },
  164. "particleVelColSizeLife": { type: "v4", value: [] }
  165. },
  166. blending: THREE.AdditiveBlending,
  167. vertexShader: GPUParticleShader.vertexShader,
  168. fragmentShader: GPUParticleShader.fragmentShader
  169. } );
  170. // define defaults for all values
  171. self.particleShaderMat.defaultAttributeValues.particlePositionsStartTime = [0, 0, 0, 0];
  172. self.particleShaderMat.defaultAttributeValues.particleVelColSizeLife = [0, 0, 0, 0];
  173. self.particleContainers = [];
  174. // extend Object3D
  175. THREE.Object3D.apply(this, arguments);
  176. this.init = function() {
  177. for( var i = 0; i < self.PARTICLE_CONTAINERS; i++ ) {
  178. var c = new THREE.GPUParticleContainer( self.PARTICLES_PER_CONTAINER, self );
  179. self.particleContainers.push( c );
  180. self.add( c );
  181. }
  182. }
  183. this.spawnParticle = function( options ) {
  184. self.PARTICLE_CURSOR++;
  185. if( self.PARTICLE_CURSOR >= self.PARTICLE_COUNT ) {
  186. self.PARTICLE_CURSOR = 1;
  187. }
  188. var currentContainer = self.particleContainers[ Math.floor( self.PARTICLE_CURSOR / self.PARTICLES_PER_CONTAINER ) ];
  189. currentContainer.spawnParticle( options );
  190. }
  191. this.update = function( time ) {
  192. for( var i = 0; i < self.PARTICLE_CONTAINERS; i++ ) {
  193. self.particleContainers[i].update( time );
  194. }
  195. };
  196. this.init();
  197. }
  198. THREE.GPUParticleSystem.prototype = Object.create(THREE.Object3D.prototype);
  199. THREE.GPUParticleSystem.prototype.constructor = THREE.GPUParticleSystem;
  200. // Subclass for particle containers, allows for very large arrays to be spread out
  201. THREE.GPUParticleContainer = function( maxParticles, particleSystem ) {
  202. var self = this;
  203. self.PARTICLE_COUNT = maxParticles || 100000;
  204. self.PARTICLE_CURSOR = 0;
  205. self.time = 0;
  206. self.DPR = window.devicePixelRatio;
  207. self.GPUParticleSystem = particleSystem;
  208. var particlesPerArray = Math.floor( self.PARTICLE_COUNT / self.MAX_ATTRIBUTES );
  209. // extend Object3D
  210. THREE.Object3D.apply(this, arguments);
  211. // construct a couple small arrays used for packing variables into floats etc
  212. var UINT8_VIEW = new Uint8Array(4)
  213. var FLOAT_VIEW = new Float32Array(UINT8_VIEW.buffer)
  214. function decodeFloat(x, y, z, w) {
  215. UINT8_VIEW[0] = Math.floor(w)
  216. UINT8_VIEW[1] = Math.floor(z)
  217. UINT8_VIEW[2] = Math.floor(y)
  218. UINT8_VIEW[3] = Math.floor(x)
  219. return FLOAT_VIEW[0]
  220. }
  221. function componentToHex(c) {
  222. var hex = c.toString(16);
  223. return hex.length == 1 ? "0" + hex : hex;
  224. }
  225. function rgbToHex(r, g, b) {
  226. return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
  227. }
  228. function hexToRgb(hex) {
  229. var r = hex >> 16;
  230. var g = (hex & 0x00FF00) >> 8;
  231. var b = hex & 0x0000FF;
  232. if( r > 0 ) r--;
  233. if( g > 0 ) g--;
  234. if( b > 0 ) b--;
  235. return [r,g,b];
  236. };
  237. self.particles = [];
  238. self.deadParticles = [];
  239. self.particlesAvailableSlot = [];
  240. // create a container for particles
  241. self.particleUpdate = false;
  242. // Shader Based Particle System
  243. self.particleShaderGeo = new THREE.BufferGeometry();
  244. // new hyper compressed attributes
  245. self.particleVertices = new Float32Array( self.PARTICLE_COUNT * 3 ); // position
  246. self.particlePositionsStartTime = new Float32Array( self.PARTICLE_COUNT * 4 ); // position
  247. self.particleVelColSizeLife = new Float32Array( self.PARTICLE_COUNT * 4 );
  248. for ( var i = 0; i < self.PARTICLE_COUNT; i++ )
  249. {
  250. self.particlePositionsStartTime[ i*4 + 0 ] = 100; //x
  251. self.particlePositionsStartTime[ i*4 + 1 ] = 0; //y
  252. self.particlePositionsStartTime[ i*4 + 2 ] = 0.0; //z
  253. self.particlePositionsStartTime[ i*4 + 3 ] = 0.0; //startTime
  254. self.particleVertices[ i*3 + 0 ] = 0; //x
  255. self.particleVertices[ i*3 + 1 ] = 0; //y
  256. self.particleVertices[ i*3 + 2 ] = 0.0; //z
  257. self.particleVelColSizeLife[ i*4 + 0 ] = decodeFloat(128,128,0,0); //vel
  258. self.particleVelColSizeLife[ i*4 + 1 ] = decodeFloat(0,254,0,254); //color
  259. self.particleVelColSizeLife[ i*4 + 2 ] = 1.0; //size
  260. self.particleVelColSizeLife[ i*4 + 3 ] = 0.0; //lifespan
  261. }
  262. self.particleShaderGeo.addAttribute( 'position', new THREE.BufferAttribute( self.particleVertices, 3 ) );
  263. self.particleShaderGeo.addAttribute( 'particlePositionsStartTime', new THREE.DynamicBufferAttribute( self.particlePositionsStartTime, 4 ) );
  264. self.particleShaderGeo.addAttribute( 'particleVelColSizeLife', new THREE.DynamicBufferAttribute( self.particleVelColSizeLife, 4 ) );
  265. self.posStart = self.particleShaderGeo.getAttribute( 'particlePositionsStartTime' )
  266. self.velCol = self.particleShaderGeo.getAttribute( 'particleVelColSizeLife' );
  267. self.particleShaderMat = self.GPUParticleSystem.particleShaderMat;
  268. this.init = function() {
  269. self.particleSystem = new THREE.PointCloud( self.particleShaderGeo, self.particleShaderMat );
  270. self.particleSystem.frustumCulled = false;
  271. this.add( self.particleSystem ) ;
  272. };
  273. var options = {}
  274. , position = new THREE.Vector3()
  275. , velocity = new THREE.Vector3()
  276. , positionRandomness = 0.
  277. , velocityRandomness = 0.
  278. , color = 0xffffff
  279. , colorRandomness = 0.
  280. , turbulence = 0.
  281. , lifetime = 0.
  282. , size = 0.
  283. , sizeRandomness = 0.
  284. , i;
  285. var maxVel = 2;
  286. var maxSource = 250;
  287. this.offset = 0;
  288. this.count = 0;
  289. this.spawnParticle = function( options ) {
  290. options = options || {};
  291. // setup reasonable default values for all arguments
  292. position = options.position !== undefined ? position.copy(options.position) : position.set(0., 0., 0.);
  293. velocity = options.velocity !== undefined ? velocity.copy(options.velocity) : velocity.set(0., 0., 0.);
  294. positionRandomness = options.positionRandomness !== undefined ? options.positionRandomness : 0.0;
  295. velocityRandomness = options.velocityRandomness !== undefined ? options.velocityRandomness : 0.0;
  296. color = options.color !== undefined ? options.color : 0xffffff;
  297. colorRandomness = options.colorRandomness !== undefined ? options.colorRandomness : 1.0;
  298. turbulence = options.turbulence !== undefined ? options.turbulence : 1.0;
  299. lifetime = options.lifetime !== undefined ? options.lifetime : 5.0;
  300. size = options.size !== undefined ? options.size : 10;
  301. sizeRandomness = options.sizeRandomness !== undefined ? options.sizeRandomness : 0.0,
  302. smoothPosition = options.smoothPosition !== undefined ? options.smoothPosition : false;
  303. if( self.DPR !== undefined ) size *= self.DPR;
  304. i = self.PARTICLE_CURSOR;
  305. self.posStart.array[ i*4 + 0 ] = position.x + ( ( particleSystem.random() ) * positionRandomness );// - ( velocity.x * particleSystem.random() ); //x
  306. self.posStart.array[ i*4 + 1 ] = position.y + ( ( particleSystem.random() ) * positionRandomness );// - ( velocity.y * particleSystem.random() ); //y
  307. self.posStart.array[ i*4 + 2 ] = position.z + ( ( particleSystem.random() ) * positionRandomness );// - ( velocity.z * particleSystem.random() ); //z
  308. self.posStart.array[ i*4 + 3 ] = self.time + ( particleSystem.random() * 2e-2 ); //startTime
  309. if( smoothPosition === true ) {
  310. self.posStart.array[ i*4 + 0 ] += - ( velocity.x * particleSystem.random() ); //x
  311. self.posStart.array[ i*4 + 1 ] += - ( velocity.y * particleSystem.random() ); //y
  312. self.posStart.array[ i*4 + 2 ] += - ( velocity.z * particleSystem.random() ); //z
  313. }
  314. var velX = velocity.x + ( particleSystem.random() ) * velocityRandomness;
  315. var velY = velocity.y + ( particleSystem.random() ) * velocityRandomness;
  316. var velZ = velocity.z + ( particleSystem.random() ) * velocityRandomness;
  317. // convert turbulence rating to something we can pack into a vec4
  318. var turbulence = Math.floor( turbulence * 254 );
  319. // clamp our value to between 0. and 1.
  320. velX = Math.floor( maxSource * ( ( velX - -maxVel ) / ( maxVel - -maxVel ) ) );
  321. velY = Math.floor( maxSource * ( ( velY - -maxVel ) / ( maxVel - -maxVel ) ) );
  322. velZ = Math.floor( maxSource * ( ( velZ - -maxVel ) / ( maxVel - -maxVel ) ) );
  323. self.velCol.array[ i*4 + 0 ] = decodeFloat( velX, velY, velZ, turbulence ); //vel
  324. var rgb = hexToRgb( color );
  325. for( var c = 0; c < rgb.length; c++) {
  326. rgb[c] = Math.floor( rgb[c] + ( ( particleSystem.random() ) * colorRandomness ) * 254 );
  327. if( rgb[c] > 254 ) rgb[c] = 254;
  328. if( rgb[c] < 0 ) rgb[c] = 0;
  329. }
  330. self.velCol.array[ i*4 + 1 ] = decodeFloat( rgb[0], rgb[1], rgb[2], 254 );//color
  331. self.velCol.array[ i*4 + 2 ] = size + ( particleSystem.random() ) * sizeRandomness; //size
  332. self.velCol.array[ i*4 + 3 ] = lifetime; //lifespan
  333. if( this.offset == 0 ) {
  334. this.offset = self.PARTICLE_CURSOR;
  335. }
  336. self.count++;
  337. self.PARTICLE_CURSOR++;
  338. if( self.PARTICLE_CURSOR >= self.PARTICLE_COUNT ) {
  339. self.PARTICLE_CURSOR = 0;
  340. }
  341. self.particleUpdate = true;
  342. }
  343. this.update = function( time ) {
  344. self.time = time;
  345. self.particleShaderMat.uniforms['uTime'].value = time;
  346. this.geometryUpdate();
  347. };
  348. this.geometryUpdate = function() {
  349. if( self.particleUpdate == true ) {
  350. self.particleUpdate = false;
  351. // if we can get away with a partial buffer update, do so
  352. if( self.offset + self.count < self.PARTICLE_COUNT ) {
  353. self.posStart.updateRange.offset = self.velCol.updateRange.offset = self.offset * 4;
  354. self.posStart.updateRange.count = self.velCol.updateRange.count = self.count * 4;
  355. } else {
  356. self.posStart.updateRange.offset = 0;
  357. self.posStart.updateRange.count = self.velCol.updateRange.count = ( self.PARTICLE_COUNT * 4 );
  358. }
  359. self.posStart.needsUpdate = true;
  360. self.velCol.needsUpdate = true;
  361. self.offset = 0;
  362. self.count = 0;
  363. }
  364. }
  365. this.init();
  366. }
  367. THREE.GPUParticleContainer.prototype = Object.create(THREE.Object3D.prototype);
  368. THREE.GPUParticleContainer.prototype.constructor = THREE.GPUParticleContainer;