babylon.gpuParticleSystem.ts 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  1. module BABYLON {
  2. /**
  3. * This represents a GPU particle system in Babylon
  4. * This is the fastest particle system in Babylon as it uses the GPU to update the individual particle data
  5. * @see https://www.babylonjs-playground.com/#PU4WYI#4
  6. */
  7. export class GPUParticleSystem extends BaseParticleSystem implements IDisposable, IParticleSystem, IAnimatable {
  8. /**
  9. * The layer mask we are rendering the particles through.
  10. */
  11. public layerMask: number = 0x0FFFFFFF;
  12. private _capacity: number;
  13. private _activeCount: number;
  14. private _currentActiveCount: number;
  15. private _accumulatedCount = 0;
  16. private _renderEffect: Effect;
  17. private _updateEffect: Effect;
  18. private _buffer0: Buffer;
  19. private _buffer1: Buffer;
  20. private _spriteBuffer: Buffer;
  21. private _updateVAO: Array<WebGLVertexArrayObject>;
  22. private _renderVAO: Array<WebGLVertexArrayObject>;
  23. private _targetIndex = 0;
  24. private _sourceBuffer: Buffer;
  25. private _targetBuffer: Buffer;
  26. private _engine: Engine;
  27. private _currentRenderId = -1;
  28. private _started = false;
  29. private _stopped = false;
  30. private _timeDelta = 0;
  31. private _randomTexture: RawTexture;
  32. private _randomTexture2: RawTexture;
  33. private _attributesStrideSize = 21;
  34. private _updateEffectOptions: EffectCreationOptions;
  35. private _randomTextureSize: number;
  36. private _actualFrame = 0;
  37. private readonly _rawTextureWidth = 256;
  38. /**
  39. * Gets a boolean indicating if the GPU particles can be rendered on current browser
  40. */
  41. public static get IsSupported(): boolean {
  42. if (!Engine.LastCreatedEngine) {
  43. return false;
  44. }
  45. return Engine.LastCreatedEngine.webGLVersion > 1;
  46. }
  47. /**
  48. * An event triggered when the system is disposed.
  49. */
  50. public onDisposeObservable = new Observable<GPUParticleSystem>();
  51. /**
  52. * Gets the maximum number of particles active at the same time.
  53. * @returns The max number of active particles.
  54. */
  55. public getCapacity(): number {
  56. return this._capacity;
  57. }
  58. /**
  59. * Forces the particle to write their depth information to the depth buffer. This can help preventing other draw calls
  60. * to override the particles.
  61. */
  62. public forceDepthWrite = false;
  63. /**
  64. * Gets or set the number of active particles
  65. */
  66. public get activeParticleCount(): number {
  67. return this._activeCount;
  68. }
  69. public set activeParticleCount(value: number) {
  70. this._activeCount = Math.min(value, this._capacity);
  71. }
  72. private _preWarmDone = false;
  73. /**
  74. * Is this system ready to be used/rendered
  75. * @return true if the system is ready
  76. */
  77. public isReady(): boolean {
  78. if (!this._updateEffect) {
  79. this._recreateUpdateEffect();
  80. this._recreateRenderEffect();
  81. return false;
  82. }
  83. if (!this.emitter || !this._updateEffect.isReady() || !this._imageProcessingConfiguration.isReady() || !this._renderEffect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
  84. return false;
  85. }
  86. return true;
  87. }
  88. /**
  89. * Gets if the system has been started. (Note: this will still be true after stop is called)
  90. * @returns True if it has been started, otherwise false.
  91. */
  92. public isStarted(): boolean {
  93. return this._started;
  94. }
  95. /**
  96. * Starts the particle system and begins to emit
  97. * @param delay defines the delay in milliseconds before starting the system (0 by default)
  98. */
  99. public start(delay = 0): void {
  100. if (delay) {
  101. setTimeout(()=> {
  102. this.start(0);
  103. }, delay);
  104. return;
  105. }
  106. this._started = true;
  107. this._stopped = false;
  108. this._preWarmDone = false;
  109. }
  110. /**
  111. * Stops the particle system.
  112. */
  113. public stop(): void {
  114. this._stopped = true;
  115. }
  116. /**
  117. * Remove all active particles
  118. */
  119. public reset(): void {
  120. this._releaseBuffers();
  121. this._releaseVAOs();
  122. this._currentActiveCount = 0;
  123. this._targetIndex = 0;
  124. }
  125. /**
  126. * Returns the string "GPUParticleSystem"
  127. * @returns a string containing the class name
  128. */
  129. public getClassName(): string {
  130. return "GPUParticleSystem";
  131. }
  132. private _colorGradientsTexture: RawTexture;
  133. private _removeGradient(gradient: number, gradients: Nullable<IValueGradient[]>, texture: RawTexture): GPUParticleSystem {
  134. if (!gradients) {
  135. return this;
  136. }
  137. let index = 0;
  138. for (var valueGradient of gradients) {
  139. if (valueGradient.gradient === gradient) {
  140. gradients.splice(index, 1);
  141. break;
  142. }
  143. index++;
  144. }
  145. if (texture) {
  146. texture.dispose();
  147. }
  148. this._releaseBuffers();
  149. return this;
  150. }
  151. /**
  152. * Adds a new color gradient
  153. * @param gradient defines the gradient to use (between 0 and 1)
  154. * @param color defines the color to affect to the specified gradient
  155. * @param color2 defines an additional color used to define a range ([color, color2]) with main color to pick the final color from
  156. * @returns the current particle system
  157. */
  158. public addColorGradient(gradient: number, color1: Color4, color2?: Color4): GPUParticleSystem {
  159. if (!this._colorGradients) {
  160. this._colorGradients = [];
  161. }
  162. let colorGradient = new ColorGradient();
  163. colorGradient.gradient = gradient;
  164. colorGradient.color1 = color1;
  165. this._colorGradients.push(colorGradient);
  166. this._colorGradients.sort((a, b) => {
  167. if (a.gradient < b.gradient) {
  168. return -1;
  169. } else if (a.gradient > b.gradient) {
  170. return 1;
  171. }
  172. return 0;
  173. });
  174. if (this._colorGradientsTexture) {
  175. this._colorGradientsTexture.dispose();
  176. (<any>this._colorGradientsTexture) = null;
  177. }
  178. this._releaseBuffers();
  179. return this;
  180. }
  181. /**
  182. * Remove a specific color gradient
  183. * @param gradient defines the gradient to remove
  184. * @returns the current particle system
  185. */
  186. public removeColorGradient(gradient: number): GPUParticleSystem {
  187. this._removeGradient(gradient, this._colorGradients, this._colorGradientsTexture);
  188. (<any>this._colorGradientsTexture) = null;
  189. return this;
  190. }
  191. private _angularSpeedGradientsTexture: RawTexture;
  192. private _sizeGradientsTexture: RawTexture;
  193. private _velocityGradientsTexture: RawTexture;
  194. private _limitVelocityGradientsTexture: RawTexture;
  195. private _addFactorGradient(factorGradients: FactorGradient[], gradient: number, factor: number) {
  196. let valueGradient = new FactorGradient();
  197. valueGradient.gradient = gradient;
  198. valueGradient.factor1 = factor;
  199. factorGradients.push(valueGradient);
  200. factorGradients.sort((a, b) => {
  201. if (a.gradient < b.gradient) {
  202. return -1;
  203. } else if (a.gradient > b.gradient) {
  204. return 1;
  205. }
  206. return 0;
  207. });
  208. this._releaseBuffers();
  209. }
  210. /**
  211. * Adds a new size gradient
  212. * @param gradient defines the gradient to use (between 0 and 1)
  213. * @param factor defines the size factor to affect to the specified gradient
  214. * @returns the current particle system
  215. */
  216. public addSizeGradient(gradient: number, factor: number): GPUParticleSystem {
  217. if (!this._sizeGradients) {
  218. this._sizeGradients = [];
  219. }
  220. this._addFactorGradient(this._sizeGradients, gradient, factor);
  221. if (this._sizeGradientsTexture) {
  222. this._sizeGradientsTexture.dispose();
  223. (<any>this._sizeGradientsTexture) = null;
  224. }
  225. this._releaseBuffers();
  226. return this;
  227. }
  228. /**
  229. * Remove a specific size gradient
  230. * @param gradient defines the gradient to remove
  231. * @returns the current particle system
  232. */
  233. public removeSizeGradient(gradient: number): GPUParticleSystem {
  234. this._removeGradient(gradient, this._sizeGradients, this._sizeGradientsTexture);
  235. (<any>this._sizeGradientsTexture) = null;
  236. return this;
  237. }
  238. /**
  239. * Adds a new angular speed gradient
  240. * @param gradient defines the gradient to use (between 0 and 1)
  241. * @param factor defines the angular speed to affect to the specified gradient
  242. * @returns the current particle system
  243. */
  244. public addAngularSpeedGradient(gradient: number, factor: number): GPUParticleSystem {
  245. if (!this._angularSpeedGradients) {
  246. this._angularSpeedGradients = [];
  247. }
  248. this._addFactorGradient(this._angularSpeedGradients, gradient, factor);
  249. if (this._angularSpeedGradientsTexture) {
  250. this._angularSpeedGradientsTexture.dispose();
  251. (<any>this._angularSpeedGradientsTexture) = null;
  252. }
  253. this._releaseBuffers();
  254. return this;
  255. }
  256. /**
  257. * Remove a specific angular speed gradient
  258. * @param gradient defines the gradient to remove
  259. * @returns the current particle system
  260. */
  261. public removeAngularSpeedGradient(gradient: number): GPUParticleSystem {
  262. this._removeGradient(gradient, this._angularSpeedGradients, this._angularSpeedGradientsTexture);
  263. (<any>this._angularSpeedGradientsTexture) = null;
  264. return this;
  265. }
  266. /**
  267. * Adds a new velocity gradient
  268. * @param gradient defines the gradient to use (between 0 and 1)
  269. * @param factor defines the velocity to affect to the specified gradient
  270. * @returns the current particle system
  271. */
  272. public addVelocityGradient(gradient: number, factor: number): GPUParticleSystem {
  273. if (!this._velocityGradients) {
  274. this._velocityGradients = [];
  275. }
  276. this._addFactorGradient(this._velocityGradients, gradient, factor);
  277. if (this._velocityGradientsTexture) {
  278. this._velocityGradientsTexture.dispose();
  279. (<any>this._velocityGradientsTexture) = null;
  280. }
  281. this._releaseBuffers();
  282. return this;
  283. }
  284. /**
  285. * Remove a specific velocity gradient
  286. * @param gradient defines the gradient to remove
  287. * @returns the current particle system
  288. */
  289. public removeVelocityGradient(gradient: number): GPUParticleSystem {
  290. this._removeGradient(gradient, this._velocityGradients, this._velocityGradientsTexture);
  291. (<any>this._velocityGradientsTexture) = null;
  292. return this;
  293. }
  294. /**
  295. * Adds a new limit velocity gradient
  296. * @param gradient defines the gradient to use (between 0 and 1)
  297. * @param factor defines the limit velocity value to affect to the specified gradient
  298. * @returns the current particle system
  299. */
  300. public addLimitVelocityGradient(gradient: number, factor: number): GPUParticleSystem {
  301. if (!this._limitVelocityGradients) {
  302. this._limitVelocityGradients = [];
  303. }
  304. this._addFactorGradient(this._limitVelocityGradients, gradient, factor);
  305. if (this._limitVelocityGradientsTexture) {
  306. this._limitVelocityGradientsTexture.dispose();
  307. (<any>this._limitVelocityGradientsTexture) = null;
  308. }
  309. this._releaseBuffers();
  310. return this;
  311. }
  312. /**
  313. * Remove a specific limit velocity gradient
  314. * @param gradient defines the gradient to remove
  315. * @returns the current particle system
  316. */
  317. public removeLimitVelocityGradient(gradient: number): GPUParticleSystem {
  318. this._removeGradient(gradient, this._limitVelocityGradients, this._limitVelocityGradientsTexture);
  319. (<any>this._limitVelocityGradientsTexture) = null;
  320. return this;
  321. }
  322. /**
  323. * Instantiates a GPU particle system.
  324. * Particles are often small sprites used to simulate hard-to-reproduce phenomena like fire, smoke, water, or abstract visual effects like magic glitter and faery dust.
  325. * @param name The name of the particle system
  326. * @param options The options used to create the system
  327. * @param scene The scene the particle system belongs to
  328. * @param isAnimationSheetEnabled Must be true if using a spritesheet to animate the particles texture
  329. */
  330. constructor(name: string, options: Partial<{
  331. capacity: number,
  332. randomTextureSize: number
  333. }>, scene: Scene, isAnimationSheetEnabled: boolean = false) {
  334. super(name);
  335. this._scene = scene || Engine.LastCreatedScene;
  336. // Setup the default processing configuration to the scene.
  337. this._attachImageProcessingConfiguration(null);
  338. this._engine = this._scene.getEngine();
  339. if (!options.randomTextureSize) {
  340. delete options.randomTextureSize;
  341. }
  342. let fullOptions = {
  343. capacity: 50000,
  344. randomTextureSize: this._engine.getCaps().maxTextureSize,
  345. ...options
  346. };
  347. var optionsAsNumber = <number>options;
  348. if (isFinite(optionsAsNumber)) {
  349. fullOptions.capacity = optionsAsNumber;
  350. }
  351. this._capacity = fullOptions.capacity;
  352. this._activeCount = fullOptions.capacity;
  353. this._currentActiveCount = 0;
  354. this._isAnimationSheetEnabled = isAnimationSheetEnabled;
  355. this._scene.particleSystems.push(this);
  356. this._updateEffectOptions = {
  357. attributes: ["position", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle", "cellIndex"],
  358. uniformsNames: ["currentCount", "timeDelta", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "scaleRange","gravity", "emitPower",
  359. "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "coneAngle", "stopFactor",
  360. "angleRange", "radiusRange", "cellInfos", "noiseStrength"],
  361. uniformBuffersNames: [],
  362. samplers:["randomSampler", "randomSampler2", "sizeGradientSampler", "angularSpeedGradientSampler", "velocityGradientSampler", "noiseSampler"],
  363. defines: "",
  364. fallbacks: null,
  365. onCompiled: null,
  366. onError: null,
  367. indexParameters: null,
  368. maxSimultaneousLights: 0,
  369. transformFeedbackVaryings: []
  370. };
  371. this.particleEmitterType = new BoxParticleEmitter();
  372. // Random data
  373. var maxTextureSize = Math.min(this._engine.getCaps().maxTextureSize, fullOptions.randomTextureSize);
  374. var d = [];
  375. for (var i = 0; i < maxTextureSize; ++i) {
  376. d.push(Math.random());
  377. d.push(Math.random());
  378. d.push(Math.random());
  379. d.push(Math.random());
  380. }
  381. this._randomTexture = new RawTexture(new Float32Array(d), maxTextureSize, 1, Engine.TEXTUREFORMAT_RGBA, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE, Engine.TEXTURETYPE_FLOAT);
  382. this._randomTexture.wrapU = Texture.WRAP_ADDRESSMODE;
  383. this._randomTexture.wrapV = Texture.WRAP_ADDRESSMODE;
  384. d = [];
  385. for (var i = 0; i < maxTextureSize; ++i) {
  386. d.push(Math.random());
  387. d.push(Math.random());
  388. d.push(Math.random());
  389. d.push(Math.random());
  390. }
  391. this._randomTexture2 = new RawTexture(new Float32Array(d), maxTextureSize, 1, Engine.TEXTUREFORMAT_RGBA, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE, Engine.TEXTURETYPE_FLOAT);
  392. this._randomTexture2.wrapU = Texture.WRAP_ADDRESSMODE;
  393. this._randomTexture2.wrapV = Texture.WRAP_ADDRESSMODE;
  394. this._randomTextureSize = maxTextureSize;
  395. }
  396. protected _reset() {
  397. this._releaseBuffers();
  398. }
  399. private _createUpdateVAO(source: Buffer): WebGLVertexArrayObject {
  400. let updateVertexBuffers: {[key: string]: VertexBuffer} = {};
  401. updateVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3);
  402. updateVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1);
  403. updateVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1);
  404. updateVertexBuffers["seed"] = source.createVertexBuffer("seed", 5, 4);
  405. updateVertexBuffers["size"] = source.createVertexBuffer("size", 9, 3);
  406. let offset = 12;
  407. if (!this._colorGradientsTexture) {
  408. updateVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4);
  409. offset += 4;
  410. }
  411. updateVertexBuffers["direction"] = source.createVertexBuffer("direction", offset, 3);
  412. offset += 3
  413. if (!this._isBillboardBased) {
  414. updateVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3);
  415. offset += 3;
  416. }
  417. if (this._angularSpeedGradientsTexture) {
  418. updateVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 1);
  419. offset += 1;
  420. } else {
  421. updateVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 2);
  422. offset += 2;
  423. }
  424. if (this._isAnimationSheetEnabled) {
  425. updateVertexBuffers["cellIndex"] = source.createVertexBuffer("cellIndex", offset, 1);
  426. offset += 1;
  427. }
  428. let vao = this._engine.recordVertexArrayObject(updateVertexBuffers, null, this._updateEffect);
  429. this._engine.bindArrayBuffer(null);
  430. return vao;
  431. }
  432. private _createRenderVAO(source: Buffer, spriteSource: Buffer): WebGLVertexArrayObject {
  433. let renderVertexBuffers: {[key: string]: VertexBuffer} = {};
  434. renderVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3, this._attributesStrideSize, true);
  435. renderVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1, this._attributesStrideSize, true);
  436. renderVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1, this._attributesStrideSize, true);
  437. renderVertexBuffers["size"] = source.createVertexBuffer("size", 9, 3, this._attributesStrideSize, true);
  438. let offset = 12;
  439. if (!this._colorGradientsTexture) {
  440. renderVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4, this._attributesStrideSize, true);
  441. offset += 4;
  442. }
  443. offset += 3; // Direction
  444. if (!this._isBillboardBased) {
  445. renderVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3, this._attributesStrideSize, true);
  446. offset += 3;
  447. }
  448. renderVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 1, this._attributesStrideSize, true);
  449. if (this._angularSpeedGradientsTexture) {
  450. offset++;
  451. } else {
  452. offset += 2;
  453. }
  454. if (this._isAnimationSheetEnabled) {
  455. renderVertexBuffers["cellIndex"] = source.createVertexBuffer("cellIndex", offset, 1, this._attributesStrideSize, true);
  456. offset += 1;
  457. }
  458. renderVertexBuffers["offset"] = spriteSource.createVertexBuffer("offset", 0, 2);
  459. renderVertexBuffers["uv"] = spriteSource.createVertexBuffer("uv", 2, 2);
  460. let vao = this._engine.recordVertexArrayObject(renderVertexBuffers, null, this._renderEffect);
  461. this._engine.bindArrayBuffer(null);
  462. return vao;
  463. }
  464. private _initialize(force = false): void {
  465. if (this._buffer0 && !force) {
  466. return;
  467. }
  468. let engine = this._scene.getEngine();
  469. var data = new Array<float>();
  470. if (!this.isBillboardBased) {
  471. this._attributesStrideSize += 3;
  472. }
  473. if (this._colorGradientsTexture) {
  474. this._attributesStrideSize -= 4;
  475. }
  476. if (this._angularSpeedGradientsTexture) {
  477. this._attributesStrideSize -= 1;
  478. }
  479. if (this._isAnimationSheetEnabled) {
  480. this._attributesStrideSize += 1;
  481. }
  482. for (var particleIndex = 0; particleIndex < this._capacity; particleIndex++) {
  483. // position
  484. data.push(0.0);
  485. data.push(0.0);
  486. data.push(0.0);
  487. // Age and life
  488. data.push(0.0); // create the particle as a dead one to create a new one at start
  489. data.push(0.0);
  490. // Seed
  491. data.push(Math.random());
  492. data.push(Math.random());
  493. data.push(Math.random());
  494. data.push(Math.random());
  495. // Size
  496. data.push(0.0);
  497. data.push(0.0);
  498. data.push(0.0);
  499. if (!this._colorGradientsTexture) {
  500. // color
  501. data.push(0.0);
  502. data.push(0.0);
  503. data.push(0.0);
  504. data.push(0.0);
  505. }
  506. // direction
  507. data.push(0.0);
  508. data.push(0.0);
  509. data.push(0.0);
  510. if (!this.isBillboardBased) {
  511. // initialDirection
  512. data.push(0.0);
  513. data.push(0.0);
  514. data.push(0.0);
  515. }
  516. // angle
  517. data.push(0.0);
  518. if (!this._angularSpeedGradientsTexture) {
  519. data.push(0.0);
  520. }
  521. if (this._isAnimationSheetEnabled) {
  522. data.push(0.0);
  523. }
  524. }
  525. // Sprite data
  526. var spriteData = new Float32Array([0.5, 0.5, 1, 1,
  527. -0.5, 0.5, 0, 1,
  528. -0.5, -0.5, 0, 0,
  529. 0.5, -0.5, 1, 0]);
  530. // Buffers
  531. this._buffer0 = new Buffer(engine, data, false, this._attributesStrideSize);
  532. this._buffer1 = new Buffer(engine, data, false, this._attributesStrideSize);
  533. this._spriteBuffer = new Buffer(engine, spriteData, false, 4);
  534. // Update VAO
  535. this._updateVAO = [];
  536. this._updateVAO.push(this._createUpdateVAO(this._buffer0));
  537. this._updateVAO.push(this._createUpdateVAO(this._buffer1));
  538. // Render VAO
  539. this._renderVAO = [];
  540. this._renderVAO.push(this._createRenderVAO(this._buffer1, this._spriteBuffer));
  541. this._renderVAO.push(this._createRenderVAO(this._buffer0, this._spriteBuffer));
  542. // Links
  543. this._sourceBuffer = this._buffer0;
  544. this._targetBuffer = this._buffer1;
  545. }
  546. /** @hidden */
  547. public _recreateUpdateEffect() {
  548. let defines = this.particleEmitterType ? this.particleEmitterType.getEffectDefines() : "";
  549. if (this._isBillboardBased) {
  550. defines += "\n#define BILLBOARD";
  551. }
  552. if (this._colorGradientsTexture) {
  553. defines += "\n#define COLORGRADIENTS";
  554. }
  555. if (this._sizeGradientsTexture) {
  556. defines += "\n#define SIZEGRADIENTS";
  557. }
  558. if (this._angularSpeedGradientsTexture) {
  559. defines += "\n#define ANGULARSPEEDGRADIENTS";
  560. }
  561. if (this._velocityGradientsTexture) {
  562. defines += "\n#define VELOCITYGRADIENTS";
  563. }
  564. if (this.isAnimationSheetEnabled) {
  565. defines += "\n#define ANIMATESHEET";
  566. }
  567. if (this.noiseTexture) {
  568. defines += "\n#define NOISE";
  569. }
  570. if (this._updateEffect && this._updateEffectOptions.defines === defines) {
  571. return;
  572. }
  573. this._updateEffectOptions.transformFeedbackVaryings = ["outPosition", "outAge", "outLife", "outSeed", "outSize"];
  574. if (!this._colorGradientsTexture) {
  575. this._updateEffectOptions.transformFeedbackVaryings.push("outColor");
  576. }
  577. this._updateEffectOptions.transformFeedbackVaryings.push("outDirection");
  578. if (!this._isBillboardBased) {
  579. this._updateEffectOptions.transformFeedbackVaryings.push("outInitialDirection");
  580. }
  581. this._updateEffectOptions.transformFeedbackVaryings.push("outAngle");
  582. if (this.isAnimationSheetEnabled) {
  583. this._updateEffectOptions.transformFeedbackVaryings.push("outCellIndex");
  584. }
  585. this._updateEffectOptions.defines = defines;
  586. this._updateEffect = new Effect("gpuUpdateParticles", this._updateEffectOptions, this._scene.getEngine());
  587. }
  588. /** @hidden */
  589. public _recreateRenderEffect() {
  590. let defines = "";
  591. if (this._scene.clipPlane) {
  592. defines = "\n#define CLIPPLANE";
  593. }
  594. if (this._scene.clipPlane2) {
  595. defines = "\n#define CLIPPLANE2";
  596. }
  597. if (this._scene.clipPlane3) {
  598. defines = "\n#define CLIPPLANE3";
  599. }
  600. if (this._scene.clipPlane4) {
  601. defines = "\n#define CLIPPLANE4";
  602. }
  603. if (this._isBillboardBased) {
  604. defines += "\n#define BILLBOARD";
  605. switch (this.billboardMode) {
  606. case AbstractMesh.BILLBOARDMODE_Y:
  607. defines += "\n#define BILLBOARDY";
  608. break;
  609. case AbstractMesh.BILLBOARDMODE_ALL:
  610. default:
  611. break;
  612. }
  613. }
  614. if (this._colorGradientsTexture) {
  615. defines += "\n#define COLORGRADIENTS";
  616. }
  617. if (this.isAnimationSheetEnabled) {
  618. defines += "\n#define ANIMATESHEET";
  619. }
  620. if (this._imageProcessingConfiguration) {
  621. this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
  622. defines += "\n" + this._imageProcessingConfigurationDefines.toString();
  623. }
  624. if (this._renderEffect && this._renderEffect.defines === defines) {
  625. return;
  626. }
  627. var uniforms = ["view", "projection", "colorDead", "invView", "vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "sheetInfos", "translationPivot", "eyePosition"];
  628. var samplers = ["textureSampler", "colorGradientSampler"];
  629. if (ImageProcessingConfiguration) {
  630. ImageProcessingConfiguration.PrepareUniforms(uniforms, this._imageProcessingConfigurationDefines);
  631. ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
  632. }
  633. this._renderEffect = new Effect("gpuRenderParticles",
  634. ["position", "age", "life", "size", "color", "offset", "uv", "initialDirection", "angle", "cellIndex"],
  635. uniforms,
  636. samplers, this._scene.getEngine(), defines);
  637. }
  638. /**
  639. * Animates the particle system for the current frame by emitting new particles and or animating the living ones.
  640. * @param preWarm defines if we are in the pre-warmimg phase
  641. */
  642. public animate(preWarm = false): void {
  643. this._timeDelta = this.updateSpeed * (preWarm ? this.preWarmStepOffset : this._scene.getAnimationRatio());
  644. this._actualFrame += this._timeDelta;
  645. if (!this._stopped) {
  646. if (this.targetStopDuration && this._actualFrame >= this.targetStopDuration) {
  647. this.stop();
  648. }
  649. }
  650. }
  651. private _createFactorGradientTexture(factorGradients: Nullable<IValueGradient[]>, textureName: string) {
  652. let texture:RawTexture = (<any>this)[textureName];
  653. if (!factorGradients || !factorGradients.length || texture) {
  654. return;
  655. }
  656. let data = new Float32Array(this._rawTextureWidth);
  657. for (var x = 0; x < this._rawTextureWidth; x++) {
  658. var ratio = x / this._rawTextureWidth;
  659. Tools.GetCurrentGradient(ratio, factorGradients, (currentGradient, nextGradient, scale) => {
  660. data[x] = Scalar.Lerp((<FactorGradient>currentGradient).factor1, (<FactorGradient>nextGradient).factor1, scale);
  661. });
  662. }
  663. (<any>this)[textureName] = RawTexture.CreateRTexture(data, this._rawTextureWidth, 1, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE);
  664. }
  665. private _createSizeGradientTexture() {
  666. this._createFactorGradientTexture(this._sizeGradients, "_sizeGradientsTexture");
  667. }
  668. private _createAngularSpeedGradientTexture() {
  669. this._createFactorGradientTexture(this._angularSpeedGradients, "_angularSpeedGradientsTexture");
  670. }
  671. private _createVelocityGradientTexture() {
  672. this._createFactorGradientTexture(this._velocityGradients, "_velocityGradientsTexture");
  673. }
  674. private _createColorGradientTexture() {
  675. if (!this._colorGradients || !this._colorGradients.length || this._colorGradientsTexture) {
  676. return;
  677. }
  678. let data = new Uint8Array(this._rawTextureWidth * 4);
  679. let tmpColor = Tmp.Color4[0];
  680. for (var x = 0; x < this._rawTextureWidth; x++) {
  681. var ratio = x / this._rawTextureWidth;
  682. Tools.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
  683. Color4.LerpToRef((<ColorGradient>currentGradient).color1, (<ColorGradient>nextGradient).color1, scale, tmpColor);
  684. data[x * 4] = tmpColor.r * 255;
  685. data[x * 4 + 1] = tmpColor.g * 255;
  686. data[x * 4 + 2] = tmpColor.b * 255;
  687. data[x * 4 + 3] = tmpColor.a * 255;
  688. });
  689. }
  690. this._colorGradientsTexture = RawTexture.CreateRGBATexture(data, this._rawTextureWidth, 1, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE);
  691. }
  692. /**
  693. * Renders the particle system in its current state
  694. * @param preWarm defines if the system should only update the particles but not render them
  695. * @returns the current number of particles
  696. */
  697. public render(preWarm = false): number {
  698. if (!this._started) {
  699. return 0;
  700. }
  701. this._createColorGradientTexture();
  702. this._createSizeGradientTexture();
  703. this._createAngularSpeedGradientTexture();
  704. this._createVelocityGradientTexture();
  705. this._recreateUpdateEffect();
  706. this._recreateRenderEffect();
  707. if (!this.isReady()) {
  708. return 0;
  709. }
  710. if (!preWarm) {
  711. if (!this._preWarmDone && this.preWarmCycles) {
  712. for (var index = 0; index < this.preWarmCycles; index++) {
  713. this.animate(true);
  714. this.render(true);
  715. }
  716. this._preWarmDone = true;
  717. }
  718. if (this._currentRenderId === this._scene.getRenderId()) {
  719. return 0;
  720. }
  721. this._currentRenderId = this._scene.getRenderId();
  722. }
  723. // Get everything ready to render
  724. this._initialize();
  725. this._accumulatedCount += this.emitRate * this._timeDelta;
  726. if (this._accumulatedCount > 1) {
  727. var intPart = this._accumulatedCount | 0;
  728. this._accumulatedCount -= intPart;
  729. this._currentActiveCount = Math.min(this._activeCount, this._currentActiveCount + intPart);
  730. }
  731. if (!this._currentActiveCount) {
  732. return 0;
  733. }
  734. // Enable update effect
  735. this._engine.enableEffect(this._updateEffect);
  736. this._engine.setState(false);
  737. this._updateEffect.setFloat("currentCount", this._currentActiveCount);
  738. this._updateEffect.setFloat("timeDelta", this._timeDelta);
  739. this._updateEffect.setFloat("stopFactor", this._stopped ? 0 : 1);
  740. this._updateEffect.setTexture("randomSampler", this._randomTexture);
  741. this._updateEffect.setTexture("randomSampler2", this._randomTexture2);
  742. this._updateEffect.setFloat2("lifeTime", this.minLifeTime, this.maxLifeTime);
  743. this._updateEffect.setFloat2("emitPower", this.minEmitPower, this.maxEmitPower);
  744. if (!this._colorGradientsTexture) {
  745. this._updateEffect.setDirectColor4("color1", this.color1);
  746. this._updateEffect.setDirectColor4("color2", this.color2);
  747. }
  748. this._updateEffect.setFloat2("sizeRange", this.minSize, this.maxSize);
  749. this._updateEffect.setFloat4("scaleRange", this.minScaleX, this.maxScaleX, this.minScaleY, this.maxScaleY);
  750. this._updateEffect.setFloat4("angleRange", this.minAngularSpeed, this.maxAngularSpeed, this.minInitialRotation, this.maxInitialRotation);
  751. this._updateEffect.setVector3("gravity", this.gravity);
  752. if (this._sizeGradientsTexture) {
  753. this._updateEffect.setTexture("sizeGradientSampler", this._sizeGradientsTexture);
  754. }
  755. if (this._angularSpeedGradientsTexture) {
  756. this._updateEffect.setTexture("angularSpeedGradientSampler", this._angularSpeedGradientsTexture);
  757. }
  758. if (this._velocityGradientsTexture) {
  759. this._updateEffect.setTexture("velocityGradientSampler", this._velocityGradientsTexture);
  760. }
  761. if (this.particleEmitterType) {
  762. this.particleEmitterType.applyToShader(this._updateEffect);
  763. }
  764. if (this._isAnimationSheetEnabled) {
  765. this._updateEffect.setFloat3("cellInfos", this.startSpriteCellID, this.endSpriteCellID, this.spriteCellChangeSpeed);
  766. }
  767. if (this.noiseTexture) {
  768. this._updateEffect.setTexture("noiseSampler", this.noiseTexture);
  769. this._updateEffect.setVector3("noiseStrength", this.noiseStrength);
  770. }
  771. let emitterWM: Matrix;
  772. if ((<AbstractMesh>this.emitter).position) {
  773. var emitterMesh = (<AbstractMesh>this.emitter);
  774. emitterWM = emitterMesh.getWorldMatrix();
  775. } else {
  776. var emitterPosition = (<Vector3>this.emitter);
  777. emitterWM = Matrix.Translation(emitterPosition.x, emitterPosition.y, emitterPosition.z);
  778. }
  779. this._updateEffect.setMatrix("emitterWM", emitterWM);
  780. // Bind source VAO
  781. this._engine.bindVertexArrayObject(this._updateVAO[this._targetIndex], null);
  782. // Update
  783. this._engine.bindTransformFeedbackBuffer(this._targetBuffer.getBuffer());
  784. this._engine.setRasterizerState(false);
  785. this._engine.beginTransformFeedback(true);
  786. this._engine.drawArraysType(Material.PointListDrawMode, 0, this._currentActiveCount);
  787. this._engine.endTransformFeedback();
  788. this._engine.setRasterizerState(true);
  789. this._engine.bindTransformFeedbackBuffer(null);
  790. if (!preWarm) {
  791. // Enable render effect
  792. this._engine.enableEffect(this._renderEffect);
  793. let viewMatrix = this._scene.getViewMatrix();
  794. this._renderEffect.setMatrix("view", viewMatrix);
  795. this._renderEffect.setMatrix("projection", this._scene.getProjectionMatrix());
  796. this._renderEffect.setTexture("textureSampler", this.particleTexture);
  797. this._renderEffect.setVector2("translationPivot", this.translationPivot);
  798. if (this._colorGradientsTexture) {
  799. this._renderEffect.setTexture("colorGradientSampler", this._colorGradientsTexture);
  800. } else {
  801. this._renderEffect.setDirectColor4("colorDead", this.colorDead);
  802. }
  803. if (this._isAnimationSheetEnabled && this.particleTexture) {
  804. let baseSize = this.particleTexture.getBaseSize();
  805. this._renderEffect.setFloat3("sheetInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
  806. }
  807. if (this._isBillboardBased) {
  808. var camera = this._scene.activeCamera!;
  809. this._renderEffect.setVector3("eyePosition", camera.globalPosition);
  810. }
  811. if (this._scene.clipPlane || this._scene.clipPlane2 || this._scene.clipPlane3 || this._scene.clipPlane4) {
  812. var invView = viewMatrix.clone();
  813. invView.invert();
  814. this._renderEffect.setMatrix("invView", invView);
  815. MaterialHelper.BindClipPlane(this._renderEffect, this._scene);
  816. }
  817. // image processing
  818. if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {
  819. this._imageProcessingConfiguration.bind(this._renderEffect);
  820. }
  821. // Draw order
  822. switch(this.blendMode)
  823. {
  824. case ParticleSystem.BLENDMODE_ADD:
  825. this._engine.setAlphaMode(Engine.ALPHA_ADD);
  826. break;
  827. case ParticleSystem.BLENDMODE_ONEONE:
  828. this._engine.setAlphaMode(Engine.ALPHA_ONEONE);
  829. break;
  830. case ParticleSystem.BLENDMODE_STANDARD:
  831. this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
  832. break;
  833. }
  834. if (this.forceDepthWrite) {
  835. this._engine.setDepthWrite(true);
  836. }
  837. // Bind source VAO
  838. this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
  839. // Render
  840. this._engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._currentActiveCount);
  841. this._engine.setAlphaMode(Engine.ALPHA_DISABLE);
  842. }
  843. // Switch VAOs
  844. this._targetIndex++;
  845. if (this._targetIndex === 2) {
  846. this._targetIndex = 0;
  847. }
  848. // Switch buffers
  849. let tmpBuffer = this._sourceBuffer;
  850. this._sourceBuffer = this._targetBuffer;
  851. this._targetBuffer = tmpBuffer;
  852. return this._currentActiveCount;
  853. }
  854. /**
  855. * Rebuilds the particle system
  856. */
  857. public rebuild(): void {
  858. this._initialize(true);
  859. }
  860. private _releaseBuffers() {
  861. if (this._buffer0) {
  862. this._buffer0.dispose();
  863. (<any>this._buffer0) = null;
  864. }
  865. if (this._buffer1) {
  866. this._buffer1.dispose();
  867. (<any>this._buffer1) = null;
  868. }
  869. if (this._spriteBuffer) {
  870. this._spriteBuffer.dispose();
  871. (<any>this._spriteBuffer) = null;
  872. }
  873. }
  874. private _releaseVAOs() {
  875. if (!this._updateVAO) {
  876. return;
  877. }
  878. for (var index = 0; index < this._updateVAO.length; index++) {
  879. this._engine.releaseVertexArrayObject(this._updateVAO[index]);
  880. }
  881. this._updateVAO = [];
  882. for (var index = 0; index < this._renderVAO.length; index++) {
  883. this._engine.releaseVertexArrayObject(this._renderVAO[index]);
  884. }
  885. this._renderVAO = [];
  886. }
  887. /**
  888. * Disposes the particle system and free the associated resources
  889. * @param disposeTexture defines if the particule texture must be disposed as well (true by default)
  890. */
  891. public dispose(disposeTexture = true): void {
  892. var index = this._scene.particleSystems.indexOf(this);
  893. if (index > -1) {
  894. this._scene.particleSystems.splice(index, 1);
  895. }
  896. this._releaseBuffers();
  897. this._releaseVAOs();
  898. if (this._colorGradientsTexture) {
  899. this._colorGradientsTexture.dispose();
  900. (<any>this._colorGradientsTexture) = null;
  901. }
  902. if (this._sizeGradientsTexture) {
  903. this._sizeGradientsTexture.dispose();
  904. (<any>this._sizeGradientsTexture) = null;
  905. }
  906. if (this._angularSpeedGradientsTexture) {
  907. this._angularSpeedGradientsTexture.dispose();
  908. (<any>this._angularSpeedGradientsTexture) = null;
  909. }
  910. if (this._velocityGradientsTexture) {
  911. this._velocityGradientsTexture.dispose();
  912. (<any>this._velocityGradientsTexture) = null;
  913. }
  914. if (this._limitVelocityGradientsTexture) {
  915. this._limitVelocityGradientsTexture.dispose();
  916. (<any>this._limitVelocityGradientsTexture) = null;
  917. }
  918. if (this._randomTexture) {
  919. this._randomTexture.dispose();
  920. (<any>this._randomTexture) = null;
  921. }
  922. if (this._randomTexture2) {
  923. this._randomTexture2.dispose();
  924. (<any>this._randomTexture2) = null;
  925. }
  926. if (disposeTexture && this.particleTexture) {
  927. this.particleTexture.dispose();
  928. this.particleTexture = null;
  929. }
  930. if (disposeTexture && this.noiseTexture) {
  931. this.noiseTexture.dispose();
  932. this.noiseTexture = null;
  933. }
  934. // Callback
  935. this.onDisposeObservable.notifyObservers(this);
  936. this.onDisposeObservable.clear();
  937. }
  938. /**
  939. * Clones the particle system.
  940. * @param name The name of the cloned object
  941. * @param newEmitter The new emitter to use
  942. * @returns the cloned particle system
  943. */
  944. public clone(name: string, newEmitter: any): GPUParticleSystem {
  945. var result = new GPUParticleSystem(name, {capacity: this._capacity, randomTextureSize: this._randomTextureSize}, this._scene);
  946. Tools.DeepCopy(this, result);
  947. if (newEmitter === undefined) {
  948. newEmitter = this.emitter;
  949. }
  950. result.emitter = newEmitter;
  951. if (this.particleTexture) {
  952. result.particleTexture = new Texture(this.particleTexture.url, this._scene);
  953. }
  954. return result;
  955. }
  956. /**
  957. * Serializes the particle system to a JSON object.
  958. * @returns the JSON object
  959. */
  960. public serialize(): any {
  961. var serializationObject: any = {};
  962. ParticleSystem._Serialize(serializationObject, this);
  963. serializationObject.activeParticleCount = this.activeParticleCount;
  964. return serializationObject;
  965. }
  966. /**
  967. * Parses a JSON object to create a GPU particle system.
  968. * @param parsedParticleSystem The JSON object to parse
  969. * @param scene The scene to create the particle system in
  970. * @param rootUrl The root url to use to load external dependencies like texture
  971. * @returns the parsed GPU particle system
  972. */
  973. public static Parse(parsedParticleSystem: any, scene: Scene, rootUrl: string): GPUParticleSystem {
  974. var name = parsedParticleSystem.name;
  975. var particleSystem = new GPUParticleSystem(name, {capacity: parsedParticleSystem.capacity, randomTextureSize: parsedParticleSystem.randomTextureSize}, scene);
  976. if (parsedParticleSystem.activeParticleCount) {
  977. particleSystem.activeParticleCount = parsedParticleSystem.activeParticleCount;
  978. }
  979. ParticleSystem._Parse(parsedParticleSystem, particleSystem, scene, rootUrl);
  980. return particleSystem;
  981. }
  982. }
  983. }