babylon.gpuParticleSystem.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. module BABYLON {
  2. /**
  3. * This represents a GPU particle system in Babylon.
  4. * This os the fastest particle system in Babylon as it uses the GPU to update the individual particle data.
  5. */
  6. export class GPUParticleSystem implements IDisposable, IParticleSystem {
  7. /**
  8. * The id of the Particle system.
  9. */
  10. public id: string;
  11. /**
  12. * The friendly name of the Particle system.
  13. */
  14. public name: string;
  15. /**
  16. * The emitter represents the Mesh or position we are attaching the particle system to.
  17. */
  18. public emitter: Nullable<AbstractMesh | Vector3> = null;
  19. /**
  20. * The rendering group used by the Particle system to chose when to render.
  21. */
  22. public renderingGroupId = 0;
  23. /**
  24. * The layer mask we are rendering the particles through.
  25. */
  26. public layerMask: number = 0x0FFFFFFF; // TODO
  27. private _capacity: number;
  28. private _activeCount: number;
  29. private _currentActiveCount: number;
  30. private _renderEffect: Effect;
  31. private _updateEffect: Effect;
  32. private _buffer0: Buffer;
  33. private _buffer1: Buffer;
  34. private _spriteBuffer: Buffer;
  35. private _updateVAO = new Array<WebGLVertexArrayObject>();
  36. private _renderVAO = new Array<WebGLVertexArrayObject>()
  37. private _targetIndex = 0;
  38. private _sourceBuffer: Buffer;
  39. private _targetBuffer: Buffer;
  40. private _scene: Scene;
  41. private _engine: Engine;
  42. private _currentRenderId = -1;
  43. private _started = false;
  44. private _timeDelta = 0;
  45. private _randomTexture: RawTexture;
  46. private readonly _attributesStrideSize = 14;
  47. /**
  48. * An event triggered when the system is disposed.
  49. */
  50. public onDisposeObservable = new Observable<GPUParticleSystem>();
  51. /**
  52. * The overall motion speed (0.01 is default update speed, faster updates = faster animation)
  53. */
  54. public updateSpeed = 0.01;
  55. /**
  56. * The texture used to render each particle. (this can be a spritesheet)
  57. */
  58. public particleTexture: Nullable<Texture>;
  59. /**
  60. * Blend mode use to render the particle, it can be either ParticleSystem.BLENDMODE_ONEONE or ParticleSystem.BLENDMODE_STANDARD.
  61. */
  62. public blendMode = ParticleSystem.BLENDMODE_ONEONE;
  63. /**
  64. * Minimum life time of emitting particles.
  65. */
  66. public minLifeTime = 1;
  67. /**
  68. * Maximum life time of emitting particles.
  69. */
  70. public maxLifeTime = 1;
  71. /**
  72. * Minimum Size of emitting particles.
  73. */
  74. public minSize = 1;
  75. /**
  76. * Maximum Size of emitting particles.
  77. */
  78. public maxSize = 1;
  79. /**
  80. * Random color of each particle after it has been emitted, between color1 and color2 vectors.
  81. */
  82. public color1 = new Color4(1.0, 1.0, 1.0, 1.0);
  83. /**
  84. * Random color of each particle after it has been emitted, between color1 and color2 vectors.
  85. */
  86. public color2 = new Color4(1.0, 1.0, 1.0, 1.0);
  87. /**
  88. * Color the particle will have at the end of its lifetime.
  89. */
  90. public colorDead = new Color4(0, 0, 0, 0);
  91. /**
  92. * The maximum number of particles to emit per frame until we reach the activeParticleCount value
  93. */
  94. public emitRate = 100;
  95. /**
  96. * You can use gravity if you want to give an orientation to your particles.
  97. */
  98. public gravity = Vector3.Zero();
  99. /**
  100. * Gets the maximum number of particles supported by this system
  101. */
  102. public get capacity(): number {
  103. return this._capacity;
  104. }
  105. /**
  106. * Gets or set the number of active particles
  107. */
  108. public get activeParticleCount(): number {
  109. return this._activeCount;
  110. }
  111. public set activeParticleCount(value: number) {
  112. this._activeCount = Math.min(value, this._capacity);
  113. }
  114. /**
  115. * Gets Wether the system has been started.
  116. * @returns True if it has been started, otherwise false.
  117. */
  118. public isStarted(): boolean {
  119. return this._started;
  120. }
  121. /**
  122. * Starts the particle system and begins to emit.
  123. */
  124. public start(): void {
  125. this._started = true;
  126. }
  127. /**
  128. * Stops the particle system.
  129. */
  130. public stop(): void {
  131. this._started = false;
  132. }
  133. /**
  134. * Instantiates a GPU particle system.
  135. * 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.
  136. * @param name The name of the particle system
  137. * @param capacity The max number of particles alive at the same time
  138. * @param scene The scene the particle system belongs to
  139. */
  140. constructor(name: string, capacity: number, scene: Scene) {
  141. this.id = name;
  142. this.name = name;
  143. this._scene = scene || Engine.LastCreatedScene;
  144. this._capacity = capacity;
  145. this._activeCount = capacity;
  146. this._currentActiveCount = 0;
  147. this._engine = this._scene.getEngine();
  148. this._scene.particleSystems.push(this);
  149. let updateEffectOptions: EffectCreationOptions = {
  150. attributes: ["position", "age", "life", "seed", "size", "color", "direction"],
  151. uniformsNames: ["currentCount", "timeDelta", "generalRandom", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "gravity"],
  152. uniformBuffersNames: [],
  153. samplers:["randomSampler"],
  154. defines: "",
  155. fallbacks: null,
  156. onCompiled: null,
  157. onError: null,
  158. indexParameters: null,
  159. maxSimultaneousLights: 0,
  160. transformFeedbackVaryings: ["outPosition", "outAge", "outLife", "outSeed", "outSize", "outColor", "outDirection"]
  161. };
  162. this._updateEffect = new Effect("gpuUpdateParticles", updateEffectOptions, this._scene.getEngine());
  163. this._renderEffect = new Effect("gpuRenderParticles", ["position", "age", "life", "size", "color", "offset", "uv"], ["view", "projection", "colorDead"], ["textureSampler"], this._scene.getEngine());
  164. // Random data
  165. var maxTextureSize = this._engine.getCaps().maxTextureSize;
  166. var d = [];
  167. for (var i = 0; i < maxTextureSize; ++i) {
  168. d.push(Math.random());
  169. d.push(Math.random());
  170. d.push(Math.random());
  171. }
  172. this._randomTexture = new RawTexture(new Float32Array(d), maxTextureSize, 1, Engine.TEXTUREFORMAT_RGB32F, this._scene, false, false, Texture.NEAREST_SAMPLINGMODE, Engine.TEXTURETYPE_FLOAT)
  173. this._randomTexture.wrapU = Texture.WRAP_ADDRESSMODE;
  174. this._randomTexture.wrapV = Texture.WRAP_ADDRESSMODE;
  175. }
  176. /**
  177. * Animates the particle system for the current frame by emitting new particles and or animating the living ones.
  178. */
  179. public animate(): void {
  180. if (this._currentRenderId === this._scene.getRenderId()) {
  181. return;
  182. }
  183. this._currentRenderId = this._scene.getRenderId();
  184. this._timeDelta = this.updateSpeed * this._scene.getAnimationRatio();
  185. }
  186. private _createUpdateVAO(source: Buffer): WebGLVertexArrayObject {
  187. let updateVertexBuffers: {[key: string]: VertexBuffer} = {};
  188. updateVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3);
  189. updateVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1);
  190. updateVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1);
  191. updateVertexBuffers["seed"] = source.createVertexBuffer("seed", 5, 1);
  192. updateVertexBuffers["size"] = source.createVertexBuffer("size", 6, 1);
  193. updateVertexBuffers["color"] = source.createVertexBuffer("color", 7, 4);
  194. updateVertexBuffers["direction"] = source.createVertexBuffer("direction", 11, 3);
  195. let vao = this._engine.recordVertexArrayObject(updateVertexBuffers, null, this._updateEffect);
  196. this._engine.bindArrayBuffer(null);
  197. return vao;
  198. }
  199. private _createRenderVAO(source: Buffer, spriteSource: Buffer): WebGLVertexArrayObject {
  200. let renderVertexBuffers: {[key: string]: VertexBuffer} = {};
  201. renderVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3, this._attributesStrideSize, true);
  202. renderVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1, this._attributesStrideSize, true);
  203. renderVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1, this._attributesStrideSize, true);
  204. renderVertexBuffers["size"] = source.createVertexBuffer("size", 6, 1, this._attributesStrideSize, true);
  205. renderVertexBuffers["color"] = source.createVertexBuffer("color", 7, 4, this._attributesStrideSize, true);
  206. renderVertexBuffers["offset"] = spriteSource.createVertexBuffer("offset", 0, 2);
  207. renderVertexBuffers["uv"] = spriteSource.createVertexBuffer("uv", 2, 2);
  208. let vao = this._engine.recordVertexArrayObject(renderVertexBuffers, null, this._renderEffect);
  209. this._engine.bindArrayBuffer(null);
  210. return vao;
  211. }
  212. private _initialize(): void {
  213. if (this._buffer0) {
  214. return;
  215. }
  216. let engine = this._scene.getEngine();
  217. var data = new Array<float>();
  218. for (var particleIndex = 0; particleIndex < this._capacity; particleIndex++) {
  219. // position
  220. data.push(0.0);
  221. data.push(0.0);
  222. data.push(0.0);
  223. // Age and life
  224. data.push(0.0); // create the particle as a dead one to create a new one at start
  225. data.push(0.0);
  226. // Seed
  227. data.push(Math.random());
  228. // Size
  229. data.push(0.0);
  230. // color
  231. data.push(0.0);
  232. data.push(0.0);
  233. data.push(0.0);
  234. data.push(0.0);
  235. // direction
  236. data.push(0.0);
  237. data.push(0.0);
  238. data.push(0.0);
  239. }
  240. // Sprite data
  241. var spriteData = new Float32Array([1, 1, 1, 1,
  242. -1, 1, 0, 1,
  243. -1, -1, 0, 0,
  244. 1, -1, 1, 0]);
  245. // Buffers
  246. this._buffer0 = new Buffer(engine, data, false, this._attributesStrideSize);
  247. this._buffer1 = new Buffer(engine, data, false, this._attributesStrideSize);
  248. this._spriteBuffer = new Buffer(engine, spriteData, false, 4);
  249. // Update VAO
  250. this._updateVAO.push(this._createUpdateVAO(this._buffer0));
  251. this._updateVAO.push(this._createUpdateVAO(this._buffer1));
  252. // Render VAO
  253. this._renderVAO.push(this._createRenderVAO(this._buffer1, this._spriteBuffer));
  254. this._renderVAO.push(this._createRenderVAO(this._buffer0, this._spriteBuffer));
  255. // Links
  256. this._sourceBuffer = this._buffer0;
  257. this._targetBuffer = this._buffer1;
  258. }
  259. /**
  260. * Renders the particle system in its current state.
  261. * @returns the current number of particles.
  262. */
  263. public render(): number {
  264. if (!this.emitter || !this._updateEffect.isReady() || !this._renderEffect.isReady() ) {
  265. return 0;
  266. }
  267. // Get everything ready to render
  268. this. _initialize();
  269. this._currentActiveCount = Math.min(this._activeCount, this._currentActiveCount + this.emitRate);
  270. // Enable update effect
  271. this._engine.enableEffect(this._updateEffect);
  272. this._engine.setState(false);
  273. this._updateEffect.setFloat("currentCount", this._currentActiveCount);
  274. this._updateEffect.setFloat("timeDelta", this._timeDelta);
  275. this._updateEffect.setFloat("generalRandom", Math.random());
  276. this._updateEffect.setTexture("randomSampler", this._randomTexture);
  277. this._updateEffect.setFloat2("lifeTime", this.minLifeTime, this.maxLifeTime);
  278. this._updateEffect.setDirectColor4("color1", this.color1);
  279. this._updateEffect.setDirectColor4("color2", this.color2);
  280. this._updateEffect.setFloat2("sizeRange", this.minSize, this.maxSize);
  281. this._updateEffect.setVector3("gravity", this.gravity);
  282. let emitterWM: Matrix;
  283. if ((<AbstractMesh>this.emitter).position) {
  284. var emitterMesh = (<AbstractMesh>this.emitter);
  285. emitterWM = emitterMesh.getWorldMatrix();
  286. } else {
  287. var emitterPosition = (<Vector3>this.emitter);
  288. emitterWM = Matrix.Translation(emitterPosition.x, emitterPosition.y, emitterPosition.z);
  289. }
  290. this._updateEffect.setMatrix("emitterWM", emitterWM);
  291. // Bind source VAO
  292. this._engine.bindVertexArrayObject(this._updateVAO[this._targetIndex], null);
  293. // Update
  294. this._engine.bindTransformFeedbackBuffer(this._targetBuffer.getBuffer());
  295. this._engine.setRasterizerState(false);
  296. this._engine.beginTransformFeedback();
  297. this._engine.drawArraysType(Material.PointListDrawMode, 0, this._currentActiveCount);
  298. this._engine.endTransformFeedback();
  299. this._engine.setRasterizerState(true);
  300. this._engine.bindTransformFeedbackBuffer(null);
  301. // Enable render effect
  302. this._engine.enableEffect(this._renderEffect);
  303. this._renderEffect.setMatrix("view", this._scene.getViewMatrix());
  304. this._renderEffect.setMatrix("projection", this._scene.getProjectionMatrix());
  305. this._renderEffect.setTexture("textureSampler", this.particleTexture);
  306. this._renderEffect.setDirectColor4("colorDead", this.colorDead);
  307. // Draw order
  308. if (this.blendMode === ParticleSystem.BLENDMODE_ONEONE) {
  309. this._engine.setAlphaMode(Engine.ALPHA_ONEONE);
  310. } else {
  311. this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
  312. }
  313. // Bind source VAO
  314. this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
  315. // Render
  316. this._engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._currentActiveCount);
  317. this._engine.setAlphaMode(Engine.ALPHA_DISABLE);
  318. // Switch VAOs
  319. this._targetIndex++;
  320. if (this._targetIndex === 2) {
  321. this._targetIndex = 0;
  322. }
  323. // Switch buffers
  324. let tmpBuffer = this._sourceBuffer;
  325. this._sourceBuffer = this._targetBuffer;
  326. this._targetBuffer = tmpBuffer;
  327. return 0;
  328. }
  329. /**
  330. * Rebuilds the particle system
  331. */
  332. public rebuild(): void {
  333. }
  334. /**
  335. * Disposes the particle system and free the associated resources.
  336. */
  337. public dispose(): void {
  338. var index = this._scene.particleSystems.indexOf(this);
  339. if (index > -1) {
  340. this._scene.particleSystems.splice(index, 1);
  341. }
  342. if (this._buffer0) {
  343. this._buffer0.dispose();
  344. (<any>this._buffer0) = null;
  345. }
  346. if (this._buffer1) {
  347. this._buffer1.dispose();
  348. (<any>this._buffer1) = null;
  349. }
  350. for (var index = 0; index < this._updateVAO.length; index++) {
  351. this._engine.releaseVertexArrayObject(this._updateVAO[index]);
  352. }
  353. this._updateVAO = [];
  354. for (var index = 0; index < this._renderVAO.length; index++) {
  355. this._engine.releaseVertexArrayObject(this._renderVAO[index]);
  356. }
  357. this._renderVAO = [];
  358. if (this._randomTexture) {
  359. this._randomTexture.dispose();
  360. (<any>this._randomTexture) = null;
  361. }
  362. // Callback
  363. this.onDisposeObservable.notifyObservers(this);
  364. this.onDisposeObservable.clear();
  365. }
  366. //TODO: Clone / Parse / serialize
  367. /**
  368. * Clones the particle system.
  369. * @param name The name of the cloned object
  370. * @param newEmitter The new emitter to use
  371. * @returns the cloned particle system
  372. */
  373. public clone(name: string, newEmitter: any): Nullable<GPUParticleSystem> {
  374. return null;
  375. }
  376. /**
  377. * Serializes the particle system to a JSON object.
  378. * @returns the JSON object
  379. */
  380. public serialize(): any {
  381. }
  382. }
  383. }