Skip to content

Commit

Permalink
feat(particle): Add GPU particle system (#204)
Browse files Browse the repository at this point in the history
Add GPU particle system
Add GPU particle system
update build script
  • Loading branch information
Codeboy-cn committed Jun 6, 2023
1 parent c30e578 commit 1cc2567
Show file tree
Hide file tree
Showing 37 changed files with 3,203 additions and 1 deletion.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
"minify:es": "uglifyjs dist/orillusion.es.js -o dist/orillusion.es.js -c -m",
"test": "electron test/ci/main.js",
"test:ci": "xvfb-maybe -- electron --enable-unsafe-webgpu --enable-features=Vulkan --use-vulkan=swiftshader --use-webgpu-adapter=swiftshader --no-sandbox test/ci/main.js",
"docs": "npm run docs:core && npm run docs:physics && npm run docs:media && npm run docs:stats",
"docs": "npm run docs:core && npm run docs:physics && npm run docs:media && npm run docs:stats && npm run docs:particle",
"docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ./script/typedoc-plugin-not-exported.js --tsconfig tsconfig.build.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out",
"docs:core": "npm run docs:typedoc docs/api src/index.ts",
"docs:physics": "cd packages/physics && npm run docs",
"docs:media": "cd packages/media-extention && npm run docs",
"docs:stats": "cd packages/stats && npm run docs",
"docs:particle": "cd packages/particle && npm run docs",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
"devDependencies": {
Expand Down
164 changes: 164 additions & 0 deletions packages/particle/ParticleSystem.ts
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);
}
}
}
63 changes: 63 additions & 0 deletions packages/particle/buffer/ParticleBuffer.ts
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);
}
}
91 changes: 91 additions & 0 deletions packages/particle/buffer/ParticleGlobalMemory.ts
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;
}
}
43 changes: 43 additions & 0 deletions packages/particle/buffer/ParticleLocalMemory.ts
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;
}
}
Loading

0 comments on commit 1cc2567

Please sign in to comment.