gpuParticleSystem.ts 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770
  1. import { Nullable, float } from "../types";
  2. import { FactorGradient, ColorGradient, Color3Gradient, IValueGradient, GradientHelper } from "../Misc/gradients";
  3. import { Observable } from "../Misc/observable";
  4. import { Vector3, Matrix, TmpVectors } from "../Maths/math.vector";
  5. import { Color4, Color3, TmpColors } from '../Maths/math.color';
  6. import { Scalar } from "../Maths/math.scalar";
  7. import { VertexBuffer } from "../Meshes/buffer";
  8. import { Buffer } from "../Meshes/buffer";
  9. import { IParticleSystem } from "./IParticleSystem";
  10. import { BaseParticleSystem } from "./baseParticleSystem";
  11. import { ParticleSystem } from "./particleSystem";
  12. import { BoxParticleEmitter } from "../Particles/EmitterTypes/boxParticleEmitter";
  13. import { IDisposable } from "../scene";
  14. import { Effect, IEffectCreationOptions } from "../Materials/effect";
  15. import { MaterialHelper } from "../Materials/materialHelper";
  16. import { ImageProcessingConfiguration } from "../Materials/imageProcessingConfiguration";
  17. import { RawTexture } from "../Materials/Textures/rawTexture";
  18. import { Constants } from "../Engines/constants";
  19. import { EngineStore } from "../Engines/engineStore";
  20. import { IAnimatable } from '../Animations/animatable.interface';
  21. import { CustomParticleEmitter } from './EmitterTypes/customParticleEmitter';
  22. import { ThinEngine } from '../Engines/thinEngine';
  23. declare type Scene = import("../scene").Scene;
  24. declare type Engine = import("../Engines/engine").Engine;
  25. declare type AbstractMesh = import("../Meshes/abstractMesh").AbstractMesh;
  26. import "../Shaders/gpuUpdateParticles.fragment";
  27. import "../Shaders/gpuUpdateParticles.vertex";
  28. import "../Shaders/gpuRenderParticles.fragment";
  29. import "../Shaders/gpuRenderParticles.vertex";
  30. /**
  31. * This represents a GPU particle system in Babylon
  32. * This is the fastest particle system in Babylon as it uses the GPU to update the individual particle data
  33. * @see https://www.babylonjs-playground.com/#PU4WYI#4
  34. */
  35. export class GPUParticleSystem extends BaseParticleSystem implements IDisposable, IParticleSystem, IAnimatable {
  36. /**
  37. * The layer mask we are rendering the particles through.
  38. */
  39. public layerMask: number = 0x0FFFFFFF;
  40. private _capacity: number;
  41. private _activeCount: number;
  42. private _currentActiveCount: number;
  43. private _accumulatedCount = 0;
  44. private _renderEffect: Effect;
  45. private _updateEffect: Effect;
  46. private _buffer0: Buffer;
  47. private _buffer1: Buffer;
  48. private _spriteBuffer: Buffer;
  49. private _updateVAO: Array<WebGLVertexArrayObject>;
  50. private _renderVAO: Array<WebGLVertexArrayObject>;
  51. private _targetIndex = 0;
  52. private _sourceBuffer: Buffer;
  53. private _targetBuffer: Buffer;
  54. private _currentRenderId = -1;
  55. private _started = false;
  56. private _stopped = false;
  57. private _timeDelta = 0;
  58. private _randomTexture: RawTexture;
  59. private _randomTexture2: RawTexture;
  60. private _attributesStrideSize: number;
  61. private _updateEffectOptions: IEffectCreationOptions;
  62. private _randomTextureSize: number;
  63. private _actualFrame = 0;
  64. private _customEffect: { [blendMode: number] : Nullable<Effect> };
  65. private readonly _rawTextureWidth = 256;
  66. /**
  67. * Gets a boolean indicating if the GPU particles can be rendered on current browser
  68. */
  69. public static get IsSupported(): boolean {
  70. if (!EngineStore.LastCreatedEngine) {
  71. return false;
  72. }
  73. return EngineStore.LastCreatedEngine.name === "WebGL" && EngineStore.LastCreatedEngine.version > 1;
  74. }
  75. /**
  76. * An event triggered when the system is disposed.
  77. */
  78. public onDisposeObservable = new Observable<IParticleSystem>();
  79. /**
  80. * An event triggered when the system is stopped
  81. */
  82. public onStoppedObservable = new Observable<IParticleSystem>();
  83. /**
  84. * Gets the maximum number of particles active at the same time.
  85. * @returns The max number of active particles.
  86. */
  87. public getCapacity(): number {
  88. return this._capacity;
  89. }
  90. /**
  91. * Forces the particle to write their depth information to the depth buffer. This can help preventing other draw calls
  92. * to override the particles.
  93. */
  94. public forceDepthWrite = false;
  95. /**
  96. * Gets or set the number of active particles
  97. */
  98. public get activeParticleCount(): number {
  99. return this._activeCount;
  100. }
  101. public set activeParticleCount(value: number) {
  102. this._activeCount = Math.min(value, this._capacity);
  103. }
  104. private _preWarmDone = false;
  105. /**
  106. * Specifies if the particles are updated in emitter local space or world space.
  107. */
  108. public isLocal = false;
  109. /** Gets or sets a matrix to use to compute projection */
  110. public defaultProjectionMatrix: Matrix;
  111. /**
  112. * Is this system ready to be used/rendered
  113. * @return true if the system is ready
  114. */
  115. public isReady(): boolean {
  116. if (!this._updateEffect) {
  117. this._recreateUpdateEffect();
  118. this._recreateRenderEffect();
  119. return false;
  120. }
  121. if (!this.emitter || !this._updateEffect.isReady() || this._imageProcessingConfiguration && !this._imageProcessingConfiguration.isReady() || !this._getEffect().isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
  122. return false;
  123. }
  124. return true;
  125. }
  126. /**
  127. * Gets if the system has been started. (Note: this will still be true after stop is called)
  128. * @returns True if it has been started, otherwise false.
  129. */
  130. public isStarted(): boolean {
  131. return this._started;
  132. }
  133. /**
  134. * Gets if the system has been stopped. (Note: rendering is still happening but the system is frozen)
  135. * @returns True if it has been stopped, otherwise false.
  136. */
  137. public isStopped(): boolean {
  138. return this._stopped;
  139. }
  140. /**
  141. * Gets a boolean indicating that the system is stopping
  142. * @returns true if the system is currently stopping
  143. */
  144. public isStopping() {
  145. return false; // Stop is immediate on GPU
  146. }
  147. /**
  148. * Gets the number of particles active at the same time.
  149. * @returns The number of active particles.
  150. */
  151. public getActiveCount() {
  152. return this._currentActiveCount;
  153. }
  154. /**
  155. * Starts the particle system and begins to emit
  156. * @param delay defines the delay in milliseconds before starting the system (this.startDelay by default)
  157. */
  158. public start(delay = this.startDelay): void {
  159. if (!this.targetStopDuration && this._hasTargetStopDurationDependantGradient()) {
  160. throw "Particle system started with a targetStopDuration dependant gradient (eg. startSizeGradients) but no targetStopDuration set";
  161. }
  162. if (delay) {
  163. setTimeout(() => {
  164. this.start(0);
  165. }, delay);
  166. return;
  167. }
  168. this._started = true;
  169. this._stopped = false;
  170. this._preWarmDone = false;
  171. // Animations
  172. if (this.beginAnimationOnStart && this.animations && this.animations.length > 0 && this._scene) {
  173. this._scene.beginAnimation(this, this.beginAnimationFrom, this.beginAnimationTo, this.beginAnimationLoop);
  174. }
  175. }
  176. /**
  177. * Stops the particle system.
  178. */
  179. public stop(): void {
  180. if (this._stopped) {
  181. return;
  182. }
  183. this._stopped = true;
  184. }
  185. /**
  186. * Remove all active particles
  187. */
  188. public reset(): void {
  189. this._releaseBuffers();
  190. this._releaseVAOs();
  191. this._currentActiveCount = 0;
  192. this._targetIndex = 0;
  193. }
  194. /**
  195. * Returns the string "GPUParticleSystem"
  196. * @returns a string containing the class name
  197. */
  198. public getClassName(): string {
  199. return "GPUParticleSystem";
  200. }
  201. /**
  202. * Gets the custom effect used to render the particles
  203. * @param blendMode Blend mode for which the effect should be retrieved
  204. * @returns The effect
  205. */
  206. public getCustomEffect(blendMode: number = 0): Nullable<Effect> {
  207. return this._customEffect[blendMode] ?? this._customEffect[0];
  208. }
  209. /**
  210. * Sets the custom effect used to render the particles
  211. * @param effect The effect to set
  212. * @param blendMode Blend mode for which the effect should be set
  213. */
  214. public setCustomEffect(effect: Nullable<Effect>, blendMode: number = 0) {
  215. this._customEffect[blendMode] = effect;
  216. }
  217. /** @hidden */
  218. protected _onBeforeDrawParticlesObservable: Nullable<Observable<Nullable<Effect>>> = null;
  219. /**
  220. * Observable that will be called just before the particles are drawn
  221. */
  222. public get onBeforeDrawParticlesObservable(): Observable<Nullable<Effect>> {
  223. if (!this._onBeforeDrawParticlesObservable) {
  224. this._onBeforeDrawParticlesObservable = new Observable<Nullable<Effect>>();
  225. }
  226. return this._onBeforeDrawParticlesObservable;
  227. }
  228. /**
  229. * Gets the name of the particle vertex shader
  230. */
  231. public get vertexShaderName(): string {
  232. return "gpuRenderParticles";
  233. }
  234. private _colorGradientsTexture: RawTexture;
  235. protected _removeGradientAndTexture(gradient: number, gradients: Nullable<IValueGradient[]>, texture: RawTexture): BaseParticleSystem {
  236. super._removeGradientAndTexture(gradient, gradients, texture);
  237. this._releaseBuffers();
  238. return this;
  239. }
  240. /**
  241. * Adds a new color gradient
  242. * @param gradient defines the gradient to use (between 0 and 1)
  243. * @param color1 defines the color to affect to the specified gradient
  244. * @param color2 defines an additional color used to define a range ([color, color2]) with main color to pick the final color from
  245. * @returns the current particle system
  246. */
  247. public addColorGradient(gradient: number, color1: Color4, color2?: Color4): GPUParticleSystem {
  248. if (!this._colorGradients) {
  249. this._colorGradients = [];
  250. }
  251. let colorGradient = new ColorGradient(gradient, color1);
  252. this._colorGradients.push(colorGradient);
  253. this._refreshColorGradient(true);
  254. this._releaseBuffers();
  255. return this;
  256. }
  257. private _refreshColorGradient(reorder = false) {
  258. if (this._colorGradients) {
  259. if (reorder) {
  260. this._colorGradients.sort((a, b) => {
  261. if (a.gradient < b.gradient) {
  262. return -1;
  263. } else if (a.gradient > b.gradient) {
  264. return 1;
  265. }
  266. return 0;
  267. });
  268. }
  269. if (this._colorGradientsTexture) {
  270. this._colorGradientsTexture.dispose();
  271. (<any>this._colorGradientsTexture) = null;
  272. }
  273. }
  274. }
  275. /** Force the system to rebuild all gradients that need to be resync */
  276. public forceRefreshGradients() {
  277. this._refreshColorGradient();
  278. this._refreshFactorGradient(this._sizeGradients, "_sizeGradientsTexture");
  279. this._refreshFactorGradient(this._angularSpeedGradients, "_angularSpeedGradientsTexture");
  280. this._refreshFactorGradient(this._velocityGradients, "_velocityGradientsTexture");
  281. this._refreshFactorGradient(this._limitVelocityGradients, "_limitVelocityGradientsTexture");
  282. this._refreshFactorGradient(this._dragGradients, "_dragGradientsTexture");
  283. this.reset();
  284. }
  285. /**
  286. * Remove a specific color gradient
  287. * @param gradient defines the gradient to remove
  288. * @returns the current particle system
  289. */
  290. public removeColorGradient(gradient: number): GPUParticleSystem {
  291. this._removeGradientAndTexture(gradient, this._colorGradients, this._colorGradientsTexture);
  292. (<any>this._colorGradientsTexture) = null;
  293. return this;
  294. }
  295. private _angularSpeedGradientsTexture: RawTexture;
  296. private _sizeGradientsTexture: RawTexture;
  297. private _velocityGradientsTexture: RawTexture;
  298. private _limitVelocityGradientsTexture: RawTexture;
  299. private _dragGradientsTexture: RawTexture;
  300. private _addFactorGradient(factorGradients: FactorGradient[], gradient: number, factor: number) {
  301. let valueGradient = new FactorGradient(gradient, factor);
  302. factorGradients.push(valueGradient);
  303. this._releaseBuffers();
  304. }
  305. /**
  306. * Adds a new size gradient
  307. * @param gradient defines the gradient to use (between 0 and 1)
  308. * @param factor defines the size factor to affect to the specified gradient
  309. * @returns the current particle system
  310. */
  311. public addSizeGradient(gradient: number, factor: number): GPUParticleSystem {
  312. if (!this._sizeGradients) {
  313. this._sizeGradients = [];
  314. }
  315. this._addFactorGradient(this._sizeGradients, gradient, factor);
  316. this._refreshFactorGradient(this._sizeGradients, "_sizeGradientsTexture", true);
  317. this._releaseBuffers();
  318. return this;
  319. }
  320. /**
  321. * Remove a specific size gradient
  322. * @param gradient defines the gradient to remove
  323. * @returns the current particle system
  324. */
  325. public removeSizeGradient(gradient: number): GPUParticleSystem {
  326. this._removeGradientAndTexture(gradient, this._sizeGradients, this._sizeGradientsTexture);
  327. (<any>this._sizeGradientsTexture) = null;
  328. return this;
  329. }
  330. private _refreshFactorGradient(factorGradients: Nullable<FactorGradient[]>, textureName: string, reorder = false) {
  331. if (!factorGradients) {
  332. return;
  333. }
  334. if (reorder) {
  335. factorGradients.sort((a, b) => {
  336. if (a.gradient < b.gradient) {
  337. return -1;
  338. } else if (a.gradient > b.gradient) {
  339. return 1;
  340. }
  341. return 0;
  342. });
  343. }
  344. let that = this as any;
  345. if (that[textureName]) {
  346. that[textureName].dispose();
  347. that[textureName] = null;
  348. }
  349. }
  350. /**
  351. * Adds a new angular speed gradient
  352. * @param gradient defines the gradient to use (between 0 and 1)
  353. * @param factor defines the angular speed to affect to the specified gradient
  354. * @returns the current particle system
  355. */
  356. public addAngularSpeedGradient(gradient: number, factor: number): GPUParticleSystem {
  357. if (!this._angularSpeedGradients) {
  358. this._angularSpeedGradients = [];
  359. }
  360. this._addFactorGradient(this._angularSpeedGradients, gradient, factor);
  361. this._refreshFactorGradient(this._angularSpeedGradients, "_angularSpeedGradientsTexture", true);
  362. this._releaseBuffers();
  363. return this;
  364. }
  365. /**
  366. * Remove a specific angular speed gradient
  367. * @param gradient defines the gradient to remove
  368. * @returns the current particle system
  369. */
  370. public removeAngularSpeedGradient(gradient: number): GPUParticleSystem {
  371. this._removeGradientAndTexture(gradient, this._angularSpeedGradients, this._angularSpeedGradientsTexture);
  372. (<any>this._angularSpeedGradientsTexture) = null;
  373. return this;
  374. }
  375. /**
  376. * Adds a new velocity gradient
  377. * @param gradient defines the gradient to use (between 0 and 1)
  378. * @param factor defines the velocity to affect to the specified gradient
  379. * @returns the current particle system
  380. */
  381. public addVelocityGradient(gradient: number, factor: number): GPUParticleSystem {
  382. if (!this._velocityGradients) {
  383. this._velocityGradients = [];
  384. }
  385. this._addFactorGradient(this._velocityGradients, gradient, factor);
  386. this._refreshFactorGradient(this._velocityGradients, "_velocityGradientsTexture", true);
  387. this._releaseBuffers();
  388. return this;
  389. }
  390. /**
  391. * Remove a specific velocity gradient
  392. * @param gradient defines the gradient to remove
  393. * @returns the current particle system
  394. */
  395. public removeVelocityGradient(gradient: number): GPUParticleSystem {
  396. this._removeGradientAndTexture(gradient, this._velocityGradients, this._velocityGradientsTexture);
  397. (<any>this._velocityGradientsTexture) = null;
  398. return this;
  399. }
  400. /**
  401. * Adds a new limit velocity gradient
  402. * @param gradient defines the gradient to use (between 0 and 1)
  403. * @param factor defines the limit velocity value to affect to the specified gradient
  404. * @returns the current particle system
  405. */
  406. public addLimitVelocityGradient(gradient: number, factor: number): GPUParticleSystem {
  407. if (!this._limitVelocityGradients) {
  408. this._limitVelocityGradients = [];
  409. }
  410. this._addFactorGradient(this._limitVelocityGradients, gradient, factor);
  411. this._refreshFactorGradient(this._limitVelocityGradients, "_limitVelocityGradientsTexture", true);
  412. this._releaseBuffers();
  413. return this;
  414. }
  415. /**
  416. * Remove a specific limit velocity gradient
  417. * @param gradient defines the gradient to remove
  418. * @returns the current particle system
  419. */
  420. public removeLimitVelocityGradient(gradient: number): GPUParticleSystem {
  421. this._removeGradientAndTexture(gradient, this._limitVelocityGradients, this._limitVelocityGradientsTexture);
  422. (<any>this._limitVelocityGradientsTexture) = null;
  423. return this;
  424. }
  425. /**
  426. * Adds a new drag gradient
  427. * @param gradient defines the gradient to use (between 0 and 1)
  428. * @param factor defines the drag value to affect to the specified gradient
  429. * @returns the current particle system
  430. */
  431. public addDragGradient(gradient: number, factor: number): GPUParticleSystem {
  432. if (!this._dragGradients) {
  433. this._dragGradients = [];
  434. }
  435. this._addFactorGradient(this._dragGradients, gradient, factor);
  436. this._refreshFactorGradient(this._dragGradients, "_dragGradientsTexture", true);
  437. this._releaseBuffers();
  438. return this;
  439. }
  440. /**
  441. * Remove a specific drag gradient
  442. * @param gradient defines the gradient to remove
  443. * @returns the current particle system
  444. */
  445. public removeDragGradient(gradient: number): GPUParticleSystem {
  446. this._removeGradientAndTexture(gradient, this._dragGradients, this._dragGradientsTexture);
  447. (<any>this._dragGradientsTexture) = null;
  448. return this;
  449. }
  450. /**
  451. * Not supported by GPUParticleSystem
  452. * @param gradient defines the gradient to use (between 0 and 1)
  453. * @param factor defines the emit rate value to affect to the specified gradient
  454. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  455. * @returns the current particle system
  456. */
  457. public addEmitRateGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  458. // Do nothing as emit rate is not supported by GPUParticleSystem
  459. return this;
  460. }
  461. /**
  462. * Not supported by GPUParticleSystem
  463. * @param gradient defines the gradient to remove
  464. * @returns the current particle system
  465. */
  466. public removeEmitRateGradient(gradient: number): IParticleSystem {
  467. // Do nothing as emit rate is not supported by GPUParticleSystem
  468. return this;
  469. }
  470. /**
  471. * Not supported by GPUParticleSystem
  472. * @param gradient defines the gradient to use (between 0 and 1)
  473. * @param factor defines the start size value to affect to the specified gradient
  474. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  475. * @returns the current particle system
  476. */
  477. public addStartSizeGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  478. // Do nothing as start size is not supported by GPUParticleSystem
  479. return this;
  480. }
  481. /**
  482. * Not supported by GPUParticleSystem
  483. * @param gradient defines the gradient to remove
  484. * @returns the current particle system
  485. */
  486. public removeStartSizeGradient(gradient: number): IParticleSystem {
  487. // Do nothing as start size is not supported by GPUParticleSystem
  488. return this;
  489. }
  490. /**
  491. * Not supported by GPUParticleSystem
  492. * @param gradient defines the gradient to use (between 0 and 1)
  493. * @param min defines the color remap minimal range
  494. * @param max defines the color remap maximal range
  495. * @returns the current particle system
  496. */
  497. public addColorRemapGradient(gradient: number, min: number, max: number): IParticleSystem {
  498. // Do nothing as start size is not supported by GPUParticleSystem
  499. return this;
  500. }
  501. /**
  502. * Not supported by GPUParticleSystem
  503. * @param gradient defines the gradient to remove
  504. * @returns the current particle system
  505. */
  506. public removeColorRemapGradient(): IParticleSystem {
  507. // Do nothing as start size is not supported by GPUParticleSystem
  508. return this;
  509. }
  510. /**
  511. * Not supported by GPUParticleSystem
  512. * @param gradient defines the gradient to use (between 0 and 1)
  513. * @param min defines the alpha remap minimal range
  514. * @param max defines the alpha remap maximal range
  515. * @returns the current particle system
  516. */
  517. public addAlphaRemapGradient(gradient: number, min: number, max: number): IParticleSystem {
  518. // Do nothing as start size is not supported by GPUParticleSystem
  519. return this;
  520. }
  521. /**
  522. * Not supported by GPUParticleSystem
  523. * @param gradient defines the gradient to remove
  524. * @returns the current particle system
  525. */
  526. public removeAlphaRemapGradient(): IParticleSystem {
  527. // Do nothing as start size is not supported by GPUParticleSystem
  528. return this;
  529. }
  530. /**
  531. * Not supported by GPUParticleSystem
  532. * @param gradient defines the gradient to use (between 0 and 1)
  533. * @param color defines the color to affect to the specified gradient
  534. * @returns the current particle system
  535. */
  536. public addRampGradient(gradient: number, color: Color3): IParticleSystem {
  537. //Not supported by GPUParticleSystem
  538. return this;
  539. }
  540. /**
  541. * Not supported by GPUParticleSystem
  542. * @param gradient defines the gradient to remove
  543. * @returns the current particle system
  544. */
  545. public removeRampGradient(): IParticleSystem {
  546. //Not supported by GPUParticleSystem
  547. return this;
  548. }
  549. /**
  550. * Not supported by GPUParticleSystem
  551. * @returns the list of ramp gradients
  552. */
  553. public getRampGradients(): Nullable<Array<Color3Gradient>> {
  554. return null;
  555. }
  556. /**
  557. * Not supported by GPUParticleSystem
  558. * Gets or sets a boolean indicating that ramp gradients must be used
  559. * @see https://doc.babylonjs.com/babylon101/particles#ramp-gradients
  560. */
  561. public get useRampGradients(): boolean {
  562. //Not supported by GPUParticleSystem
  563. return false;
  564. }
  565. public set useRampGradients(value: boolean) {
  566. //Not supported by GPUParticleSystem
  567. }
  568. /**
  569. * Not supported by GPUParticleSystem
  570. * @param gradient defines the gradient to use (between 0 and 1)
  571. * @param factor defines the life time factor to affect to the specified gradient
  572. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  573. * @returns the current particle system
  574. */
  575. public addLifeTimeGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  576. //Not supported by GPUParticleSystem
  577. return this;
  578. }
  579. /**
  580. * Not supported by GPUParticleSystem
  581. * @param gradient defines the gradient to remove
  582. * @returns the current particle system
  583. */
  584. public removeLifeTimeGradient(gradient: number): IParticleSystem {
  585. //Not supported by GPUParticleSystem
  586. return this;
  587. }
  588. /**
  589. * Instantiates a GPU particle system.
  590. * 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.
  591. * @param name The name of the particle system
  592. * @param options The options used to create the system
  593. * @param sceneOrEngine The scene the particle system belongs to or the engine to use if no scene
  594. * @param isAnimationSheetEnabled Must be true if using a spritesheet to animate the particles texture
  595. * @param customEffect a custom effect used to change the way particles are rendered by default
  596. */
  597. constructor(name: string, options: Partial<{
  598. capacity: number,
  599. randomTextureSize: number
  600. }>, sceneOrEngine: Scene | ThinEngine, isAnimationSheetEnabled: boolean = false, customEffect: Nullable<Effect> = null) {
  601. super(name);
  602. if (!sceneOrEngine || sceneOrEngine.getClassName() === "Scene") {
  603. this._scene = (sceneOrEngine as Scene) || EngineStore.LastCreatedScene;
  604. this._engine = this._scene.getEngine();
  605. this.uniqueId = this._scene.getUniqueId();
  606. this._scene.particleSystems.push(this);
  607. } else {
  608. this._engine = (sceneOrEngine as ThinEngine);
  609. this.defaultProjectionMatrix = Matrix.PerspectiveFovLH(0.8, 1, 0.1, 100);
  610. }
  611. this._customEffect = { 0: customEffect };
  612. // Setup the default processing configuration to the scene.
  613. this._attachImageProcessingConfiguration(null);
  614. if (!options.randomTextureSize) {
  615. delete options.randomTextureSize;
  616. }
  617. let fullOptions = {
  618. capacity: 50000,
  619. randomTextureSize: this._engine.getCaps().maxTextureSize,
  620. ...options
  621. };
  622. var optionsAsNumber = <number>options;
  623. if (isFinite(optionsAsNumber)) {
  624. fullOptions.capacity = optionsAsNumber;
  625. }
  626. this._capacity = fullOptions.capacity;
  627. this._activeCount = fullOptions.capacity;
  628. this._currentActiveCount = 0;
  629. this._isAnimationSheetEnabled = isAnimationSheetEnabled;
  630. this._updateEffectOptions = {
  631. attributes: ["position", "initialPosition", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle", "cellIndex", "cellStartOffset", "noiseCoordinates1", "noiseCoordinates2"],
  632. uniformsNames: ["currentCount", "timeDelta", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "scaleRange", "gravity", "emitPower",
  633. "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "coneAngle", "stopFactor",
  634. "angleRange", "radiusRange", "cellInfos", "noiseStrength", "limitVelocityDamping"],
  635. uniformBuffersNames: [],
  636. samplers: ["randomSampler", "randomSampler2", "sizeGradientSampler", "angularSpeedGradientSampler", "velocityGradientSampler", "limitVelocityGradientSampler", "noiseSampler", "dragGradientSampler"],
  637. defines: "",
  638. fallbacks: null,
  639. onCompiled: null,
  640. onError: null,
  641. indexParameters: null,
  642. maxSimultaneousLights: 0,
  643. transformFeedbackVaryings: []
  644. };
  645. this.particleEmitterType = new BoxParticleEmitter();
  646. // Random data
  647. var maxTextureSize = Math.min(this._engine.getCaps().maxTextureSize, fullOptions.randomTextureSize);
  648. var d = [];
  649. for (var i = 0; i < maxTextureSize; ++i) {
  650. d.push(Math.random());
  651. d.push(Math.random());
  652. d.push(Math.random());
  653. d.push(Math.random());
  654. }
  655. this._randomTexture = new RawTexture(new Float32Array(d), maxTextureSize, 1, Constants.TEXTUREFORMAT_RGBA, sceneOrEngine, false, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE, Constants.TEXTURETYPE_FLOAT);
  656. this._randomTexture.wrapU = Constants.TEXTURE_WRAP_ADDRESSMODE;
  657. this._randomTexture.wrapV = Constants.TEXTURE_WRAP_ADDRESSMODE;
  658. d = [];
  659. for (var i = 0; i < maxTextureSize; ++i) {
  660. d.push(Math.random());
  661. d.push(Math.random());
  662. d.push(Math.random());
  663. d.push(Math.random());
  664. }
  665. this._randomTexture2 = new RawTexture(new Float32Array(d), maxTextureSize, 1, Constants.TEXTUREFORMAT_RGBA, sceneOrEngine, false, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE, Constants.TEXTURETYPE_FLOAT);
  666. this._randomTexture2.wrapU = Constants.TEXTURE_WRAP_ADDRESSMODE;
  667. this._randomTexture2.wrapV = Constants.TEXTURE_WRAP_ADDRESSMODE;
  668. this._randomTextureSize = maxTextureSize;
  669. }
  670. protected _reset() {
  671. this._releaseBuffers();
  672. }
  673. private _createUpdateVAO(source: Buffer): WebGLVertexArrayObject {
  674. let updateVertexBuffers: { [key: string]: VertexBuffer } = {};
  675. updateVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3);
  676. let offset = 3;
  677. if (this.particleEmitterType instanceof CustomParticleEmitter) {
  678. updateVertexBuffers["initialPosition"] = source.createVertexBuffer("initialPosition", offset, 3);
  679. offset += 3;
  680. }
  681. updateVertexBuffers["age"] = source.createVertexBuffer("age", offset, 1);
  682. offset += 1;
  683. updateVertexBuffers["life"] = source.createVertexBuffer("life", offset, 1);
  684. offset += 1;
  685. updateVertexBuffers["seed"] = source.createVertexBuffer("seed", offset, 4);
  686. offset += 4;
  687. updateVertexBuffers["size"] = source.createVertexBuffer("size", offset, 3);
  688. offset += 3;
  689. if (!this._colorGradientsTexture) {
  690. updateVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4);
  691. offset += 4;
  692. }
  693. updateVertexBuffers["direction"] = source.createVertexBuffer("direction", offset, 3);
  694. offset += 3;
  695. if (!this._isBillboardBased) {
  696. updateVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3);
  697. offset += 3;
  698. }
  699. if (this._angularSpeedGradientsTexture) {
  700. updateVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 1);
  701. offset += 1;
  702. } else {
  703. updateVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 2);
  704. offset += 2;
  705. }
  706. if (this._isAnimationSheetEnabled) {
  707. updateVertexBuffers["cellIndex"] = source.createVertexBuffer("cellIndex", offset, 1);
  708. offset += 1;
  709. if (this.spriteRandomStartCell) {
  710. updateVertexBuffers["cellStartOffset"] = source.createVertexBuffer("cellStartOffset", offset, 1);
  711. offset += 1;
  712. }
  713. }
  714. if (this.noiseTexture) {
  715. updateVertexBuffers["noiseCoordinates1"] = source.createVertexBuffer("noiseCoordinates1", offset, 3);
  716. offset += 3;
  717. updateVertexBuffers["noiseCoordinates2"] = source.createVertexBuffer("noiseCoordinates2", offset, 3);
  718. offset += 3;
  719. }
  720. let vao = this._engine.recordVertexArrayObject(updateVertexBuffers, null, this._updateEffect);
  721. this._engine.bindArrayBuffer(null);
  722. return vao;
  723. }
  724. private _createRenderVAO(source: Buffer, spriteSource: Buffer): WebGLVertexArrayObject {
  725. let renderVertexBuffers: { [key: string]: VertexBuffer } = {};
  726. renderVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3, this._attributesStrideSize, true);
  727. let offset = 3;
  728. if (this.particleEmitterType instanceof CustomParticleEmitter) {
  729. offset += 3;
  730. }
  731. renderVertexBuffers["age"] = source.createVertexBuffer("age", offset, 1, this._attributesStrideSize, true);
  732. offset += 1;
  733. renderVertexBuffers["life"] = source.createVertexBuffer("life", offset, 1, this._attributesStrideSize, true);
  734. offset += 5;
  735. renderVertexBuffers["size"] = source.createVertexBuffer("size", offset, 3, this._attributesStrideSize, true);
  736. offset += 3;
  737. if (!this._colorGradientsTexture) {
  738. renderVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4, this._attributesStrideSize, true);
  739. offset += 4;
  740. }
  741. if (this.billboardMode === ParticleSystem.BILLBOARDMODE_STRETCHED) {
  742. renderVertexBuffers["direction"] = source.createVertexBuffer("direction", offset, 3, this._attributesStrideSize, true);
  743. }
  744. offset += 3; // Direction
  745. if (!this._isBillboardBased) {
  746. renderVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3, this._attributesStrideSize, true);
  747. offset += 3;
  748. }
  749. renderVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 1, this._attributesStrideSize, true);
  750. if (this._angularSpeedGradientsTexture) {
  751. offset++;
  752. } else {
  753. offset += 2;
  754. }
  755. if (this._isAnimationSheetEnabled) {
  756. renderVertexBuffers["cellIndex"] = source.createVertexBuffer("cellIndex", offset, 1, this._attributesStrideSize, true);
  757. offset += 1;
  758. if (this.spriteRandomStartCell) {
  759. renderVertexBuffers["cellStartOffset"] = source.createVertexBuffer("cellStartOffset", offset, 1, this._attributesStrideSize, true);
  760. offset += 1;
  761. }
  762. }
  763. if (this.noiseTexture) {
  764. renderVertexBuffers["noiseCoordinates1"] = source.createVertexBuffer("noiseCoordinates1", offset, 3, this._attributesStrideSize, true);
  765. offset += 3;
  766. renderVertexBuffers["noiseCoordinates2"] = source.createVertexBuffer("noiseCoordinates2", offset, 3, this._attributesStrideSize, true);
  767. offset += 3;
  768. }
  769. renderVertexBuffers["offset"] = spriteSource.createVertexBuffer("offset", 0, 2);
  770. renderVertexBuffers["uv"] = spriteSource.createVertexBuffer("uv", 2, 2);
  771. let vao = this._engine.recordVertexArrayObject(renderVertexBuffers, null, this._getEffect());
  772. this._engine.bindArrayBuffer(null);
  773. return vao;
  774. }
  775. private _initialize(force = false): void {
  776. if (this._buffer0 && !force) {
  777. return;
  778. }
  779. let engine = this._engine;
  780. var data = new Array<float>();
  781. this._attributesStrideSize = 21;
  782. this._targetIndex = 0;
  783. if (this.particleEmitterType instanceof CustomParticleEmitter) {
  784. this._attributesStrideSize += 3;
  785. }
  786. if (!this.isBillboardBased) {
  787. this._attributesStrideSize += 3;
  788. }
  789. if (this._colorGradientsTexture) {
  790. this._attributesStrideSize -= 4;
  791. }
  792. if (this._angularSpeedGradientsTexture) {
  793. this._attributesStrideSize -= 1;
  794. }
  795. if (this._isAnimationSheetEnabled) {
  796. this._attributesStrideSize += 1;
  797. if (this.spriteRandomStartCell) {
  798. this._attributesStrideSize += 1;
  799. }
  800. }
  801. if (this.noiseTexture) {
  802. this._attributesStrideSize += 6;
  803. }
  804. const usingCustomEmitter = this.particleEmitterType instanceof CustomParticleEmitter;
  805. const tmpVector = TmpVectors.Vector3[0];
  806. for (var particleIndex = 0; particleIndex < this._capacity; particleIndex++) {
  807. // position
  808. data.push(0.0);
  809. data.push(0.0);
  810. data.push(0.0);
  811. if (usingCustomEmitter) {
  812. (this.particleEmitterType as CustomParticleEmitter).particlePositionGenerator(particleIndex, null, tmpVector);
  813. data.push(tmpVector.x);
  814. data.push(tmpVector.y);
  815. data.push(tmpVector.z);
  816. }
  817. // Age and life
  818. data.push(0.0); // create the particle as a dead one to create a new one at start
  819. data.push(0.0);
  820. // Seed
  821. data.push(Math.random());
  822. data.push(Math.random());
  823. data.push(Math.random());
  824. data.push(Math.random());
  825. // Size
  826. data.push(0.0);
  827. data.push(0.0);
  828. data.push(0.0);
  829. if (!this._colorGradientsTexture) {
  830. // color
  831. data.push(0.0);
  832. data.push(0.0);
  833. data.push(0.0);
  834. data.push(0.0);
  835. }
  836. // direction
  837. if (usingCustomEmitter) {
  838. (this.particleEmitterType as CustomParticleEmitter).particleDestinationGenerator(particleIndex, null, tmpVector);
  839. data.push(tmpVector.x);
  840. data.push(tmpVector.y);
  841. data.push(tmpVector.z);
  842. } else {
  843. data.push(0.0);
  844. data.push(0.0);
  845. data.push(0.0);
  846. }
  847. if (!this.isBillboardBased) {
  848. // initialDirection
  849. data.push(0.0);
  850. data.push(0.0);
  851. data.push(0.0);
  852. }
  853. // angle
  854. data.push(0.0);
  855. if (!this._angularSpeedGradientsTexture) {
  856. data.push(0.0);
  857. }
  858. if (this._isAnimationSheetEnabled) {
  859. data.push(0.0);
  860. if (this.spriteRandomStartCell) {
  861. data.push(0.0);
  862. }
  863. }
  864. if (this.noiseTexture) { // Random coordinates for reading into noise texture
  865. data.push(Math.random());
  866. data.push(Math.random());
  867. data.push(Math.random());
  868. data.push(Math.random());
  869. data.push(Math.random());
  870. data.push(Math.random());
  871. }
  872. }
  873. // Sprite data
  874. var spriteData = new Float32Array([0.5, 0.5, 1, 1,
  875. -0.5, 0.5, 0, 1,
  876. -0.5, -0.5, 0, 0,
  877. 0.5, -0.5, 1, 0]);
  878. // Buffers
  879. this._buffer0 = new Buffer(engine, data, false, this._attributesStrideSize);
  880. this._buffer1 = new Buffer(engine, data, false, this._attributesStrideSize);
  881. this._spriteBuffer = new Buffer(engine, spriteData, false, 4);
  882. // Update VAO
  883. this._updateVAO = [];
  884. this._updateVAO.push(this._createUpdateVAO(this._buffer0));
  885. this._updateVAO.push(this._createUpdateVAO(this._buffer1));
  886. // Render VAO
  887. this._renderVAO = [];
  888. this._renderVAO.push(this._createRenderVAO(this._buffer1, this._spriteBuffer));
  889. this._renderVAO.push(this._createRenderVAO(this._buffer0, this._spriteBuffer));
  890. // Links
  891. this._sourceBuffer = this._buffer0;
  892. this._targetBuffer = this._buffer1;
  893. }
  894. /** @hidden */
  895. public _recreateUpdateEffect() {
  896. let defines = this.particleEmitterType ? this.particleEmitterType.getEffectDefines() : "";
  897. if (this._isBillboardBased) {
  898. defines += "\n#define BILLBOARD";
  899. }
  900. if (this._colorGradientsTexture) {
  901. defines += "\n#define COLORGRADIENTS";
  902. }
  903. if (this._sizeGradientsTexture) {
  904. defines += "\n#define SIZEGRADIENTS";
  905. }
  906. if (this._angularSpeedGradientsTexture) {
  907. defines += "\n#define ANGULARSPEEDGRADIENTS";
  908. }
  909. if (this._velocityGradientsTexture) {
  910. defines += "\n#define VELOCITYGRADIENTS";
  911. }
  912. if (this._limitVelocityGradientsTexture) {
  913. defines += "\n#define LIMITVELOCITYGRADIENTS";
  914. }
  915. if (this._dragGradientsTexture) {
  916. defines += "\n#define DRAGGRADIENTS";
  917. }
  918. if (this.isAnimationSheetEnabled) {
  919. defines += "\n#define ANIMATESHEET";
  920. if (this.spriteRandomStartCell) {
  921. defines += "\n#define ANIMATESHEETRANDOMSTART";
  922. }
  923. }
  924. if (this.noiseTexture) {
  925. defines += "\n#define NOISE";
  926. }
  927. if (this.isLocal) {
  928. defines += "\n#define LOCAL";
  929. }
  930. if (this._updateEffect && this._updateEffectOptions.defines === defines) {
  931. return;
  932. }
  933. this._updateEffectOptions.transformFeedbackVaryings = ["outPosition"];
  934. if (this.particleEmitterType instanceof CustomParticleEmitter) {
  935. this._updateEffectOptions.transformFeedbackVaryings.push("outInitialPosition");
  936. }
  937. this._updateEffectOptions.transformFeedbackVaryings.push("outAge");
  938. this._updateEffectOptions.transformFeedbackVaryings.push("outLife");
  939. this._updateEffectOptions.transformFeedbackVaryings.push("outSeed");
  940. this._updateEffectOptions.transformFeedbackVaryings.push("outSize");
  941. if (!this._colorGradientsTexture) {
  942. this._updateEffectOptions.transformFeedbackVaryings.push("outColor");
  943. }
  944. this._updateEffectOptions.transformFeedbackVaryings.push("outDirection");
  945. if (!this._isBillboardBased) {
  946. this._updateEffectOptions.transformFeedbackVaryings.push("outInitialDirection");
  947. }
  948. this._updateEffectOptions.transformFeedbackVaryings.push("outAngle");
  949. if (this.isAnimationSheetEnabled) {
  950. this._updateEffectOptions.transformFeedbackVaryings.push("outCellIndex");
  951. if (this.spriteRandomStartCell) {
  952. this._updateEffectOptions.transformFeedbackVaryings.push("outCellStartOffset");
  953. }
  954. }
  955. if (this.noiseTexture) {
  956. this._updateEffectOptions.transformFeedbackVaryings.push("outNoiseCoordinates1");
  957. this._updateEffectOptions.transformFeedbackVaryings.push("outNoiseCoordinates2");
  958. }
  959. this._updateEffectOptions.defines = defines;
  960. this._updateEffect = new Effect("gpuUpdateParticles", this._updateEffectOptions, this._engine);
  961. }
  962. private _getEffect(): Effect {
  963. return this.getCustomEffect() ?? this._renderEffect;
  964. }
  965. /**
  966. * Fill the defines array according to the current settings of the particle system
  967. * @param defines Array to be updated
  968. * @param blendMode blend mode to take into account when updating the array
  969. */
  970. public fillDefines(defines: Array<string>, blendMode: number = 0) {
  971. if (this._scene) {
  972. if (this._scene.clipPlane) {
  973. defines.push("#define CLIPPLANE");
  974. }
  975. if (this._scene.clipPlane2) {
  976. defines.push("#define CLIPPLANE2");
  977. }
  978. if (this._scene.clipPlane3) {
  979. defines.push("#define CLIPPLANE3");
  980. }
  981. if (this._scene.clipPlane4) {
  982. defines.push("#define CLIPPLANE4");
  983. }
  984. if (this._scene.clipPlane5) {
  985. defines.push("#define CLIPPLANE5");
  986. }
  987. if (this._scene.clipPlane6) {
  988. defines.push("#define CLIPPLANE6");
  989. }
  990. }
  991. if (this.blendMode === ParticleSystem.BLENDMODE_MULTIPLY) {
  992. defines.push("#define BLENDMULTIPLYMODE");
  993. }
  994. if (this.isLocal) {
  995. defines.push("#define LOCAL");
  996. }
  997. if (this._isBillboardBased) {
  998. defines.push("#define BILLBOARD");
  999. switch (this.billboardMode) {
  1000. case ParticleSystem.BILLBOARDMODE_Y:
  1001. defines.push("#define BILLBOARDY");
  1002. break;
  1003. case ParticleSystem.BILLBOARDMODE_STRETCHED:
  1004. defines.push("#define BILLBOARDSTRETCHED");
  1005. break;
  1006. case ParticleSystem.BILLBOARDMODE_ALL:
  1007. defines.push("#define BILLBOARDMODE_ALL");
  1008. break;
  1009. default:
  1010. break;
  1011. }
  1012. }
  1013. if (this._colorGradientsTexture) {
  1014. defines.push("#define COLORGRADIENTS");
  1015. }
  1016. if (this.isAnimationSheetEnabled) {
  1017. defines.push("#define ANIMATESHEET");
  1018. }
  1019. if (this._imageProcessingConfiguration) {
  1020. this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
  1021. defines.push("" + this._imageProcessingConfigurationDefines.toString());
  1022. }
  1023. }
  1024. /**
  1025. * Fill the uniforms, attributes and samplers arrays according to the current settings of the particle system
  1026. * @param uniforms Uniforms array to fill
  1027. * @param attributes Attributes array to fill
  1028. * @param samplers Samplers array to fill
  1029. */
  1030. public fillUniformsAttributesAndSamplerNames(uniforms: Array<string>, attributes: Array<string>, samplers: Array<string>) {
  1031. attributes.push("position", "age", "life", "size", "color", "offset", "uv", "direction", "initialDirection", "angle", "cellIndex");
  1032. uniforms.push("emitterWM", "worldOffset", "view", "projection", "colorDead", "invView", "vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "vClipPlane5", "vClipPlane6", "sheetInfos", "translationPivot", "eyePosition");
  1033. samplers.push("diffuseSampler", "colorGradientSampler");
  1034. if (this._imageProcessingConfiguration) {
  1035. ImageProcessingConfiguration.PrepareUniforms(uniforms, this._imageProcessingConfigurationDefines);
  1036. ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
  1037. }
  1038. }
  1039. /** @hidden */
  1040. public _recreateRenderEffect(): Effect {
  1041. const customEffect = this.getCustomEffect();
  1042. if (customEffect) {
  1043. return customEffect;
  1044. }
  1045. let defines: Array<string> = [];
  1046. this.fillDefines(defines);
  1047. var join = defines.join("\n");
  1048. if (this._renderEffect && this._renderEffect.defines === join) {
  1049. return this._renderEffect;
  1050. }
  1051. var attributes: Array<string> = [];
  1052. var uniforms: Array<string> = [];
  1053. var samplers: Array<string> = [];
  1054. this.fillUniformsAttributesAndSamplerNames(uniforms, attributes, samplers);
  1055. this._renderEffect = new Effect("gpuRenderParticles",
  1056. attributes,
  1057. uniforms,
  1058. samplers, this._engine, join);
  1059. return this._renderEffect;
  1060. }
  1061. /**
  1062. * Animates the particle system for the current frame by emitting new particles and or animating the living ones.
  1063. * @param preWarm defines if we are in the pre-warmimg phase
  1064. */
  1065. public animate(preWarm = false): void {
  1066. this._timeDelta = this.updateSpeed * (preWarm ? this.preWarmStepOffset : this._scene?.getAnimationRatio() || 1);
  1067. this._actualFrame += this._timeDelta;
  1068. if (!this._stopped) {
  1069. if (this.targetStopDuration && this._actualFrame >= this.targetStopDuration) {
  1070. this.stop();
  1071. }
  1072. }
  1073. }
  1074. private _createFactorGradientTexture(factorGradients: Nullable<IValueGradient[]>, textureName: string) {
  1075. let texture: RawTexture = (<any>this)[textureName];
  1076. if (!factorGradients || !factorGradients.length || texture) {
  1077. return;
  1078. }
  1079. let data = new Float32Array(this._rawTextureWidth);
  1080. for (var x = 0; x < this._rawTextureWidth; x++) {
  1081. var ratio = x / this._rawTextureWidth;
  1082. GradientHelper.GetCurrentGradient(ratio, factorGradients, (currentGradient, nextGradient, scale) => {
  1083. data[x] = Scalar.Lerp((<FactorGradient>currentGradient).factor1, (<FactorGradient>nextGradient).factor1, scale);
  1084. });
  1085. }
  1086. (<any>this)[textureName] = RawTexture.CreateRTexture(data, this._rawTextureWidth, 1, this._scene || this._engine, false, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE);
  1087. }
  1088. private _createSizeGradientTexture() {
  1089. this._createFactorGradientTexture(this._sizeGradients, "_sizeGradientsTexture");
  1090. }
  1091. private _createAngularSpeedGradientTexture() {
  1092. this._createFactorGradientTexture(this._angularSpeedGradients, "_angularSpeedGradientsTexture");
  1093. }
  1094. private _createVelocityGradientTexture() {
  1095. this._createFactorGradientTexture(this._velocityGradients, "_velocityGradientsTexture");
  1096. }
  1097. private _createLimitVelocityGradientTexture() {
  1098. this._createFactorGradientTexture(this._limitVelocityGradients, "_limitVelocityGradientsTexture");
  1099. }
  1100. private _createDragGradientTexture() {
  1101. this._createFactorGradientTexture(this._dragGradients, "_dragGradientsTexture");
  1102. }
  1103. private _createColorGradientTexture() {
  1104. if (!this._colorGradients || !this._colorGradients.length || this._colorGradientsTexture) {
  1105. return;
  1106. }
  1107. let data = new Uint8Array(this._rawTextureWidth * 4);
  1108. let tmpColor = TmpColors.Color4[0];
  1109. for (var x = 0; x < this._rawTextureWidth; x++) {
  1110. var ratio = x / this._rawTextureWidth;
  1111. GradientHelper.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
  1112. Color4.LerpToRef((<ColorGradient>currentGradient).color1, (<ColorGradient>nextGradient).color1, scale, tmpColor);
  1113. data[x * 4] = tmpColor.r * 255;
  1114. data[x * 4 + 1] = tmpColor.g * 255;
  1115. data[x * 4 + 2] = tmpColor.b * 255;
  1116. data[x * 4 + 3] = tmpColor.a * 255;
  1117. });
  1118. }
  1119. this._colorGradientsTexture = RawTexture.CreateRGBATexture(data, this._rawTextureWidth, 1, this._scene, false, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE);
  1120. }
  1121. /**
  1122. * Renders the particle system in its current state
  1123. * @param preWarm defines if the system should only update the particles but not render them
  1124. * @returns the current number of particles
  1125. */
  1126. public render(preWarm = false): number {
  1127. if (!this._started) {
  1128. return 0;
  1129. }
  1130. this._createColorGradientTexture();
  1131. this._createSizeGradientTexture();
  1132. this._createAngularSpeedGradientTexture();
  1133. this._createVelocityGradientTexture();
  1134. this._createLimitVelocityGradientTexture();
  1135. this._createDragGradientTexture();
  1136. this._recreateUpdateEffect();
  1137. this._recreateRenderEffect();
  1138. if (!this.isReady()) {
  1139. return 0;
  1140. }
  1141. if (!preWarm && this._scene) {
  1142. if (!this._preWarmDone && this.preWarmCycles) {
  1143. for (var index = 0; index < this.preWarmCycles; index++) {
  1144. this.animate(true);
  1145. this.render(true);
  1146. }
  1147. this._preWarmDone = true;
  1148. }
  1149. if (this._currentRenderId === this._scene.getFrameId()) {
  1150. return 0;
  1151. }
  1152. this._currentRenderId = this._scene.getFrameId();
  1153. }
  1154. // Get everything ready to render
  1155. this._initialize();
  1156. this._accumulatedCount += this.emitRate * this._timeDelta;
  1157. if (this._accumulatedCount > 1) {
  1158. var intPart = this._accumulatedCount | 0;
  1159. this._accumulatedCount -= intPart;
  1160. this._currentActiveCount = Math.min(this._activeCount, this._currentActiveCount + intPart);
  1161. }
  1162. if (!this._currentActiveCount) {
  1163. return 0;
  1164. }
  1165. // Enable update effect
  1166. this._engine.enableEffect(this._updateEffect);
  1167. var engine = this._engine as Engine;
  1168. if (!engine.setState) {
  1169. throw new Error("GPU particles cannot work with a full Engine. ThinEngine is not supported");
  1170. }
  1171. this._updateEffect.setFloat("currentCount", this._currentActiveCount);
  1172. this._updateEffect.setFloat("timeDelta", this._timeDelta);
  1173. this._updateEffect.setFloat("stopFactor", this._stopped ? 0 : 1);
  1174. this._updateEffect.setTexture("randomSampler", this._randomTexture);
  1175. this._updateEffect.setTexture("randomSampler2", this._randomTexture2);
  1176. this._updateEffect.setFloat2("lifeTime", this.minLifeTime, this.maxLifeTime);
  1177. this._updateEffect.setFloat2("emitPower", this.minEmitPower, this.maxEmitPower);
  1178. if (!this._colorGradientsTexture) {
  1179. this._updateEffect.setDirectColor4("color1", this.color1);
  1180. this._updateEffect.setDirectColor4("color2", this.color2);
  1181. }
  1182. this._updateEffect.setFloat2("sizeRange", this.minSize, this.maxSize);
  1183. this._updateEffect.setFloat4("scaleRange", this.minScaleX, this.maxScaleX, this.minScaleY, this.maxScaleY);
  1184. this._updateEffect.setFloat4("angleRange", this.minAngularSpeed, this.maxAngularSpeed, this.minInitialRotation, this.maxInitialRotation);
  1185. this._updateEffect.setVector3("gravity", this.gravity);
  1186. if (this._sizeGradientsTexture) {
  1187. this._updateEffect.setTexture("sizeGradientSampler", this._sizeGradientsTexture);
  1188. }
  1189. if (this._angularSpeedGradientsTexture) {
  1190. this._updateEffect.setTexture("angularSpeedGradientSampler", this._angularSpeedGradientsTexture);
  1191. }
  1192. if (this._velocityGradientsTexture) {
  1193. this._updateEffect.setTexture("velocityGradientSampler", this._velocityGradientsTexture);
  1194. }
  1195. if (this._limitVelocityGradientsTexture) {
  1196. this._updateEffect.setTexture("limitVelocityGradientSampler", this._limitVelocityGradientsTexture);
  1197. this._updateEffect.setFloat("limitVelocityDamping", this.limitVelocityDamping);
  1198. }
  1199. if (this._dragGradientsTexture) {
  1200. this._updateEffect.setTexture("dragGradientSampler", this._dragGradientsTexture);
  1201. }
  1202. if (this.particleEmitterType) {
  1203. this.particleEmitterType.applyToShader(this._updateEffect);
  1204. }
  1205. if (this._isAnimationSheetEnabled) {
  1206. this._updateEffect.setFloat3("cellInfos", this.startSpriteCellID, this.endSpriteCellID, this.spriteCellChangeSpeed);
  1207. }
  1208. if (this.noiseTexture) {
  1209. this._updateEffect.setTexture("noiseSampler", this.noiseTexture);
  1210. this._updateEffect.setVector3("noiseStrength", this.noiseStrength);
  1211. }
  1212. let emitterWM: Matrix;
  1213. if ((<AbstractMesh>this.emitter).position) {
  1214. var emitterMesh = (<AbstractMesh>this.emitter);
  1215. emitterWM = emitterMesh.getWorldMatrix();
  1216. } else {
  1217. var emitterPosition = (<Vector3>this.emitter);
  1218. emitterWM = Matrix.Translation(emitterPosition.x, emitterPosition.y, emitterPosition.z);
  1219. }
  1220. if (!this.isLocal) {
  1221. this._updateEffect.setMatrix("emitterWM", emitterWM);
  1222. }
  1223. // Bind source VAO
  1224. this._engine.bindVertexArrayObject(this._updateVAO[this._targetIndex], null);
  1225. // Update
  1226. engine.bindTransformFeedbackBuffer(this._targetBuffer.getBuffer());
  1227. engine.setRasterizerState(false);
  1228. engine.beginTransformFeedback(true);
  1229. engine.drawArraysType(Constants.MATERIAL_PointListDrawMode, 0, this._currentActiveCount);
  1230. engine.endTransformFeedback();
  1231. engine.setRasterizerState(true);
  1232. engine.bindTransformFeedbackBuffer(null);
  1233. if (!preWarm) {
  1234. // Enable render effect
  1235. const effect = this._getEffect();
  1236. this._engine.enableEffect(effect);
  1237. let viewMatrix = this._scene?.getViewMatrix() || Matrix.IdentityReadOnly;
  1238. effect.setMatrix("view", viewMatrix);
  1239. effect.setMatrix("projection", this.defaultProjectionMatrix ?? this._scene!.getProjectionMatrix());
  1240. effect.setTexture("diffuseSampler", this.particleTexture);
  1241. effect.setVector2("translationPivot", this.translationPivot);
  1242. effect.setVector3("worldOffset", this.worldOffset);
  1243. if (this.isLocal) {
  1244. effect.setMatrix("emitterWM", emitterWM);
  1245. }
  1246. if (this._colorGradientsTexture) {
  1247. effect.setTexture("colorGradientSampler", this._colorGradientsTexture);
  1248. } else {
  1249. effect.setDirectColor4("colorDead", this.colorDead);
  1250. }
  1251. if (this._isAnimationSheetEnabled && this.particleTexture) {
  1252. let baseSize = this.particleTexture.getBaseSize();
  1253. effect.setFloat3("sheetInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
  1254. }
  1255. if (this._isBillboardBased && this._scene) {
  1256. var camera = this._scene.activeCamera!;
  1257. effect.setVector3("eyePosition", camera.globalPosition);
  1258. }
  1259. const defines = effect.defines;
  1260. if (this._scene) {
  1261. if (this._scene.clipPlane || this._scene.clipPlane2 || this._scene.clipPlane3 || this._scene.clipPlane4 || this._scene.clipPlane5 || this._scene.clipPlane6) {
  1262. MaterialHelper.BindClipPlane(effect, this._scene);
  1263. }
  1264. }
  1265. if (defines.indexOf("#define BILLBOARDMODE_ALL") >= 0) {
  1266. var invView = viewMatrix.clone();
  1267. invView.invert();
  1268. effect.setMatrix("invView", invView);
  1269. }
  1270. // image processing
  1271. if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {
  1272. this._imageProcessingConfiguration.bind(effect);
  1273. }
  1274. // Draw order
  1275. switch (this.blendMode) {
  1276. case ParticleSystem.BLENDMODE_ADD:
  1277. this._engine.setAlphaMode(Constants.ALPHA_ADD);
  1278. break;
  1279. case ParticleSystem.BLENDMODE_ONEONE:
  1280. this._engine.setAlphaMode(Constants.ALPHA_ONEONE);
  1281. break;
  1282. case ParticleSystem.BLENDMODE_STANDARD:
  1283. this._engine.setAlphaMode(Constants.ALPHA_COMBINE);
  1284. break;
  1285. case ParticleSystem.BLENDMODE_MULTIPLY:
  1286. this._engine.setAlphaMode(Constants.ALPHA_MULTIPLY);
  1287. break;
  1288. }
  1289. if (this.forceDepthWrite) {
  1290. engine.setDepthWrite(true);
  1291. }
  1292. // Bind source VAO
  1293. this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
  1294. if (this._onBeforeDrawParticlesObservable) {
  1295. this._onBeforeDrawParticlesObservable.notifyObservers(effect);
  1296. }
  1297. // Render
  1298. this._engine.drawArraysType(Constants.MATERIAL_TriangleFanDrawMode, 0, 4, this._currentActiveCount);
  1299. this._engine.setAlphaMode(Constants.ALPHA_DISABLE);
  1300. }
  1301. // Switch VAOs
  1302. this._targetIndex++;
  1303. if (this._targetIndex === 2) {
  1304. this._targetIndex = 0;
  1305. }
  1306. // Switch buffers
  1307. let tmpBuffer = this._sourceBuffer;
  1308. this._sourceBuffer = this._targetBuffer;
  1309. this._targetBuffer = tmpBuffer;
  1310. return this._currentActiveCount;
  1311. }
  1312. /**
  1313. * Rebuilds the particle system
  1314. */
  1315. public rebuild(): void {
  1316. this._initialize(true);
  1317. }
  1318. private _releaseBuffers() {
  1319. if (this._buffer0) {
  1320. this._buffer0.dispose();
  1321. (<any>this._buffer0) = null;
  1322. }
  1323. if (this._buffer1) {
  1324. this._buffer1.dispose();
  1325. (<any>this._buffer1) = null;
  1326. }
  1327. if (this._spriteBuffer) {
  1328. this._spriteBuffer.dispose();
  1329. (<any>this._spriteBuffer) = null;
  1330. }
  1331. }
  1332. private _releaseVAOs() {
  1333. if (!this._updateVAO) {
  1334. return;
  1335. }
  1336. for (var index = 0; index < this._updateVAO.length; index++) {
  1337. this._engine.releaseVertexArrayObject(this._updateVAO[index]);
  1338. }
  1339. this._updateVAO = [];
  1340. for (var index = 0; index < this._renderVAO.length; index++) {
  1341. this._engine.releaseVertexArrayObject(this._renderVAO[index]);
  1342. }
  1343. this._renderVAO = [];
  1344. }
  1345. /**
  1346. * Disposes the particle system and free the associated resources
  1347. * @param disposeTexture defines if the particule texture must be disposed as well (true by default)
  1348. */
  1349. public dispose(disposeTexture = true): void {
  1350. if (this._scene) {
  1351. var index = this._scene.particleSystems.indexOf(this);
  1352. if (index > -1) {
  1353. this._scene.particleSystems.splice(index, 1);
  1354. }
  1355. }
  1356. this._releaseBuffers();
  1357. this._releaseVAOs();
  1358. if (this._colorGradientsTexture) {
  1359. this._colorGradientsTexture.dispose();
  1360. (<any>this._colorGradientsTexture) = null;
  1361. }
  1362. if (this._sizeGradientsTexture) {
  1363. this._sizeGradientsTexture.dispose();
  1364. (<any>this._sizeGradientsTexture) = null;
  1365. }
  1366. if (this._angularSpeedGradientsTexture) {
  1367. this._angularSpeedGradientsTexture.dispose();
  1368. (<any>this._angularSpeedGradientsTexture) = null;
  1369. }
  1370. if (this._velocityGradientsTexture) {
  1371. this._velocityGradientsTexture.dispose();
  1372. (<any>this._velocityGradientsTexture) = null;
  1373. }
  1374. if (this._limitVelocityGradientsTexture) {
  1375. this._limitVelocityGradientsTexture.dispose();
  1376. (<any>this._limitVelocityGradientsTexture) = null;
  1377. }
  1378. if (this._dragGradientsTexture) {
  1379. this._dragGradientsTexture.dispose();
  1380. (<any>this._dragGradientsTexture) = null;
  1381. }
  1382. if (this._randomTexture) {
  1383. this._randomTexture.dispose();
  1384. (<any>this._randomTexture) = null;
  1385. }
  1386. if (this._randomTexture2) {
  1387. this._randomTexture2.dispose();
  1388. (<any>this._randomTexture2) = null;
  1389. }
  1390. if (disposeTexture && this.particleTexture) {
  1391. this.particleTexture.dispose();
  1392. this.particleTexture = null;
  1393. }
  1394. if (disposeTexture && this.noiseTexture) {
  1395. this.noiseTexture.dispose();
  1396. this.noiseTexture = null;
  1397. }
  1398. // Callback
  1399. this.onStoppedObservable.clear();
  1400. this.onDisposeObservable.notifyObservers(this);
  1401. this.onDisposeObservable.clear();
  1402. }
  1403. /**
  1404. * Clones the particle system.
  1405. * @param name The name of the cloned object
  1406. * @param newEmitter The new emitter to use
  1407. * @returns the cloned particle system
  1408. */
  1409. public clone(name: string, newEmitter: any): GPUParticleSystem {
  1410. let serialization = this.serialize();
  1411. var result = GPUParticleSystem.Parse(serialization, this._scene || this._engine, this._rootUrl);
  1412. var custom = { ...this._customEffect };
  1413. result.name = name;
  1414. result._customEffect = custom;
  1415. if (newEmitter === undefined) {
  1416. newEmitter = this.emitter;
  1417. }
  1418. result.emitter = newEmitter;
  1419. result.noiseTexture = this.noiseTexture;
  1420. return result;
  1421. }
  1422. /**
  1423. * Serializes the particle system to a JSON object
  1424. * @param serializeTexture defines if the texture must be serialized as well
  1425. * @returns the JSON object
  1426. */
  1427. public serialize(serializeTexture = false): any {
  1428. var serializationObject: any = {};
  1429. ParticleSystem._Serialize(serializationObject, this, serializeTexture);
  1430. serializationObject.activeParticleCount = this.activeParticleCount;
  1431. serializationObject.randomTextureSize = this._randomTextureSize;
  1432. return serializationObject;
  1433. }
  1434. /**
  1435. * Parses a JSON object to create a GPU particle system.
  1436. * @param parsedParticleSystem The JSON object to parse
  1437. * @param sceneOrEngine The scene or the engine to create the particle system in
  1438. * @param rootUrl The root url to use to load external dependencies like texture
  1439. * @param doNotStart Ignore the preventAutoStart attribute and does not start
  1440. * @returns the parsed GPU particle system
  1441. */
  1442. public static Parse(parsedParticleSystem: any, sceneOrEngine: Scene | ThinEngine, rootUrl: string, doNotStart = false): GPUParticleSystem {
  1443. var name = parsedParticleSystem.name;
  1444. var particleSystem = new GPUParticleSystem(name, { capacity: parsedParticleSystem.capacity, randomTextureSize: parsedParticleSystem.randomTextureSize }, sceneOrEngine);
  1445. particleSystem._rootUrl = rootUrl;
  1446. if (parsedParticleSystem.activeParticleCount) {
  1447. particleSystem.activeParticleCount = parsedParticleSystem.activeParticleCount;
  1448. }
  1449. ParticleSystem._Parse(parsedParticleSystem, particleSystem, sceneOrEngine, rootUrl);
  1450. // Auto start
  1451. if (parsedParticleSystem.preventAutoStart) {
  1452. particleSystem.preventAutoStart = parsedParticleSystem.preventAutoStart;
  1453. }
  1454. if (!doNotStart && !particleSystem.preventAutoStart) {
  1455. particleSystem.start();
  1456. }
  1457. return particleSystem;
  1458. }
  1459. }