-
Notifications
You must be signed in to change notification settings - Fork 464
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(particle): Add GPU particle system (#204)
Add GPU particle system Add GPU particle system update build script
- Loading branch information
1 parent
c30e578
commit 1cc2567
Showing
37 changed files
with
3,203 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import { MaterialBase, ShaderLib, RenderNode, RendererMask, GeometryBase, Ctor, PlaneGeometry, Vector3, View3D, Time } from "@orillusion/core"; | ||
import { ParticleMaterial } from "./material/ParticleMaterial"; | ||
import { ParticleSimulator } from "./simulator/ParticleSimulator"; | ||
import { ParticleDataStructShader } from "./shader/ParticleDataStruct"; | ||
|
||
/** | ||
* A particle system can simulate and render many small images or geometries, it called particles to produce visual effects | ||
* @group Particle | ||
*/ | ||
export class ParticleSystem extends RenderNode { | ||
/** | ||
* whether the animation will auto play | ||
*/ | ||
public autoPlay: boolean = true; | ||
|
||
/** | ||
* the simulator of particle. | ||
*/ | ||
public particleSimulator: ParticleSimulator; | ||
|
||
/** | ||
* playing status | ||
*/ | ||
public playing: boolean = false; | ||
|
||
/** | ||
* animation playing speed | ||
*/ | ||
public playSpeed: number = 1.0; | ||
|
||
constructor() { | ||
super(); | ||
this.alwaysRender = true; | ||
this._rendererMask = RendererMask.Particle; | ||
ShaderLib.register('ParticleDataStruct', ParticleDataStructShader); | ||
} | ||
|
||
/** | ||
* material | ||
*/ | ||
public get material(): MaterialBase { | ||
return this._materials[0]; | ||
} | ||
|
||
public set material(value: MaterialBase) { | ||
this.materials = [value]; | ||
} | ||
|
||
/** | ||
* The geometry of the mesh determines its shape | ||
*/ | ||
public get geometry(): GeometryBase { | ||
return this._geometry; | ||
} | ||
|
||
public set geometry(value: GeometryBase) { | ||
//this must use super geometry has reference in super | ||
super.geometry = value; | ||
this.object3D.bound = this._geometry.bounds.clone(); | ||
if (this._readyPipeline) { | ||
this.initPipeline(); | ||
} | ||
} | ||
|
||
/** | ||
* Set preheat time(second) | ||
*/ | ||
public set preheatTime(value: number) { | ||
this.particleSimulator.preheatTime = value; | ||
} | ||
|
||
/** | ||
* Get preheat time(second) | ||
*/ | ||
public get preheatTime(): number { | ||
return this.particleSimulator.preheatTime; | ||
} | ||
|
||
/** | ||
* Set particle simulator's looping | ||
*/ | ||
public set looping(value: boolean) { | ||
this.particleSimulator.looping = value; | ||
} | ||
|
||
/** | ||
* Get particle simulator's looping | ||
*/ | ||
public get looping(): boolean { | ||
return this.particleSimulator.looping; | ||
} | ||
|
||
public init(): void { | ||
super.init(); | ||
} | ||
|
||
/** | ||
* Set to use the specified particle emulator | ||
* @param c class of particle emulator | ||
*/ | ||
public useSimulator<T extends ParticleSimulator>(c: Ctor<T>) { | ||
this.particleSimulator = new c(); | ||
this.particleSimulator[`initBuffer`](this); | ||
return this.particleSimulator; | ||
} | ||
|
||
/** | ||
* start to play animation, with a speed value | ||
* @param speed playSpeed, see{@link playSpeed} | ||
*/ | ||
public play(speed: number = 1.0) { | ||
this.playing = true; | ||
this.playSpeed = speed; | ||
} | ||
|
||
/** | ||
* stop playing | ||
*/ | ||
public stop(): void { | ||
this.playing = false; | ||
} | ||
|
||
public start(): void { | ||
if (!this.geometry) { | ||
this.geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); | ||
} | ||
|
||
if (!this.material) { | ||
this.material = new ParticleMaterial(); | ||
} | ||
|
||
this.particleSimulator.build(); | ||
|
||
if (this.autoPlay) { | ||
this.playing = true; | ||
} | ||
|
||
let renderShader = this.material.getShader(); | ||
renderShader.setStorageBuffer(`particleGlobalData`, this.particleSimulator.particleGlobalMemory); | ||
renderShader.setStorageBuffer(`particleLocalDatas`, this.particleSimulator.particleLocalMemory); | ||
this.instanceCount = this.particleSimulator.maxParticle; | ||
} | ||
|
||
private _frame: number = -1; | ||
private _time: number = 0; | ||
public onCompute(view: View3D, command: GPUCommandEncoder) { | ||
if (this._frame == -1) { | ||
this._frame = Time.frame; | ||
this._time += this.preheatTime; | ||
this.particleSimulator.updateBuffer(this.preheatTime); | ||
this.particleSimulator.compute(command); | ||
return; | ||
} | ||
|
||
if (this.playing) { | ||
this._frame = Time.frame; | ||
let delta = Time.delta * 0.001; | ||
delta *= this.playSpeed; | ||
this._time += delta; | ||
this.particleSimulator.updateBuffer(delta); | ||
this.particleSimulator.compute(command); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { MemoryInfo, GPUBufferBase } from "@orillusion/core"; | ||
|
||
/** | ||
* Basic class of particle memory data | ||
* @group Particle | ||
*/ | ||
export class ParticleBuffer extends GPUBufferBase { | ||
constructor(size: number, data?: Float32Array) { | ||
super(); | ||
if (size > 0) { | ||
this.createBuffer(GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, size, data); | ||
} | ||
} | ||
|
||
public alloc(name: string, byte: number): MemoryInfo { | ||
let node = this.memoryNodes.get(name); | ||
if (!node) { | ||
node = this.memory.allocation_node(byte); | ||
this.memoryNodes.set(name, node); | ||
} | ||
return node; | ||
} | ||
|
||
public allocInt8(name: string): MemoryInfo { | ||
return this.alloc(name, 1); | ||
} | ||
|
||
public allocUint8(name: string): MemoryInfo { | ||
return this.alloc(name, 1); | ||
} | ||
|
||
public allocInt16(name: string): MemoryInfo { | ||
return this.alloc(name, 2); | ||
} | ||
|
||
public allocUint16(name: string): MemoryInfo { | ||
return this.alloc(name, 2); | ||
} | ||
|
||
public allocInt32(name: string): MemoryInfo { | ||
return this.alloc(name, 4); | ||
} | ||
|
||
public allocUint32(name: string): MemoryInfo { | ||
return this.alloc(name, 4); | ||
} | ||
|
||
public allocFloat32(name: string): MemoryInfo { | ||
return this.alloc(name, 4); | ||
} | ||
|
||
public allocVec2(name: string): MemoryInfo { | ||
return this.alloc(name, 4 * 2); | ||
} | ||
|
||
public allocVec3(name: string): MemoryInfo { | ||
return this.alloc(name, 4 * 3); | ||
} | ||
|
||
public allocVec4(name: string): MemoryInfo { | ||
return this.alloc(name, 4 * 4); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { MemoryInfo, Vector3 } from "@orillusion/core"; | ||
import { ParticleBuffer } from './ParticleBuffer'; | ||
|
||
/** | ||
* @internal | ||
* global particle data for all quad | ||
* @group Plugin | ||
*/ | ||
export class ParticleGlobalMemory extends ParticleBuffer { | ||
protected _instanceID: MemoryInfo; | ||
protected _maxParticles: MemoryInfo; | ||
protected _time: MemoryInfo; | ||
protected _timeDelta: MemoryInfo; | ||
protected _duration: MemoryInfo; | ||
protected _isLoop: MemoryInfo; | ||
protected _simulatorSpace: MemoryInfo; | ||
protected _retain1: MemoryInfo; | ||
protected _emitterPos: MemoryInfo; | ||
public onChange: boolean = false; | ||
|
||
constructor(size: number, data?: Float32Array) { | ||
super(size, data); | ||
this._instanceID = this.allocUint32(`instance_index`); | ||
this._maxParticles = this.allocUint32(`maxParticles`); | ||
this._time = this.allocFloat32(`time`); | ||
this._timeDelta = this.allocFloat32(`timeDelta`); | ||
this._duration = this.allocFloat32(`duration`); | ||
this._isLoop = this.allocFloat32(`isLoop`); | ||
this._simulatorSpace = this.allocUint32(`simulatorSpace`); | ||
this._retain1 = this.allocFloat32(`retain1`); | ||
this._emitterPos = this.allocVec4(`emitterPos`); | ||
} | ||
|
||
public setInstanceID(v: number) { | ||
this._instanceID.setUint32(v); | ||
this.onChange = true; | ||
} | ||
|
||
public getInstanceID(): number { | ||
return this._instanceID.getUint32(); | ||
} | ||
|
||
public setMaxParticles(v: number) { | ||
this._maxParticles.setUint32(v); | ||
this.onChange = true; | ||
} | ||
|
||
public getMaxParticles(): number { | ||
return this._maxParticles.getUint32(); | ||
} | ||
|
||
public setTime(v: number) { | ||
this._time.setFloat(v); | ||
} | ||
|
||
public getTime(): number { | ||
return this._time.getFloat() | ||
} | ||
|
||
public setTimeDelta(v: number) { | ||
this._timeDelta.setFloat(v); | ||
this.onChange = true; | ||
} | ||
|
||
public getTimeDelta(): number { | ||
return this._timeDelta.getFloat(); | ||
} | ||
|
||
public setDuration(v: number) { | ||
this._duration.setFloat(v); | ||
this.onChange = true; | ||
} | ||
|
||
public getDuration(): number { | ||
return this._duration.getFloat(); | ||
} | ||
|
||
public setSimulatorSpace(v: number) { | ||
this._simulatorSpace.setUint32(v); | ||
this.onChange = true; | ||
} | ||
|
||
public getSimulatorSpace(): number { | ||
return this._simulatorSpace.getUint32(); | ||
} | ||
|
||
public setEmitterPos(pos: Vector3) { | ||
this._emitterPos.setXYZ(pos.x, pos.y, pos.z); | ||
this.onChange = true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Ctor } from "@orillusion/core"; | ||
import { ParticleData } from '../data/ParticleData'; | ||
import { ParticleBuffer } from './ParticleBuffer'; | ||
|
||
/** | ||
* @internal | ||
* particle data for each quad | ||
* @group Plugin | ||
*/ | ||
export class ParticleLocalMemory extends ParticleBuffer { | ||
public particlesData: ParticleData[] = []; | ||
|
||
public onChange: boolean = false; | ||
|
||
public allocationParticle<T extends ParticleData>(count: number, c: Ctor<T>): void { | ||
if (this.particlesData.length >= count) { | ||
return | ||
} | ||
|
||
for (let i = this.particlesData.length; i < count; i++) { | ||
let pd = c[`generateParticleData`](); | ||
this.particlesData.push(pd); | ||
} | ||
let singleCount = this.particlesData.length > 0 ? this.particlesData[0].totalCount : 0; | ||
|
||
let byteSize = Math.max(singleCount * count * 4, 32); | ||
if (this.byteSize == undefined || this.byteSize < byteSize) { | ||
this.createBuffer(GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, byteSize); | ||
} | ||
this.reset(); | ||
|
||
for (let i = 0; i < count; i++) { | ||
const pd = this.particlesData[i]; | ||
pd.memoryList.forEach((v) => { | ||
this.memory.allocation_memory(v); | ||
}); | ||
// pd.memoryList.length = 0; | ||
// pd.memoryList = null; | ||
} | ||
|
||
this.onChange = true; | ||
} | ||
} |
Oops, something went wrong.