From a1ce393fc8ba8f0c79f1a41ad94767f6459d9ac1 Mon Sep 17 00:00:00 2001 From: ChenMo Date: Thu, 7 Sep 2023 14:47:58 +0800 Subject: [PATCH] Add new particle renderer (#1682) * feat: add new particle renderer --- packages/core/src/Camera.ts | 14 +- packages/core/src/Engine.ts | 16 +- .../core/src/RenderPipeline/MeshRenderData.ts | 28 - .../core/src/RenderPipeline/RenderData.ts | 22 +- .../core/src/RenderPipeline/RenderQueue.ts | 11 +- .../core/src/RenderPipeline/SpriteBatcher.ts | 2 +- .../RenderPipeline/SpriteMaskRenderData.ts | 2 +- .../src/RenderPipeline/SpriteRenderData.ts | 2 +- .../core/src/RenderPipeline/TextRenderData.ts | 2 +- packages/core/src/Renderer.ts | 6 +- .../animationCurve/AnimationCurve.ts | 2 +- packages/core/src/asset/GraphicsResource.ts | 10 + packages/core/src/asset/ResourceManager.ts | 10 + packages/core/src/clone/CloneManager.ts | 129 +-- packages/core/src/clone/ComponentCloner.ts | 54 +- packages/core/src/graphic/Buffer.ts | 1 + packages/core/src/graphic/Mesh.ts | 128 +-- packages/core/src/graphic/Primitive.ts | 173 +++ packages/core/src/graphic/SubPrimitive.ts | 10 + packages/core/src/graphic/index.ts | 1 + packages/core/src/mesh/BlendShapeManager.ts | 15 +- packages/core/src/mesh/BufferMesh.ts | 14 +- packages/core/src/mesh/MeshRenderer.ts | 13 +- packages/core/src/mesh/ModelMesh.ts | 61 +- .../core/src/particle/ParticleBufferUtils.ts | 79 ++ .../core/src/particle/ParticleGenerator.ts | 686 +++++++++++ .../core/src/particle/ParticleMaterial.ts | 63 + .../core/src/particle/ParticleRenderer.ts | 1024 +++-------------- .../src/particle/enums/ParticleCurveMode.ts | 13 + .../particle/enums/ParticleGradientMode.ts | 13 + .../particle/enums/ParticleRandomSubSeeds.ts | 19 + .../src/particle/enums/ParticleRenderMode.ts | 17 + .../src/particle/enums/ParticleScaleMode.ts | 11 + .../particle/enums/ParticleSimulationSpace.ts | 9 + .../src/particle/enums/ParticleStopMode.ts | 6 + .../BillboardParticleVertexAttribute.ts | 6 + .../attributes/MeshParticleVertexAttribute.ts | 5 + .../ParticleInstanceVertexAttribute.ts | 16 + packages/core/src/particle/index.ts | 13 +- packages/core/src/particle/modules/Burst.ts | 21 + .../modules/ColorOverLifetimeModule.ts | 95 ++ .../src/particle/modules/EmissionModule.ts | 168 +++ .../core/src/particle/modules/MainModule.ts | 216 ++++ .../modules/ParticleCompositeCurve.ts | 109 ++ .../modules/ParticleCompositeGradient.ts | 114 ++ .../src/particle/modules/ParticleCurve.ts | 122 ++ .../modules/ParticleGeneratorModule.ts | 26 + .../src/particle/modules/ParticleGradient.ts | 227 ++++ .../modules/RotationOverLifetimeModule.ts | 141 +++ .../modules/SizeOverLifetimeModule.ts | 108 ++ .../modules/TextureSheetAnimationModule.ts | 105 ++ .../modules/VelocityOverLifetimeModule.ts | 125 ++ .../src/particle/modules/shape/BaseShape.ts | 21 + .../src/particle/modules/shape/BoxShape.ts | 34 + .../src/particle/modules/shape/CircleShape.ts | 51 + .../src/particle/modules/shape/ConeShape.ts | 75 ++ .../particle/modules/shape/HemisphereShape.ts | 31 + .../src/particle/modules/shape/ShapeUtils.ts | 57 + .../src/particle/modules/shape/SphereShape.ts | 28 + .../shape/enums/ParticleShapeArcMode.ts | 9 + .../modules/shape/enums/ParticleShapeType.ts | 15 + .../core/src/particle/modules/shape/index.ts | 6 + packages/core/src/shader/ShaderData.ts | 11 +- packages/core/src/shaderlib/ShaderLib.ts | 4 +- .../core/src/shaderlib/extra/particle.fs.glsl | 52 +- .../core/src/shaderlib/extra/particle.vs.glsl | 223 ++-- .../particle/color_over_lifetime_module.glsl | 73 ++ .../particle/horizontal_billboard.glsl | 13 + packages/core/src/shaderlib/particle/index.ts | 27 + .../shaderlib/particle/particle_common.glsl | 115 ++ .../src/shaderlib/particle/particle_mesh.glsl | 87 ++ .../rotation_over_lifetime_module.glsl | 108 ++ .../particle/size_over_lifetime_module.glsl | 65 ++ .../shaderlib/particle/sphere_billboard.glsl | 29 + .../particle/stretched_billboard.glsl | 32 + .../texture_sheet_animation_module.glsl | 30 + .../velocity_over_lifetime_module.glsl | 102 ++ .../particle/vertical_billboard.glsl | 13 + packages/core/src/texture/RenderTarget.ts | 1 + packages/core/src/texture/Texture.ts | 6 +- packages/core/src/texture/Texture2D.ts | 2 + packages/core/src/texture/Texture2DArray.ts | 2 + packages/core/src/texture/TextureCube.ts | 2 + .../IPlatformPrimitive.ts | 12 - packages/math/src/Rand.ts | 50 + packages/math/src/index.ts | 1 + packages/rhi-webgl/src/GLPrimitive.ts | 49 +- packages/rhi-webgl/src/WebGLGraphicDevice.ts | 5 +- 88 files changed, 4323 insertions(+), 1401 deletions(-) delete mode 100644 packages/core/src/RenderPipeline/MeshRenderData.ts create mode 100644 packages/core/src/graphic/Primitive.ts create mode 100644 packages/core/src/graphic/SubPrimitive.ts create mode 100644 packages/core/src/particle/ParticleBufferUtils.ts create mode 100644 packages/core/src/particle/ParticleGenerator.ts create mode 100644 packages/core/src/particle/ParticleMaterial.ts create mode 100644 packages/core/src/particle/enums/ParticleCurveMode.ts create mode 100644 packages/core/src/particle/enums/ParticleGradientMode.ts create mode 100644 packages/core/src/particle/enums/ParticleRandomSubSeeds.ts create mode 100644 packages/core/src/particle/enums/ParticleRenderMode.ts create mode 100644 packages/core/src/particle/enums/ParticleScaleMode.ts create mode 100644 packages/core/src/particle/enums/ParticleSimulationSpace.ts create mode 100644 packages/core/src/particle/enums/ParticleStopMode.ts create mode 100644 packages/core/src/particle/enums/attributes/BillboardParticleVertexAttribute.ts create mode 100644 packages/core/src/particle/enums/attributes/MeshParticleVertexAttribute.ts create mode 100644 packages/core/src/particle/enums/attributes/ParticleInstanceVertexAttribute.ts create mode 100644 packages/core/src/particle/modules/Burst.ts create mode 100644 packages/core/src/particle/modules/ColorOverLifetimeModule.ts create mode 100644 packages/core/src/particle/modules/EmissionModule.ts create mode 100644 packages/core/src/particle/modules/MainModule.ts create mode 100644 packages/core/src/particle/modules/ParticleCompositeCurve.ts create mode 100644 packages/core/src/particle/modules/ParticleCompositeGradient.ts create mode 100644 packages/core/src/particle/modules/ParticleCurve.ts create mode 100644 packages/core/src/particle/modules/ParticleGeneratorModule.ts create mode 100644 packages/core/src/particle/modules/ParticleGradient.ts create mode 100644 packages/core/src/particle/modules/RotationOverLifetimeModule.ts create mode 100644 packages/core/src/particle/modules/SizeOverLifetimeModule.ts create mode 100644 packages/core/src/particle/modules/TextureSheetAnimationModule.ts create mode 100644 packages/core/src/particle/modules/VelocityOverLifetimeModule.ts create mode 100644 packages/core/src/particle/modules/shape/BaseShape.ts create mode 100644 packages/core/src/particle/modules/shape/BoxShape.ts create mode 100644 packages/core/src/particle/modules/shape/CircleShape.ts create mode 100644 packages/core/src/particle/modules/shape/ConeShape.ts create mode 100644 packages/core/src/particle/modules/shape/HemisphereShape.ts create mode 100644 packages/core/src/particle/modules/shape/ShapeUtils.ts create mode 100644 packages/core/src/particle/modules/shape/SphereShape.ts create mode 100644 packages/core/src/particle/modules/shape/enums/ParticleShapeArcMode.ts create mode 100644 packages/core/src/particle/modules/shape/enums/ParticleShapeType.ts create mode 100644 packages/core/src/particle/modules/shape/index.ts create mode 100644 packages/core/src/shaderlib/particle/color_over_lifetime_module.glsl create mode 100644 packages/core/src/shaderlib/particle/horizontal_billboard.glsl create mode 100644 packages/core/src/shaderlib/particle/index.ts create mode 100644 packages/core/src/shaderlib/particle/particle_common.glsl create mode 100644 packages/core/src/shaderlib/particle/particle_mesh.glsl create mode 100644 packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl create mode 100644 packages/core/src/shaderlib/particle/size_over_lifetime_module.glsl create mode 100644 packages/core/src/shaderlib/particle/sphere_billboard.glsl create mode 100644 packages/core/src/shaderlib/particle/stretched_billboard.glsl create mode 100644 packages/core/src/shaderlib/particle/texture_sheet_animation_module.glsl create mode 100644 packages/core/src/shaderlib/particle/velocity_over_lifetime_module.glsl create mode 100644 packages/core/src/shaderlib/particle/vertical_billboard.glsl create mode 100644 packages/math/src/Rand.ts diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index d087f3ef8f..6abb420a7b 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -37,6 +37,8 @@ export class Camera extends Component { private static _inverseViewMatrixProperty = ShaderProperty.getByName("camera_ViewInvMat"); private static _cameraPositionProperty = ShaderProperty.getByName("camera_Position"); + private static _cameraForwardProperty = ShaderProperty.getByName("camera_Forward"); + private static _cameraUpProperty = ShaderProperty.getByName("camera_Up"); private static _cameraDepthBufferParamsProperty = ShaderProperty.getByName("camera_DepthBufferParams"); /** Whether to enable frustum culling, it is enabled by default. */ @@ -625,13 +627,17 @@ export class Camera extends Component { } private _updateShaderData(): void { + const shaderData = this.shaderData; + + const transform = this._transform; + shaderData.setMatrix(Camera._inverseViewMatrixProperty, transform.worldMatrix); + shaderData.setVector3(Camera._cameraPositionProperty, transform.worldPosition); + shaderData.setVector3(Camera._cameraForwardProperty, transform.worldForward); + shaderData.setVector3(Camera._cameraUpProperty, transform.worldUp); + const depthBufferParams = this._depthBufferParams; const farDivideNear = this._farClipPlane / this._nearClipPlane; depthBufferParams.set(1.0 - farDivideNear, farDivideNear, 0, 0); - - const shaderData = this.shaderData; - shaderData.setMatrix(Camera._inverseViewMatrixProperty, this._transform.worldMatrix); - shaderData.setVector3(Camera._cameraPositionProperty, this._transform.worldPosition); shaderData.setVector4(Camera._cameraDepthBufferParamsProperty, depthBufferParams); } diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index c0341f673a..78cb0b7fc8 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -5,8 +5,8 @@ import { Canvas } from "./Canvas"; import { EngineSettings } from "./EngineSettings"; import { Entity } from "./Entity"; import { ClassPool } from "./RenderPipeline/ClassPool"; -import { MeshRenderData } from "./RenderPipeline/MeshRenderData"; import { RenderContext } from "./RenderPipeline/RenderContext"; +import { RenderData } from "./RenderPipeline/RenderData"; import { RenderElement } from "./RenderPipeline/RenderElement"; import { SpriteMaskManager } from "./RenderPipeline/SpriteMaskManager"; import { SpriteMaskRenderData } from "./RenderPipeline/SpriteMaskRenderData"; @@ -21,6 +21,7 @@ import { GLCapabilityType } from "./base/Constant"; import { ColorSpace } from "./enums/ColorSpace"; import { InputManager } from "./input"; import { Material } from "./material/Material"; +import { ParticleBufferUtils } from "./particle/ParticleBufferUtils"; import { PhysicsScene } from "./physics/PhysicsScene"; import { ColliderShape } from "./physics/shape/ColliderShape"; import { IHardwareRenderer } from "./renderingHardwareInterface"; @@ -55,6 +56,8 @@ export class Engine extends EventDispatcher { /** Input manager of Engine. */ readonly inputManager: InputManager; + /** @internal */ + _particleBufferUtils: ParticleBufferUtils; /** @internal */ _physicsInitialized: boolean = false; /** @internal */ @@ -69,7 +72,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderElementPool: ClassPool = new ClassPool(RenderElement); /* @internal */ - _meshRenderDataPool: ClassPool = new ClassPool(MeshRenderData); + _renderDataPool: ClassPool = new ClassPool(RenderData); /* @internal */ _spriteRenderDataPool: ClassPool = new ClassPool(SpriteRenderData); /* @internal */ @@ -263,6 +266,8 @@ export class Engine extends EventDispatcher { const colorSpace = configuration.colorSpace || ColorSpace.Linear; colorSpace === ColorSpace.Gamma && this._macroCollection.enable(Engine._gammaMacro); innerSettings.colorSpace = colorSpace; + + this._particleBufferUtils = new ParticleBufferUtils(this); } /** @@ -308,7 +313,7 @@ export class Engine extends EventDispatcher { this._frameInProcess = true; this._renderElementPool.resetPool(); - this._meshRenderDataPool.resetPool(); + this._renderDataPool.resetPool(); this._spriteRenderDataPool.resetPool(); this._spriteMaskRenderDataPool.resetPool(); this._textRenderDataPool.resetPool(); @@ -644,6 +649,8 @@ export class Engine extends EventDispatcher { private _onDeviceLost(): void { this._isDeviceLost = true; + // Lose graphic resources + this.resourceManager._lostGraphicResources(); console.log("Device lost."); this.dispatch("devicelost", this); } @@ -660,6 +667,7 @@ export class Engine extends EventDispatcher { console.log("Graphic resource restored."); // Restore resources content + this._particleBufferUtils.setBufferData(); resourceManager ._restoreResourcesContent() .then(() => { @@ -674,7 +682,7 @@ export class Engine extends EventDispatcher { private _gc(): void { this._renderElementPool.garbageCollection(); - this._meshRenderDataPool.garbageCollection(); + this._renderDataPool.garbageCollection(); this._spriteRenderDataPool.garbageCollection(); this._spriteMaskRenderDataPool.garbageCollection(); this._textRenderDataPool.garbageCollection(); diff --git a/packages/core/src/RenderPipeline/MeshRenderData.ts b/packages/core/src/RenderPipeline/MeshRenderData.ts deleted file mode 100644 index cfc41cd32c..0000000000 --- a/packages/core/src/RenderPipeline/MeshRenderData.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Mesh } from "../graphic/Mesh"; -import { SubMesh } from "../graphic/SubMesh"; -import { Material } from "../material/Material"; -import { Renderer } from "../Renderer"; -import { IPoolElement } from "./IPoolElement"; -import { RenderData } from "./RenderData"; - -/** - * Render element. - */ -export class MeshRenderData extends RenderData implements IPoolElement { - /** Mesh. */ - mesh: Mesh; - /** Sub mesh. */ - subMesh: SubMesh; - - set(component: Renderer, material: Material, mesh: Mesh, subMesh: SubMesh): void { - this.component = component; - this.material = material; - - this.mesh = mesh; - this.subMesh = subMesh; - } - - dispose(): void { - this.component = this.material = this.mesh = this.subMesh = null; - } -} diff --git a/packages/core/src/RenderPipeline/RenderData.ts b/packages/core/src/RenderPipeline/RenderData.ts index f5bcca0ce1..be3c236921 100644 --- a/packages/core/src/RenderPipeline/RenderData.ts +++ b/packages/core/src/RenderPipeline/RenderData.ts @@ -1,9 +1,29 @@ +import { SubMesh } from "../graphic"; +import { Primitive } from "../graphic/Primitive"; import { Material } from "../material"; import { Renderer } from "../Renderer"; +import { IPoolElement } from "./IPoolElement"; -export class RenderData { +export class RenderData implements IPoolElement { component: Renderer; material: Material; + primitive: Primitive; + subPrimitive: SubMesh; multiRenderData: boolean; + + setX(component: Renderer, material: Material, primitive: Primitive, subPrimitive: SubMesh): void { + this.component = component; + this.material = material; + + this.primitive = primitive; + this.subPrimitive = subPrimitive; + } + + dispose(): void { + this.component = null; + this.material = null; + this.primitive = null; + this.subPrimitive = null; + } } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index b002584bcd..2093fee5e4 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -3,7 +3,6 @@ import { Engine } from "../Engine"; import { Layer } from "../Layer"; import { Shader } from "../shader"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; -import { MeshRenderData } from "./MeshRenderData"; import { RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; import { SpriteBatcher } from "./SpriteBatcher"; @@ -70,13 +69,13 @@ export class RenderQueue { continue; } - if (!!(data as MeshRenderData).mesh) { + if (data.primitive) { this._spriteBatcher.flush(camera); const compileMacros = Shader._compileMacros; - const meshData = data; - const renderer = meshData.component; - const material = meshData.material.destroyed ? engine._magentaMaterial : meshData.material; + const primitive = data.primitive; + const renderer = data.component; + const material = data.material.destroyed ? engine._magentaMaterial : data.material; const rendererData = renderer.shaderData; const materialData = material.shaderData; @@ -157,7 +156,7 @@ export class RenderQueue { material.shaderData ); - rhi.drawPrimitive(meshData.mesh, meshData.subMesh, program); + rhi.drawPrimitive(primitive, data.subPrimitive, program); } } else { this._spriteBatcher.drawElement(element, camera); diff --git a/packages/core/src/RenderPipeline/SpriteBatcher.ts b/packages/core/src/RenderPipeline/SpriteBatcher.ts index 85df90ff65..9d03ca9510 100644 --- a/packages/core/src/RenderPipeline/SpriteBatcher.ts +++ b/packages/core/src/RenderPipeline/SpriteBatcher.ts @@ -126,7 +126,7 @@ export class SpriteBatcher extends Basic2DBatcher { program.uploadAll(program.materialUniformBlock, material.shaderData); material.renderState._apply(engine, false, shaderPass._renderStateDataMap, material.shaderData); - engine._hardwareRenderer.drawPrimitive(mesh, subMesh, program); + engine._hardwareRenderer.drawPrimitive(mesh._primitive, subMesh, program); maskManager.postRender(renderer); } diff --git a/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts b/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts index d07d62cef6..1eac81aa0c 100644 --- a/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts +++ b/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts @@ -19,7 +19,7 @@ export class SpriteMaskRenderData extends RenderData implements IPoolElement { this.verticesData = verticesData; } - dispose(): void { + override dispose(): void { this.component = this.material = this.verticesData = null; } } diff --git a/packages/core/src/RenderPipeline/SpriteRenderData.ts b/packages/core/src/RenderPipeline/SpriteRenderData.ts index 2cd8984bb3..8a499c6445 100644 --- a/packages/core/src/RenderPipeline/SpriteRenderData.ts +++ b/packages/core/src/RenderPipeline/SpriteRenderData.ts @@ -30,7 +30,7 @@ export class SpriteRenderData extends RenderData implements IPoolElement { this.dataIndex = dataIndex; } - dispose(): void { + override dispose(): void { this.component = this.material = this.verticesData = this.texture = null; } } diff --git a/packages/core/src/RenderPipeline/TextRenderData.ts b/packages/core/src/RenderPipeline/TextRenderData.ts index c02fcfec2f..e82a7f38d4 100644 --- a/packages/core/src/RenderPipeline/TextRenderData.ts +++ b/packages/core/src/RenderPipeline/TextRenderData.ts @@ -10,7 +10,7 @@ export class TextRenderData extends RenderData implements IPoolElement { this.multiRenderData = true; } - dispose(): void { + override dispose(): void { this.component = this.material = null; this.charsData.length = 0; } diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index 4a5e1ea9a6..3e59e3e7c9 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -6,7 +6,7 @@ import { Entity } from "./Entity"; import { RenderContext } from "./RenderPipeline/RenderContext"; import { Transform, TransformModifyFlags } from "./Transform"; import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager"; -import { ICustomClone } from "./clone/ComponentCloner"; +import { IComponentCustomClone } from "./clone/ComponentCloner"; import { Material } from "./material"; import { ShaderMacro, ShaderProperty } from "./shader"; import { ShaderData } from "./shader/ShaderData"; @@ -18,7 +18,7 @@ import { ShaderDataGroup } from "./shader/enums/ShaderDataGroup"; * @decorator `@dependentComponents(Transform, DependentMode.CheckOnly)` */ @dependentComponents(Transform, DependentMode.CheckOnly) -export class Renderer extends Component implements ICustomClone { +export class Renderer extends Component implements IComponentCustomClone { private static _tempVector0 = new Vector3(); private static _receiveShadowMacro = ShaderMacro.getByName("RENDERER_IS_RECEIVE_SHADOWS"); @@ -67,7 +67,7 @@ export class Renderer extends Component implements ICustomClone { private _normalMatrix: Matrix = new Matrix(); @ignoreClone private _materialsInstanced: boolean[] = []; - @ignoreClone + @assignmentClone private _priority: number = 0; @assignmentClone private _receiveShadows: boolean = true; diff --git a/packages/core/src/animation/animationCurve/AnimationCurve.ts b/packages/core/src/animation/animationCurve/AnimationCurve.ts index f664a1bb3b..63e95fc870 100644 --- a/packages/core/src/animation/animationCurve/AnimationCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationCurve.ts @@ -82,7 +82,7 @@ export abstract class AnimationCurve { let newLength = 0; for (let i = keys.length - 1; i >= 0; i--) { const key = keys[i]; - if (key.time > length) { + if (key.time > this._length) { newLength = key.time; } } diff --git a/packages/core/src/asset/GraphicsResource.ts b/packages/core/src/asset/GraphicsResource.ts index a9fdb0b0b5..2be9c4b857 100644 --- a/packages/core/src/asset/GraphicsResource.ts +++ b/packages/core/src/asset/GraphicsResource.ts @@ -2,6 +2,16 @@ import { Engine } from "../Engine"; import { ReferResource } from "./ReferResource"; export abstract class GraphicsResource extends ReferResource { + /** @internal */ + _isContentLost: boolean = false; + + /** + * Whether the content of the resource is lost. + */ + get isContentLost(): boolean { + return this._isContentLost; + } + protected constructor(engine: Engine) { super(engine); engine.resourceManager._addGraphicResource(this); diff --git a/packages/core/src/asset/ResourceManager.ts b/packages/core/src/asset/ResourceManager.ts index 3bc8bc209b..594ebec844 100644 --- a/packages/core/src/asset/ResourceManager.ts +++ b/packages/core/src/asset/ResourceManager.ts @@ -258,6 +258,16 @@ export class ResourceManager { } } + /** + * @internal + */ + _lostGraphicResources(): void { + const graphicResourcePool = this._graphicResourcePool; + for (const id in graphicResourcePool) { + graphicResourcePool[id]._isContentLost = true; + } + } + /** * @internal */ diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts index 7fc38db7b8..2f43b059cb 100644 --- a/packages/core/src/clone/CloneManager.ts +++ b/packages/core/src/clone/CloneManager.ts @@ -1,16 +1,7 @@ -import { IClone } from "@galacean/engine-design"; +import { TypedArray } from "../base/Constant"; +import { ICustomClone } from "./ComponentCloner"; import { CloneMode } from "./enums/CloneMode"; -type TypeArray = - | Uint8Array - | Uint16Array - | Uint32Array - | Int8Array - | Int16Array - | Int32Array - | Float32Array - | Float64Array; - /** * Property decorator, ignore the property when cloning. */ @@ -102,51 +93,20 @@ export class CloneManager { return cloneModes; } - /** - * Deep clone the object. - * @param source - Clone source - * @param target - Clone target - */ - static deepCloneObject(source: Object, target: Object): void { - const type = source.constructor; - switch (type) { - case Uint8Array: - case Uint16Array: - case Uint32Array: - case Int8Array: - case Int16Array: - case Int32Array: - case Float32Array: - case Float64Array: - // Type array clone. - (target).set(source); - break; - case Array: - // Array clone. - for (let i = 0, n = (<[]>source).length; i < n; i++) { - CloneManager._deepCloneObjectItem(source, target, i); - } - break; - default: - const customSource = source; - if (customSource.clone && customSource.cloneTo) { - // Custom clone. - customSource.cloneTo(target); - } else { - // Object or other class not implements custom clone. - const keys = Object.keys(source); - for (let i = 0, n = keys.length; i < n; i++) { - CloneManager._deepCloneObjectItem(source, target, keys[i]); - } - } + static cloneProperty(source: Object, target: Object, k: string | number, cloneMode: CloneMode): void { + if (cloneMode === CloneMode.Ignore) { + return; } - } - private static _deepCloneObjectItem(source: object, target: object, k: number | string): void { - const sourceItem = source[k]; - if (sourceItem instanceof Object) { - const itemType = (sourceItem).constructor; - switch (itemType) { + const sourceProperty = source[k]; + if (sourceProperty instanceof Object) { + if (cloneMode === undefined || cloneMode === CloneMode.Assignment) { + target[k] = sourceProperty; + return; + } + + const type = sourceProperty.constructor; + switch (type) { case Uint8Array: case Uint16Array: case Uint32Array: @@ -155,47 +115,46 @@ export class CloneManager { case Int32Array: case Float32Array: case Float64Array: - // Type array clone. - const sourceTypeArrayItem = sourceItem; - let targetTypeArrayItem = target[k]; - if (targetTypeArrayItem == null) { - target[k] = sourceTypeArrayItem.slice(); + let targetPropertyT = target[k]; + if (targetPropertyT == null || targetPropertyT.length !== (sourceProperty).length) { + target[k] = (sourceProperty).slice(); } else { - targetTypeArrayItem.set(sourceTypeArrayItem); + targetPropertyT.set(sourceProperty); } break; case Array: - // Array clone. - const sourceArrayItem = <[]>sourceItem; - let targetArrayItem = <[]>target[k]; - if (targetArrayItem == null) { - target[k] = new Array(sourceArrayItem.length); + let targetPropertyA = >target[k]; + const length = (>sourceProperty).length; + if (targetPropertyA == null) { + target[k] = targetPropertyA = new Array(length); } else { - targetArrayItem.length = sourceArrayItem.length; + targetPropertyA.length = length; + } + for (let i = 0; i < length; i++) { + CloneManager.cloneProperty(>sourceProperty, targetPropertyA, i, cloneMode); } - CloneManager.deepCloneObject(sourceArrayItem, targetArrayItem); - break; default: - if (sourceItem.clone && sourceItem.cloneTo) { - // Custom clone. - let sourceCustomItem = sourceItem; - let targetCustomItem = target[k]; - if (targetCustomItem) { - sourceCustomItem.cloneTo(targetCustomItem); - } else { - target[k] = sourceCustomItem.clone(); - } - } else { - // Object or other class not implements custom clone. - let targetItem = target[k]; - targetItem == null && (target[k] = targetItem = new sourceItem.constructor()); - CloneManager.deepCloneObject(sourceItem, targetItem); - break; + const targetOProperty = (target[k] ||= new sourceProperty.constructor()); + const cloneModes = CloneManager.getCloneMode(sourceProperty.constructor); + for (let k in sourceProperty) { + CloneManager.cloneProperty(sourceProperty, targetOProperty, k, cloneModes[k]); + } + + // Custom clone + if ((sourceProperty)._cloneTo) { + (sourceProperty)._cloneTo(targetOProperty); } + break; } } else { - // Null or undefined and primitive type. - target[k] = sourceItem; + // null, undefined, primitive type, function + target[k] = sourceProperty; + } + } + + static deepCloneObject(source: Object, target: Object): void { + for (let k in source) { + CloneManager.cloneProperty(source, target, k, CloneMode.Deep); } } } diff --git a/packages/core/src/clone/ComponentCloner.ts b/packages/core/src/clone/ComponentCloner.ts index 25314a3c36..72f9f4873d 100644 --- a/packages/core/src/clone/ComponentCloner.ts +++ b/packages/core/src/clone/ComponentCloner.ts @@ -1,7 +1,6 @@ import { Component } from "../Component"; import { Entity } from "../Entity"; import { CloneManager } from "./CloneManager"; -import { CloneMode } from "./enums/CloneMode"; /** * Custom clone interface. @@ -10,7 +9,14 @@ export interface ICustomClone { /** * @internal */ - _cloneTo(target: ICustomClone, srcRoot: Entity, targetRoot: Entity): void; + _cloneTo(target: ICustomClone): void; +} + +export interface IComponentCustomClone { + /** + * @internal + */ + _cloneTo(target: IComponentCustomClone, srcRoot: Entity, targetRoot: Entity): void; } export class ComponentCloner { @@ -21,41 +27,17 @@ export class ComponentCloner { */ static cloneComponent(source: Component, target: Component, srcRoot: Entity, targetRoot: Entity): void { const cloneModes = CloneManager.getCloneMode(source.constructor); - const keys = Object.keys(source); - for (let i = 0, n = keys.length; i < n; i++) { - const k = keys[i]; - const cloneMode = cloneModes[k]; - switch (cloneMode) { - case undefined: - case CloneMode.Assignment: - target[k] = source[k]; - break; - case CloneMode.Shallow: - const sourcePropS: Object = source[k]; - if (sourcePropS instanceof Object) { - let tarProp = target[k]; - tarProp == null && (tarProp = target[k] = sourcePropS.constructor()); - Object.assign(tarProp, sourcePropS); - } else { - // Null or undefined and primitive type. - target[k] = sourcePropS; - } - break; - case CloneMode.Deep: - const sourcePropD: Object = source[k]; - if (sourcePropD instanceof Object) { - let tarProp = target[k]; - tarProp == null && (tarProp = target[k] = sourcePropD.constructor()); - CloneManager.deepCloneObject(sourcePropD, tarProp); - } else { - // Null or undefined and primitive type. - target[k] = sourcePropD; - } - break; - } + + for (let k in source) { + CloneManager.cloneProperty(source, target, k, cloneModes[k]); } - if ((source)._cloneTo) { - (source)._cloneTo(target, srcRoot, targetRoot); + + if (((source as unknown))._cloneTo) { + ((source as unknown))._cloneTo( + (target as unknown), + srcRoot, + targetRoot + ); } } } diff --git a/packages/core/src/graphic/Buffer.ts b/packages/core/src/graphic/Buffer.ts index b5c1ad7084..3d526cbbe8 100644 --- a/packages/core/src/graphic/Buffer.ts +++ b/packages/core/src/graphic/Buffer.ts @@ -187,6 +187,7 @@ export class Buffer extends GraphicsResource { this._data.set(srcData, bufferByteOffset); } } + this._isContentLost = false; this._dataUpdateManager.dispatch(); } diff --git a/packages/core/src/graphic/Mesh.ts b/packages/core/src/graphic/Mesh.ts index 784d041fd1..bc48151f85 100644 --- a/packages/core/src/graphic/Mesh.ts +++ b/packages/core/src/graphic/Mesh.ts @@ -1,44 +1,24 @@ -import { IPlatformPrimitive } from "@galacean/engine-design/types/renderingHardwareInterface/IPlatformPrimitive"; import { BoundingBox } from "@galacean/engine-math"; import { Engine } from "../Engine"; import { UpdateFlagManager } from "../UpdateFlagManager"; -import { GraphicsResource } from "../asset/GraphicsResource"; -import { BufferUtil } from "../graphic/BufferUtil"; +import { ReferResource } from "../asset/ReferResource"; import { IndexBufferBinding } from "../graphic/IndexBufferBinding"; import { SubMesh } from "../graphic/SubMesh"; import { VertexBufferBinding } from "../graphic/VertexBufferBinding"; import { VertexElement } from "../graphic/VertexElement"; import { MeshTopology } from "../graphic/enums/MeshTopology"; -import { ShaderProgram } from "../shader/ShaderProgram"; +import { Primitive } from "./Primitive"; /** * Mesh. */ -export abstract class Mesh extends GraphicsResource { +export abstract class Mesh extends ReferResource { /** Name. */ name: string; /** @internal */ - _vertexElementMap: Record = {}; - /** @internal */ - _glIndexType: number; - /** @internal */ - _glIndexByteCount: number; - /** @internal */ - _bufferStructChanged: boolean; - /** @internal */ - _platformPrimitive: IPlatformPrimitive; + _primitive: Primitive; - /** @internal */ - _instanceCount: number = 0; - /** @internal */ - _vertexBufferBindings: VertexBufferBinding[] = []; - /** @internal */ - _indexBufferBinding: IndexBufferBinding = null; - /** @internal */ - _vertexElements: VertexElement[] = []; - /** @internal */ - _enableVAO: boolean = true; /** @internal */ _updateFlagManager: UpdateFlagManager = new UpdateFlagManager(); @@ -80,7 +60,7 @@ export abstract class Mesh extends GraphicsResource { constructor(engine: Engine, name?: string) { super(engine); this.name = name; - this._platformPrimitive = this._engine._hardwareRenderer.createPlatformPrimitive(this); + this._primitive = new Primitive(engine); this._onBoundsChanged = this._onBoundsChanged.bind(this); const bounds = this._bounds; @@ -141,45 +121,24 @@ export abstract class Mesh extends GraphicsResource { * @internal */ _clearVertexElements(): void { - this._vertexElements.length = 0; - const vertexElementMap = this._vertexElementMap; - for (const k in vertexElementMap) { - delete vertexElementMap[k]; - } + this._primitive.clearVertexElements(); + this._updateFlagManager.dispatch(MeshModifyFlags.VertexElements); } /** * @internal */ _addVertexElement(element: VertexElement): void { - const vertexElementMap = this._vertexElementMap; - const vertexElements = this._vertexElements; - - const semantic = element.semantic; - const oldVertexElement = vertexElementMap[semantic]; - if (oldVertexElement) { - console.warn(`VertexElement ${semantic} already exists.`); - vertexElements.splice(vertexElements.indexOf(oldVertexElement), 1); - } - vertexElementMap[semantic] = element; - vertexElements.push(element); + this._primitive.addVertexElement(element); this._updateFlagManager.dispatch(MeshModifyFlags.VertexElements); - this._bufferStructChanged = true; } /** * @internal */ _removeVertexElement(index: number): void { - const vertexElements = this._vertexElements; - - // Delete the old vertex element - const vertexElement = vertexElements[index]; - vertexElements.splice(index, 1); - delete this._vertexElementMap[vertexElement.semantic]; - + this._primitive.removeVertexElement(index); this._updateFlagManager.dispatch(MeshModifyFlags.VertexElements); - this._bufferStructChanged = true; } /** @@ -187,17 +146,8 @@ export abstract class Mesh extends GraphicsResource { * @remarks should use together with `_setVertexElementsLength` */ _setVertexElement(index: number, element: VertexElement): void { - const vertexElementMap = this._vertexElementMap; - const vertexElements = this._vertexElements; - - // Delete the old vertex element - const oldVertexElement = vertexElements[index]; - oldVertexElement && delete vertexElementMap[oldVertexElement.semantic]; - - vertexElementMap[element.semantic] = element; - vertexElements[index] = element; + this._primitive.setVertexElement(index, element); this._updateFlagManager.dispatch(MeshModifyFlags.VertexElements); - this._bufferStructChanged = true; } /** @@ -205,48 +155,19 @@ export abstract class Mesh extends GraphicsResource { * */ _setVertexElementsLength(length: number): void { - const vertexElementMap = this._vertexElementMap; - const vertexElements = this._vertexElements; - - for (let i = length, n = vertexElements.length; i < n; i++) { - const element = vertexElements[i]; - delete vertexElementMap[element.semantic]; - } - vertexElements.length = length; + this._primitive.setVertexElementsLength(length); } /** * @internal */ _setVertexBufferBinding(index: number, binding: VertexBufferBinding): void { - const referCount = this._getReferCount(); - if (referCount > 0) { - this._vertexBufferBindings[index]?.buffer._addReferCount(-referCount); - binding?.buffer._addReferCount(referCount); - } - this._vertexBufferBindings[index] = binding; - this._bufferStructChanged = true; - } - - /** - * @internal - */ - _draw(shaderProgram: ShaderProgram, subMesh: SubMesh): void { - this._platformPrimitive.draw(shaderProgram, subMesh); - this._bufferStructChanged = false; + this._primitive.setVertexBufferBinding(index, binding); } override _addReferCount(value: number): void { super._addReferCount(value); - const vertexBufferBindings = this._vertexBufferBindings; - for (let i = 0, n = vertexBufferBindings.length; i < n; i++) { - vertexBufferBindings[i]?.buffer._addReferCount(value); - } - this._indexBufferBinding?._buffer._addReferCount(value); - } - - override _rebuild(): void { - this._engine._hardwareRenderer.createPlatformPrimitive(this); + this._primitive._addReferCount(value); } /** @@ -254,11 +175,7 @@ export abstract class Mesh extends GraphicsResource { */ protected override _onDestroy(): void { super._onDestroy(); - this._vertexBufferBindings = null; - this._indexBufferBinding = null; - this._vertexElements = null; - this._vertexElementMap = null; - this._platformPrimitive.destroy(); + this._primitive.destroy(); } /** @@ -275,22 +192,7 @@ export abstract class Mesh extends GraphicsResource { * @internal */ protected _setIndexBufferBinding(binding: IndexBufferBinding | null): void { - const lastBinding = this._indexBufferBinding; - const referCount = this._getReferCount(); - if (referCount > 0) { - lastBinding?.buffer._addReferCount(-referCount); - binding?.buffer._addReferCount(referCount); - } - if (binding) { - this._indexBufferBinding = binding; - this._glIndexType = BufferUtil._getGLIndexType(binding.format); - this._glIndexByteCount = BufferUtil._getGLIndexByteCount(binding.format); - (!lastBinding || lastBinding._buffer !== binding._buffer) && (this._bufferStructChanged = true); - } else { - this._indexBufferBinding = null; - this._glIndexType = undefined; - lastBinding && (this._bufferStructChanged = true); - } + this._primitive.setIndexBufferBinding(binding); } private _onBoundsChanged(): void { diff --git a/packages/core/src/graphic/Primitive.ts b/packages/core/src/graphic/Primitive.ts new file mode 100644 index 0000000000..5e6acd4b60 --- /dev/null +++ b/packages/core/src/graphic/Primitive.ts @@ -0,0 +1,173 @@ +import { IPlatformPrimitive } from "@galacean/engine-design"; +import { Engine } from "../Engine"; +import { GraphicsResource } from "../asset/GraphicsResource"; +import { ShaderProgram } from "../shader/ShaderProgram"; +import { BufferUtil } from "./BufferUtil"; +import { IndexBufferBinding } from "./IndexBufferBinding"; +import { SubPrimitive } from "./SubPrimitive"; +import { VertexBufferBinding } from "./VertexBufferBinding"; +import { VertexElement } from "./VertexElement"; + +/** + * @internal + * Primitive. + */ +export class Primitive extends GraphicsResource { + enableVAO: boolean = true; + instanceCount: number; + vertexBufferBindings: VertexBufferBinding[] = []; + + /** @internal */ + _vertexElementMap: Record = {}; + /** @internal */ + _glIndexType: number; + /** @internal */ + _glIndexByteCount: number; + /** @internal */ + _bufferStructChanged: boolean = false; + + private _vertexElements: VertexElement[] = []; + private _indexBufferBinding: IndexBufferBinding; + private _platformPrimitive: IPlatformPrimitive; + + get vertexElements(): VertexElement[] { + return this._vertexElements; + } + + get indexBufferBinding(): IndexBufferBinding { + return this._indexBufferBinding; + } + + constructor(engine: Engine) { + super(engine); + this._platformPrimitive = engine._hardwareRenderer.createPlatformPrimitive(this); + } + + addVertexElement(element: VertexElement): void { + const vertexElementMap = this._vertexElementMap; + const vertexElements = this._vertexElements; + + const semantic = element.attribute; + const oldVertexElement = vertexElementMap[semantic]; + if (oldVertexElement) { + console.warn(`VertexElement ${semantic} already exists.`); + vertexElements.splice(vertexElements.indexOf(oldVertexElement), 1); + } + vertexElementMap[semantic] = element; + vertexElements.push(element); + this._bufferStructChanged = true; + } + + removeVertexElement(index: number): void { + const vertexElements = this._vertexElements; + // Delete the old vertex element + const vertexElement = vertexElements[index]; + vertexElements.splice(index, 1); + delete this._vertexElementMap[vertexElement.attribute]; + this._bufferStructChanged = true; + } + + clearVertexElements(): void { + this._vertexElements.length = 0; + const vertexElementMap = this._vertexElementMap; + for (const k in vertexElementMap) { + delete vertexElementMap[k]; + } + this._bufferStructChanged = true; + } + + /** + * @remarks should use together with `setVertexElementsLength` + */ + setVertexElement(index: number, element: VertexElement): void { + const vertexElementMap = this._vertexElementMap; + const vertexElements = this._vertexElements; + + // Delete the old vertex element + const oldVertexElement = vertexElements[index]; + oldVertexElement && delete vertexElementMap[oldVertexElement.attribute]; + + vertexElementMap[element.attribute] = element; + vertexElements[index] = element; + this._bufferStructChanged = true; + } + + setVertexElementsLength(length: number): void { + const vertexElementMap = this._vertexElementMap; + const vertexElements = this._vertexElements; + + for (let i = length, n = vertexElements.length; i < n; i++) { + const element = vertexElements[i]; + delete vertexElementMap[element.attribute]; + } + vertexElements.length = length; + } + + setVertexBufferBinding(index: number, binding: VertexBufferBinding): void { + const referCount = this._getReferCount(); + const vertexBufferBindings = this.vertexBufferBindings; + if (referCount > 0) { + vertexBufferBindings[index]?.buffer._addReferCount(-referCount); + binding?.buffer._addReferCount(referCount); + } + vertexBufferBindings[index] = binding; + this._bufferStructChanged = true; + } + + /** + * Set vertex buffer binding. + * @param vertexBufferBindings - Vertex buffer binding + * @param firstIndex - First vertex buffer index, the default value is 0 + */ + setVertexBufferBindings(vertexBufferBindings: VertexBufferBinding[], firstIndex: number = 0): void { + const bindings = this.vertexBufferBindings; + const count = vertexBufferBindings.length; + const needLength = firstIndex + count; + bindings.length < needLength && (bindings.length = needLength); + for (let i = 0; i < count; i++) { + this.setVertexBufferBinding(firstIndex + i, vertexBufferBindings[i]); + } + } + + setIndexBufferBinding(binding: IndexBufferBinding | null): void { + const lastBinding = this.indexBufferBinding; + const referCount = this._getReferCount(); + + if (lastBinding !== binding) { + this._indexBufferBinding = binding; + referCount > 0 && lastBinding?.buffer._addReferCount(-referCount); + if (binding) { + referCount > 0 && binding.buffer._addReferCount(referCount); + this._glIndexType = BufferUtil._getGLIndexType(binding.format); + this._glIndexByteCount = BufferUtil._getGLIndexByteCount(binding.format); + } else { + this._glIndexType = undefined; + } + this._bufferStructChanged = lastBinding?.buffer !== binding?.buffer; + } + } + + draw(shaderProgram: ShaderProgram, subMesh: SubPrimitive): void { + this._platformPrimitive.draw(shaderProgram, subMesh); + this._bufferStructChanged = false; + } + + override _addReferCount(value: number): void { + super._addReferCount(value); + const vertexBufferBindings = this.vertexBufferBindings; + for (let i = 0, n = vertexBufferBindings.length; i < n; i++) { + vertexBufferBindings[i]?.buffer._addReferCount(value); + } + this.indexBufferBinding?._buffer._addReferCount(value); + } + + override _rebuild(): void { + this._engine._hardwareRenderer.createPlatformPrimitive(this); + this._isContentLost = false; + } + + protected override _onDestroy(): void { + this._platformPrimitive.destroy(); + this._vertexElementMap = null; + } +} diff --git a/packages/core/src/graphic/SubPrimitive.ts b/packages/core/src/graphic/SubPrimitive.ts new file mode 100644 index 0000000000..1d920dfbd7 --- /dev/null +++ b/packages/core/src/graphic/SubPrimitive.ts @@ -0,0 +1,10 @@ +import { MeshTopology } from "./enums/MeshTopology"; + +export class SubPrimitive { + /** Start drawing offset. */ + start: number; + /** Drawing count. */ + count: number; + /** Drawing topology. */ + topology: MeshTopology; +} diff --git a/packages/core/src/graphic/index.ts b/packages/core/src/graphic/index.ts index 6e95b20518..cf4bf3c17c 100644 --- a/packages/core/src/graphic/index.ts +++ b/packages/core/src/graphic/index.ts @@ -11,3 +11,4 @@ export { Mesh } from "./Mesh"; export { SubMesh } from "./SubMesh"; export { VertexBufferBinding } from "./VertexBufferBinding"; export { VertexElement } from "./VertexElement"; +export { Primitive } from "./Primitive"; diff --git a/packages/core/src/mesh/BlendShapeManager.ts b/packages/core/src/mesh/BlendShapeManager.ts index bfc2c93e2d..1d4ddce8e5 100644 --- a/packages/core/src/mesh/BlendShapeManager.ts +++ b/packages/core/src/mesh/BlendShapeManager.ts @@ -123,11 +123,11 @@ export class BlendShapeManager { } this._filterCondensedBlendShapeWeights(skinnedMeshRenderer.blendShapeWeights, condensedBlendShapeWeights); shaderData.setFloatArray(BlendShapeManager._blendShapeWeightsProperty, condensedBlendShapeWeights); - this._modelMesh._enableVAO = false; + this._modelMesh._primitive.enableVAO = false; blendShapeCount = maxBlendCount; } else { shaderData.setFloatArray(BlendShapeManager._blendShapeWeightsProperty, skinnedMeshRenderer.blendShapeWeights); - this._modelMesh._enableVAO = true; + this._modelMesh._primitive.enableVAO = true; } shaderData.disableMacro(BlendShapeManager._blendShapeTextureMacro); shaderData.disableMacro("RENDERER_BLENDSHAPE_COUNT"); @@ -203,11 +203,14 @@ export class BlendShapeManager { if (this._bufferBindingOffset !== -1) { return; } - const { _internalVertexBufferIndex, _vertexBufferBindings } = this._modelMesh; + + const modelMesh = this._modelMesh; + const internalVertexBufferIndex = modelMesh._internalVertexBufferIndex; + const vertexBufferBindings = modelMesh._primitive.vertexBufferBindings; let i = 0; - const n = Math.max(_vertexBufferBindings.length, _internalVertexBufferIndex + 1); + const n = Math.max(vertexBufferBindings.length, internalVertexBufferIndex + 1); for (; i < n; i++) { - if (!_vertexBufferBindings[i] && i !== _internalVertexBufferIndex) { + if (!vertexBufferBindings[i] && i !== internalVertexBufferIndex) { break; } } @@ -543,7 +546,7 @@ export class BlendShapeManager { condensedBlendShapeWeights: Float32Array ): void { const condensedWeightsCount = condensedBlendShapeWeights.length; - const vertexElements = this._modelMesh._vertexElements; + const vertexElements = this._modelMesh._primitive.vertexElements; const vertexBufferStoreInfo = this._storeInVertexBufferInfo; let thresholdWeight = Number.POSITIVE_INFINITY; let thresholdIndex: number; diff --git a/packages/core/src/mesh/BufferMesh.ts b/packages/core/src/mesh/BufferMesh.ts index b0f25de682..aa0b88c7b1 100644 --- a/packages/core/src/mesh/BufferMesh.ts +++ b/packages/core/src/mesh/BufferMesh.ts @@ -8,32 +8,32 @@ export class BufferMesh extends Mesh { * Instanced count, disable instanced drawing when set zero. */ get instanceCount(): number { - return this._instanceCount; + return this._primitive.instanceCount; } set instanceCount(value: number) { - this._instanceCount = value; + this._primitive.instanceCount = value; } /** * Vertex buffer binding collection. */ get vertexBufferBindings(): Readonly { - return this._vertexBufferBindings; + return this._primitive.vertexBufferBindings; } /** * Index buffer binding. */ get indexBufferBinding(): IndexBufferBinding { - return this._indexBufferBinding; + return this._primitive.indexBufferBinding; } /** * Vertex element collection. */ get vertexElements(): Readonly { - return this._vertexElements; + return this._primitive.vertexElements; } /** @@ -68,7 +68,7 @@ export class BufferMesh extends Mesh { const isBinding = binding.buffer !== undefined; isBinding || (binding = new VertexBufferBinding(bufferOrBinding, strideOrFirstIndex)); - const bindings = this._vertexBufferBindings; + const bindings = this._primitive.vertexBufferBindings; bindings.length <= index && (bindings.length = index + 1); this._setVertexBufferBinding(isBinding ? strideOrFirstIndex : index, binding); } @@ -79,7 +79,7 @@ export class BufferMesh extends Mesh { * @param firstIndex - First vertex buffer index, the default value is 0 */ setVertexBufferBindings(vertexBufferBindings: VertexBufferBinding[], firstIndex: number = 0): void { - const bindings = this._vertexBufferBindings; + const bindings = this._primitive.vertexBufferBindings; const count = vertexBufferBindings.length; const needLength = firstIndex + count; bindings.length < needLength && (bindings.length = needLength); diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index fd569ff0b1..3ad094d143 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -112,7 +112,7 @@ export class MeshRenderer extends Renderer { const mesh = this._mesh; if (this._dirtyUpdateFlag & MeshRendererUpdateFlags.VertexElementMacro) { const shaderData = this.shaderData; - const vertexElements = mesh._vertexElements; + const vertexElements = mesh._primitive.vertexElements; shaderData.disableMacro(MeshRenderer._uvMacro); shaderData.disableMacro(MeshRenderer._uv1Macro); @@ -121,7 +121,7 @@ export class MeshRenderer extends Renderer { shaderData.disableMacro(MeshRenderer._enableVertexColorMacro); for (let i = 0, n = vertexElements.length; i < n; i++) { - switch (vertexElements[i].semantic) { + switch (vertexElements[i].attribute) { case "TEXCOORD_0": shaderData.enableMacro(MeshRenderer._uvMacro); break; @@ -145,12 +145,15 @@ export class MeshRenderer extends Renderer { const materials = this._materials; const subMeshes = mesh.subMeshes; const renderPipeline = context.camera._renderPipeline; - const meshRenderDataPool = this._engine._meshRenderDataPool; + const meshRenderDataPool = this._engine._renderDataPool; for (let i = 0, n = subMeshes.length; i < n; i++) { const material = materials[i]; - if (!material) continue; + if (!material) { + continue; + } + const renderData = meshRenderDataPool.getFromPool(); - renderData.set(this, material, mesh, subMeshes[i]); + renderData.setX(this, material, mesh._primitive, subMeshes[i]); renderPipeline.pushRenderData(context, renderData); } } diff --git a/packages/core/src/mesh/ModelMesh.ts b/packages/core/src/mesh/ModelMesh.ts index 3c39f0df40..972cfedbe2 100644 --- a/packages/core/src/mesh/ModelMesh.ts +++ b/packages/core/src/mesh/ModelMesh.ts @@ -69,9 +69,9 @@ export class ModelMesh extends Mesh { get vertexCount(): number { if (this._vertexCountDirty) { let vertexCount = 0; - const positionElement = this._vertexElementMap[VertexAttribute.Position]; + const positionElement = this._primitive._vertexElementMap[VertexAttribute.Position]; if (positionElement) { - const positionBufferBinding = this._vertexBufferBindings[positionElement.bindingIndex]; + const positionBufferBinding = this._primitive.vertexBufferBindings[positionElement.bindingIndex]; if (positionBufferBinding) { vertexCount = positionBufferBinding.buffer.byteLength / positionBufferBinding.stride; } @@ -87,15 +87,15 @@ export class ModelMesh extends Mesh { */ get vertexElements(): Readonly { this._updateVertexElements(); - return this._vertexElements; + return this._primitive.vertexElements; } /** * Vertex buffer binding collection. */ - get vertexBufferBindings(): Readonly { + get vertexBufferBindings(): ReadonlyArray { // @todo: update if dirty like `vertexElements` is better - return this._vertexBufferBindings; + return this._primitive.vertexBufferBindings; } /** @@ -462,7 +462,7 @@ export class ModelMesh extends Mesh { } // Clear data if vertex element not contains - const vertexElementMap = this._vertexElementMap; + const vertexElementMap = this._primitive._vertexElementMap; vertexElementMap[VertexAttribute.Position] || this.setPositions(null); vertexElementMap[VertexAttribute.Normal] || this.setNormals(null); vertexElementMap[VertexAttribute.Color] || this.setColors(null); @@ -481,7 +481,7 @@ export class ModelMesh extends Mesh { // Destroy internal vertex buffer immediately const internalVertexBufferIndex = this._internalVertexBufferCreatedInfo.z; if (internalVertexBufferIndex !== -1) { - this._vertexBufferBindings[internalVertexBufferIndex]?.buffer.destroy(); + this._primitive.vertexBufferBindings[internalVertexBufferIndex]?.buffer.destroy(); this._setVertexBufferBinding(internalVertexBufferIndex, null); this._internalVertexBufferCreatedInfo.z = -1; } @@ -519,7 +519,7 @@ export class ModelMesh extends Mesh { isBinding || (binding = new VertexBufferBinding(bufferOrBinding, strideOrIndex)); const index = isBinding ? strideOrIndex : indexOrNull; - const bindings = this._vertexBufferBindings; + const bindings = this._primitive.vertexBufferBindings; const updateInfos = this._vertexBufferInfos; const needLength = index + 1; @@ -548,7 +548,7 @@ export class ModelMesh extends Mesh { */ setVertexBufferBindings(vertexBufferBindings: VertexBufferBinding[], firstIndex: number = 0): void { const count = vertexBufferBindings.length; - const bindings = this._vertexBufferBindings; + const bindings = this._primitive.vertexBufferBindings; const updateInfos = this._vertexBufferInfos; const needLength = firstIndex + count; @@ -572,6 +572,16 @@ export class ModelMesh extends Mesh { this._vertexCountDirty = true; } + /** + * Get `VertexElement` by attribute. + * @param attribute - Vertex attribute + * @returns Vertex element + */ + getVertexElement(attribute: VertexAttribute): VertexElement | null { + this._updateVertexElements(); + return this._primitive._vertexElementMap[attribute]; + } + /** * Add a BlendShape for this ModelMesh. * @param blendShape - The BlendShape @@ -617,7 +627,7 @@ export class ModelMesh extends Mesh { this._updateAdvancedVertices(); const vertexBufferInfos = this._vertexBufferInfos; - const vertexBufferBindings = this._vertexBufferBindings; + const vertexBufferBindings = this._primitive.vertexBufferBindings; for (let i = 0, n = vertexBufferBindings.length; i < n; i++) { const vertexBufferInfo = vertexBufferInfos[i]; // VertexBufferInfo maybe undefined @@ -632,7 +642,7 @@ export class ModelMesh extends Mesh { if (this._indicesChangeFlag) { const { _indices: indices } = this; - const indexBuffer = this._indexBufferBinding?._buffer; + const indexBuffer = this._primitive.indexBufferBinding?._buffer; if (indices) { if (!indexBuffer || indices.byteLength != indexBuffer.byteLength) { indexBuffer?.destroy(); @@ -640,7 +650,7 @@ export class ModelMesh extends Mesh { this._setIndexBufferBinding(new IndexBufferBinding(newIndexBuffer, this._indicesFormat)); } else { indexBuffer.setData(indices); - if (this._indexBufferBinding._format !== this._indicesFormat) { + if (this._primitive.indexBufferBinding._format !== this._indicesFormat) { this._setIndexBufferBinding(new IndexBufferBinding(indexBuffer, this._indicesFormat)); } } @@ -753,7 +763,7 @@ export class ModelMesh extends Mesh { * @internal */ override _setVertexBufferBinding(index: number, binding: VertexBufferBinding): void { - const vertexBufferBindings = this._vertexBufferBindings; + const vertexBufferBindings = this._primitive.vertexBufferBindings; const updateInfos = this._vertexBufferInfos; const onVertexBufferChanged = () => { if (!this._advancedDataSyncToBuffer) { @@ -813,7 +823,7 @@ export class ModelMesh extends Mesh { ): T[] | null { const advancedVertexDataVersions = this._advancedVertexDataVersions; const advancedDataVersion = advancedVertexDataVersions[vertexElementIndex] ?? -1; - const vertexElement = this._vertexElementMap[vertexAttribute]; + const vertexElement = this._primitive._vertexElementMap[vertexAttribute]; const bufferDataVersion = vertexElement ? this._vertexBufferInfos[vertexElement.bindingIndex].dataVersion : -1; if (advancedDataVersion >= bufferDataVersion) { return vertices; @@ -857,7 +867,7 @@ export class ModelMesh extends Mesh { // Destroy old internal vertex buffer const createdInternalBufferIndex = bufferCreatedInfo.z; if (createdInternalBufferIndex !== -1) { - this._vertexBufferBindings[createdInternalBufferIndex]?.buffer.destroy(); + this._primitive.vertexBufferBindings[createdInternalBufferIndex]?.buffer.destroy(); this._setVertexBufferBinding(createdInternalBufferIndex, null); } @@ -903,12 +913,13 @@ export class ModelMesh extends Mesh { attributeType: string, onVertexParse: (dataReader: TypedArray, offset: number) => T ): T[] { - const vertexElement = this._vertexElementMap[attributeType]; + const primitive = this._primitive; + const vertexElement = primitive._vertexElementMap[attributeType]; if (!vertexElement) { return null; } - const bufferBinding = this._vertexBufferBindings[vertexElement.bindingIndex]; + const bufferBinding = primitive.vertexBufferBindings[vertexElement.bindingIndex]; const buffer = bufferBinding?.buffer; if (!buffer) { return null; @@ -939,7 +950,8 @@ export class ModelMesh extends Mesh { attribute: VertexAttribute, flag: VertexElementFlags ): void { - const vertexElementMap = this._vertexElementMap; + const primitive = this._primitive; + const vertexElementMap = primitive._vertexElementMap; if (vertices) { if (!vertexElementMap[attribute]) { @@ -955,7 +967,7 @@ export class ModelMesh extends Mesh { } else { const vertexElement = vertexElementMap[attribute]; if (vertexElement) { - const index = this._vertexElements.indexOf(vertexElement); + const index = this._primitive.vertexElements.indexOf(vertexElement); if (index >= this._internalVertexElementsOffset) { this._internalVertexBufferStride -= this._getAttributeByteLength(attribute); this._internalVertexElementsFlags &= ~flag; @@ -1031,7 +1043,7 @@ export class ModelMesh extends Mesh { } private _updateVertexElements(): void { - const vertexElements = this._vertexElements; + const vertexElements = this._primitive.vertexElements; const bsManager = this._blendShapeManager; const previousCount = vertexElements.length; const previousBSOffset = bsManager._vertexElementOffset; @@ -1116,9 +1128,10 @@ export class ModelMesh extends Mesh { elementIndex: VertexElementIndex, onVertexWrite: (typedArray: TypedArray, offset: number, index: number) => void ): void { - const vertexElement = this._vertexElementMap[attribute]; + const primitive = this._primitive; + const vertexElement = primitive._vertexElementMap[attribute]; const bindingIndex = vertexElement.bindingIndex; - const bufferBinding = this._vertexBufferBindings[bindingIndex]; + const bufferBinding = primitive.vertexBufferBindings[bindingIndex]; const buffer = bufferBinding?.buffer; if (!buffer) { return; @@ -1220,7 +1233,7 @@ export class ModelMesh extends Mesh { } let i = 0; - const vertexBufferBindings = this._vertexBufferBindings; + const vertexBufferBindings = this._primitive.vertexBufferBindings; for (let n = vertexBufferBindings.length; i < n; i++) { if (!vertexBufferBindings[i]) { break; @@ -1295,7 +1308,7 @@ export class ModelMesh extends Mesh { this._blendShapeManager._releaseMemoryCache(); if (!isDestroy) { - this._vertexBufferBindings[this._internalVertexBufferIndex]?.buffer.markAsUnreadable(); + this._primitive.vertexBufferBindings[this._internalVertexBufferIndex]?.buffer.markAsUnreadable(); // If release data, we need update buffer data version to ensure get data method can read buffer const dataVersion = this._dataVersionCounter++; diff --git a/packages/core/src/particle/ParticleBufferUtils.ts b/packages/core/src/particle/ParticleBufferUtils.ts new file mode 100644 index 0000000000..8f5434f85a --- /dev/null +++ b/packages/core/src/particle/ParticleBufferUtils.ts @@ -0,0 +1,79 @@ +import { Engine } from "../Engine"; +import { Buffer } from "../graphic/Buffer"; +import { IndexBufferBinding } from "../graphic/IndexBufferBinding"; +import { VertexBufferBinding } from "../graphic/VertexBufferBinding"; +import { VertexElement } from "../graphic/VertexElement"; +import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; +import { BufferUsage } from "../graphic/enums/BufferUsage"; +import { IndexFormat } from "../graphic/enums/IndexFormat"; +import { VertexElementFormat } from "../graphic/enums/VertexElementFormat"; +import { ParticleBillboardVertexAttribute } from "./enums/attributes/BillboardParticleVertexAttribute"; +import { ParticleInstanceVertexAttribute } from "./enums/attributes/ParticleInstanceVertexAttribute"; + +/** + * @internal + */ +export class ParticleBufferUtils { + readonly billboardVertexElement = new VertexElement( + ParticleBillboardVertexAttribute.cornerTextureCoordinate, + 0, + VertexElementFormat.Vector4, + 0 + ); + + readonly instanceVertexElements = [ + new VertexElement(ParticleInstanceVertexAttribute.ShapePositionStartLifeTime, 0, VertexElementFormat.Vector4, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.DirectionTime, 16, VertexElementFormat.Vector4, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.StartColor, 32, VertexElementFormat.Vector4, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.StartSize, 48, VertexElementFormat.Vector3, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.StartRotation0, 60, VertexElementFormat.Vector3, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.StartSpeed, 72, VertexElementFormat.Float, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.Random0, 76, VertexElementFormat.Vector4, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.Random1, 92, VertexElementFormat.Vector4, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.SimulationWorldPosition, 108, VertexElementFormat.Vector3, 1, 1), //TODO:local模式下可省去内存 + new VertexElement(ParticleInstanceVertexAttribute.SimulationWorldRotation, 120, VertexElementFormat.Vector4, 1, 1), + new VertexElement(ParticleInstanceVertexAttribute.SimulationUV, 136, VertexElementFormat.Vector4, 1, 1) + ]; + + readonly instanceVertexStride = 152; + readonly instanceVertexFloatStride = this.instanceVertexStride / 4; + + readonly startLifeTimeOffset = 3; + readonly timeOffset = 7; + readonly simulationUVOffset = 34; + + readonly billboardIndexCount = 6; + + readonly billboardVertexBufferBinding: VertexBufferBinding; + readonly billboardIndexBufferBinding: IndexBufferBinding; + + constructor(engine: Engine) { + const stride = 16; + const billboardGeometryBuffer = new Buffer( + engine, + BufferBindFlag.VertexBuffer, + stride * 4, + BufferUsage.Static, + false + ); + this.billboardVertexBufferBinding = new VertexBufferBinding(billboardGeometryBuffer, stride); + + const indexBuffer = new Buffer( + engine, + BufferBindFlag.IndexBuffer, + this.billboardIndexCount, + BufferUsage.Static, + false + ); + this.billboardIndexBufferBinding = new IndexBufferBinding(indexBuffer, IndexFormat.UInt8); + + this.setBufferData(); + } + + setBufferData(): void { + this.billboardVertexBufferBinding.buffer.setData( + new Float32Array([-0.5, -0.5, 0, 1, 0.5, -0.5, 1, 1, 0.5, 0.5, 1, 0, -0.5, 0.5, 0, 0]) + ); + this.billboardIndexBufferBinding.buffer.setData(new Uint8Array([0, 2, 3, 0, 1, 2])); + } +} diff --git a/packages/core/src/particle/ParticleGenerator.ts b/packages/core/src/particle/ParticleGenerator.ts new file mode 100644 index 0000000000..2212c8b13f --- /dev/null +++ b/packages/core/src/particle/ParticleGenerator.ts @@ -0,0 +1,686 @@ +import { Color, MathUtil, Quaternion, Vector3 } from "@galacean/engine-math"; +import { Transform } from "../Transform"; +import { deepClone, ignoreClone } from "../clone/CloneManager"; +import { ColorSpace } from "../enums/ColorSpace"; +import { Primitive } from "../graphic/Primitive"; +import { SubMesh } from "../graphic/SubMesh"; +import { SubPrimitive } from "../graphic/SubPrimitive"; +import { VertexBufferBinding } from "../graphic/VertexBufferBinding"; +import { VertexElement } from "../graphic/VertexElement"; +import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; +import { BufferUsage } from "../graphic/enums/BufferUsage"; +import { MeshTopology } from "../graphic/enums/MeshTopology"; +import { SetDataOptions } from "../graphic/enums/SetDataOptions"; +import { VertexAttribute } from "../mesh"; +import { ShaderData } from "../shader"; +import { Buffer } from "./../graphic/Buffer"; +import { ParticleRenderer } from "./ParticleRenderer"; +import { ParticleCurveMode } from "./enums/ParticleCurveMode"; +import { ParticleGradientMode } from "./enums/ParticleGradientMode"; +import { ParticleRenderMode } from "./enums/ParticleRenderMode"; +import { ParticleSimulationSpace } from "./enums/ParticleSimulationSpace"; +import { ParticleStopMode } from "./enums/ParticleStopMode"; +import { ColorOverLifetimeModule } from "./modules/ColorOverLifetimeModule"; +import { EmissionModule } from "./modules/EmissionModule"; +import { MainModule } from "./modules/MainModule"; +import { RotationOverLifetimeModule } from "./modules/RotationOverLifetimeModule"; +import { SizeOverLifetimeModule } from "./modules/SizeOverLifetimeModule"; +import { TextureSheetAnimationModule } from "./modules/TextureSheetAnimationModule"; +import { VelocityOverLifetimeModule } from "./modules/VelocityOverLifetimeModule"; + +/** + * Particle Generator. + */ +export class ParticleGenerator { + /** @internal */ + private static _tempVector30 = new Vector3(); + /** @internal */ + private static _tempVector31 = new Vector3(); + /** @internal */ + private static _tempColor0 = new Color(); + /** @internal */ + private static _tempParticleRenderers = new Array(); + private static readonly _particleIncreaseCount = 128; + + /** Use auto random seed. */ + useAutoRandomSeed = true; + + /** Main module. */ + @deepClone + readonly main = new MainModule(this); + /** Emission module. */ + @deepClone + readonly emission = new EmissionModule(this); + /** Velocity over lifetime module. */ + @deepClone + readonly velocityOverLifetime = new VelocityOverLifetimeModule(this); + /** Size over lifetime module. */ + @deepClone + readonly sizeOverLifetime = new SizeOverLifetimeModule(this); + /** Rotation over lifetime module. */ + @deepClone + readonly rotationOverLifetime = new RotationOverLifetimeModule(this); + /** Color over lifetime module. */ + @deepClone + readonly colorOverLifetime = new ColorOverLifetimeModule(this); + /** Texture sheet animation module. */ + @deepClone + readonly textureSheetAnimation = new TextureSheetAnimationModule(this); + + /** @internal */ + _currentParticleCount = 0; + /** @internal */ + @ignoreClone + _playTime = 0; + + /** @internal */ + @ignoreClone + _firstNewElement = 0; + /** @internal */ + @ignoreClone + _firstActiveElement = 0; + /** @internal */ + @ignoreClone + _firstFreeElement = 0; + /** @internal */ + @ignoreClone + _firstRetiredElement = 0; + /** @internal */ + @ignoreClone + _primitive: Primitive; + /** @internal */ + @ignoreClone + _vertexBufferBindings = new Array(); + /** @internal */ + @ignoreClone + _subPrimitive = new SubMesh(0, 0, MeshTopology.Triangles); + /** @internal */ + @ignoreClone + readonly _renderer: ParticleRenderer; + + @ignoreClone + private _isPlaying = false; + @ignoreClone + private _instanceBufferResized = false; + @ignoreClone + private _waitProcessRetiredElementCount = 0; + @ignoreClone + private _instanceVertexBufferBinding: VertexBufferBinding; + @ignoreClone + private _instanceVertices: Float32Array; + private _randomSeed = 0; + + /** + * Whether the particle generator is contain alive or is still creating particles. + */ + get isAlive(): boolean { + if (this._isPlaying) { + return true; + } + + return this._firstActiveElement !== this._firstFreeElement; + } + + /** + * Random seed. + * + * @remarks + * If `useAutoRandomSeed` is true, this value will be random changed when play. + * If you set this value custom, `useAutoRandomSeed` will be false. + */ + get randomSeed(): number { + return this._randomSeed; + } + + set randomSeed(value: number) { + this._resetGlobalRandSeed(value); + this.useAutoRandomSeed = false; + } + + constructor(renderer: ParticleRenderer) { + this._renderer = renderer; + const subPrimitive = new SubPrimitive(); + subPrimitive.start = 0; + + this._primitive = new Primitive(renderer.engine); + this._reorganizeGeometryBuffers(); + this._resizeInstanceBuffer(ParticleGenerator._particleIncreaseCount); + + this.emission.enabled = true; + } + + /** + * Start emitting particles. + * @param withChildren - Whether to start the particle generator of the child entity + */ + play(withChildren: boolean = true): void { + if (withChildren) { + const particleRenderers = this._renderer.entity.getComponentsIncludeChildren( + ParticleRenderer, + ParticleGenerator._tempParticleRenderers + ); + for (let i = 0, n = particleRenderers.length; i < n; i++) { + const particleRenderer = particleRenderers[i]; + particleRenderer.generator.play(false); + } + } else { + this._isPlaying = true; + if (this.useAutoRandomSeed) { + this._resetGlobalRandSeed(Math.floor(Math.random() * 0xffffffff)); // 2^32 - 1 + } + } + } + + /** + * Stop emitting particles. + * @param withChildren - Whether to stop the particle generator of the child entity + * @param stopMode - Stop mode + */ + stop(withChildren: boolean = true, stopMode: ParticleStopMode = ParticleStopMode.StopEmitting): void { + if (withChildren) { + const particleRenderers = this._renderer.entity.getComponentsIncludeChildren( + ParticleRenderer, + ParticleGenerator._tempParticleRenderers + ); + for (let i = 0, n = particleRenderers.length; i < n; i++) { + const particleRenderer = particleRenderers[i]; + particleRenderer.generator.stop(false, stopMode); + } + } else { + this._isPlaying = false; + if (stopMode === ParticleStopMode.StopEmittingAndClear) { + // Move the pointer to free immediately + const firstFreeElement = this._firstFreeElement; + this._firstRetiredElement = firstFreeElement; + this._firstActiveElement = firstFreeElement; + this._firstNewElement = firstFreeElement; + this._playTime = 0; + + this.emission._resetBurst(); + } + } + } + + /** + * Manually emit certain number of particles immediately. + * @param count - Number of particles to emit + */ + emit(count: number): void { + this._emit(this._playTime, count); + } + + /** + * @internal + */ + _emit(time: number, count: number): void { + const position = ParticleGenerator._tempVector30; + const direction = ParticleGenerator._tempVector31; + if (this.emission.enabled) { + const transform = this._renderer.entity.transform; + const shape = this.emission.shape; + for (let i = 0; i < count; i++) { + if (shape) { + shape._generatePositionAndDirection(this.emission._shapeRand, time, position, direction); + const positionScale = this.main._getPositionScale(); + position.multiply(positionScale); + direction.normalize().multiply(positionScale); + } else { + position.set(0, 0, 0); + direction.set(0, 0, -1); + } + this._addNewParticle(position, direction, transform, time); + } + } + } + + /** + * @internal + */ + _update(elapsedTime: number): void { + const lastPlayTime = this._playTime; + this._playTime += elapsedTime; + + this._retireActiveParticles(); + this._freeRetiredParticles(); + + if (this.emission.enabled && this._isPlaying) { + this.emission._emit(lastPlayTime, this._playTime); + + if (!this.main.isLoop && this._playTime > this.main.duration) { + this._isPlaying = false; + } + } + + // Reset play time when is not playing and no active particles to avoid potential precision problems in GPU + if (!this.isAlive) { + this._playTime = 0; + } + + // Add new particles to vertex buffer when has wait process retired element or new particle + // + // Another choice is just add new particles to vertex buffer and render all particles ignore the retired particle in shader, especially billboards + // But webgl don't support map buffer range, so this choice don't have performance advantage even less set data to GPU + if ( + this._firstNewElement != this._firstFreeElement || + this._waitProcessRetiredElementCount > 0 || + this._instanceBufferResized || + this._instanceVertexBufferBinding._buffer.isContentLost + ) { + this._addActiveParticlesToVertexBuffer(); + } + } + + /** + * @internal + */ + _reorganizeGeometryBuffers(): void { + const renderer = this._renderer; + const particleUtils = renderer.engine._particleBufferUtils; + const primitive = this._primitive; + const vertexBufferBindings = this._vertexBufferBindings; + + primitive.clearVertexElements(); + vertexBufferBindings.length = 0; + + if (renderer.renderMode === ParticleRenderMode.Mesh) { + const mesh = renderer.mesh; + if (!mesh) { + return; + } + + const positionElement = mesh.getVertexElement(VertexAttribute.Position); + const colorElement = mesh.getVertexElement(VertexAttribute.Color); + const uvElement = mesh.getVertexElement(VertexAttribute.UV); + const positionBufferBinding = positionElement ? mesh.vertexBufferBindings[positionElement.bindingIndex] : null; + const colorBufferBinding = colorElement ? mesh.vertexBufferBindings[colorElement.bindingIndex] : null; + const uvBufferBinding = uvElement ? mesh.vertexBufferBindings[uvElement.bindingIndex] : null; + + if (positionBufferBinding) { + const index = this._addVertexBufferBindingsFilterDuplicate(positionBufferBinding, vertexBufferBindings); + primitive.addVertexElement( + new VertexElement(VertexAttribute.Position, positionElement.offset, positionElement.format, index) + ); + } + + if (colorBufferBinding) { + const index = this._addVertexBufferBindingsFilterDuplicate(colorBufferBinding, vertexBufferBindings); + primitive.addVertexElement( + new VertexElement(VertexAttribute.Color, colorElement.offset, colorElement.format, index) + ); + } + + if (uvBufferBinding) { + const index = this._addVertexBufferBindingsFilterDuplicate(uvBufferBinding, vertexBufferBindings); + primitive.addVertexElement(new VertexElement(VertexAttribute.UV, uvElement.offset, uvElement.format, index)); + } + + // @todo: multi subMesh or not support + const indexBufferBinding = mesh._primitive.indexBufferBinding; + primitive.setIndexBufferBinding(indexBufferBinding); + this._subPrimitive.count = indexBufferBinding.buffer.byteLength / primitive._glIndexByteCount; + } else { + primitive.addVertexElement(particleUtils.billboardVertexElement); + vertexBufferBindings.push(particleUtils.billboardVertexBufferBinding); + primitive.setIndexBufferBinding(particleUtils.billboardIndexBufferBinding); + this._subPrimitive.count = particleUtils.billboardIndexCount; + } + primitive.setVertexBufferBindings(vertexBufferBindings); + + const instanceVertexElements = particleUtils.instanceVertexElements; + const bindingIndex = vertexBufferBindings.length; + for (let i = 0, n = instanceVertexElements.length; i < n; i++) { + const element = instanceVertexElements[i]; + primitive.addVertexElement( + new VertexElement(element.attribute, element.offset, element.format, bindingIndex, element.instanceStepRate) + ); + } + } + + /** + * @internal + */ + _resizeInstanceBuffer(increaseCount: number): void { + this._instanceVertexBufferBinding?.buffer.destroy(); + + const particleUtils = this._renderer.engine._particleBufferUtils; + const stride = particleUtils.instanceVertexStride; + const newParticleCount = this._currentParticleCount + increaseCount; + const newByteLength = stride * newParticleCount; + const engine = this._renderer.engine; + const vertexInstanceBuffer = new Buffer( + engine, + BufferBindFlag.VertexBuffer, + newByteLength, + BufferUsage.Dynamic, + false + ); + vertexInstanceBuffer.isGCIgnored = true; + + const vertexBufferBindings = this._primitive.vertexBufferBindings; + const vertexBufferBinding = new VertexBufferBinding(vertexInstanceBuffer, stride); + + const instanceVertices = new Float32Array(newByteLength / 4); + const lastInstanceVertices = this._instanceVertices; + if (lastInstanceVertices) { + const floatStride = particleUtils.instanceVertexFloatStride; + + const freeOffset = this._firstFreeElement * floatStride; + instanceVertices.set(new Float32Array(lastInstanceVertices.buffer, 0, freeOffset)); + const freeEndOffset = (this._firstFreeElement + increaseCount) * floatStride; + instanceVertices.set(new Float32Array(lastInstanceVertices.buffer, freeOffset * 4), freeEndOffset); + + this._instanceBufferResized = true; + } + // Instance buffer always at last + this._primitive.setVertexBufferBinding( + lastInstanceVertices ? vertexBufferBindings.length - 1 : vertexBufferBindings.length, + vertexBufferBinding + ); + + this._instanceVertices = instanceVertices; + this._instanceVertexBufferBinding = vertexBufferBinding; + this._currentParticleCount = newParticleCount; + } + + private _addNewParticle(position: Vector3, direction: Vector3, transform: Transform, time: number): void { + const particleUtils = this._renderer.engine._particleBufferUtils; + + const firstFreeElement = this._firstFreeElement; + let nextFreeElement = firstFreeElement + 1; + if (nextFreeElement >= this._currentParticleCount) { + nextFreeElement = 0; + } + + const main = this.main; + + // Check if can be expanded + if (nextFreeElement === this._firstRetiredElement) { + const increaseCount = Math.min( + ParticleGenerator._particleIncreaseCount, + main.maxParticles - this._currentParticleCount + ); + if (increaseCount === 0) { + return; + } + + this._resizeInstanceBuffer(increaseCount); + + // Recalculate nextFreeElement after resize + nextFreeElement = firstFreeElement + 1; + + // Maintain expanded pointers + this._firstNewElement > firstFreeElement && (this._firstNewElement += increaseCount); + this._firstActiveElement > firstFreeElement && (this._firstActiveElement += increaseCount); + this._firstRetiredElement > firstFreeElement && (this._firstRetiredElement += increaseCount); + } + + let pos: Vector3, rot: Quaternion; + if (main.simulationSpace === ParticleSimulationSpace.World) { + pos = transform.worldPosition; + rot = transform.worldRotationQuaternion; + } + + const startSpeed = main.startSpeed.evaluate(undefined, main._startSpeedRand.random()); + + const instanceVertices = this._instanceVertices; + const offset = firstFreeElement * particleUtils.instanceVertexFloatStride; + + // Position + instanceVertices[offset] = position.x; + instanceVertices[offset + 1] = position.y; + instanceVertices[offset + 2] = position.z; + + // Start life time + instanceVertices[offset + particleUtils.startLifeTimeOffset] = main.startLifetime.evaluate( + undefined, + main._startLifeTimeRand.random() + ); + + // Direction + instanceVertices[offset + 4] = direction.x; + instanceVertices[offset + 5] = direction.y; + instanceVertices[offset + 6] = direction.z; + + // Time + instanceVertices[offset + particleUtils.timeOffset] = time; + + // Color + const startColor = ParticleGenerator._tempColor0; + main.startColor.evaluate(undefined, main._startColorRand.random(), startColor); + if (this._renderer.engine.settings.colorSpace === ColorSpace.Linear) { + startColor.toLinear(startColor); + } + + instanceVertices[offset + 8] = startColor.r; + instanceVertices[offset + 9] = startColor.g; + instanceVertices[offset + 10] = startColor.b; + instanceVertices[offset + 11] = startColor.a; + + // Start size + const startSizeRand = main._startSizeRand; + if (main.startSize3D) { + instanceVertices[offset + 12] = main.startSizeX.evaluate(undefined, startSizeRand.random()); + instanceVertices[offset + 13] = main.startSizeY.evaluate(undefined, startSizeRand.random()); + instanceVertices[offset + 14] = main.startSizeZ.evaluate(undefined, startSizeRand.random()); + } else { + const size = main.startSize.evaluate(undefined, startSizeRand.random()); + instanceVertices[offset + 12] = size; + instanceVertices[offset + 13] = size; + instanceVertices[offset + 14] = size; + } + + // Start rotation + const startRotationRand = main._startRotationRand; + if (main.startRotation3D) { + instanceVertices[offset + 15] = MathUtil.degreeToRadian( + main.startRotationX.evaluate(undefined, startRotationRand.random()) + ); + instanceVertices[offset + 16] = MathUtil.degreeToRadian( + main.startRotationY.evaluate(undefined, startRotationRand.random()) + ); + instanceVertices[offset + 17] = MathUtil.degreeToRadian( + main.startRotationZ.evaluate(undefined, startRotationRand.random()) + ); + } else { + instanceVertices[offset + 15] = MathUtil.degreeToRadian( + main.startRotation.evaluate(undefined, startRotationRand.random()) + ); + } + + // Start speed + instanceVertices[offset + 18] = startSpeed; + + // Unused, Color, size, rotation, + // instanceVertices[offset + 19] = rand.random(); + const colorOverLifetime = this.colorOverLifetime; + if (colorOverLifetime.enabled && colorOverLifetime.color.mode === ParticleGradientMode.TwoGradients) { + instanceVertices[offset + 20] = colorOverLifetime._colorGradientRand.random(); + } + + // instanceVertices[offset + 21] = rand.random(); + + const rotationOverLifetime = this.rotationOverLifetime; + if (rotationOverLifetime.enabled && rotationOverLifetime.rotationZ.mode === ParticleCurveMode.TwoConstants) { + instanceVertices[offset + 22] = rotationOverLifetime._rotationRand.random(); + } + + // Texture sheet animation + const textureSheetAnimation = this.textureSheetAnimation; + if (textureSheetAnimation.enabled && textureSheetAnimation.frameOverTime.mode === ParticleCurveMode.TwoCurves) { + instanceVertices[offset + 23] = textureSheetAnimation._frameOverTimeRand.random(); + } + + // Velocity random + const velocityOverLifetime = this.velocityOverLifetime; + if ( + velocityOverLifetime.enabled && + velocityOverLifetime.velocityX.mode === ParticleCurveMode.TwoConstants && + velocityOverLifetime.velocityY.mode === ParticleCurveMode.TwoConstants && + velocityOverLifetime.velocityZ.mode === ParticleCurveMode.TwoConstants + ) { + const rand = velocityOverLifetime._velocityRand; + instanceVertices[offset + 24] = rand.random(); + instanceVertices[offset + 25] = rand.random(); + instanceVertices[offset + 26] = rand.random(); + } + + if (this.main.simulationSpace === ParticleSimulationSpace.World) { + // Simulation world position + instanceVertices[offset + 27] = pos.x; + instanceVertices[offset + 28] = pos.y; + instanceVertices[offset + 29] = pos.z; + + // Simulation world position + instanceVertices[offset + 30] = rot.x; + instanceVertices[offset + 31] = rot.y; + instanceVertices[offset + 32] = rot.z; + instanceVertices[offset + 33] = rot.w; + } + + // Simulation UV + if (this.textureSheetAnimation.enabled) { + const tillingInfo = this.textureSheetAnimation._tillingInfo; + instanceVertices[offset + particleUtils.simulationUVOffset] = tillingInfo.x; + instanceVertices[offset + 35] = tillingInfo.y; + instanceVertices[offset + 36] = 0; + instanceVertices[offset + 37] = 0; + } else { + instanceVertices[offset + particleUtils.simulationUVOffset] = 1; + instanceVertices[offset + 35] = 1; + instanceVertices[offset + 36] = 0; + instanceVertices[offset + 37] = 0; + } + + this._firstFreeElement = nextFreeElement; + } + + private _retireActiveParticles(): void { + const engine = this._renderer.engine; + const particleUtils = engine._particleBufferUtils; + + const frameCount = engine.time.frameCount; + const instanceVertices = this._instanceVertices; + + while (this._firstActiveElement != this._firstNewElement) { + const activeParticleOffset = this._firstActiveElement * particleUtils.instanceVertexFloatStride; + const activeParticleTimeOffset = activeParticleOffset + particleUtils.timeOffset; + + const particleAge = this._playTime - instanceVertices[activeParticleTimeOffset]; + // Use `Math.fround` to ensure the precision of comparison is same + if (Math.fround(particleAge) < instanceVertices[activeParticleOffset + particleUtils.startLifeTimeOffset]) { + break; + } + + // Store frame count in time offset to free retired particle + instanceVertices[activeParticleTimeOffset] = frameCount; + if (++this._firstActiveElement >= this._currentParticleCount) { + this._firstActiveElement = 0; + } + + // Record wait process retired element count + this._waitProcessRetiredElementCount++; + } + } + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + this.main._updateShaderData(shaderData); + this.velocityOverLifetime._updateShaderData(shaderData); + this.textureSheetAnimation._updateShaderData(shaderData); + this.sizeOverLifetime._updateShaderData(shaderData); + this.rotationOverLifetime._updateShaderData(shaderData); + this.colorOverLifetime._updateShaderData(shaderData); + } + + /** + * @internal + */ + _resetGlobalRandSeed(seed: number): void { + this._randomSeed = seed; + this.main._resetRandomSeed(seed); + this.emission._resetRandomSeed(seed); + this.textureSheetAnimation._resetRandomSeed(seed); + this.velocityOverLifetime._resetRandomSeed(seed); + this.rotationOverLifetime._resetRandomSeed(seed); + this.colorOverLifetime._resetRandomSeed(seed); + } + + /** + * @internal + */ + _destroy(): void { + this._instanceVertexBufferBinding.buffer.destroy(); + this._primitive.destroy(); + } + + private _freeRetiredParticles(): void { + const particleUtils = this._renderer.engine._particleBufferUtils; + const frameCount = this._renderer.engine.time.frameCount; + + while (this._firstRetiredElement != this._firstActiveElement) { + const offset = + this._firstRetiredElement * particleUtils.instanceVertexFloatStride + particleUtils.startLifeTimeOffset; + const age = frameCount - this._instanceVertices[offset]; + + // WebGL don't support map buffer range, so off this optimization + if (age < 0) { + break; + } + + if (++this._firstRetiredElement >= this._currentParticleCount) { + this._firstRetiredElement = 0; + } + } + } + + private _addActiveParticlesToVertexBuffer(): void { + const firstActiveElement = this._firstActiveElement; + const firstFreeElement = this._firstFreeElement; + + // firstActiveElement == firstFreeElement should not update + if (firstActiveElement === firstFreeElement) { + return; + } + + const byteStride = this._renderer.engine._particleBufferUtils.instanceVertexStride; + const start = firstActiveElement * byteStride; + const instanceBuffer = this._instanceVertexBufferBinding.buffer; + const dataBuffer = this._instanceVertices.buffer; + + if (firstActiveElement < firstFreeElement) { + instanceBuffer.setData( + dataBuffer, + 0, + start, + (firstFreeElement - firstActiveElement) * byteStride, + SetDataOptions.Discard + ); + } else { + const firstSegmentCount = (this._currentParticleCount - firstActiveElement) * byteStride; + instanceBuffer.setData(dataBuffer, 0, start, firstSegmentCount, SetDataOptions.Discard); + + if (firstFreeElement > 0) { + instanceBuffer.setData(dataBuffer, firstSegmentCount, 0, firstFreeElement * byteStride); + } + } + this._firstNewElement = firstFreeElement; + this._waitProcessRetiredElementCount = 0; + this._instanceBufferResized = false; + } + + private _addVertexBufferBindingsFilterDuplicate( + vertexBufferBinding: VertexBufferBinding, + out: VertexBufferBinding[] + ): number { + let index = 0; + for (let n = out.length; index < n; index++) { + if (out[index] === vertexBufferBinding) { + return index; + } + } + out.push(vertexBufferBinding); + return index; + } +} diff --git a/packages/core/src/particle/ParticleMaterial.ts b/packages/core/src/particle/ParticleMaterial.ts new file mode 100644 index 0000000000..400e63e216 --- /dev/null +++ b/packages/core/src/particle/ParticleMaterial.ts @@ -0,0 +1,63 @@ +import { Color } from "@galacean/engine-math"; +import { Engine } from "../Engine"; +import { BaseMaterial } from "../material/BaseMaterial"; +import { Shader } from "../shader/Shader"; +import { Texture2D } from "../texture/Texture2D"; + +/** + * Particle Material. + */ +export class ParticleMaterial extends BaseMaterial { + /** + * Base color. + */ + get baseColor(): Color { + return this.shaderData.getColor(BaseMaterial._baseColorProp); + } + + set baseColor(value: Color) { + const baseColor = this.shaderData.getColor(BaseMaterial._baseColorProp); + if (value !== baseColor) { + baseColor.copyFrom(value); + } + } + + /** + * Base texture. + */ + get baseTexture(): Texture2D { + return this.shaderData.getTexture(BaseMaterial._baseTextureProp); + } + + set baseTexture(value: Texture2D) { + this.shaderData.setTexture(BaseMaterial._baseTextureProp, value); + if (value) { + this.shaderData.enableMacro(BaseMaterial._baseTextureMacro); + } else { + this.shaderData.disableMacro(BaseMaterial._baseTextureMacro); + } + } + + /** + * Create a unlit material instance. + * @param engine - Engine to which the material belongs + */ + constructor(engine: Engine) { + super(engine, Shader.find("particle-shader")); + + const shaderData = this.shaderData; + shaderData.enableMacro("MATERIAL_OMIT_NORMAL"); + shaderData.setColor(BaseMaterial._baseColorProp, new Color(1, 1, 1, 1)); + + this.isTransparent = true; + } + + /** + * @inheritdoc + */ + override clone(): ParticleMaterial { + const dest = new ParticleMaterial(this._engine); + this.cloneTo(dest); + return dest; + } +} diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index ef94a9ba34..bc94a10538 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -1,944 +1,204 @@ -import { Color, MathUtil, Vector3 } from "@galacean/engine-math"; +import { BoundingBox, Vector3 } from "@galacean/engine-math"; +import { Entity } from "../Entity"; +import { RenderContext } from "../RenderPipeline/RenderContext"; +import { Renderer } from "../Renderer"; +import { deepClone, shallowClone } from "../clone/CloneManager"; +import { ModelMesh } from "../mesh/ModelMesh"; +import { ShaderMacro } from "../shader/ShaderMacro"; +import { ShaderProperty } from "../shader/ShaderProperty"; +import { ParticleGenerator } from "./ParticleGenerator"; +import { ParticleRenderMode } from "./enums/ParticleRenderMode"; +import { ParticleStopMode } from "./enums/ParticleStopMode"; import { GLCapabilityType } from "../base/Constant"; -import { ignoreClone } from "../clone/CloneManager"; -import { Buffer } from "../graphic/Buffer"; -import { VertexElement } from "../graphic/VertexElement"; -import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; -import { BufferUsage } from "../graphic/enums/BufferUsage"; -import { IndexFormat } from "../graphic/enums/IndexFormat"; -import { VertexElementFormat } from "../graphic/enums/VertexElementFormat"; -import { Material } from "../material/Material"; -import { BufferMesh } from "../mesh/BufferMesh"; -import { MeshRenderer } from "../mesh/MeshRenderer"; -import { CullMode, Shader } from "../shader"; -import { BlendFactor } from "../shader/enums/BlendFactor"; -import { RenderQueueType } from "../shader/enums/RenderQueueType"; -import { Texture } from "../texture"; - -enum DirtyFlagType { - Position = 0x1, - Velocity = 0x2, - Acceleration = 0x4, - Color = 0x8, - Alpha = 0x10, - Size = 0x20, - StartAngle = 0x40, - StartTime = 0x80, - LifeTime = 0x100, - RotateVelocity = 0x200, - Scale = 0x400, - Everything = 0xffffffff -} - -/** - * Blend mode enums of the particle renderer's material. - */ -export enum ParticleRendererBlendMode { - Transparent = 0, - Additive = 1 -} /** * Particle Renderer Component. */ -export class ParticleRenderer extends MeshRenderer { - /** The max number of indices that Uint16Array can support. */ - private static _uint16VertexLimit: number = 65535; - - private static _getRandom(): number { - return Math.random() - 0.5; - } - - private _vertexStride: number; - private _vertices: Float32Array; - private _vertexBuffer: Buffer; - private _maxCount: number = 1000; - private _position: Vector3 = new Vector3(); - private _positionRandomness: Vector3 = new Vector3(); - private _positionArray: Vector3[]; - private _velocity: Vector3 = new Vector3(); - private _velocityRandomness: Vector3 = new Vector3(); - private _acceleration: Vector3 = new Vector3(); - private _accelerationRandomness: Vector3 = new Vector3(); - private _color: Color = new Color(1, 1, 1, 1); - private _colorRandomness: number = 0; - private _size: number = 1; - private _sizeRandomness: number = 0; - private _alpha: number = 1; - private _alphaRandomness: number = 0; - private _startAngle: number = 0; - private _startAngleRandomness: number = 0; - private _rotateVelocity: number = 0; - private _rotateVelocityRandomness: number = 0; - private _lifetime: number = 5; - private _startTimeRandomness: number = 0; - private _scale: number = 1; - private _isOnce: boolean = false; - private _onceTime: number = 0; - private _time: number = 0; - private _isInit: boolean = false; - private _isStart: boolean = false; - private _updateDirtyFlag: number = DirtyFlagType.Everything; - private _isRotateToVelocity: boolean = false; - private _isUseOriginColor: boolean = false; - private _isScaleByLifetime: boolean = false; - private _is2d: boolean = true; - private _isFadeIn: boolean = false; - private _isFadeOut: boolean = false; - private _playOnEnable: boolean = true; - private _blendMode: ParticleRendererBlendMode = ParticleRendererBlendMode.Transparent; - - /** - * Sprite sheet of texture. - */ - public spriteSheet: { x: number; y: number; w: number; h: number }[]; - - /** - * Texture of particle. - */ - get texture(): Texture { - return this.getMaterial().shaderData.getTexture("u_texture"); - } - - set texture(texture: Texture) { - if (texture) { - this.shaderData.enableMacro("particleTexture"); - this.getMaterial().shaderData.setTexture("u_texture", texture); - } else { - this.shaderData.disableMacro("particleTexture"); - } - } - - /** - * Position of particles. - */ - get position(): Vector3 { - return this._position; - } - - set position(value: Vector3) { - this._updateDirtyFlag |= DirtyFlagType.Position; - this._position = value; - } - - /** - * Random range of positions. - */ - get positionRandomness(): Vector3 { - return this._positionRandomness; - } - - set positionRandomness(value: Vector3) { - this._updateDirtyFlag |= DirtyFlagType.Position; - this._positionRandomness = value; - } - - /** - * Array of fixed positions. - */ - get positionArray(): Vector3[] { - return this._positionArray; - } - - set positionArray(value: Vector3[]) { - this._updateDirtyFlag |= DirtyFlagType.Position; - this._positionArray = value; - } - - /** - * Velocity of particles. - */ - get velocity(): Vector3 { - return this._velocity; - } - - set velocity(value: Vector3) { - this._updateDirtyFlag |= DirtyFlagType.Velocity; - this._velocity = value; - } - - /** - * Random range of velocity. - */ - get velocityRandomness(): Vector3 { - return this._velocityRandomness; - } - - set velocityRandomness(value: Vector3) { - this._updateDirtyFlag |= DirtyFlagType.Velocity; - this._velocityRandomness = value; - } - - /** - * Acceleration of particles. - */ - get acceleration(): Vector3 { - return this._acceleration; - } - - set acceleration(value: Vector3) { - this._updateDirtyFlag |= DirtyFlagType.Acceleration; - this._acceleration = value; - } - - /** - * Random range of acceleration. - */ - get accelerationRandomness(): Vector3 { - return this._accelerationRandomness; - } - - set accelerationRandomness(value: Vector3) { - this._updateDirtyFlag |= DirtyFlagType.Acceleration; - this._accelerationRandomness = value; - } - - /** - * Color of particles. - */ - get color(): Color { - return this._color; - } - - set color(value: Color) { - this._color.copyFrom(value); - } - - /** - * Random range of color. - */ - get colorRandomness(): number { - return this._colorRandomness; - } - - set colorRandomness(value: number) { - this._updateDirtyFlag |= DirtyFlagType.Color; - this._colorRandomness = value; - } - - /** - * Size of particles. - */ - get size(): number { - return this._size; - } - - set size(value: number) { - this._updateDirtyFlag |= DirtyFlagType.Size; - this._size = value; - } - - /** - * Random range of size. - */ - get sizeRandomness(): number { - return this._sizeRandomness; - } - - set sizeRandomness(value: number) { - this._updateDirtyFlag |= DirtyFlagType.Size; - this._sizeRandomness = value; - } - - /** - * Alpha of particles. - */ - get alpha(): number { - return this._alpha; - } - - set alpha(value: number) { - this._updateDirtyFlag |= DirtyFlagType.Alpha; - this._alpha = value; - } - - /** - * Random range of alpha. - */ - get alphaRandomness(): number { - return this._alphaRandomness; - } - - set alphaRandomness(value: number) { - this._updateDirtyFlag |= DirtyFlagType.Alpha; - this._alphaRandomness = value; - } - - /** - * Angle of particles. - */ - get angle(): number { - return this._startAngle; - } - - set angle(value: number) { - this._updateDirtyFlag |= DirtyFlagType.StartAngle; - this._startAngle = value; - } - - /** - * Random range of angle. - */ - get angleRandomness(): number { - return this._startAngleRandomness; - } - - set angleRandomness(value: number) { - this._updateDirtyFlag |= DirtyFlagType.StartAngle; - this._startAngleRandomness = value; - } - - /** - * Rotate velocity of particles. - */ - get rotateVelocity(): number { - return this._rotateVelocity; - } - - set rotateVelocity(value: number) { - this._updateDirtyFlag |= DirtyFlagType.RotateVelocity; - this._rotateVelocity = value; - } - - /** - * Random range of rotate velocity. - */ - get rotateVelocityRandomness(): number { - return this._rotateVelocityRandomness; - } - - set rotateVelocityRandomness(value: number) { - this._updateDirtyFlag |= DirtyFlagType.RotateVelocity; - this._rotateVelocityRandomness = value; - } - - /** - * Lifetime of particles. - */ - get lifetime(): number { - return this._lifetime; - } - - set lifetime(value: number) { - this._updateDirtyFlag |= DirtyFlagType.LifeTime; - this._lifetime = value; - this._onceTime = 0; - } - - /** - * Random range of start time. - */ - get startTimeRandomness(): number { - return this._startTimeRandomness; - } - - set startTimeRandomness(value: number) { - this._updateDirtyFlag |= DirtyFlagType.StartTime; - this._startTimeRandomness = value; - this._onceTime = 0; - } - - /** - * Scale factor of particles. - */ - get scale(): number { - return this._scale; - } - - set scale(value: number) { - this._updateDirtyFlag |= DirtyFlagType.Scale; - this._scale = value; - } - - /** - * Max count of particles. - */ - get maxCount(): number { - return this._maxCount; - } - - set maxCount(value: number) { - this._isStart = false; - this._isInit = false; - this._maxCount = value; - this._updateDirtyFlag = DirtyFlagType.Everything; - this.mesh = this._createMesh(); - - this._updateBuffer(); - - this._isInit = true; - this.shaderData.setFloat("u_time", 0); - } - - /** - * Whether play once. - */ - get isOnce(): boolean { - return this._isOnce; - } - - set isOnce(value: boolean) { - this._time = 0; - this.shaderData.setInt("u_once", value ? 1 : 0); - this._isOnce = value; - } - - /** - * Whether follow the direction of velocity. - */ - get isRotateToVelocity(): boolean { - return this._isRotateToVelocity; - } - - set isRotateToVelocity(value: boolean) { - if (value) { - this.shaderData.enableMacro("rotateToVelocity"); - } else { - this.shaderData.disableMacro("rotateToVelocity"); - } - - this._isRotateToVelocity = value; - } - - /** - * Whether use origin color. - */ - get isUseOriginColor(): boolean { - return this._isUseOriginColor; - } - - set isUseOriginColor(value: boolean) { - if (value) { - this.shaderData.enableMacro("useOriginColor"); - } else { - this.shaderData.disableMacro("useOriginColor"); - } - - this._isUseOriginColor = value; - } - - /** - * Whether scale by lifetime. - */ - get isScaleByLifetime(): boolean { - return this._isScaleByLifetime; - } - - set isScaleByLifetime(value: boolean) { - if (value) { - this.shaderData.enableMacro("isScaleByLifetime"); - } else { - this.shaderData.disableMacro("isScaleByLifetime"); - } - - this._isScaleByLifetime = value; - } +export class ParticleRenderer extends Renderer { + private static readonly _billboardModeMacro = ShaderMacro.getByName("RENDERER_MODE_SPHERE_BILLBOARD"); + private static readonly _stretchedBillboardModeMacro = ShaderMacro.getByName("RENDERER_MODE_STRETCHED_BILLBOARD"); + private static readonly _horizontalBillboardModeMacro = ShaderMacro.getByName("RENDERER_MODE_HORIZONTAL_BILLBOARD"); + private static readonly _verticalBillboardModeMacro = ShaderMacro.getByName("RENDERER_MODE_VERTICAL_BILLBOARD"); + private static readonly _renderModeMeshMacro = ShaderMacro.getByName("RENDERER_MODE_MESH"); + + private static readonly _pivotOffsetProperty = ShaderProperty.getByName("renderer_PivotOffset"); + private static readonly _lengthScale = ShaderProperty.getByName("renderer_StretchedBillboardLengthScale"); + private static readonly _speedScale = ShaderProperty.getByName("renderer_StretchedBillboardSpeedScale"); + private static readonly _currentTime = ShaderProperty.getByName("renderer_CurrentTime"); + + /** Particle generator. */ + @deepClone + readonly generator = new ParticleGenerator(this); + /** Specifies how much particles stretch depending on their velocity. */ + velocityScale = 0; + /** How much are the particles stretched in their direction of motion, defined as the length of the particle compared to its width. */ + lengthScale = 2; + /** The pivot of particle. */ + @shallowClone + pivot = new Vector3(); + + private _renderMode: ParticleRenderMode; + private _currentRenderModeMacro: ShaderMacro; + private _mesh: ModelMesh; + private _supportInstancedArrays: boolean; + + /** + * Specifies how particles are rendered. + */ + get renderMode(): ParticleRenderMode { + return this._renderMode; + } + + set renderMode(value: ParticleRenderMode) { + if (this._renderMode !== value) { + const lastRenderMode = this._renderMode; + this._renderMode = value; + + let renderModeMacro = null; + const shaderData = this.shaderData; + switch (value) { + case ParticleRenderMode.Billboard: + renderModeMacro = ParticleRenderer._billboardModeMacro; + break; + case ParticleRenderMode.StretchBillboard: + renderModeMacro = ParticleRenderer._stretchedBillboardModeMacro; + break; + case ParticleRenderMode.HorizontalBillboard: + throw "Not implemented"; + renderModeMacro = ParticleRenderer._horizontalBillboardModeMacro; + break; + case ParticleRenderMode.VerticalBillboard: + throw "Not implemented"; + renderModeMacro = ParticleRenderer._verticalBillboardModeMacro; + break; + case ParticleRenderMode.Mesh: + throw "Not implemented"; + renderModeMacro = ParticleRenderer._renderModeMeshMacro; + break; + } - /** - * Whether 2D rendering. - */ - get is2d(): boolean { - return this._is2d; - } + if (this._currentRenderModeMacro !== renderModeMacro) { + this._currentRenderModeMacro && shaderData.disableMacro(this._currentRenderModeMacro); + renderModeMacro && shaderData.enableMacro(renderModeMacro); + this._currentRenderModeMacro = renderModeMacro; + } - set is2d(value: boolean) { - if (value) { - this.shaderData.enableMacro("is2d"); - } else { - this.shaderData.disableMacro("is2d"); - this.getMaterial().renderState.rasterState.cullMode = CullMode.Off; + // @ts-ignore + if ((lastRenderMode !== ParticleRenderMode.Mesh) !== (value === ParticleRenderMode.Mesh)) { + this.generator._reorganizeGeometryBuffers(); + } } - - this._is2d = value; } /** - * Whether fade in. + * The mesh of particle. + * @remarks Valid when `renderMode` is `Mesh`. */ - get isFadeIn(): boolean { - return this._isFadeIn; + get mesh(): ModelMesh { + return this._mesh; } - set isFadeIn(value: boolean) { - if (value) { - this.shaderData.enableMacro("fadeIn"); - } else { - this.shaderData.disableMacro("fadeIn"); + set mesh(value: ModelMesh) { + const lastMesh = this._mesh; + if (lastMesh !== value) { + this._mesh = value; + lastMesh?._addReferCount(-1); + value?._addReferCount(1); + if (this.renderMode === ParticleRenderMode.Mesh) { + this.generator._reorganizeGeometryBuffers(); + } } - - this._isFadeIn = value; } /** - * Whether fade out. + * @internal */ - get isFadeOut(): boolean { - return this._isFadeOut; - } + constructor(entity: Entity) { + super(entity); - set isFadeOut(value: boolean) { - if (value) { - this.shaderData.enableMacro("fadeOut"); - } else { - this.shaderData.disableMacro("fadeOut"); - } + this._currentRenderModeMacro = ParticleRenderer._billboardModeMacro; + this.shaderData.enableMacro(ParticleRenderer._billboardModeMacro); - this._isFadeOut = value; + this._supportInstancedArrays = this.engine._hardwareRenderer.canIUse(GLCapabilityType.instancedArrays); } /** - * Whether play on enable. + * @internal */ - get playOnEnable(): boolean { - return this._playOnEnable; - } - - set playOnEnable(value: boolean) { - this._playOnEnable = value; - - if (value) { - this.start(); - } else { - this.stop(); + override _onEnable(): void { + if (this.generator.main.playOnEnabled) { + this.generator.play(false); } } /** - * Blend mode of the particle renderer's material. + * @internal */ - get blendMode(): ParticleRendererBlendMode { - return this._blendMode; - } - - set blendMode(value: ParticleRendererBlendMode) { - const blendState = this.getMaterial().renderState.blendState; - const target = blendState.targetBlendState; - - if (value === ParticleRendererBlendMode.Transparent) { - target.enabled = true; - target.sourceColorBlendFactor = BlendFactor.SourceAlpha; - target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.sourceAlphaBlendFactor = BlendFactor.One; - target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; - } else if (value === ParticleRendererBlendMode.Additive) { - target.enabled = true; - target.sourceColorBlendFactor = BlendFactor.SourceAlpha; - target.destinationColorBlendFactor = BlendFactor.One; - target.sourceAlphaBlendFactor = BlendFactor.One; - target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; - } - - this._blendMode = value; - } - - constructor(props) { - super(props); - - this._onColorChanged = this._onColorChanged.bind(this); - //@ts-ignore - this._color._onValueChanged = this._onColorChanged; - - this.setMaterial(this._createMaterial()); + override _onDisable(): void { + this.generator.stop(false, ParticleStopMode.StopEmittingAndClear); } /** * @internal */ - override update(deltaTime: number): void { - if (!this._isInit || !this._isStart) { + override _prepareRender(context: RenderContext): void { + if (!this._supportInstancedArrays) { return; } - // Stop after play once - if (this._isOnce && this._time > this._onceTime) { - return this.stop(); - } + const generator = this.generator; + generator._update(this.engine.time.deltaTime); - if (this._updateDirtyFlag) { - this._updateBuffer(); - this._updateDirtyFlag = 0; + // No particles to render + if (generator._firstActiveElement === generator._firstFreeElement) { + return; } - this._time += deltaTime; - this.shaderData.setFloat("u_time", this._time); + super._prepareRender(context); } /** * @internal */ - override _onEnable(): void { - super._onEnable(); - - if (this._playOnEnable) { - this.start(); - } + protected override _updateBounds(worldBounds: BoundingBox): void { + worldBounds.min.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); + worldBounds.max.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); } /** - * Start emitting. + * @internal */ - start(): void { - this._isStart = true; - this._time = 0; - } + protected override _updateShaderData(context: RenderContext): void { + const shaderData = this.shaderData; + shaderData.setFloat(ParticleRenderer._lengthScale, this.lengthScale); + shaderData.setFloat(ParticleRenderer._speedScale, this.velocityScale); + shaderData.setFloat(ParticleRenderer._currentTime, this.generator._playTime); + shaderData.setVector3(ParticleRenderer._pivotOffsetProperty, this.pivot); - /** - * Stop emitting. - */ - stop(): void { - this._isStart = false; + this.generator._updateShaderData(shaderData); } - private _createMaterial(): Material { - const material = new Material(this.engine, Shader.find("particle-shader")); - const { renderState } = material; - const target = renderState.blendState.targetBlendState; - - target.enabled = true; - target.sourceColorBlendFactor = BlendFactor.SourceAlpha; - target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.sourceAlphaBlendFactor = BlendFactor.One; - target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; - - renderState.depthState.writeEnabled = false; + protected override _render(context: RenderContext): void { + const generator = this.generator; + const primitive = generator._primitive; - material.renderState.renderQueueType = RenderQueueType.Transparent; - - this.isUseOriginColor = true; - this.is2d = true; - this.isFadeOut = true; - - return material; - } - - private _createMesh(): BufferMesh { - const mesh = new BufferMesh(this._entity.engine, "particleMesh"); - const vertexStride = 96; - const vertexCount = this._maxCount * 4; - const vertexFloatCount = vertexCount * vertexStride; - const vertices = new Float32Array(vertexFloatCount); - let indices: Uint16Array | Uint32Array = null; - let useUint32: boolean = false; - if (vertexCount > ParticleRenderer._uint16VertexLimit) { - if (this.engine._hardwareRenderer.canIUse(GLCapabilityType.elementIndexUint)) { - useUint32 = true; - indices = new Uint32Array(6 * this._maxCount); - } else { - throw Error("The vertex count is over limit."); - } + if (generator._firstActiveElement < generator._firstFreeElement) { + primitive.instanceCount = generator._firstFreeElement - generator._firstActiveElement; } else { - indices = new Uint16Array(6 * this._maxCount); - } - - for (let i = 0, idx = 0; i < this._maxCount; ++i) { - let startIndex = i * 4; - indices[idx++] = startIndex; - indices[idx++] = startIndex + 1; - indices[idx++] = startIndex + 2; - indices[idx++] = startIndex; - indices[idx++] = startIndex + 2; - indices[idx++] = startIndex + 3; - } - - const vertexElements = [ - new VertexElement("a_position", 0, VertexElementFormat.Vector3, 0), - new VertexElement("a_velocity", 12, VertexElementFormat.Vector3, 0), - new VertexElement("a_acceleration", 24, VertexElementFormat.Vector3, 0), - new VertexElement("a_color", 36, VertexElementFormat.Vector4, 0), - new VertexElement("a_lifeAndSize", 52, VertexElementFormat.Vector4, 0), - new VertexElement("a_rotation", 68, VertexElementFormat.Vector2, 0), - new VertexElement("a_uv", 76, VertexElementFormat.Vector3, 0), - new VertexElement("a_normalizedUv", 88, VertexElementFormat.Vector2, 0) - ]; - - const vertexBuffer = new Buffer( - this.engine, - BufferBindFlag.VertexBuffer, - vertexFloatCount * 4, - BufferUsage.Dynamic - ); - - const indexBuffer = new Buffer(this.engine, BufferBindFlag.IndexBuffer, indices, BufferUsage.Dynamic); - - mesh.setVertexBufferBinding(vertexBuffer, vertexStride); - mesh.setIndexBufferBinding(indexBuffer, useUint32 ? IndexFormat.UInt32 : IndexFormat.UInt16); - mesh.setVertexElements(vertexElements); - mesh.addSubMesh(0, indices.length); - - this._vertexBuffer = vertexBuffer; - this._vertexStride = vertexStride / 4; - this._vertices = vertices; - - const { bounds } = mesh; - const minValue = Number.MIN_SAFE_INTEGER; - const maxValue = Number.MAX_SAFE_INTEGER; - bounds.min.set(minValue, minValue, minValue); - bounds.max.set(maxValue, maxValue, maxValue); - - return mesh; - } - - private _updateBuffer(): void { - for (let x = 0; x < this._maxCount; x++) { - this._updateSingleBuffer(x); - } - - this._vertexBuffer.setData(this._vertices); - } - - private _updateSingleBuffer(i: number): void { - const { _updateDirtyFlag, _vertices: vertices, _vertexStride: vertexStride } = this; - const { _getRandom: getRandom } = ParticleRenderer; - const offset = i * 4; - - const k0 = offset * vertexStride; - const k1 = (offset + 1) * vertexStride; - const k2 = (offset + 2) * vertexStride; - const k3 = (offset + 3) * vertexStride; - - if (_updateDirtyFlag & DirtyFlagType.Position) { - let { x, y, z } = this._position; - const { _positionArray, _positionRandomness } = this; - - if (_positionArray) { - if (_positionArray.length !== this._maxCount) { - throw Error("The length of positionArray must be equal to maxCount."); - } - const pos = _positionArray[i]; - - x += pos.x; - y += pos.y; - z += pos.z; - } else { - x += getRandom() * _positionRandomness.x; - y += getRandom() * _positionRandomness.y; - z += getRandom() * _positionRandomness.z; - } - - vertices[k0] = vertices[k1] = vertices[k2] = vertices[k3] = x; - vertices[k0 + 1] = vertices[k1 + 1] = vertices[k2 + 1] = vertices[k3 + 1] = y; - vertices[k0 + 2] = vertices[k1 + 2] = vertices[k2 + 2] = vertices[k3 + 2] = z; - } - - if (_updateDirtyFlag & DirtyFlagType.Velocity) { - const { _velocity, _velocityRandomness } = this; - - vertices[k0 + 3] = - vertices[k1 + 3] = - vertices[k2 + 3] = - vertices[k3 + 3] = - _velocity.x + getRandom() * _velocityRandomness.x; - vertices[k0 + 4] = - vertices[k1 + 4] = - vertices[k2 + 4] = - vertices[k3 + 4] = - _velocity.y + getRandom() * _velocityRandomness.y; - vertices[k0 + 5] = - vertices[k1 + 5] = - vertices[k2 + 5] = - vertices[k3 + 5] = - _velocity.z + getRandom() * _velocityRandomness.z; - } - - if (_updateDirtyFlag & DirtyFlagType.Acceleration) { - const { _acceleration, _accelerationRandomness } = this; - - vertices[k0 + 6] = - vertices[k1 + 6] = - vertices[k2 + 6] = - vertices[k3 + 6] = - _acceleration.x + getRandom() * _accelerationRandomness.x; - vertices[k0 + 7] = - vertices[k1 + 7] = - vertices[k2 + 7] = - vertices[k3 + 7] = - _acceleration.y + getRandom() * _accelerationRandomness.y; - vertices[k0 + 8] = - vertices[k1 + 8] = - vertices[k2 + 8] = - vertices[k3 + 8] = - _acceleration.z + getRandom() * _accelerationRandomness.z; - } - - if (_updateDirtyFlag & DirtyFlagType.Color) { - const { _color, _colorRandomness } = this; - - vertices[k0 + 9] = - vertices[k1 + 9] = - vertices[k2 + 9] = - vertices[k3 + 9] = - MathUtil.clamp(_color.r + getRandom() * _colorRandomness, 0, 1); - - vertices[k0 + 10] = - vertices[k1 + 10] = - vertices[k2 + 10] = - vertices[k3 + 10] = - MathUtil.clamp(_color.g + getRandom() * _colorRandomness, 0, 1); - vertices[k0 + 11] = - vertices[k1 + 11] = - vertices[k2 + 11] = - vertices[k3 + 11] = - MathUtil.clamp(_color.b + getRandom() * _colorRandomness, 0, 1); - } - - if (_updateDirtyFlag & DirtyFlagType.Alpha) { - vertices[k0 + 12] = - vertices[k1 + 12] = - vertices[k2 + 12] = - vertices[k3 + 12] = - MathUtil.clamp(this._alpha + getRandom() * this._alphaRandomness, 0, 1); - } - - if (_updateDirtyFlag & DirtyFlagType.StartTime) { - vertices[k0 + 13] = - vertices[k1 + 13] = - vertices[k2 + 13] = - vertices[k3 + 13] = - Math.random() * this._startTimeRandomness; - } - - if (_updateDirtyFlag & DirtyFlagType.LifeTime) { - const { _lifetime } = this; - - vertices[k0 + 14] = - vertices[k1 + 14] = - vertices[k2 + 14] = - vertices[k3 + 14] = - _lifetime + getRandom() * _lifetime; - } - - // Update the duration of play once when startTime or lifetime changes. - if (_updateDirtyFlag & DirtyFlagType.StartTime || _updateDirtyFlag & DirtyFlagType.LifeTime) { - this._onceTime = Math.max(this._onceTime, vertices[k0 + 13] + vertices[k0 + 14]); - } - - if (_updateDirtyFlag & DirtyFlagType.Size) { - const { _size } = this; - - vertices[k0 + 15] = - vertices[k1 + 15] = - vertices[k2 + 15] = - vertices[k3 + 15] = - Math.max(_size + getRandom() * this._sizeRandomness * _size * 2, 0); - } - - if (_updateDirtyFlag & DirtyFlagType.Scale) { - vertices[k0 + 16] = vertices[k1 + 16] = vertices[k2 + 16] = vertices[k3 + 16] = this._scale; - } - - if (_updateDirtyFlag & DirtyFlagType.StartAngle) { - vertices[k0 + 17] = - vertices[k1 + 17] = - vertices[k2 + 17] = - vertices[k3 + 17] = - this._startAngle + getRandom() * Math.PI * this._startAngleRandomness * 2; - } - - if (_updateDirtyFlag & DirtyFlagType.RotateVelocity) { - vertices[k0 + 18] = - vertices[k1 + 18] = - vertices[k2 + 18] = - vertices[k3 + 18] = - this._rotateVelocity + getRandom() * this._rotateVelocityRandomness; - } - - this._updateSingleUv(i, k0, k1, k2, k3); - } - - private _updateSingleUv(i: number, k0: number, k1: number, k2: number, k3: number): void { - const { spriteSheet } = this; - const texture = this.getMaterial().shaderData.getTexture("u_texture"); - const vertices = this._vertices; - - if (texture) { - const width = texture.width; - const height = texture.height; - - if (spriteSheet) { - const { x, y, w, h } = spriteSheet[i % spriteSheet.length]; - - const u = x / width; - const v = y / height; - const p = u + w / width; - const q = v + h / height; - const ratio = h / w; - - // left bottom - vertices[k0 + 19] = u; - vertices[k0 + 20] = q; - vertices[k0 + 21] = ratio; - - // right bottom - vertices[k1 + 19] = p; - vertices[k1 + 20] = q; - vertices[k1 + 21] = ratio; - - // right top - vertices[k2 + 19] = p; - vertices[k2 + 20] = v; - vertices[k2 + 21] = ratio; - - // left top - vertices[k3 + 19] = u; - vertices[k3 + 20] = v; - vertices[k3 + 21] = ratio; - } else { - const ratio = height / width; - - // left bottom - vertices[k0 + 19] = 0; - vertices[k0 + 20] = 1; - vertices[k0 + 21] = ratio; - - // right bottom - vertices[k1 + 19] = 1; - vertices[k1 + 20] = 1; - vertices[k1 + 21] = ratio; - - // right top - vertices[k2 + 19] = 1; - vertices[k2 + 20] = 0; - vertices[k2 + 21] = ratio; - - // left top - vertices[k3 + 19] = 0; - vertices[k3 + 20] = 0; - vertices[k3 + 21] = ratio; + let instanceCount = generator._currentParticleCount - generator._firstActiveElement; + if (generator._firstFreeElement > 0) { + instanceCount += generator._firstFreeElement; } - } else { - // left bottom - vertices[k0 + 19] = 0; - vertices[k0 + 20] = 0; - vertices[k0 + 21] = 1; - - // right bottom - vertices[k1 + 19] = 1; - vertices[k1 + 20] = 0; - vertices[k1 + 21] = 1; - - // right top - vertices[k2 + 19] = 1; - vertices[k2 + 20] = 1; - vertices[k2 + 21] = 1; - - // left top - vertices[k3 + 19] = 0; - vertices[k3 + 20] = 1; - vertices[k3 + 21] = 1; + primitive.instanceCount = instanceCount; } - vertices[k0 + 22] = -0.5; - vertices[k0 + 23] = -0.5; - vertices[k1 + 22] = 0.5; - vertices[k1 + 23] = -0.5; - vertices[k2 + 22] = 0.5; - vertices[k2 + 23] = 0.5; - vertices[k3 + 22] = -0.5; - vertices[k3 + 23] = 0.5; + const material = this.getMaterial(); + const renderData = this._engine._renderDataPool.getFromPool(); + renderData.setX(this, material, generator._primitive, generator._subPrimitive); + context.camera._renderPipeline.pushRenderData(context, renderData); } - @ignoreClone - private _onColorChanged(): void { - this._updateDirtyFlag |= DirtyFlagType.Color; + protected override _onDestroy(): void { + this.generator._destroy(); } } diff --git a/packages/core/src/particle/enums/ParticleCurveMode.ts b/packages/core/src/particle/enums/ParticleCurveMode.ts new file mode 100644 index 0000000000..aeed4fae0d --- /dev/null +++ b/packages/core/src/particle/enums/ParticleCurveMode.ts @@ -0,0 +1,13 @@ +/** + * Particle curve mode. + */ +export enum ParticleCurveMode { + /* Single constant mode. */ + Constant, + /* Random value between two constants mode. */ + TwoConstants, + /* Single curve mode. */ + Curve, + /* Random value between two curves mode. */ + TwoCurves +} diff --git a/packages/core/src/particle/enums/ParticleGradientMode.ts b/packages/core/src/particle/enums/ParticleGradientMode.ts new file mode 100644 index 0000000000..ab74494551 --- /dev/null +++ b/packages/core/src/particle/enums/ParticleGradientMode.ts @@ -0,0 +1,13 @@ +/** + * Particle gradient mode. + */ +export enum ParticleGradientMode { + /* Single constant color. */ + Constant, + /* Random value between two constant colors. */ + TwoConstants, + /* Single color gradient curve mode. */ + Gradient, + /* Random value between two color gradients. */ + TwoGradients +} diff --git a/packages/core/src/particle/enums/ParticleRandomSubSeeds.ts b/packages/core/src/particle/enums/ParticleRandomSubSeeds.ts new file mode 100644 index 0000000000..0b20e1028a --- /dev/null +++ b/packages/core/src/particle/enums/ParticleRandomSubSeeds.ts @@ -0,0 +1,19 @@ +/** + * @internal + */ +export enum ParticleRandomSubSeeds { + Burst = 0x23571a3e, + StartDelay = 0x13371337, + StartColor = 0x12460f3b, + StartSize = 0x6aed452e, + StartRotation = 0xdec4aea1, + randomizeRotationDirection = 0x96aa4de3, + StartLifetime = 0x8d2c8431, + StartSpeed = 0xf3857f6f, + VelocityOverLifetime = 0xe0fbd834, + ColorOverLifetime = 0x13740583, + SizeOverLifetime = 0x591bc05c, + RotationOverLifetime = 0x40eb95e4, + TextureSheetAnimation = 0xbc524e5, + Shape = 0xaf502044 +} diff --git a/packages/core/src/particle/enums/ParticleRenderMode.ts b/packages/core/src/particle/enums/ParticleRenderMode.ts new file mode 100644 index 0000000000..269cda562c --- /dev/null +++ b/packages/core/src/particle/enums/ParticleRenderMode.ts @@ -0,0 +1,17 @@ +/** + * The rendering mode for particle renderer. + */ +export enum ParticleRenderMode { + /** Render particles as billboards facing the active camera. */ + Billboard, + /** Stretch particles in the direction of motion. */ + StretchBillboard, + /** Render particles as billboards always facing up along the y-Axis. */ + HorizontalBillboard, + /** Render particles as billboards always facing the player, but not pitching along the x-Axis. */ + VerticalBillboard, + /** Render particles as meshes. */ + Mesh, + /** Do not render particles. */ + None +} diff --git a/packages/core/src/particle/enums/ParticleScaleMode.ts b/packages/core/src/particle/enums/ParticleScaleMode.ts new file mode 100644 index 0000000000..0f780ef943 --- /dev/null +++ b/packages/core/src/particle/enums/ParticleScaleMode.ts @@ -0,0 +1,11 @@ +/** + * Control how Particle Generator apply transform scale. + */ +export enum ParticleScaleMode { + /** Scale the Particle Generator using the entire transform hierarchy. */ + Hierarchy, + /** Scale the Particle Generator using only its own transform scale. (Ignores parent scale). */ + Local, + /** Only apply transform scale to the shape component, which controls where particles are spawned, but does not affect their size or movement. */ + World +} diff --git a/packages/core/src/particle/enums/ParticleSimulationSpace.ts b/packages/core/src/particle/enums/ParticleSimulationSpace.ts new file mode 100644 index 0000000000..290ff7a4ba --- /dev/null +++ b/packages/core/src/particle/enums/ParticleSimulationSpace.ts @@ -0,0 +1,9 @@ +/** + * The space to simulate particles in. + */ +export enum ParticleSimulationSpace { + /** Simulate particles in local space. */ + Local, + /** Simulate particles in world space. */ + World +} diff --git a/packages/core/src/particle/enums/ParticleStopMode.ts b/packages/core/src/particle/enums/ParticleStopMode.ts new file mode 100644 index 0000000000..b261d6c86b --- /dev/null +++ b/packages/core/src/particle/enums/ParticleStopMode.ts @@ -0,0 +1,6 @@ +export enum ParticleStopMode { + /** Stop emitting new particles and clear existing particles immediately. */ + StopEmittingAndClear, + /** Stop emitting new particles, but keep existing particles until they expire. */ + StopEmitting +} diff --git a/packages/core/src/particle/enums/attributes/BillboardParticleVertexAttribute.ts b/packages/core/src/particle/enums/attributes/BillboardParticleVertexAttribute.ts new file mode 100644 index 0000000000..6c9c67f220 --- /dev/null +++ b/packages/core/src/particle/enums/attributes/BillboardParticleVertexAttribute.ts @@ -0,0 +1,6 @@ +/** + * @internal + */ +export enum ParticleBillboardVertexAttribute { + cornerTextureCoordinate = "a_CornerTextureCoordinate" +} diff --git a/packages/core/src/particle/enums/attributes/MeshParticleVertexAttribute.ts b/packages/core/src/particle/enums/attributes/MeshParticleVertexAttribute.ts new file mode 100644 index 0000000000..3e1cdb0e08 --- /dev/null +++ b/packages/core/src/particle/enums/attributes/MeshParticleVertexAttribute.ts @@ -0,0 +1,5 @@ +export enum MeshParticleVertexAttribute { + MeshPosition = "a_MeshPosition", + MeshColor = "a_MeshColor", + MeshTextureCoordinate = "a_MeshTextureCoordinate" +} diff --git a/packages/core/src/particle/enums/attributes/ParticleInstanceVertexAttribute.ts b/packages/core/src/particle/enums/attributes/ParticleInstanceVertexAttribute.ts new file mode 100644 index 0000000000..e6cd238fb0 --- /dev/null +++ b/packages/core/src/particle/enums/attributes/ParticleInstanceVertexAttribute.ts @@ -0,0 +1,16 @@ +/** + * @internal + */ +export enum ParticleInstanceVertexAttribute { + ShapePositionStartLifeTime = "a_ShapePositionStartLifeTime", + DirectionTime = "a_DirectionTime", + StartColor = "a_StartColor", + StartSize = "a_StartSize", + StartRotation0 = "a_StartRotation0", + StartSpeed = "a_StartSpeed", + Random0 = "a_Random0", + Random1 = "a_Random1", + SimulationWorldPosition = "a_SimulationWorldPosition", + SimulationWorldRotation = "a_SimulationWorldRotation", + SimulationUV = "a_SimulationUV" +} diff --git a/packages/core/src/particle/index.ts b/packages/core/src/particle/index.ts index 1ff89faaa2..93c6ef3089 100644 --- a/packages/core/src/particle/index.ts +++ b/packages/core/src/particle/index.ts @@ -1 +1,12 @@ -export { ParticleRenderer, ParticleRendererBlendMode } from "./ParticleRenderer"; +export { ParticleMaterial } from "./ParticleMaterial"; +export { ParticleRenderer } from "./ParticleRenderer"; +export { ParticleCurveMode } from "./enums/ParticleCurveMode"; +export { ParticleGradientMode } from "./enums/ParticleGradientMode"; +export { ParticleRenderMode } from "./enums/ParticleRenderMode"; +export { ParticleScaleMode } from "./enums/ParticleScaleMode"; +export { ParticleSimulationSpace } from "./enums/ParticleSimulationSpace"; +export { ParticleStopMode } from "./enums/ParticleStopMode"; +export { Burst } from "./modules/Burst"; +export { ParticleCompositeCurve } from "./modules/ParticleCompositeCurve"; +export { CurveKey, ParticleCurve } from "./modules/ParticleCurve"; +export * from "./modules/shape/index"; diff --git a/packages/core/src/particle/modules/Burst.ts b/packages/core/src/particle/modules/Burst.ts new file mode 100644 index 0000000000..be046cb583 --- /dev/null +++ b/packages/core/src/particle/modules/Burst.ts @@ -0,0 +1,21 @@ +import { deepClone } from "../../clone/CloneManager"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; + +/** + * A burst is a particle emission event, where a number of particles are all emitted at the same time + */ +export class Burst { + public time: number; + @deepClone + public count: ParticleCompositeCurve; + + /** + * Create burst object. + * @param time - Time to emit the burst + * @param count - Count of particles to emit + */ + constructor(time: number, count: ParticleCompositeCurve) { + this.time = time; + this.count = count; + } +} diff --git a/packages/core/src/particle/modules/ColorOverLifetimeModule.ts b/packages/core/src/particle/modules/ColorOverLifetimeModule.ts new file mode 100644 index 0000000000..e895a893d3 --- /dev/null +++ b/packages/core/src/particle/modules/ColorOverLifetimeModule.ts @@ -0,0 +1,95 @@ +import { Color, Rand, Vector4 } from "@galacean/engine-math"; +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ShaderData } from "../../shader/ShaderData"; +import { ShaderMacro } from "../../shader/ShaderMacro"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import { ParticleGradientMode } from "../enums/ParticleGradientMode"; +import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleCompositeGradient } from "./ParticleCompositeGradient"; +import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; +import { GradientAlphaKey, GradientColorKey, ParticleGradient } from "./ParticleGradient"; + +/** + * Color over lifetime module. + */ +export class ColorOverLifetimeModule extends ParticleGeneratorModule { + static readonly _gradientMacro = ShaderMacro.getByName("RENDERER_COL_GRADIENT"); + static readonly _randomGradientsMacro = ShaderMacro.getByName("RENDERER_COL_RANDOM_GRADIENTS"); + + static readonly _minGradientColor = ShaderProperty.getByName("renderer_COLMinGradientColor"); + static readonly _minGradientAlpha = ShaderProperty.getByName("renderer_COLMinGradientAlpha"); + static readonly _maxGradientColor = ShaderProperty.getByName("renderer_COLMaxGradientColor"); + static readonly _maxGradientAlpha = ShaderProperty.getByName("renderer_COLMaxGradientAlpha"); + static readonly _gradientKeysCount = ShaderProperty.getByName("renderer_COLGradientKeysMaxTime"); + + /** Color gradient over lifetime. */ + @deepClone + color = new ParticleCompositeGradient( + new ParticleGradient( + [new GradientColorKey(0.0, new Color(1, 1, 1)), new GradientColorKey(1.0, new Color(1, 1, 1))], + [new GradientAlphaKey(0, 1), new GradientAlphaKey(1, 1)] + ) + ); + + /** @internal */ + @ignoreClone + _colorGradientRand = new Rand(0, ParticleRandomSubSeeds.ColorOverLifetime); + + @ignoreClone + private _gradientKeysCount = new Vector4(0, 0, 0, 0); // x: minColorKeysMaxTime, y: minAlphaKeysMaxTime, z: maxColorKeysMaxTime, w: maxAlphaKeysMaxTime + @ignoreClone + private _colorMacro: ShaderMacro; + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + let colorMacro = null; + if (this.enabled) { + const mode = this.color.mode; + + if (mode === ParticleGradientMode.Gradient || mode === ParticleGradientMode.TwoGradients) { + const color = this.color; + const colorSpace = this._generator._renderer.engine.settings.colorSpace; + shaderData.setFloatArray( + ColorOverLifetimeModule._maxGradientColor, + color.gradientMax._getColorTypeArray(colorSpace) + ); + shaderData.setFloatArray(ColorOverLifetimeModule._maxGradientAlpha, color.gradientMax._getAlphaTypeArray()); + + if (mode === ParticleGradientMode.Gradient) { + colorMacro = ColorOverLifetimeModule._gradientMacro; + } else { + shaderData.setFloatArray( + ColorOverLifetimeModule._minGradientColor, + color.gradientMin._getColorTypeArray(colorSpace) + ); + shaderData.setFloatArray(ColorOverLifetimeModule._minGradientAlpha, color.gradientMin._getAlphaTypeArray()); + colorMacro = ColorOverLifetimeModule._randomGradientsMacro; + } + + const colorMinKeys = color.gradientMin.colorKeys; + const alphaMinKeys = color.gradientMin.alphaKeys; + const colorMaxKeys = color.gradientMax.colorKeys; + const alphaMaxKeys = color.gradientMax.alphaKeys; + + this._gradientKeysCount.set( + colorMinKeys.length ? colorMinKeys[colorMinKeys.length - 1].time : 0, + alphaMinKeys.length ? alphaMinKeys[alphaMinKeys.length - 1].time : 0, + colorMaxKeys.length ? colorMaxKeys[colorMaxKeys.length - 1].time : 0, + alphaMaxKeys.length ? alphaMaxKeys[alphaMaxKeys.length - 1].time : 0 + ); + shaderData.setVector4(ColorOverLifetimeModule._gradientKeysCount, this._gradientKeysCount); + } + } + + this._colorMacro = this._enableMacro(shaderData, this._colorMacro, colorMacro); + } + + /** + * @internal + */ + _resetRandomSeed(seed: number): void { + this._colorGradientRand.reset(seed, ParticleRandomSubSeeds.ColorOverLifetime); + } +} diff --git a/packages/core/src/particle/modules/EmissionModule.ts b/packages/core/src/particle/modules/EmissionModule.ts new file mode 100644 index 0000000000..02e0ae76bf --- /dev/null +++ b/packages/core/src/particle/modules/EmissionModule.ts @@ -0,0 +1,168 @@ +import { Rand } from "@galacean/engine-math"; +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { Burst } from "./Burst"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; +import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; +import { BaseShape } from "./shape/BaseShape"; + +/** + * The EmissionModule of a Particle Generator. + */ +export class EmissionModule extends ParticleGeneratorModule { + /** The rate of particle emission. */ + @deepClone + rateOverTime: ParticleCompositeCurve = new ParticleCompositeCurve(10); + /** The rate at which the emitter spawns new particles over distance. */ + @deepClone + rateOverDistance: ParticleCompositeCurve = new ParticleCompositeCurve(0); + /** The shape of the emitter. */ + @deepClone + shape: BaseShape; + + /** @internal */ + @ignoreClone + _shapeRand = new Rand(0, ParticleRandomSubSeeds.Shape); + + @deepClone + private _bursts: Burst[] = []; + + private _frameRateTime: number = 0; + private _currentBurstIndex: number = 0; + + @ignoreClone + private _burstRand: Rand = new Rand(0, ParticleRandomSubSeeds.Burst); + + /** + * Gets the burst array. + */ + get bursts(): ReadonlyArray { + return this._bursts; + } + + /** + * Add a single burst. + * @param burst - The burst + */ + addBurst(burst: Burst): void { + const bursts = this._bursts; + let burstIndex = bursts.length; + while (--burstIndex >= 0 && burst.time < bursts[burstIndex].time); + bursts.splice(burstIndex + 1, 0, burst); + } + + /** + * Remove a single burst from the array of bursts. + * @param burst - The burst data + */ + removeBurst(burst: Burst): void { + const index = this._bursts.indexOf(burst); + if (index !== -1) { + this._bursts.splice(index, 1); + } + } + + /** + * Remove a single burst from the array of bursts. + * @param index - The burst data index + */ + removeBurstByIndex(index: number): void { + this._bursts.splice(index, 1); + } + + /** + * Clear burst data. + */ + clearBurst(): void { + this._bursts.length = 0; + } + + /** + * @internal + */ + _emit(lastPlayTime: number, playTime: number): void { + this._emitByRateOverTime(playTime); + this._emitByBurst(lastPlayTime, playTime); + } + + /** + * @internal + */ + _resetRandomSeed(seed: number): void { + this._burstRand.reset(seed, ParticleRandomSubSeeds.Burst); + this._shapeRand.reset(seed, ParticleRandomSubSeeds.Shape); + } + + /** + * @internal + */ + _resetBurst(): void { + this._currentBurstIndex = 0; + } + + private _emitByRateOverTime(playTime: number): void { + const ratePerSeconds = this.rateOverTime.evaluate(undefined, undefined); + if (ratePerSeconds > 0) { + const generator = this._generator; + const emitInterval = 1.0 / ratePerSeconds; + + let cumulativeTime = playTime - this._frameRateTime; + while (cumulativeTime >= emitInterval) { + cumulativeTime -= emitInterval; + this._frameRateTime += emitInterval; + generator._emit(this._frameRateTime, 1); + } + } + } + + private _emitByBurst(lastPlayTime: number, playTime: number): void { + const main = this._generator.main; + const duration = main.duration; + const cycleCount = Math.floor((playTime - lastPlayTime) / duration); + + // Across one cycle + if (main.isLoop && (cycleCount > 0 || playTime % duration < lastPlayTime % duration)) { + let middleTime = Math.ceil(lastPlayTime / duration) * duration; + this._emitBySubBurst(lastPlayTime, middleTime, duration); + this._currentBurstIndex = 0; + + for (let i = 0; i < cycleCount; i++) { + const lastMiddleTime = middleTime; + middleTime += duration; + this._emitBySubBurst(lastMiddleTime, middleTime, duration); + this._currentBurstIndex = 0; + } + + this._emitBySubBurst(middleTime, playTime, duration); + } else { + this._emitBySubBurst(lastPlayTime, playTime, duration); + } + } + + private _emitBySubBurst(lastPlayTime: number, playTime: number, duration: number): void { + const generator = this._generator; + const rand = this._burstRand; + const bursts = this.bursts; + + // Calculate the relative time of the burst + const baseTime = Math.floor(lastPlayTime / duration) * duration; + const startTime = lastPlayTime % duration; + const endTime = startTime + (playTime - lastPlayTime); + + let index = this._currentBurstIndex; + for (let n = bursts.length; index < n; index++) { + const burst = bursts[index]; + const burstTime = burst.time; + + if (burstTime > endTime) { + break; + } + + if (burstTime >= startTime) { + const count = burst.count.evaluate(undefined, rand.random()); + generator._emit(baseTime + burstTime, count); + } + } + this._currentBurstIndex = index; + } +} diff --git a/packages/core/src/particle/modules/MainModule.ts b/packages/core/src/particle/modules/MainModule.ts new file mode 100644 index 0000000000..e9b9cbd0cf --- /dev/null +++ b/packages/core/src/particle/modules/MainModule.ts @@ -0,0 +1,216 @@ +import { Color, Rand, Vector3, Vector4 } from "@galacean/engine-math"; +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ICustomClone } from "../../clone/ComponentCloner"; +import { ShaderData } from "../../shader/ShaderData"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import { ParticleGenerator } from "../ParticleGenerator"; +import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleScaleMode } from "../enums/ParticleScaleMode"; +import { ParticleSimulationSpace } from "../enums/ParticleSimulationSpace"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; +import { ParticleCompositeGradient } from "./ParticleCompositeGradient"; + +export class MainModule implements ICustomClone { + private static _tempVector40 = new Vector4(); + private static _vector3One = new Vector3(1, 1, 1); + + private static readonly _positionScale = ShaderProperty.getByName("renderer_PositionScale"); + private static readonly _sizeScale = ShaderProperty.getByName("renderer_SizeScale"); + private static readonly _worldPosition = ShaderProperty.getByName("renderer_WorldPosition"); + private static readonly _worldRotation = ShaderProperty.getByName("renderer_WorldRotation"); + private static readonly _gravity = ShaderProperty.getByName("renderer_Gravity"); + private static readonly _simulationSpace = ShaderProperty.getByName("renderer_SimulationSpace"); + private static readonly _startRotation3D = ShaderProperty.getByName("renderer_ThreeDStartRotation"); + private static readonly _scaleMode = ShaderProperty.getByName("renderer_ScalingMode"); + + /** The duration of the Particle Generator in seconds. */ + duration = 5.0; + /** Specifies whether the Particle Generator loops. */ + isLoop = true; + + /** Start delay in seconds. */ + @deepClone + startDelay = new ParticleCompositeCurve(0); + /** The initial lifetime of particles when emitted. */ + @deepClone + startLifetime = new ParticleCompositeCurve(5); + /** The initial speed of particles when the Particle Generator first spawns them. */ + @deepClone + startSpeed = new ParticleCompositeCurve(5); + + /** A flag to enable specifying particle size individually for each axis. */ + startSize3D = false; + /** The initial size of particles when the Particle Generator first spawns them. */ + @deepClone + startSize = new ParticleCompositeCurve(1); + /** The initial size of particles along the x-axis when the Particle Generator first spawns them. */ + @deepClone + startSizeX = new ParticleCompositeCurve(1); + /** The initial size of particles along the y-axis when the Particle Generator first spawns them. */ + @deepClone + startSizeY = new ParticleCompositeCurve(1); + /** The initial size of particles along the z-axis when the Particle Generator first spawns them. */ + @deepClone + startSizeZ = new ParticleCompositeCurve(1); + + /** A flag to enable 3D particle rotation. */ + startRotation3D = false; + /** The initial rotation of particles when the Particle Generator first spawns them. */ + @deepClone + startRotation = new ParticleCompositeCurve(0); + /** The initial rotation of particles around the x-axis when emitted.*/ + @deepClone + startRotationX = new ParticleCompositeCurve(0); + /** The initial rotation of particles around the y-axis when emitted. */ + @deepClone + startRotationY = new ParticleCompositeCurve(0); + @deepClone + /** The initial rotation of particles around the z-axis when emitted. */ + @deepClone + startRotationZ = new ParticleCompositeCurve(0); + /** Makes some particles spin in the opposite direction. */ + flipRotation = 0; + + /** The mode of start color */ + @deepClone + startColor = new ParticleCompositeGradient(new Color(1, 1, 1, 1)); + /** A scale that this Particle Generator applies to gravity, defined by Physics.gravity. */ + @deepClone + gravityModifier = new ParticleCompositeCurve(0); + /** This selects the space in which to simulate particles. It can be either world or local space. */ + simulationSpace = ParticleSimulationSpace.Local; + /** Override the default playback speed of the Particle Generator. */ + simulationSpeed = 1.0; + /** Control how the Particle Generator applies its Transform component to the particles it emits. */ + scalingMode = ParticleScaleMode.Local; + /** If set to true, the Particle Generator automatically begins to play on startup. */ + playOnEnabled = true; + + @ignoreClone + private _maxParticles = 1000; + @ignoreClone + private _generator: ParticleGenerator; + @ignoreClone + private _gravity = new Vector3(); + + /** @internal */ + @ignoreClone + readonly _startSpeedRand = new Rand(0, ParticleRandomSubSeeds.StartSpeed); + /** @internal */ + @ignoreClone + readonly _startLifeTimeRand = new Rand(0, ParticleRandomSubSeeds.StartLifetime); + /** @internal */ + @ignoreClone + readonly _startColorRand = new Rand(0, ParticleRandomSubSeeds.StartColor); + /** @internal */ + @ignoreClone + readonly _startSizeRand = new Rand(0, ParticleRandomSubSeeds.StartSize); + /** @internal */ + @ignoreClone + readonly _startRotationRand = new Rand(0, ParticleRandomSubSeeds.StartRotation); + + /** + * Max particles count. + */ + get maxParticles(): number { + return this._maxParticles; + } + + set maxParticles(value: number) { + if (this._maxParticles !== value) { + this._maxParticles = value; + + const generator = this._generator; + if (value < generator._currentParticleCount) { + generator._resizeInstanceBuffer(value); + } + } + } + + /** + * @internal + */ + constructor(generator: ParticleGenerator) { + this._generator = generator; + } + + /** + * @internal + */ + _resetRandomSeed(randomSeed: number): void { + this._startSpeedRand.reset(randomSeed, ParticleRandomSubSeeds.StartSpeed); + this._startLifeTimeRand.reset(randomSeed, ParticleRandomSubSeeds.StartLifetime); + this._startColorRand.reset(randomSeed, ParticleRandomSubSeeds.StartColor); + this._startSizeRand.reset(randomSeed, ParticleRandomSubSeeds.StartSize); + this._startRotationRand.reset(randomSeed, ParticleRandomSubSeeds.StartRotation); + } + + /** + * @internal + */ + _getPositionScale(): Vector3 { + const transform = this._generator._renderer.entity.transform; + switch (this.scalingMode) { + case ParticleScaleMode.Hierarchy: + case ParticleScaleMode.World: + return transform.lossyWorldScale; + case ParticleScaleMode.Local: + return transform.scale; + } + } + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + const renderer = this._generator._renderer; + const transform = renderer.entity.transform; + + switch (this.simulationSpace) { + case ParticleSimulationSpace.Local: + shaderData.setVector3(MainModule._worldPosition, transform.worldPosition); + const worldRotation = transform.worldRotationQuaternion; + const worldRotationV4 = MainModule._tempVector40; + worldRotationV4.copyFrom(worldRotation); + shaderData.setVector4(MainModule._worldRotation, worldRotationV4); + break; + case ParticleSimulationSpace.World: + break; + default: + throw new Error("ParticleRenderer: SimulationSpace value is invalid."); + } + + switch (this.scalingMode) { + case ParticleScaleMode.Hierarchy: + var scale = transform.lossyWorldScale; + shaderData.setVector3(MainModule._positionScale, scale); + shaderData.setVector3(MainModule._sizeScale, scale); + break; + case ParticleScaleMode.Local: + var scale = transform.scale; + shaderData.setVector3(MainModule._positionScale, scale); + shaderData.setVector3(MainModule._sizeScale, scale); + break; + case ParticleScaleMode.World: + shaderData.setVector3(MainModule._positionScale, transform.lossyWorldScale); + shaderData.setVector3(MainModule._sizeScale, MainModule._vector3One); + break; + } + + const particleGravity = this._gravity; + const gravityModifierValue = this.gravityModifier.evaluate(undefined, undefined); + Vector3.scale(renderer.scene.physics.gravity, gravityModifierValue, particleGravity); + + shaderData.setVector3(MainModule._gravity, particleGravity); + shaderData.setInt(MainModule._simulationSpace, this.simulationSpace); + shaderData.setFloat(MainModule._startRotation3D, +this.startRotation3D); + shaderData.setInt(MainModule._scaleMode, this.scalingMode); + } + + /** + * @internal + */ + _cloneTo(target: MainModule): void { + target.maxParticles = this.maxParticles; + } +} diff --git a/packages/core/src/particle/modules/ParticleCompositeCurve.ts b/packages/core/src/particle/modules/ParticleCompositeCurve.ts new file mode 100644 index 0000000000..0fd65f9c1a --- /dev/null +++ b/packages/core/src/particle/modules/ParticleCompositeCurve.ts @@ -0,0 +1,109 @@ +import { deepClone } from "../../clone/CloneManager"; +import { ParticleCurveMode } from "../enums/ParticleCurveMode"; +import { ParticleCurve } from "./ParticleCurve"; + +/** + * Particle composite curve. + */ +export class ParticleCompositeCurve { + /** The curve mode. */ + mode: ParticleCurveMode = ParticleCurveMode.Constant; + /** The min constant value used by the curve if mode is set to `TwoConstants`.*/ + constantMin: number = 0; + /** The max constant value used by the curve if mode is set to `TwoConstants`.*/ + constantMax: number = 0; + /** The min curve used by the curve if mode is set to `TwoCurves`. */ + @deepClone + curveMin: ParticleCurve; + /** The max curve used by the curve if mode is set to `TwoCurves`. */ + @deepClone + curveMax: ParticleCurve; + + /** + * The constant value used by the curve if mode is set to `Constant`. + */ + get constant(): number { + return this.constantMax; + } + + set constant(value: number) { + this.constantMax = value; + } + + /** + * The curve used by the curve if mode is set to `Curve`. + */ + get curve(): ParticleCurve { + return this.curveMax; + } + + set curve(value: ParticleCurve) { + this.curveMax = value; + } + + /** + * Create a particle curve that generates a constant value. + * @param constant - The constant value + */ + constructor(constant: number); + + /** + * Create a particle curve that can generate values between a minimum constant and a maximum constant. + * @param constantMin - The min constant value + * @param constantMax - The max constant value + */ + constructor(constantMin: number, constantMax: number); + + /** + * Create a particle composite curve by a curve. + * @param curve - The curve + */ + constructor(curve: ParticleCurve); + + /** + * Create a particle composite curve by min and max curves. + * @param curveMin - The min curve + * @param curveMax - The max curve + */ + constructor(curveMin: ParticleCurve, curveMax: ParticleCurve); + + constructor(constantOrCurve: number | ParticleCurve, constantMaxOrCurveMax?: number | ParticleCurve) { + if (typeof constantOrCurve === "number") { + if (constantMaxOrCurveMax) { + this.constantMin = constantOrCurve; + this.constantMax = constantMaxOrCurveMax; + this.mode = ParticleCurveMode.TwoConstants; + } else { + this.constant = constantOrCurve; + this.mode = ParticleCurveMode.Constant; + } + } else { + if (constantMaxOrCurveMax) { + this.curveMin = constantOrCurve; + this.curveMax = constantMaxOrCurveMax; + this.mode = ParticleCurveMode.TwoCurves; + } else { + this.curve = constantOrCurve; + this.mode = ParticleCurveMode.Curve; + } + } + } + + /** + * Query the value at the specified time. + * @param time - Normalized time at which to evaluate the curve, Valid when `mode` is set to `Curve` or `TwoCurves` + * @param lerpFactor - Lerp factor between two constants or curves, Valid when `mode` is set to `TwoConstants` or `TwoCurves` + * @returns - The result curve value + */ + + evaluate(time: number, lerpFactor: number): number { + switch (this.mode) { + case ParticleCurveMode.Constant: + return this.constant; + case ParticleCurveMode.TwoConstants: + return this.constantMin + (this.constantMax - this.constantMin) * lerpFactor; + default: + break; + } + } +} diff --git a/packages/core/src/particle/modules/ParticleCompositeGradient.ts b/packages/core/src/particle/modules/ParticleCompositeGradient.ts new file mode 100644 index 0000000000..59294998b4 --- /dev/null +++ b/packages/core/src/particle/modules/ParticleCompositeGradient.ts @@ -0,0 +1,114 @@ +import { Color } from "@galacean/engine-math"; +import { deepClone } from "../../clone/CloneManager"; +import { ParticleGradientMode } from "../enums/ParticleGradientMode"; +import { ParticleGradient } from "./ParticleGradient"; + +/** + * Particle composite gradient. + */ +export class ParticleCompositeGradient { + /** The gradient mode. */ + mode: ParticleGradientMode = ParticleGradientMode.Constant; + /* The min constant color used by the gradient if mode is set to `TwoConstants`. */ + @deepClone + constantMin: Color = new Color(); + /* The max constant color used by the gradient if mode is set to `TwoConstants`. */ + @deepClone + constantMax: Color = new Color(); + /** The min gradient used by the gradient if mode is set to `Gradient`. */ + @deepClone + gradientMin: ParticleGradient = new ParticleGradient(); + /** The max gradient used by the gradient if mode is set to `Gradient`. */ + @deepClone + gradientMax: ParticleGradient = new ParticleGradient(); + + /** + * The constant color used by the gradient if mode is set to `Constant`. + */ + get constant(): Color { + return this.constantMax; + } + + set constant(value: Color) { + this.constantMax = value; + } + + /** + * The gradient used by the gradient if mode is set to `Gradient`. + */ + get gradient(): ParticleGradient { + return this.gradientMax; + } + + set gradient(value: ParticleGradient) { + this.gradientMax = value; + } + + /** + * Create a particle gradient that generates a constant color. + * @param constant - The constant color + */ + constructor(constant: Color); + + /** + * Create a particle gradient that can generate color between a minimum constant and a maximum constant. + * @param constantMin - The min constant color + * @param constantMax - The max constant color + */ + constructor(constantMin: Color, constantMax: Color); + + /** + * Create a particle gradient that generates a color from a gradient. + * @param gradient - The gradient + */ + constructor(gradient: ParticleGradient); + + /** + * Create a particle gradient that can generate color from a minimum gradient and a maximum gradient. + * @param gradientMin - The min gradient + * + */ + constructor(gradientMin: ParticleGradient, gradientMax: ParticleGradient); + + constructor(constantOrGradient: Color | ParticleGradient, constantMaxOrGradientMax?: Color | ParticleGradient) { + if (constantOrGradient.constructor === Color) { + if (constantMaxOrGradientMax) { + this.constantMin.copyFrom(constantOrGradient); + this.constantMax.copyFrom(constantMaxOrGradientMax); + this.mode = ParticleGradientMode.TwoConstants; + } else { + this.constant.copyFrom(constantOrGradient); + this.mode = ParticleGradientMode.Constant; + } + } else { + if (constantMaxOrGradientMax) { + this.gradientMin = constantOrGradient; + this.gradientMax = constantMaxOrGradientMax; + this.mode = ParticleGradientMode.TwoGradients; + } else { + this.gradient = constantOrGradient; + this.mode = ParticleGradientMode.Gradient; + } + } + } + + /** + * Query the color at the specified time. + * @param time - Normalized time at which to evaluate the gradient, Valid when `mode` is set to `Gradient` or `TwoGradients` + * @param lerpFactor - Lerp factor between two constants or gradients, Valid when `mode` is set to `TwoConstants` or `TwoGradients` + * @param out - The result color + */ + + evaluate(time: number, lerpFactor: number, out: Color): void { + switch (this.mode) { + case ParticleGradientMode.Constant: + out.copyFrom(this.constant); + break; + case ParticleGradientMode.TwoConstants: + Color.lerp(this.constantMin, this.constantMax, lerpFactor, out); + break; + default: + break; + } + } +} diff --git a/packages/core/src/particle/modules/ParticleCurve.ts b/packages/core/src/particle/modules/ParticleCurve.ts new file mode 100644 index 0000000000..2a4066a205 --- /dev/null +++ b/packages/core/src/particle/modules/ParticleCurve.ts @@ -0,0 +1,122 @@ +import { deepClone, ignoreClone } from "../../clone/CloneManager"; + +/** + * Particle curve. + */ +export class ParticleCurve { + @deepClone + private _keys: CurveKey[] = []; + @ignoreClone + private _typeArray: Float32Array; + private _typeArrayDirty: boolean = false; + + /** + * The keys of the curve. + */ + get keys(): ReadonlyArray { + return this._keys; + } + + /** + * Create a new particle curve. + * @param keys - The keys of the curve + */ + constructor(...keys: CurveKey[]) { + for (let i = 0, n = keys.length; i < n; i++) { + const key = keys[i]; + this.addKey(key); + } + } + + /** + * Add an key to the curve. + * @param key - The key + */ + addKey(key: CurveKey): void; + + /** + * Add an key to the curve. + * @param time - The key time + * @param value - The key value + */ + addKey(time: number, value: number): void; + + addKey(timeOrKey: number | CurveKey, value?: number): void { + const keys = this._keys; + + if (keys.length === 4) { + throw new Error("Curve can only have 4 keys"); + } + + const key = typeof timeOrKey === "number" ? new CurveKey(timeOrKey, value) : timeOrKey; + this._addKey(keys, key); + this._typeArrayDirty = true; + } + + /** + * Remove a key from the curve. + * @param index - The remove key index + */ + removeKey(index: number): void { + this._keys.splice(index, 1); + this._typeArrayDirty = true; + } + + /** + * Set the keys of the curve. + * @param keys - The keys + */ + setKeys(keys: CurveKey[]): void { + this._keys.length = 0; + for (let i = 0, n = keys.length; i < n; i++) { + this.addKey(keys[i]); + } + this._typeArrayDirty = true; + } + + /** + * @internal + */ + _getTypeArray(): Float32Array { + const typeArray = (this._typeArray ||= new Float32Array(4 * 2)); + if (this._typeArrayDirty) { + const keys = this._keys; + for (let i = 0, n = Math.min(keys.length, 4); i < n; i++) { + const offset = i * 2; + const key = keys[i]; + typeArray[offset] = key.time; + typeArray[offset + 1] = key.value; + } + this._typeArrayDirty = false; + } + return typeArray; + } + + private _addKey(keys: CurveKey[], key: CurveKey): void { + const count = keys.length; + const time = key.time; + const duration = count ? keys[count - 1].time : 0; + if (time >= duration) { + keys.push(key); + } else { + let index = count; + while (--index >= 0 && time < keys[index].time); + keys.splice(index + 1, 0, key); + } + } +} + +/** + * The key of the curve. + */ +export class CurveKey { + /** + * Create a new key. + */ + constructor( + /** The key time. */ + public time: number, + /** The key value. */ + public value: number + ) {} +} diff --git a/packages/core/src/particle/modules/ParticleGeneratorModule.ts b/packages/core/src/particle/modules/ParticleGeneratorModule.ts new file mode 100644 index 0000000000..6f739c9589 --- /dev/null +++ b/packages/core/src/particle/modules/ParticleGeneratorModule.ts @@ -0,0 +1,26 @@ +import { ignoreClone } from "../../clone/CloneManager"; +import { ShaderData, ShaderMacro } from "../../shader"; +import { ParticleGenerator } from "../ParticleGenerator"; + +/** + * Particle generator module. + */ +export abstract class ParticleGeneratorModule { + /** Specifies whether the module is enabled or not. */ + enabled: boolean = false; + + @ignoreClone + protected _generator: ParticleGenerator; + + constructor(generator: ParticleGenerator) { + this._generator = generator; + } + + protected _enableMacro(shaderData: ShaderData, lastEnableMacro: ShaderMacro, enableMacro: ShaderMacro): ShaderMacro { + if (lastEnableMacro !== enableMacro) { + lastEnableMacro && shaderData.disableMacro(lastEnableMacro); + enableMacro && shaderData.enableMacro(enableMacro); + } + return enableMacro; + } +} diff --git a/packages/core/src/particle/modules/ParticleGradient.ts b/packages/core/src/particle/modules/ParticleGradient.ts new file mode 100644 index 0000000000..bac527323d --- /dev/null +++ b/packages/core/src/particle/modules/ParticleGradient.ts @@ -0,0 +1,227 @@ +import { Color } from "@galacean/engine-math"; +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ColorSpace } from "../../enums/ColorSpace"; + +/** + * Particle gradient. + */ +export class ParticleGradient { + @deepClone + private _colorKeys: GradientColorKey[] = []; + @deepClone + private _alphaKeys: GradientAlphaKey[] = []; + @ignoreClone + private _colorTypeArray: Float32Array; + @ignoreClone + private _alphaTypeArray: Float32Array; + private _colorTypeArrayDirty: boolean = false; + private _alphaTypeArrayDirty: boolean = false; + + /** + * The color keys of the gradient. + */ + get colorKeys(): ReadonlyArray { + return this._colorKeys; + } + + /** + * The alpha keys of the gradient. + */ + get alphaKeys(): ReadonlyArray { + return this._alphaKeys; + } + + /** + * Create a new particle gradient. + * @param colorKeys - The color keys of the gradient + * @param alphaKeys - The alpha keys of the gradient + */ + constructor(colorKeys: GradientColorKey[] = null, alphaKeys: GradientAlphaKey[] = null) { + if (colorKeys) { + for (let i = 0, n = colorKeys.length; i < n; i++) { + const key = colorKeys[i]; + this.addColorKey(key); + } + } + + if (alphaKeys) { + for (let i = 0, n = alphaKeys.length; i < n; i++) { + const key = alphaKeys[i]; + this.addAlphaKey(key); + } + } + } + + /** + * Add a color key to the gradient. + * @param key - The key + */ + addColorKey(key: GradientColorKey): void; + /** + * Add a color key to the gradient. + * @param time - The key time + * @param color - The key color + */ + addColorKey(time: number, color: Color): void; + + addColorKey(timeOrKey: number | GradientColorKey, color?: Color): void { + const colorKeys = this._colorKeys; + + if (colorKeys.length === 4) { + throw new Error("Gradient can only have 4 color keys"); + } + + const key = typeof timeOrKey === "number" ? new GradientColorKey(timeOrKey, color) : timeOrKey; + this._addKey(colorKeys, key); + this._colorTypeArrayDirty = true; + } + + /** + * Add an alpha key to the gradient. + * @param key - The key + */ + addAlphaKey(key: GradientAlphaKey): void; + + /** + * Add an alpha key to the gradient. + * @param time - The key time + * @param alpha - The key alpha + */ + addAlphaKey(time: number, alpha: number): void; + + addAlphaKey(timeOrKey: number | GradientAlphaKey, alpha?: number): void { + const alphaKeys = this._alphaKeys; + + if (alphaKeys.length === 4) { + throw new Error("Gradient can only have 4 color keys"); + } + + const key = typeof timeOrKey === "number" ? new GradientAlphaKey(timeOrKey, alpha) : timeOrKey; + this._addKey(alphaKeys, key); + this._alphaTypeArrayDirty = true; + } + + /** + * Remove a color key from the gradient. + * @param index - The remove color key index + */ + removeColorKey(index: number): void { + this._removeKey(this._colorKeys, index); + this._colorTypeArrayDirty = true; + } + + /** + * Remove an alpha key from the gradient. + * @param index - The remove alpha key index + */ + removeAlphaKey(index: number): void { + this._removeKey(this._alphaKeys, index); + this._alphaTypeArrayDirty = true; + } + + /** + * Set the keys of the gradient. + * @param colorKeys - The color keys + * @param alphaKeys - The alpha keys + */ + setKeys(colorKeys: GradientColorKey[], alphaKeys: GradientAlphaKey[]): void { + this._alphaKeys.length = 0; + this._colorKeys.length = 0; + for (let i = 0, n = colorKeys.length; i < n; i++) { + this._addKey(this._colorKeys, colorKeys[i]); + } + for (let i = 0, n = alphaKeys.length; i < n; i++) { + this._addKey(this._alphaKeys, alphaKeys[i]); + } + this._alphaTypeArrayDirty = true; + this._colorTypeArrayDirty = true; + } + + /** + * @internal + */ + _getColorTypeArray(colorSpace: ColorSpace): Float32Array { + const typeArray = (this._colorTypeArray ||= new Float32Array(4 * 4)); + if (this._colorTypeArrayDirty) { + const keys = this._colorKeys; + for (let i = 0, n = Math.min(keys.length, 4); i < n; i++) { + const offset = i * 4; + const key = keys[i]; + typeArray[offset] = key.time; + const color = key.color; + if (colorSpace === ColorSpace.Linear) { + typeArray[offset + 1] = Color.gammaToLinearSpace(color.r); + typeArray[offset + 2] = Color.gammaToLinearSpace(color.g); + typeArray[offset + 3] = Color.gammaToLinearSpace(color.b); + } else { + typeArray[offset + 1] = color.r; + typeArray[offset + 2] = color.g; + typeArray[offset + 3] = color.b; + } + } + this._colorTypeArrayDirty = false; + } + + return typeArray; + } + + /** + * @internal + */ + _getAlphaTypeArray(): Float32Array { + const typeArray = (this._alphaTypeArray ||= new Float32Array(4 * 2)); + if (this._alphaTypeArrayDirty) { + const keys = this._alphaKeys; + for (let i = 0, n = Math.min(keys.length, 4); i < n; i++) { + const offset = i * 2; + const key = keys[i]; + typeArray[offset] = key.time; + typeArray[offset + 1] = key.alpha; + } + this._alphaTypeArrayDirty = false; + } + + return typeArray; + } + + private _addKey(keys: T[], key: T): void { + const time = key.time; + const count = keys.length; + const duration = count ? keys[count - 1].time : 0; + if (time >= duration) { + keys.push(key); + } else { + let index = count; + while (--index >= 0 && time < keys[index].time); + keys.splice(index + 1, 0, key); + } + } + + private _removeKey(keys: T[], index: number): void { + keys.splice(index, 1); + } +} + +/** + * The color key of the particle gradient. + */ +export class GradientColorKey { + constructor( + /** The key time. */ + public time: number, + /** The key color. */ + public color: Color + ) {} +} + +/** + * The alpha key of the particle gradient. + */ +export class GradientAlphaKey { + constructor( + /** The key time. */ + public time: number, + /** The key alpha. */ + public alpha: number + ) {} +} diff --git a/packages/core/src/particle/modules/RotationOverLifetimeModule.ts b/packages/core/src/particle/modules/RotationOverLifetimeModule.ts new file mode 100644 index 0000000000..2861d2c398 --- /dev/null +++ b/packages/core/src/particle/modules/RotationOverLifetimeModule.ts @@ -0,0 +1,141 @@ +import { MathUtil, Rand, Vector3 } from "@galacean/engine-math"; +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ShaderData } from "../../shader/ShaderData"; +import { ShaderMacro } from "../../shader/ShaderMacro"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import { ParticleCurveMode } from "../enums/ParticleCurveMode"; +import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; +import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; + +/** + * Rotate particles throughout their lifetime. + */ +export class RotationOverLifetimeModule extends ParticleGeneratorModule { + static readonly _constantModeMacro = ShaderMacro.getByName("RENDERER_ROL_CONSTANT_MODE"); + static readonly _curveModeMacro = ShaderMacro.getByName("RENDERER_ROL_CURVE_MODE"); + static readonly _isSeparateMacro = ShaderMacro.getByName("RENDERER_ROL_IS_SEPARATE"); + static readonly _isRandomTwoMacro = ShaderMacro.getByName("RENDERER_ROL_IS_RANDOM_TWO"); + + static readonly _minConstantProperty = ShaderProperty.getByName("renderer_ROLMinConst"); + static readonly _minCurveXProperty = ShaderProperty.getByName("renderer_ROLMinCurveX"); + static readonly _minCurveYProperty = ShaderProperty.getByName("renderer_ROLMinCurveY"); + static readonly _minCurveZProperty = ShaderProperty.getByName("renderer_ROLMinCurveZ"); + static readonly _maxConstantProperty = ShaderProperty.getByName("renderer_ROLMaxConst"); + static readonly _maxCurveXProperty = ShaderProperty.getByName("renderer_ROLMaxCurveX"); + static readonly _maxCurveYProperty = ShaderProperty.getByName("renderer_ROLMaxCurveY"); + static readonly _maxCurveZProperty = ShaderProperty.getByName("renderer_ROLMaxCurveZ"); + + /** Specifies whether the rotation is separate on each axis, when disabled only z axis is used. */ + separateAxes: boolean = false; + + /** Rotation over lifetime for z axis. */ + @deepClone + rotationX = new ParticleCompositeCurve(0); + /** Rotation over lifetime for z axis. */ + @deepClone + rotationY = new ParticleCompositeCurve(0); + /** Rotation over lifetime for z axis. */ + @deepClone + rotationZ = new ParticleCompositeCurve(45); + + /** @internal */ + @ignoreClone + _rotationRand = new Rand(0, ParticleRandomSubSeeds.RotationOverLifetime); + + @ignoreClone + private _rotationMinConstant = new Vector3(); + @ignoreClone + private _rotationMaxConstant = new Vector3(); + @ignoreClone + private _enableSeparateMacro: ShaderMacro; + @ignoreClone + private _isCurveMacro: ShaderMacro; + @ignoreClone + private _isRandomTwoMacro: ShaderMacro; + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + let enableSeparateMacro = null; + let isCurveMacro = null; + let isRandomTwoMacro = null; + if (this.enabled) { + const rotationX = this.rotationX; + const rotationY = this.rotationY; + const rotationZ = this.rotationZ; + const separateAxes = this.separateAxes; + + const isRandomCurveMode = separateAxes + ? rotationX.mode === ParticleCurveMode.TwoCurves && + rotationY.mode === ParticleCurveMode.TwoCurves && + rotationZ.mode === ParticleCurveMode.TwoCurves + : rotationZ.mode === ParticleCurveMode.TwoCurves; + + const isCurveMode = + isRandomCurveMode || separateAxes + ? rotationX.mode === ParticleCurveMode.Curve && + rotationY.mode === ParticleCurveMode.Curve && + rotationZ.mode === ParticleCurveMode.Curve + : rotationZ.mode === ParticleCurveMode.Curve; + + if (isCurveMode) { + shaderData.setFloatArray(RotationOverLifetimeModule._maxCurveZProperty, rotationZ.curveMax._getTypeArray()); + if (separateAxes) { + shaderData.setFloatArray(RotationOverLifetimeModule._maxCurveXProperty, rotationX.curveMax._getTypeArray()); + shaderData.setFloatArray(RotationOverLifetimeModule._maxCurveYProperty, rotationY.curveMax._getTypeArray()); + } + if (isRandomCurveMode) { + shaderData.setFloatArray(RotationOverLifetimeModule._minCurveZProperty, rotationZ.curveMin._getTypeArray()); + if (separateAxes) { + shaderData.setFloatArray(RotationOverLifetimeModule._minCurveXProperty, rotationX.curveMin._getTypeArray()); + shaderData.setFloatArray(RotationOverLifetimeModule._minCurveYProperty, rotationY.curveMin._getTypeArray()); + } + isRandomTwoMacro = RotationOverLifetimeModule._isRandomTwoMacro; + } + isCurveMacro = RotationOverLifetimeModule._curveModeMacro; + } else { + const constantMax = this._rotationMaxConstant; + constantMax.set( + MathUtil.degreeToRadian(rotationX.constantMax), + MathUtil.degreeToRadian(rotationY.constantMax), + MathUtil.degreeToRadian(rotationZ.constantMax) + ); + shaderData.setVector3(RotationOverLifetimeModule._maxConstantProperty, constantMax); + + if ( + separateAxes + ? rotationX.mode === ParticleCurveMode.TwoConstants && + rotationY.mode === ParticleCurveMode.TwoConstants && + rotationZ.mode === ParticleCurveMode.TwoConstants + : rotationZ.mode === ParticleCurveMode.TwoConstants + ) { + const constantMin = this._rotationMinConstant; + constantMin.set( + MathUtil.degreeToRadian(rotationX.constantMin), + MathUtil.degreeToRadian(rotationY.constantMin), + MathUtil.degreeToRadian(rotationZ.constantMin) + ); + shaderData.setVector3(RotationOverLifetimeModule._minConstantProperty, constantMin); + isRandomTwoMacro = RotationOverLifetimeModule._isRandomTwoMacro; + } + isCurveMacro = RotationOverLifetimeModule._constantModeMacro; + } + + if (separateAxes) { + enableSeparateMacro = RotationOverLifetimeModule._isSeparateMacro; + } + } + this._enableSeparateMacro = this._enableMacro(shaderData, this._enableSeparateMacro, enableSeparateMacro); + this._isCurveMacro = this._enableMacro(shaderData, this._isCurveMacro, isCurveMacro); + this._isRandomTwoMacro = this._enableMacro(shaderData, this._isRandomTwoMacro, isRandomTwoMacro); + } + + /** + * @internal + */ + _resetRandomSeed(seed: number): void { + this._rotationRand.reset(seed, ParticleRandomSubSeeds.RotationOverLifetime); + } +} diff --git a/packages/core/src/particle/modules/SizeOverLifetimeModule.ts b/packages/core/src/particle/modules/SizeOverLifetimeModule.ts new file mode 100644 index 0000000000..3c80e938cf --- /dev/null +++ b/packages/core/src/particle/modules/SizeOverLifetimeModule.ts @@ -0,0 +1,108 @@ +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ShaderData } from "../../shader/ShaderData"; +import { ShaderMacro } from "../../shader/ShaderMacro"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import { ParticleCurveMode } from "../enums/ParticleCurveMode"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; +import { CurveKey, ParticleCurve } from "./ParticleCurve"; +import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; + +/** + * Size over lifetime module. + */ +export class SizeOverLifetimeModule extends ParticleGeneratorModule { + static readonly _curveModeMacro = ShaderMacro.getByName("RENDERER_SOL_CURVE_MODE"); + static readonly _isSeparateMacro = ShaderMacro.getByName("RENDERER_SOL_IS_SEPARATE"); + static readonly _isRandomTwoMacro = ShaderMacro.getByName("RENDERER_SOL_IS_RANDOM_TWO"); + + static readonly _minCurveXProperty = ShaderProperty.getByName("renderer_SOLMinCurveX"); + static readonly _minCurveYProperty = ShaderProperty.getByName("renderer_SOLMinCurveY"); + static readonly _minCurveZProperty = ShaderProperty.getByName("renderer_SOLMinCurveZ"); + static readonly _maxCurveXProperty = ShaderProperty.getByName("renderer_SOLMaxCurveX"); + static readonly _maxCurveYProperty = ShaderProperty.getByName("renderer_SOLMaxCurveY"); + static readonly _maxCurveZProperty = ShaderProperty.getByName("renderer_SOLMaxCurveZ"); + + /** Specifies whether the Size is separate on each axis. */ + separateAxes = false; + /** Size curve over lifetime for x axis. */ + @deepClone + sizeX = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1))); + /** Size curve over lifetime for y axis. */ + @deepClone + sizeY = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1))); + /** Size curve over lifetime for z axis. */ + @deepClone + sizeZ = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1))); + + @ignoreClone + private _enableSeparateMacro: ShaderMacro; + @ignoreClone + private _isCurveMacro: ShaderMacro; + @ignoreClone + private _isRandomTwoMacro: ShaderMacro; + + /** + * Size curve over lifetime. + */ + get size(): ParticleCompositeCurve { + return this.sizeX; + } + + set size(value: ParticleCompositeCurve) { + this.sizeX = value; + } + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + let enableSeparateMacro = null; + let isCurveMacro = null; + let isRandomTwoMacro = null; + + if (this.enabled) { + const sizeX = this.sizeX; + const sizeY = this.sizeY; + const sizeZ = this.sizeZ; + + const separateAxes = this.separateAxes; + const isRandomCurveMode = separateAxes + ? sizeX.mode === ParticleCurveMode.TwoCurves && + sizeY.mode === ParticleCurveMode.TwoCurves && + sizeZ.mode === ParticleCurveMode.TwoCurves + : sizeX.mode === ParticleCurveMode.TwoCurves; + + const isCurveMode = + isRandomCurveMode || separateAxes + ? sizeX.mode === ParticleCurveMode.Curve && + sizeY.mode === ParticleCurveMode.Curve && + sizeZ.mode === ParticleCurveMode.Curve + : sizeX.mode === ParticleCurveMode.Curve; + + if (isCurveMode) { + shaderData.setFloatArray(SizeOverLifetimeModule._maxCurveXProperty, sizeX.curveMax._getTypeArray()); + if (separateAxes) { + shaderData.setFloatArray(SizeOverLifetimeModule._maxCurveYProperty, sizeY.curveMax._getTypeArray()); + shaderData.setFloatArray(SizeOverLifetimeModule._maxCurveZProperty, sizeZ.curveMax._getTypeArray()); + } + if (isRandomCurveMode) { + shaderData.setFloatArray(SizeOverLifetimeModule._minCurveXProperty, sizeX.curveMin._getTypeArray()); + if (separateAxes) { + shaderData.setFloatArray(SizeOverLifetimeModule._minCurveYProperty, sizeY.curveMin._getTypeArray()); + shaderData.setFloatArray(SizeOverLifetimeModule._minCurveZProperty, sizeZ.curveMin._getTypeArray()); + } + isRandomTwoMacro = SizeOverLifetimeModule._isRandomTwoMacro; + } + isCurveMacro = SizeOverLifetimeModule._curveModeMacro; + } + + if (separateAxes) { + enableSeparateMacro = SizeOverLifetimeModule._isSeparateMacro; + } + } + + this._enableSeparateMacro = this._enableMacro(shaderData, this._enableSeparateMacro, enableSeparateMacro); + this._isCurveMacro = this._enableMacro(shaderData, this._isCurveMacro, isCurveMacro); + this._isRandomTwoMacro = this._enableMacro(shaderData, this._isRandomTwoMacro, isRandomTwoMacro); + } +} diff --git a/packages/core/src/particle/modules/TextureSheetAnimationModule.ts b/packages/core/src/particle/modules/TextureSheetAnimationModule.ts new file mode 100644 index 0000000000..4356605772 --- /dev/null +++ b/packages/core/src/particle/modules/TextureSheetAnimationModule.ts @@ -0,0 +1,105 @@ +import { Rand, Vector2, Vector3 } from "@galacean/engine-math"; +import { deepClone, ignoreClone, shallowClone } from "../../clone/CloneManager"; +import { ShaderData } from "../../shader/ShaderData"; +import { ShaderMacro } from "../../shader/ShaderMacro"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import { ParticleCurveMode } from "../enums/ParticleCurveMode"; +import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; +import { CurveKey, ParticleCurve } from "./ParticleCurve"; +import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; + +/** + * Texture sheet animation module. + */ +export class TextureSheetAnimationModule extends ParticleGeneratorModule { + private static readonly _frameCurveMacro = ShaderMacro.getByName("RENDERER_TSA_FRAME_CURVE"); + private static readonly _frameRandomCurvesMacro = ShaderMacro.getByName("RENDERER_TSA_FRAME_RANDOM_CURVES"); + + private static readonly _cycleCountProperty = ShaderProperty.getByName("renderer_TSACycles"); + private static readonly _tillingParamsProperty = ShaderProperty.getByName("renderer_TSATillingParams"); + private static readonly _frameMinCurveProperty = ShaderProperty.getByName("renderer_TSAFrameMinCurve"); + private static readonly _frameMaxCurveProperty = ShaderProperty.getByName("renderer_TSAFrameMaxCurve"); + + /** Start frame of the texture sheet. */ + @deepClone + readonly startFrame = new ParticleCompositeCurve(0); + /** Frame over time curve of the texture sheet. */ + @deepClone + readonly frameOverTime = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1))); + /** Texture sheet animation type. */ + type = TextureSheetAnimationType.WholeSheet; + /** Cycle count. */ + cycleCount = 1; + + /** @internal */ + @shallowClone + _tillingInfo = new Vector3(1, 1, 1); // x:subU, y:subV, z:tileCount + /** @internal */ + @ignoreClone + _frameOverTimeRand = new Rand(0, ParticleRandomSubSeeds.TextureSheetAnimation); + + @deepClone + private _tiling = new Vector2(1, 1); + @ignoreClone + private _textureSheetMacro: ShaderMacro; + + /** + * Tiling of the texture sheet. + * */ + get tiling(): Vector2 { + return this._tiling; + } + + set tiling(value: Vector2) { + this._tiling = value; + this._tillingInfo.set(1.0 / value.x, 1.0 / value.y, value.x * value.y); + } + + /** + * @inheritDoc + */ + cloneTo(dest: TextureSheetAnimationModule): void {} + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + let frameMacro = null; + if (this.enabled) { + const mode = this.frameOverTime.mode; + if (mode === ParticleCurveMode.Curve || mode === ParticleCurveMode.TwoCurves) { + const frame = this.frameOverTime; + shaderData.setFloatArray(TextureSheetAnimationModule._frameMaxCurveProperty, frame.curveMax._getTypeArray()); + if (mode === ParticleCurveMode.Curve) { + frameMacro = TextureSheetAnimationModule._frameCurveMacro; + } else { + shaderData.setFloatArray(TextureSheetAnimationModule._frameMinCurveProperty, frame.curveMin._getTypeArray()); + frameMacro = TextureSheetAnimationModule._frameRandomCurvesMacro; + } + + shaderData.setFloat(TextureSheetAnimationModule._cycleCountProperty, this.cycleCount); + shaderData.setVector3(TextureSheetAnimationModule._tillingParamsProperty, this._tillingInfo); + } + } + + this._textureSheetMacro = this._enableMacro(shaderData, this._textureSheetMacro, frameMacro); + } + + /** + * @internal + */ + _resetRandomSeed(randomSeed: number): void { + this._frameOverTimeRand.reset(randomSeed, ParticleRandomSubSeeds.TextureSheetAnimation); + } +} + +/** + * Texture sheet animation type. + */ +export enum TextureSheetAnimationType { + /** Animate over the whole texture sheet from left to right, top to bottom. */ + WholeSheet, + /** Animate a single row in the sheet from left to right. */ + SingleRow +} diff --git a/packages/core/src/particle/modules/VelocityOverLifetimeModule.ts b/packages/core/src/particle/modules/VelocityOverLifetimeModule.ts new file mode 100644 index 0000000000..7e986b7dc9 --- /dev/null +++ b/packages/core/src/particle/modules/VelocityOverLifetimeModule.ts @@ -0,0 +1,125 @@ +import { Rand, Vector3 } from "@galacean/engine-math"; +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ShaderMacro } from "../../shader"; +import { ShaderData } from "../../shader/ShaderData"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import { ParticleCurveMode } from "../enums/ParticleCurveMode"; +import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleSimulationSpace } from "../enums/ParticleSimulationSpace"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; +import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; + +/** + * Velocity over lifetime module. + */ +export class VelocityOverLifetimeModule extends ParticleGeneratorModule { + static readonly _constantMacro = ShaderMacro.getByName("RENDERER_VOL_CONSTANT"); + static readonly _curveMacro = ShaderMacro.getByName("RENDERER_VOL_CURVE"); + static readonly _randomConstantMacro = ShaderMacro.getByName("RENDERER_VOL_RANDOM_CONSTANT"); + static readonly _randomCurveMacro = ShaderMacro.getByName("RENDERER_VOL_RANDOM_CURVE"); + + static readonly _minConstantProperty = ShaderProperty.getByName("renderer_VOLMinConst"); + static readonly _maxConstantProperty = ShaderProperty.getByName("renderer_VOLMaxConst"); + static readonly _minGradientXProperty = ShaderProperty.getByName("renderer_VOLMinGradientX"); + static readonly _minGradientYProperty = ShaderProperty.getByName("renderer_VOLMinGradientY"); + static readonly _minGradientZProperty = ShaderProperty.getByName("renderer_VOLMinGradientZ"); + static readonly _maxGradientXProperty = ShaderProperty.getByName("renderer_VOLMaxGradientX"); + static readonly _maxGradientYProperty = ShaderProperty.getByName("renderer_VOLMaxGradientY"); + static readonly _maxGradientZProperty = ShaderProperty.getByName("renderer_VOLMaxGradientZ"); + static readonly _spaceProperty = ShaderProperty.getByName("renderer_VOLSpace"); + + /** Velocity over lifetime for x axis. */ + @deepClone + velocityX = new ParticleCompositeCurve(0); + /** Velocity over lifetime for z axis. */ + @deepClone + velocityY = new ParticleCompositeCurve(0); + /** Velocity over lifetime for z axis. */ + @deepClone + velocityZ = new ParticleCompositeCurve(0); + + /** Velocity space. */ + space = ParticleSimulationSpace.Local; + + /** @internal */ + @ignoreClone + _velocityRand = new Rand(0, ParticleRandomSubSeeds.VelocityOverLifetime); + + @ignoreClone + private _velocityMinConstant = new Vector3(); + @ignoreClone + private _velocityMaxConstant = new Vector3(); + @ignoreClone + private _velocityMacro: ShaderMacro; + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + let velocityMacro = null; + if (this.enabled) { + const velocityX = this.velocityX; + const velocityY = this.velocityY; + const velocityZ = this.velocityZ; + + const isRandomCurveMode = + velocityX.mode === ParticleCurveMode.TwoCurves && + velocityY.mode === ParticleCurveMode.TwoCurves && + velocityZ.mode === ParticleCurveMode.TwoCurves; + + if ( + isRandomCurveMode || + (velocityX.mode === ParticleCurveMode.Curve && + velocityY.mode === ParticleCurveMode.Curve && + velocityZ.mode === ParticleCurveMode.Curve) + ) { + shaderData.setFloatArray(VelocityOverLifetimeModule._maxGradientXProperty, velocityX.curveMax._getTypeArray()); + shaderData.setFloatArray(VelocityOverLifetimeModule._maxGradientYProperty, velocityY.curveMax._getTypeArray()); + shaderData.setFloatArray(VelocityOverLifetimeModule._maxGradientZProperty, velocityZ.curveMax._getTypeArray()); + if (isRandomCurveMode) { + shaderData.setFloatArray( + VelocityOverLifetimeModule._minGradientXProperty, + velocityX.curveMin._getTypeArray() + ); + shaderData.setFloatArray( + VelocityOverLifetimeModule._minGradientYProperty, + velocityY.curveMin._getTypeArray() + ); + shaderData.setFloatArray( + VelocityOverLifetimeModule._minGradientZProperty, + velocityZ.curveMin._getTypeArray() + ); + velocityMacro = VelocityOverLifetimeModule._randomCurveMacro; + } else { + velocityMacro = VelocityOverLifetimeModule._curveMacro; + } + } else { + const constantMax = this._velocityMaxConstant; + constantMax.set(velocityX.constantMax, velocityY.constantMax, velocityZ.constantMax); + shaderData.setVector3(VelocityOverLifetimeModule._maxConstantProperty, constantMax); + if ( + velocityX.mode === ParticleCurveMode.TwoConstants && + velocityY.mode === ParticleCurveMode.TwoConstants && + velocityZ.mode === ParticleCurveMode.TwoConstants + ) { + const constantMin = this._velocityMinConstant; + constantMin.set(velocityX.constantMin, velocityY.constantMin, velocityZ.constantMin); + shaderData.setVector3(VelocityOverLifetimeModule._minConstantProperty, constantMin); + velocityMacro = VelocityOverLifetimeModule._randomConstantMacro; + } else { + velocityMacro = VelocityOverLifetimeModule._constantMacro; + } + } + + shaderData.setInt(VelocityOverLifetimeModule._spaceProperty, this.space); + } + this._velocityMacro = this._enableMacro(shaderData, this._velocityMacro, velocityMacro); + } + + /** + * @internal + */ + _resetRandomSeed(seed: number): void { + this._velocityRand.reset(seed, ParticleRandomSubSeeds.VelocityOverLifetime); + } +} diff --git a/packages/core/src/particle/modules/shape/BaseShape.ts b/packages/core/src/particle/modules/shape/BaseShape.ts new file mode 100644 index 0000000000..3112bf403b --- /dev/null +++ b/packages/core/src/particle/modules/shape/BaseShape.ts @@ -0,0 +1,21 @@ +import { Rand, Vector3 } from "@galacean/engine-math"; +import { ParticleShapeType } from "./enums/ParticleShapeType"; + +/** + * Base class for all particle shapes. + */ +export abstract class BaseShape { + /** The type of shape to emit particles from. */ + shapeType: ParticleShapeType; + /** Specifies whether the ShapeModule is enabled or disabled. */ + enable: boolean = true; + /** Randomizes the starting direction of particles. */ + randomDirectionAmount: number = 0; + + /** + * @internal + */ + _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + throw new Error("BaseShape: must override it."); + } +} diff --git a/packages/core/src/particle/modules/shape/BoxShape.ts b/packages/core/src/particle/modules/shape/BoxShape.ts new file mode 100644 index 0000000000..413a7d9433 --- /dev/null +++ b/packages/core/src/particle/modules/shape/BoxShape.ts @@ -0,0 +1,34 @@ +import { Rand, Vector3 } from "@galacean/engine-math"; +import { deepClone } from "../../../clone/CloneManager"; +import { BaseShape } from "./BaseShape"; +import { ShapeUtils } from "./ShapeUtils"; +import { ParticleShapeType } from "./enums/ParticleShapeType"; + +/** + * Particle shape that emits particles from a box. + */ +export class BoxShape extends BaseShape { + private static _tempVector30 = new Vector3(); + + /** The size of the box. */ + @deepClone + size = new Vector3(1, 1, 1); + + constructor() { + super(); + this.shapeType = ParticleShapeType.Box; + } + + /** + * @internal + */ + override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + ShapeUtils._randomPointInsideHalfUnitBox(position, rand); + position.multiply(this.size); + + const defaultDirection = BoxShape._tempVector30; + defaultDirection.set(0.0, 0.0, -1.0); + ShapeUtils._randomPointUnitSphere(direction, rand); + Vector3.lerp(defaultDirection, direction, this.randomDirectionAmount, direction); + } +} diff --git a/packages/core/src/particle/modules/shape/CircleShape.ts b/packages/core/src/particle/modules/shape/CircleShape.ts new file mode 100644 index 0000000000..eadaf895d3 --- /dev/null +++ b/packages/core/src/particle/modules/shape/CircleShape.ts @@ -0,0 +1,51 @@ +import { MathUtil, Rand, Vector2, Vector3 } from "@galacean/engine-math"; +import { BaseShape } from "./BaseShape"; +import { ShapeUtils } from "./ShapeUtils"; +import { ParticleShapeArcMode } from "./enums/ParticleShapeArcMode"; +import { ParticleShapeType } from "./enums/ParticleShapeType"; + +/** + * Particle shape that emits particles from a circle. + */ +export class CircleShape extends BaseShape { + private static _tempPositionPoint: Vector2 = new Vector2(); + + /** Radius of the shape to emit particles from. */ + radius = 1.0; + /** Angle of the circle arc to emit particles from. */ + arc = 360.0; + /** The mode to generate particles around the arc. */ + arcMode = ParticleShapeArcMode.Random; + /** The speed of complete 360 degree rotation. */ + arcSpeed = 1.0; + + constructor() { + super(); + this.shapeType = ParticleShapeType.Circle; + } + + /** + * @internal + */ + override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + const positionPoint = CircleShape._tempPositionPoint; + + switch (this.arcMode) { + case ParticleShapeArcMode.Loop: + const normalizedEmitTime = (emitTime * this.arcSpeed * (360 / this.arc)) % 1; + const radian = MathUtil.degreeToRadian(this.arc * normalizedEmitTime); + positionPoint.set(Math.cos(radian), Math.sin(radian)); + positionPoint.scale(rand.random()); + break; + case ParticleShapeArcMode.Random: + ShapeUtils.randomPointInsideUnitArcCircle(MathUtil.degreeToRadian(this.arc), positionPoint, rand); + break; + } + + position.set(positionPoint.x, positionPoint.y, 0); + position.scale(this.radius); + + ShapeUtils._randomPointUnitSphere(direction, rand); + Vector3.lerp(position, direction, this.randomDirectionAmount, direction); + } +} diff --git a/packages/core/src/particle/modules/shape/ConeShape.ts b/packages/core/src/particle/modules/shape/ConeShape.ts new file mode 100644 index 0000000000..cca9b10267 --- /dev/null +++ b/packages/core/src/particle/modules/shape/ConeShape.ts @@ -0,0 +1,75 @@ +import { MathUtil, Rand, Vector2, Vector3 } from "@galacean/engine-math"; +import { BaseShape } from "./BaseShape"; +import { ShapeUtils } from "./ShapeUtils"; +import { ParticleShapeType } from "./enums/ParticleShapeType"; + +/** + * Cone shape. + */ +export class ConeShape extends BaseShape { + private static _tempVector20 = new Vector2(); + private static _tempVector21 = new Vector2(); + private static _tempVector30 = new Vector3(); + private static _tempVector31 = new Vector3(); + + /** Angle of the cone to emit particles from. */ + angle = 25.0; + /** Radius of the shape to emit particles from. */ + radius = 1.0; + /** Length of the cone to emit particles from. */ + length = 5.0; + /** Cone emitter type. */ + emitType = ConeEmitType.Base; + + constructor() { + super(); + this.shapeType = ParticleShapeType.Cone; + } + + /** + * @internal + */ + override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + const unitPosition = ConeShape._tempVector20; + const radian = MathUtil.degreeToRadian(this.angle); + const dirSinA = Math.sin(radian); + const dirCosA = Math.cos(radian); + + switch (this.emitType) { + case ConeEmitType.Base: + ShapeUtils.randomPointInsideUnitCircle(unitPosition, rand); + position.set(unitPosition.x * this.radius, unitPosition.y * this.radius, 0); + + const unitDirection = ConeShape._tempVector21; + ShapeUtils.randomPointInsideUnitCircle(unitDirection, rand); + Vector2.lerp(unitPosition, unitDirection, this.randomDirectionAmount, unitDirection); + direction.set(unitDirection.x * dirSinA, unitDirection.y * dirSinA, -dirCosA); + break; + case ConeEmitType.Volume: + ShapeUtils.randomPointInsideUnitCircle(unitPosition, rand); + position.set(unitPosition.x * this.radius, unitPosition.y * this.radius, 0); + + direction.set(unitPosition.x * dirSinA, unitPosition.y * dirSinA, -dirCosA); + direction.normalize(); + + const distance = ConeShape._tempVector30; + Vector3.scale(direction, this.length * rand.random(), distance); + position.add(distance); + + const randomDirection = ConeShape._tempVector31; + ShapeUtils._randomPointUnitSphere(randomDirection, rand); + Vector3.lerp(direction, randomDirection, this.randomDirectionAmount, direction); + break; + } + } +} + +/** + * Cone emitter type. + */ +export enum ConeEmitType { + /** Emit particles from the base of the cone. */ + Base, + /** Emit particles from the volume of the cone. */ + Volume +} diff --git a/packages/core/src/particle/modules/shape/HemisphereShape.ts b/packages/core/src/particle/modules/shape/HemisphereShape.ts new file mode 100644 index 0000000000..4e43b91d4e --- /dev/null +++ b/packages/core/src/particle/modules/shape/HemisphereShape.ts @@ -0,0 +1,31 @@ +import { Rand, Vector3 } from "@galacean/engine-math"; +import { BaseShape } from "./BaseShape"; +import { ShapeUtils } from "./ShapeUtils"; +import { ParticleShapeType } from "./enums/ParticleShapeType"; + +/** + * Particle shape that emits particles from a hemisphere. + */ +export class HemisphereShape extends BaseShape { + /** Radius of the shape to emit particles from. */ + radius = 1.0; + + constructor() { + super(); + this.shapeType = ParticleShapeType.Hemisphere; + } + + /** + * @internal + */ + override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + ShapeUtils._randomPointInsideUnitSphere(position, rand); + position.scale(this.radius); + + const z = position.z; + z > 0.0 && (position.z = -z); + + ShapeUtils._randomPointUnitSphere(direction, rand); + Vector3.lerp(position, direction, this.randomDirectionAmount, direction); + } +} diff --git a/packages/core/src/particle/modules/shape/ShapeUtils.ts b/packages/core/src/particle/modules/shape/ShapeUtils.ts new file mode 100644 index 0000000000..622ac6ac67 --- /dev/null +++ b/packages/core/src/particle/modules/shape/ShapeUtils.ts @@ -0,0 +1,57 @@ +import { Rand, Vector2, Vector3 } from "@galacean/engine-math"; + +/** + * @internal + */ +export class ShapeUtils { + static randomPointUnitArcCircle(arc: number, out: Vector2, rand: Rand): void { + const angle = rand.random() * arc; + out.x = Math.cos(angle); + out.y = Math.sin(angle); + } + + static randomPointInsideUnitArcCircle(arc: number, out: Vector2, rand: Rand): void { + ShapeUtils.randomPointUnitArcCircle(arc, out, rand); + const range = Math.sqrt(rand.random()); + out.x = out.x * range; + out.y = out.y * range; + } + + static randomPointUnitCircle(out: Vector2, rand: Rand): void { + const angle = rand.random() * Math.PI * 2; + out.x = Math.cos(angle); + out.y = Math.sin(angle); + } + + static randomPointInsideUnitCircle(out: Vector2, rand: Rand): void { + ShapeUtils.randomPointUnitCircle(out, rand); + const range = Math.sqrt(rand.random()); + out.x = out.x * range; + out.y = out.y * range; + } + + static _randomPointUnitSphere(out: Vector3, rand: Rand): void { + const z = rand.random() * 2 - 1.0; + const a = rand.random() * Math.PI * 2; + + const r = Math.sqrt(1.0 - z * z); + + out.x = r * Math.cos(a); + out.y = r * Math.sin(a); + out.z = z; + } + + static _randomPointInsideUnitSphere(out: Vector3, rand: Rand): void { + ShapeUtils._randomPointUnitSphere(out, rand); + const range = Math.pow(rand.random(), 1.0 / 3.0); + out.x = out.x * range; + out.y = out.y * range; + out.z = out.z * range; + } + + static _randomPointInsideHalfUnitBox(out: Vector3, rand: Rand = null): void { + out.x = rand.random() - 0.5; + out.y = rand.random() - 0.5; + out.z = rand.random() - 0.5; + } +} diff --git a/packages/core/src/particle/modules/shape/SphereShape.ts b/packages/core/src/particle/modules/shape/SphereShape.ts new file mode 100644 index 0000000000..8c01213fe3 --- /dev/null +++ b/packages/core/src/particle/modules/shape/SphereShape.ts @@ -0,0 +1,28 @@ +import { Rand, Vector3 } from "@galacean/engine-math"; +import { BaseShape } from "./BaseShape"; +import { ShapeUtils } from "./ShapeUtils"; +import { ParticleShapeType } from "./enums/ParticleShapeType"; + +/** + * Particle shape that emits particles from a sphere. + */ +export class SphereShape extends BaseShape { + /** Radius of the shape to emit particles from. */ + radius = 1.0; + + constructor() { + super(); + this.shapeType = ParticleShapeType.Sphere; + } + + /** + * @internal + */ + override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + ShapeUtils._randomPointInsideUnitSphere(position, rand); + position.scale(this.radius); + + ShapeUtils._randomPointUnitSphere(direction, rand); + Vector3.lerp(position, direction, this.randomDirectionAmount, direction); + } +} diff --git a/packages/core/src/particle/modules/shape/enums/ParticleShapeArcMode.ts b/packages/core/src/particle/modules/shape/enums/ParticleShapeArcMode.ts new file mode 100644 index 0000000000..28ccc4b81d --- /dev/null +++ b/packages/core/src/particle/modules/shape/enums/ParticleShapeArcMode.ts @@ -0,0 +1,9 @@ +/** + * Particle shape multi mode value. + */ +export enum ParticleShapeArcMode { + /** Generate points randomly. */ + Random, + /** Animate the emission point around the shape. */ + Loop +} diff --git a/packages/core/src/particle/modules/shape/enums/ParticleShapeType.ts b/packages/core/src/particle/modules/shape/enums/ParticleShapeType.ts new file mode 100644 index 0000000000..383c371560 --- /dev/null +++ b/packages/core/src/particle/modules/shape/enums/ParticleShapeType.ts @@ -0,0 +1,15 @@ +/** + * The emission shape. + */ +export enum ParticleShapeType { + /** Emit from the volume of a box. */ + Box = 0, + /** Emit from a circle. */ + Circle = 1, + /** Emit from the base of a cone. */ + Cone = 2, + /** Emit from a half-sphere. */ + Hemisphere = 3, + /** Emit from a sphere. */ + Sphere = 4 +} diff --git a/packages/core/src/particle/modules/shape/index.ts b/packages/core/src/particle/modules/shape/index.ts new file mode 100644 index 0000000000..3a24e22745 --- /dev/null +++ b/packages/core/src/particle/modules/shape/index.ts @@ -0,0 +1,6 @@ +export { BoxShape } from "./BoxShape"; +export { CircleShape } from "./CircleShape"; +export { ConeShape, ConeEmitType } from "./ConeShape"; +export { HemisphereShape } from "./HemisphereShape"; +export { SphereShape } from "./SphereShape"; +export { ParticleShapeArcMode } from "./enums/ParticleShapeArcMode"; diff --git a/packages/core/src/shader/ShaderData.ts b/packages/core/src/shader/ShaderData.ts index a52c03fa04..50cbdcbfa6 100644 --- a/packages/core/src/shader/ShaderData.ts +++ b/packages/core/src/shader/ShaderData.ts @@ -1,26 +1,31 @@ import { IClone } from "@galacean/engine-design"; import { Color, Matrix, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { IReferable } from "../asset/IReferable"; -import { CloneManager } from "../clone/CloneManager"; +import { CloneManager, ignoreClone } from "../clone/CloneManager"; import { Texture } from "../texture/Texture"; -import { ShaderDataGroup } from "./enums/ShaderDataGroup"; -import { ShaderPropertyType } from "./enums/ShaderPropertyType"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderProperty } from "./ShaderProperty"; +import { ShaderDataGroup } from "./enums/ShaderDataGroup"; +import { ShaderPropertyType } from "./enums/ShaderPropertyType"; /** * Shader data collection,Correspondence includes shader properties data and macros data. */ export class ShaderData implements IReferable, IClone { /** @internal */ + @ignoreClone _group: ShaderDataGroup; /** @internal */ + @ignoreClone _propertyValueMap: Record = Object.create(null); /** @internal */ + @ignoreClone _macroCollection: ShaderMacroCollection = new ShaderMacroCollection(); + @ignoreClone private _macroMap: Record = Object.create(null); + @ignoreClone private _refCount: number = 0; /** diff --git a/packages/core/src/shaderlib/ShaderLib.ts b/packages/core/src/shaderlib/ShaderLib.ts index 3b219afc85..312a823d1a 100644 --- a/packages/core/src/shaderlib/ShaderLib.ts +++ b/packages/core/src/shaderlib/ShaderLib.ts @@ -51,6 +51,7 @@ import noise_simplex_4D from "./noise_simplex_4D.glsl"; import PBRShaderLib from "./pbr"; import ShadowLib from "./shadow"; +import ParticleShaderLib from "./particle"; import normal_get from "./normal_get.glsl"; @@ -108,5 +109,6 @@ export const ShaderLib = { ...ShadowLib, ...PBRShaderLib, - normal_get + normal_get, + ...ParticleShaderLib }; diff --git a/packages/core/src/shaderlib/extra/particle.fs.glsl b/packages/core/src/shaderlib/extra/particle.fs.glsl index 5a13de8ac7..77c43ec4bc 100644 --- a/packages/core/src/shaderlib/extra/particle.fs.glsl +++ b/packages/core/src/shaderlib/extra/particle.fs.glsl @@ -1,33 +1,31 @@ -varying vec4 v_color; -varying float v_lifeLeft; -varying vec2 v_uv; -uniform sampler2D u_texture; +#include -void main() { - if (v_lifeLeft == 1.0) { - discard; - } +varying vec4 v_Color; +varying vec2 v_TextureCoordinate; +uniform sampler2D material_BaseTexture; +uniform vec4 material_BaseColor; + +#ifdef RENDERER_MODE_MESH + varying vec4 v_MeshColor; +#endif - float alphaFactor = 1.0; +void main() { + vec4 color = material_BaseColor * v_Color; - #ifdef fadeIn - float fadeInFactor = step(0.5, v_lifeLeft); - alphaFactor = 2.0 * fadeInFactor * (1.0 - v_lifeLeft) + (1.0 - fadeInFactor); - #endif + #ifdef RENDERER_MODE_MESH + color *= v_MeshColor; + #endif - #ifdef fadeOut - float fadeOutFactor = step(0.5, v_lifeLeft); - alphaFactor = alphaFactor * 2.0 * (1.0 - fadeOutFactor) * v_lifeLeft + alphaFactor * fadeOutFactor; - #endif + #ifdef MATERIAL_HAS_BASETEXTURE + vec4 textureColor = texture2D(material_BaseTexture,v_TextureCoordinate); + #ifndef ENGINE_IS_COLORSPACE_GAMMA + textureColor = gammaToLinear(textureColor); + #endif + color *= textureColor; + #endif + gl_FragColor = color; - #ifdef particleTexture - vec4 tex = texture2D(u_texture, v_uv); - #ifdef useOriginColor - gl_FragColor = vec4(tex.rgb, alphaFactor * tex.a * v_color.w); - #else - gl_FragColor = vec4(v_color.xyz * tex.rgb, alphaFactor * tex.a * v_color.w); + #ifndef ENGINE_IS_COLORSPACE_GAMMA + gl_FragColor = linearToGamma(gl_FragColor); #endif - #else - gl_FragColor = vec4( v_color.xyz, alphaFactor * v_color.w); - #endif -} +} \ No newline at end of file diff --git a/packages/core/src/shaderlib/extra/particle.vs.glsl b/packages/core/src/shaderlib/extra/particle.vs.glsl index 9a102b7a8e..e41918c787 100644 --- a/packages/core/src/shaderlib/extra/particle.vs.glsl +++ b/packages/core/src/shaderlib/extra/particle.vs.glsl @@ -1,122 +1,113 @@ -attribute vec3 a_position; -attribute vec3 a_velocity; -attribute vec3 a_acceleration; -attribute vec4 a_color; - -attribute vec4 a_lifeAndSize; -attribute vec2 a_rotation; - -attribute vec3 a_uv; -attribute vec2 a_normalizedUv; - -uniform float u_time; -uniform bool u_once; -uniform mat4 renderer_MVPMat; - -varying vec4 v_color; -varying float v_lifeLeft; -varying vec2 v_uv; - -#ifdef is2d - uniform mat4 camera_ViewInvMat; - uniform mat4 camera_ProjMat; - uniform mat4 camera_ViewMat; - uniform mat4 renderer_ModelMat; +#if defined(RENDERER_MODE_SPHERE_BILLBOARD) || defined(RENDERER_MODE_STRETCHED_BILLBOARD) || defined(RENDERER_MODE_HORIZONTAL_BILLBOARD) || defined(RENDERER_MODE_VERTICAL_BILLBOARD) + attribute vec4 a_CornerTextureCoordinate; #endif -mat2 rotation2d(float angle) { - float s = sin(angle); - float c = cos(angle); +#ifdef RENDERER_MODE_MESH + attribute vec3 a_MeshPosition; + attribute vec4 a_MeshColor; + attribute vec2 a_MeshTextureCoordinate; + varying vec4 v_MeshColor; +#endif - return mat2( - c, -s, - s, c - ); -} +attribute vec4 a_ShapePositionStartLifeTime; +attribute vec4 a_DirectionTime; +attribute vec4 a_StartColor; +attribute vec3 a_StartSize; +attribute vec3 a_StartRotation0; +attribute float a_StartSpeed; + +//#if defined(COLOR_OVER_LIFETIME) || defined(RENDERER_COL_RANDOM_GRADIENTS) || defined(RENDERER_SOL_RANDOM_CURVES) || defined(RENDERER_SOL_RANDOM_CURVES_SEPARATE) || defined(ROTATION_OVER_LIFE_TIME_RANDOM_CONSTANTS) || defined(ROTATION_OVER_LIFETIME_RANDOM_CURVES) + attribute vec4 a_Random0; +//#endif + +#if defined(RENDERER_TSA_FRAME_RANDOM_CURVES) || defined(RENDERER_VOL_RANDOM_CONSTANT) || defined(RENDERER_VOL_RANDOM_CURVE) + attribute vec4 a_Random1; // x:texture sheet animation random +#endif + +attribute vec3 a_SimulationWorldPosition; +attribute vec4 a_SimulationWorldRotation; +varying vec4 v_Color; +#ifdef MATERIAL_HAS_BASETEXTURE + attribute vec4 a_SimulationUV; + varying vec2 v_TextureCoordinate; +#endif + +uniform float renderer_CurrentTime; +uniform vec3 renderer_Gravity; +uniform vec2 u_DragConstant; +uniform vec3 renderer_WorldPosition; +uniform vec4 renderer_WorldRotation; +uniform bool renderer_ThreeDStartRotation; +uniform int renderer_ScalingMode; +uniform vec3 renderer_PositionScale; +uniform vec3 renderer_SizeScale; +uniform vec3 renderer_PivotOffset; + +uniform mat4 camera_ViewMat; +uniform mat4 camera_ProjMat; + +#ifdef RENDERER_MODE_STRETCHED_BILLBOARD + uniform vec3 camera_Position; +#endif +uniform vec3 camera_Forward; // TODO:只有几种广告牌模式需要用 +uniform vec3 camera_Up; + +uniform float renderer_StretchedBillboardLengthScale; +uniform float renderer_StretchedBillboardSpeedScale; +uniform int renderer_SimulationSpace; + +#include +#include +#include +#include +#include +#include void main() { - v_color = a_color; - v_uv = a_uv.xy; - - // life time - float life = a_lifeAndSize.y; - float startTime = a_lifeAndSize.x; - - // Elapsed time - float deltaTime = max(mod(u_time - startTime, life), 0.0); - - if ((u_once && u_time > life + startTime)) { - deltaTime = 0.0; - } - - v_lifeLeft = 1.0 - deltaTime / life; - float scale = a_lifeAndSize.z; - vec3 position = a_position + (a_velocity + a_acceleration * deltaTime * 0.5) * deltaTime; - - #ifdef isScaleByLifetime - scale *= v_lifeLeft; - #else - scale *= pow(a_lifeAndSize.w, deltaTime); - #endif - - #ifdef rotateToVelocity - vec3 v = a_velocity + a_acceleration * deltaTime; - float angle = atan(v.z, v.x) * 2.0; - #else - float deltaAngle = deltaTime * a_rotation.y; - float angle = a_rotation.x + deltaAngle; - #endif - - #ifdef is2d - - vec2 rotatedPoint = rotation2d(angle) * vec2(a_normalizedUv.x, a_normalizedUv.y * a_uv.z); - - vec3 basisX = camera_ViewInvMat[0].xyz; - vec3 basisZ = camera_ViewInvMat[1].xyz; - - vec3 localPosition = vec3(basisX * rotatedPoint.x + - basisZ * rotatedPoint.y) * scale + position; - - gl_Position = camera_ProjMat * camera_ViewMat * vec4(localPosition + renderer_ModelMat[3].xyz, 1.); - #else - #ifdef rotateToVelocity - float s = sin(angle); - float c = cos(angle); - #else - float s = sin(angle); - float c = cos(angle); - #endif - - vec4 rotatedPoint = vec4((a_normalizedUv.x * c + a_normalizedUv.y * a_uv.z * s) * scale , 0., - (a_normalizedUv.x * s - a_normalizedUv.y * a_uv.z * c) * scale, 1.); - - vec4 orientation = vec4(0, 0, 0, 1); - vec4 q2 = orientation + orientation; - vec4 qx = orientation.xxxw * q2.xyzx; - vec4 qy = orientation.xyyw * q2.xyzy; - vec4 qz = orientation.xxzw * q2.xxzz; - - mat4 localMatrix = mat4( - (1.0 - qy.y) - qz.z, - qx.y + qz.w, - qx.z - qy.w, - 0, - - qx.y - qz.w, - (1.0 - qx.x) - qz.z, - qy.z + qx.w, - 0, - - qx.z + qy.w, - qy.z - qx.w, - (1.0 - qx.x) - qy.y, - 0, - - position.x, position.y, position.z, 1); - - rotatedPoint = localMatrix * rotatedPoint; - - gl_Position = renderer_MVPMat * rotatedPoint; - #endif -} \ No newline at end of file + float age = renderer_CurrentTime - a_DirectionTime.w; + float normalizedAge = age / a_ShapePositionStartLifeTime.w; + vec3 lifeVelocity; + if (normalizedAge < 1.0) { + vec3 startVelocity = a_DirectionTime.xyz * a_StartSpeed; + #if defined(RENDERER_VOL_CONSTANT) || defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CONSTANT) || defined(RENDERER_VOL_RANDOM_CURVE) + lifeVelocity = computeParticleLifeVelocity(normalizedAge); + #endif + + vec3 gravityVelocity = renderer_Gravity * age; + + vec4 worldRotation; + if (renderer_SimulationSpace == 0) { + worldRotation = renderer_WorldRotation; + } else { + worldRotation = a_SimulationWorldRotation; + } + + //drag + vec3 dragData = a_DirectionTime.xyz * mix(u_DragConstant.x, u_DragConstant.y, a_Random0.x); + vec3 center = computeParticlePosition(startVelocity, lifeVelocity, age, normalizedAge, gravityVelocity, worldRotation, dragData); //计算粒子位置 + + #include + #include + #include + #include + #include + + gl_Position = camera_ProjMat * camera_ViewMat * vec4(center, 1.0); + v_Color = computeParticleColor(a_StartColor, normalizedAge); + + #ifdef MATERIAL_HAS_BASETEXTURE + vec2 simulateUV; + #if defined(RENDERER_MODE_SPHERE_BILLBOARD) || defined(RENDERER_MODE_STRETCHED_BILLBOARD) || defined(RENDERER_MODE_HORIZONTAL_BILLBOARD) || defined(RENDERER_MODE_VERTICAL_BILLBOARD) + simulateUV = a_CornerTextureCoordinate.zw * a_SimulationUV.xy + a_SimulationUV.zw; + v_TextureCoordinate = computeParticleUV(simulateUV, normalizedAge); + #endif + #ifdef RENDERER_MODE_MESH + simulateUV = a_SimulationUV.xy + a_MeshTextureCoordinate * a_SimulationUV.zw; + v_TextureCoordinate = computeParticleUV(simulateUV, normalizedAge); + #endif + #endif + } else { + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Discard use out of X(-1,1),Y(-1,1),Z(0,1) + } +} diff --git a/packages/core/src/shaderlib/particle/color_over_lifetime_module.glsl b/packages/core/src/shaderlib/particle/color_over_lifetime_module.glsl new file mode 100644 index 0000000000..d4f4f8ce23 --- /dev/null +++ b/packages/core/src/shaderlib/particle/color_over_lifetime_module.glsl @@ -0,0 +1,73 @@ + +#if defined(RENDERER_COL_GRADIENT) || defined(RENDERER_COL_RANDOM_GRADIENTS) + uniform vec4 renderer_COLMaxGradientColor[4]; // x:time y:r z:g w:b + uniform vec2 renderer_COLMaxGradientAlpha[4]; // x:time y:alpha + + #ifdef RENDERER_COL_RANDOM_GRADIENTS + uniform vec4 renderer_COLMinGradientColor[4]; // x:time y:r z:g w:b + uniform vec2 renderer_COLMinGradientAlpha[4]; // x:time y:alpha + #endif + + uniform vec4 renderer_COLGradientKeysMaxTime; // x: minColorKeysMaxTime, y: minAlphaKeysMaxTime, z: maxColorKeysMaxTime, w: maxAlphaKeysMaxTime +#endif + + + +#if defined(RENDERER_COL_GRADIENT) || defined(RENDERER_COL_RANDOM_GRADIENTS) + vec4 evaluateParticleGradient(in vec4 colorKeys[4], in float colorKeysMaxTime, in vec2 alphaKeys[4], in float alphaKeysMaxTime, in float normalizedAge){ + vec4 value; + float alphaAge = min(normalizedAge, alphaKeysMaxTime); + for(int i = 0; i < 4; i++){ + vec2 key = alphaKeys[i]; + float time = key.x; + if(alphaAge <= time){ + if(i == 0){ + value.a = colorKeys[0].y; + } + else { + vec2 lastKey = alphaKeys[i-1]; + float lastTime = lastKey.x; + float age = (alphaAge - lastTime) / (time - lastTime); + value.a = mix(lastKey.y, key.y, age); + } + break; + } + } + + float colorAge = min(normalizedAge, colorKeysMaxTime); + for(int i = 0; i < 4; i++){ + vec4 key = colorKeys[i]; + float time = key.x; + if(colorAge <= time){ + if(i == 0){ + value.rgb = colorKeys[0].yzw; + } + else { + vec4 lastKey = colorKeys[i-1]; + float lastTime = lastKey.x; + float age = (colorAge - lastTime) / (time-lastTime); + value.rgb = mix(lastKey.yzw, key.yzw, age); + } + break; + } + } + return value; + } +#endif + + +vec4 computeParticleColor(in vec4 color, in float normalizedAge) { + #if defined(RENDERER_COL_GRADIENT) || defined(RENDERER_COL_RANDOM_GRADIENTS) + vec4 gradientColor = evaluateParticleGradient(renderer_COLMaxGradientColor, renderer_COLGradientKeysMaxTime.z, renderer_COLMaxGradientAlpha, renderer_COLGradientKeysMaxTime.w, normalizedAge); + #endif + + #ifdef RENDERER_COL_RANDOM_GRADIENTS + gradientColor = mix(evaluateParticleGradient(renderer_COLMinGradientColor,renderer_COLGradientKeysMaxTime.x, renderer_COLMinGradientAlpha, renderer_COLGradientKeysMaxTime.y, normalizedAge), gradientColor, a_Random0.y); + #endif + + #if defined(RENDERER_COL_GRADIENT) || defined(RENDERER_COL_RANDOM_GRADIENTS) + color *= gradientColor; + #endif + + return color; +} diff --git a/packages/core/src/shaderlib/particle/horizontal_billboard.glsl b/packages/core/src/shaderlib/particle/horizontal_billboard.glsl new file mode 100644 index 0000000000..f2a3b18b05 --- /dev/null +++ b/packages/core/src/shaderlib/particle/horizontal_billboard.glsl @@ -0,0 +1,13 @@ +#ifdef RENDERER_MODE_HORIZONTAL_BILLBOARD + vec2 corner = a_CornerTextureCoordinate.xy + renderer_PivotOffset.xy; // Billboard模式z轴无效 + const vec3 cameraUpVector = vec3(0.0, 0.0, 1.0); + const vec3 sideVector = vec3(-1.0, 0.0, 0.0); + + float rot = computeParticleRotationFloat(a_StartRotation0.x, age, normalizedAge); + float c = cos(rot); + float s = sin(rot); + mat2 rotation = mat2(c, -s, s, c); + corner = rotation * corner * cos(0.78539816339744830961566084581988); // TODO:临时缩小cos45,不确定U3D原因 + corner *= computeParticleSizeBillboard(a_StartSize.xy, normalizedAge); + center += renderer_SizeScale.xzy * (corner.x * sideVector + corner.y * cameraUpVector); +#endif \ No newline at end of file diff --git a/packages/core/src/shaderlib/particle/index.ts b/packages/core/src/shaderlib/particle/index.ts new file mode 100644 index 0000000000..3ca06ae968 --- /dev/null +++ b/packages/core/src/shaderlib/particle/index.ts @@ -0,0 +1,27 @@ +import particle_common from "./particle_common.glsl"; +import velocity_over_lifetime_module from "./velocity_over_lifetime_module.glsl"; +import rotation_over_lifetime_module from "./rotation_over_lifetime_module.glsl"; +import size_over_lifetime_module from "./size_over_lifetime_module.glsl"; +import color_over_lifetime_module from "./color_over_lifetime_module.glsl"; +import texture_sheet_animation_module from "./texture_sheet_animation_module.glsl"; + +import sphere_billboard from "./sphere_billboard.glsl"; +import stretched_billboard from "./stretched_billboard.glsl"; +import vertical_billboard from "./vertical_billboard.glsl"; +import horizontal_billboard from "./horizontal_billboard.glsl"; +import particle_mesh from "./particle_mesh.glsl"; + +export default { + particle_common, + velocity_over_lifetime_module, + rotation_over_lifetime_module, + size_over_lifetime_module, + color_over_lifetime_module, + texture_sheet_animation_module, + + sphere_billboard, + stretched_billboard, + vertical_billboard, + horizontal_billboard, + particle_mesh +}; diff --git a/packages/core/src/shaderlib/particle/particle_common.glsl b/packages/core/src/shaderlib/particle/particle_common.glsl new file mode 100644 index 0000000000..d10f4762a3 --- /dev/null +++ b/packages/core/src/shaderlib/particle/particle_common.glsl @@ -0,0 +1,115 @@ + + +vec3 rotationByEuler(in vec3 vector, in vec3 rot) { + float halfRoll = rot.z * 0.5; + float halfPitch = rot.x * 0.5; + float halfYaw = rot.y * 0.5; + + float sinRoll = sin(halfRoll); + float cosRoll = cos(halfRoll); + float sinPitch = sin(halfPitch); + float cosPitch = cos(halfPitch); + float sinYaw = sin(halfYaw); + float cosYaw = cos(halfYaw); + + float quaX = (cosYaw * sinPitch * cosRoll) + (sinYaw * cosPitch * sinRoll); + float quaY = (sinYaw * cosPitch * cosRoll) - (cosYaw * sinPitch * sinRoll); + float quaZ = (cosYaw * cosPitch * sinRoll) - (sinYaw * sinPitch * cosRoll); + float quaW = (cosYaw * cosPitch * cosRoll) + (sinYaw * sinPitch * sinRoll); + + // vec4 q=vec4(quaX,quaY,quaZ,quaW); + // vec3 temp = cross(q.xyz, vector) + q.w * vector; + // return (cross(temp, -q.xyz) + dot(q.xyz,vector) * q.xyz + q.w * temp); + + float x = quaX + quaX; + float y = quaY + quaY; + float z = quaZ + quaZ; + float wx = quaW * x; + float wy = quaW * y; + float wz = quaW * z; + float xx = quaX * x; + float xy = quaX * y; + float xz = quaX * z; + float yy = quaY * y; + float yz = quaY * z; + float zz = quaZ * z; + + return vec3(((vector.x * ((1.0 - yy) - zz)) + (vector.y * (xy - wz))) + (vector.z * (xz + wy)), + ((vector.x * (xy + wz)) + (vector.y * ((1.0 - xx) - zz))) + (vector.z * (yz - wx)), + ((vector.x * (xz - wy)) + (vector.y * (yz + wx))) + (vector.z * ((1.0 - xx) - yy))); +} + +//假定axis已经归一化 +vec3 rotationByAxis(in vec3 vector, in vec3 axis, in float angle) { + float halfAngle = angle * 0.5; + float sin = sin(halfAngle); + + float quaX = axis.x * sin; + float quaY = axis.y * sin; + float quaZ = axis.z * sin; + float quaW = cos(halfAngle); + + // vec4 q=vec4(quaX,quaY,quaZ,quaW); + // vec3 temp = cross(q.xyz, vector) + q.w * vector; + // return (cross(temp, -q.xyz) + dot(q.xyz,vector) * q.xyz + q.w * temp); + + float x = quaX + quaX; + float y = quaY + quaY; + float z = quaZ + quaZ; + float wx = quaW * x; + float wy = quaW * y; + float wz = quaW * z; + float xx = quaX * x; + float xy = quaX * y; + float xz = quaX * z; + float yy = quaY * y; + float yz = quaY * z; + float zz = quaZ * z; + + return vec3(((vector.x * ((1.0 - yy) - zz)) + (vector.y * (xy - wz))) + (vector.z * (xz + wy)), + ((vector.x * (xy + wz)) + (vector.y * ((1.0 - xx) - zz))) + (vector.z * (yz - wx)), + ((vector.x * (xz - wy)) + (vector.y * (yz + wx))) + (vector.z * ((1.0 - xx) - yy))); +} + +vec3 rotationByQuaternions(in vec3 v, in vec4 q) { + return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); +} + + +float evaluateParticleCurve(in vec2 keys[4], in float normalizedAge) { + float value; + for (int i = 1; i < 4; i++) { + vec2 key = keys[i]; + float time = key.x; + if (time >= normalizedAge) { + vec2 lastKey = keys[i - 1]; + float lastTime = lastKey.x; + float age = (normalizedAge - lastTime) / (time - lastTime); + value = mix(lastKey.y, key.y, age); + break; + } + } + return value; +} + +float evaluateParticleCurveCumulative(in vec2 keys[4], in float normalizedAge){ + float cumulativeValue = 0.0; + for (int i = 1; i < 4; i++){ + vec2 key = keys[i]; + float time = key.x; + vec2 lastKey = keys[i - 1]; + float lastValue = lastKey.y; + + if (time >= normalizedAge){ + float lastTime = lastKey.x; + float offsetTime = normalizedAge - lastTime; + float age = offsetTime / (time - lastTime); + cumulativeValue += (lastValue + mix(lastValue, key.y, age)) * 0.5 * offsetTime; + break; + } + else{ + cumulativeValue += (lastValue + key.y) * 0.5 * (time - lastKey.x); + } + } + return cumulativeValue; +} \ No newline at end of file diff --git a/packages/core/src/shaderlib/particle/particle_mesh.glsl b/packages/core/src/shaderlib/particle/particle_mesh.glsl new file mode 100644 index 0000000000..6c5a414c40 --- /dev/null +++ b/packages/core/src/shaderlib/particle/particle_mesh.glsl @@ -0,0 +1,87 @@ +#ifdef RENDERER_MODE_MESH + vec3 size = computeParticleSizeMesh(a_StartSize, normalizedAge); + #if defined(RENDERER_ROL_CONSTANT_MODE) || defined(RENDERER_ROL_CURVE_MODE) + if (renderer_ThreeDStartRotation) { + vec3 rotation = vec3( + a_StartRotation0.xy, + computeParticleRotationFloat(a_StartRotation0.z, age, normalizedAge)); + center += rotationByQuaternions( + renderer_SizeScale * rotationByEuler(a_MeshPosition * size, rotation), + worldRotation); + } else { + #ifdef RENDERER_ROL_IS_SEPARATE + float angle = computeParticleRotationFloat(a_StartRotation0.x, age, normalizedAge); + if (a_ShapePositionStartLifeTime.x != 0.0 || a_ShapePositionStartLifeTime.y != 0.0) { + center += (rotationByQuaternions( + rotationByAxis( + renderer_SizeScale * a_MeshPosition * size, + normalize(cross(vec3(0.0, 0.0, 1.0), + vec3(a_ShapePositionStartLifeTime.xy, 0.0))), + angle), + worldRotation)); //已验证 + } else { + #ifdef SHAPE + center += renderer_SizeScale.xzy * (rotationByQuaternions(rotationByAxis(a_MeshPosition * size, vec3(0.0, -1.0, 0.0), angle), worldRotation)); + #else + if (renderer_SimulationSpace == 1) + center += rotationByAxis(renderer_SizeScale * a_MeshPosition * size, + vec3(0.0, 0.0, -1.0), + angle); //已验证 + else if (renderer_SimulationSpace == 0) + center += rotationByQuaternions( + renderer_SizeScale * rotationByAxis(a_MeshPosition * size, vec3(0.0, 0.0, -1.0), angle), + worldRotation); //已验证 + #endif + } + #endif + #ifdef ROTATION_OVER_LIFETIME_SEPARATE + // TODO:是否应合并if(renderer_ThreeDStartRotation)分支代码,待测试 + vec3 angle = computeParticleRotationVec3( + vec3(0.0, 0.0, -a_StartRotation0.x), age, normalizedAge); + center += (rotationByQuaternions( + rotationByEuler(renderer_SizeScale * a_MeshPosition * size, + vec3(angle.x, angle.y, angle.z)), + worldRotation)); //已验证 + #endif + } + #else + if (renderer_ThreeDStartRotation) { + center += rotationByQuaternions( + renderer_SizeScale * rotationByEuler(a_MeshPosition * size, a_StartRotation0), + worldRotation); //已验证 + } else { + if (a_ShapePositionStartLifeTime.x != 0.0 || a_ShapePositionStartLifeTime.y != 0.0) { + if (renderer_SimulationSpace == 1) + center += rotationByAxis( + renderer_SizeScale * a_MeshPosition * size, + normalize(cross(vec3(0.0, 0.0, 1.0), + vec3(a_ShapePositionStartLifeTime.xy, 0.0))), + a_StartRotation0.x); + else if (renderer_SimulationSpace == 0) + center += (rotationByQuaternions( + renderer_SizeScale * rotationByAxis(a_MeshPosition * size, normalize(cross(vec3(0.0, 0.0, 1.0), + vec3(a_ShapePositionStartLifeTime.xy, 0.0))), a_StartRotation0.x), + worldRotation)); //已验证 + } else { + #ifdef SHAPE + if (renderer_SimulationSpace == 1) + center += renderer_SizeScale * rotationByAxis(a_MeshPosition * size, vec3(0.0, -1.0, 0.0), a_StartRotation0.x); + else if (renderer_SimulationSpace == 0) + center += rotationByQuaternions( + renderer_SizeScale * rotationByAxis(a_MeshPosition * size, vec3(0.0, -1.0, 0.0), a_StartRotation0.x), + worldRotation); + #else + if (renderer_SimulationSpace == 1) + center += rotationByAxis(renderer_SizeScale * a_MeshPosition * size, + vec3(0.0, 0.0, -1.0), + a_StartRotation0.x); + else if (renderer_SimulationSpace == 0) + center += rotationByQuaternions( + renderer_SizeScale * rotationByAxis(a_MeshPosition * size, vec3(0.0, 0.0, -1.0), a_StartRotation0.x), + worldRotation); //已验证 + #endif + } + } + #endif + v_MeshColor = a_MeshColor; +#endif \ No newline at end of file diff --git a/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl b/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl new file mode 100644 index 0000000000..4b6cd1e82a --- /dev/null +++ b/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl @@ -0,0 +1,108 @@ +#if defined(RENDERER_ROL_CONSTANT_MODE) || defined(RENDERER_ROL_CURVE_MODE) + #ifdef RENDERER_ROL_CURVE_MODE + uniform vec2 renderer_ROLMaxCurveZ[4]; + // #ifdef RENDERER_ROL_IS_SEPARATE + // uniform vec2 renderer_ROLMaxCurveX[4]; + // uniform vec2 renderer_ROLMaxCurveY[4]; + // #endif + #ifdef RENDERER_ROL_IS_RANDOM_TWO + uniform vec2 renderer_ROLMinCurveZ[4]; + // #ifdef RENDERER_ROL_IS_SEPARATE + // uniform vec2 renderer_ROLMinCurveX[4]; + // uniform vec2 renderer_ROLMinCurveY[4]; + // #endif + #endif + #else + uniform vec3 renderer_ROLMaxConst; + #ifdef RENDERER_ROL_IS_RANDOM_TWO + uniform vec3 renderer_ROLMinConst; + #endif + #endif +#endif + +float computeParticleRotationFloat(in float rotation, in float age, in float normalizedAge) { + #if defined(RENDERER_ROL_CONSTANT_MODE) || defined(RENDERER_ROL_CURVE_MODE) + #ifdef RENDERER_ROL_CURVE_MODE + float lifeRotation = evaluateParticleCurveCumulative(renderer_ROLMaxCurveZ, normalizedAge); + #ifdef RENDERER_ROL_IS_RANDOM_TWO + lifeRotation = mix(evaluateParticleCurveCumulative(renderer_ROLMinCurveZ, normalizedAge), lifeRotation, a_Random0.w); + #endif + rotation += lifeRotation * a_ShapePositionStartLifeTime.w; + #else + float lifeRotation = renderer_ROLMaxConst.z; + #ifdef RENDERER_ROL_IS_RANDOM_TWO + lifeRotation = mix(renderer_ROLMinConst.z, lifeRotation, a_Random0.w); + #endif + rotation += lifeRotation * age; + #endif + #endif + return rotation; +} + + +#if defined(RENDERER_MODE_MESH) && (defined(ROTATION_OVER_LIFETIME) || defined(ROTATION_OVER_LIFETIME_SEPARATE)) +vec3 computeParticleRotationVec3(in vec3 rotation, + in float age, + in float normalizedAge) { +#ifdef ROTATION_OVER_LIFETIME + #ifdef ROTATION_OVER_LIFETIME_CONSTANT + float ageRot = u_ROLAngularVelocityConst * age; + rotation += ageRot; + #endif + #ifdef ROTATION_OVER_LIFETIME_CURVE + rotation += getTotalValueFromGradientFloat(u_ROLAngularVelocityGradient, normalizedAge); + #endif + #ifdef ROTATION_OVER_LIFETIME_RANDOM_CONSTANTS + float ageRot = mix(u_ROLAngularVelocityConst, u_ROLAngularVelocityConstMax, a_Random0.w) * age; + rotation += ageRot; + #endif + #ifdef ROTATION_OVER_LIFETIME_RANDOM_CURVES + rotation += mix( + getTotalValueFromGradientFloat(u_ROLAngularVelocityGradient, normalizedAge), + getTotalValueFromGradientFloat(u_ROLAngularVelocityGradientMax, + normalizedAge), + a_Random0.w); + #endif +#endif + +#ifdef ROTATION_OVER_LIFETIME_SEPARATE + #ifdef ROTATION_OVER_LIFETIME_CONSTANT + vec3 ageRot = u_ROLAngularVelocityConstSeparate * age; + rotation += ageRot; + #endif + #ifdef ROTATION_OVER_LIFETIME_CURVE + rotation += vec3(getTotalValueFromGradientFloat(u_ROLAngularVelocityGradientX, + normalizedAge), + getTotalValueFromGradientFloat(u_ROLAngularVelocityGradientY, + normalizedAge), + getTotalValueFromGradientFloat(u_ROLAngularVelocityGradientZ, + normalizedAge)); + #endif + #ifdef ROTATION_OVER_LIFETIME_RANDOM_CONSTANTS + vec3 ageRot = mix(u_ROLAngularVelocityConstSeparate, + renderer_ROLMaxConst, + a_Random0.w) + * age; + rotation += ageRot; + #endif + #ifdef ROTATION_OVER_LIFETIME_RANDOM_CURVES + rotation += vec3(mix(getTotalValueFromGradientFloat(u_ROLAngularVelocityGradientX, + normalizedAge), + getTotalValueFromGradientFloat(renderer_ROLMaxCurveX, + normalizedAge), + a_Random0.w), + mix(getTotalValueFromGradientFloat(u_ROLAngularVelocityGradientY, + normalizedAge), + getTotalValueFromGradientFloat(renderer_ROLMaxCurveY, + normalizedAge), + a_Random0.w), + mix(getTotalValueFromGradientFloat(u_ROLAngularVelocityGradientZ, + normalizedAge), + getTotalValueFromGradientFloat(renderer_ROLMaxCurveZ, + normalizedAge), + a_Random0.w)); + #endif +#endif + return rotation; +} +#endif diff --git a/packages/core/src/shaderlib/particle/size_over_lifetime_module.glsl b/packages/core/src/shaderlib/particle/size_over_lifetime_module.glsl new file mode 100644 index 0000000000..06e65f243c --- /dev/null +++ b/packages/core/src/shaderlib/particle/size_over_lifetime_module.glsl @@ -0,0 +1,65 @@ +#ifdef RENDERER_SOL_CURVE_MODE + uniform vec2 renderer_SOLMaxCurveX[4]; // x:time y:value + #ifdef RENDERER_SOL_IS_SEPARATE + uniform vec2 renderer_SOLMaxCurveY[4]; // x:time y:value + uniform vec2 renderer_SOLMaxCurveZ[4]; // x:time y:value + #endif + + #ifdef RENDERER_SOL_IS_RANDOM_TWO + uniform vec2 renderer_SOLMinCurveX[4]; // x:time y:value + #ifdef RENDERER_SOL_IS_SEPARATE + uniform vec2 renderer_SOLMinCurveY[4]; // x:time y:value + uniform vec2 renderer_SOLMinCurveZ[4]; // x:time y:value + #endif + #endif +#endif + +vec2 computeParticleSizeBillboard(in vec2 size, in float normalizedAge) { + #ifdef RENDERER_SOL_CURVE_MODE + float lifeSizeX = evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge); + #ifdef RENDERER_SOL_IS_RANDOM_TWO + lifeSizeX = mix(evaluateParticleCurve(renderer_SOLMinCurveX, normalizedAge), lifeSizeX, a_Random0.z); + #endif + + #ifdef RENDERER_SOL_IS_SEPARATE + float lifeSizeY = evaluateParticleCurve(renderer_SOLMaxCurveY, normalizedAge); + #ifdef RENDERER_SOL_IS_RANDOM_TWO + lifeSizeY = mix(evaluateParticleCurve(renderer_SOLMinCurveY, normalizedAge), lifeSizeY, a_Random0.z); + #endif + size *= vec2(lifeSizeX, lifeSizeY); + #else + size *= lifeSizeX; + #endif + #endif + return size; +} + +#ifdef RENDERER_MODE_MESH + vec3 computeParticleSizeMesh(in vec3 size, in float normalizedAge) { + #ifdef RENDERER_SOL_CURVE + size *= evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge); + #endif + #ifdef RENDERER_SOL_RANDOM_CURVES + size *= mix(evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge), + evaluateParticleCurve(u_SOLSizeGradientMax, normalizedAge), + a_Random0.z); + #endif + #ifdef RENDERER_SOL_CURVE_SEPARATE + size *= vec3(evaluateParticleCurve(renderer_SOLMinCurveX, normalizedAge), + evaluateParticleCurve(renderer_SOLMinCurveY, normalizedAge), + evaluateParticleCurve(renderer_SOLMinCurveZ, normalizedAge)); + #endif + #ifdef RENDERER_SOL_RANDOM_CURVES_SEPARATE + size *= vec3(mix(evaluateParticleCurve(renderer_SOLMinCurveX, normalizedAge), + evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge), + a_Random0.z), + mix(evaluateParticleCurve(renderer_SOLMinCurveY, normalizedAge), + evaluateParticleCurve(renderer_SOLMaxCurveY, normalizedAge), + a_Random0.z), + mix(evaluateParticleCurve(renderer_SOLMinCurveZ, normalizedAge), + evaluateParticleCurve(renderer_SOLMaxCurveZ, normalizedAge), + a_Random0.z)); + #endif + return size; + } +#endif \ No newline at end of file diff --git a/packages/core/src/shaderlib/particle/sphere_billboard.glsl b/packages/core/src/shaderlib/particle/sphere_billboard.glsl new file mode 100644 index 0000000000..63eaea9b86 --- /dev/null +++ b/packages/core/src/shaderlib/particle/sphere_billboard.glsl @@ -0,0 +1,29 @@ +#ifdef RENDERER_MODE_SPHERE_BILLBOARD + vec2 corner = a_CornerTextureCoordinate.xy + renderer_PivotOffset.xy; + vec3 sideVector = normalize(cross(camera_Forward, camera_Up)); + vec3 upVector = normalize(cross(sideVector, camera_Forward)); + corner *= computeParticleSizeBillboard(a_StartSize.xy, normalizedAge); + #if defined(RENDERER_ROL_CONSTANT_MODE) || defined(RENDERER_ROL_CURVE_MODE) + if (renderer_ThreeDStartRotation) { + vec3 rotation = vec3(a_StartRotation0.xy, computeParticleRotationFloat(a_StartRotation0.z, age, normalizedAge)); + center += renderer_SizeScale.xzy * rotationByEuler(corner.x * sideVector + corner.y * upVector, rotation); + } else { + float rot = computeParticleRotationFloat(a_StartRotation0.x, age, normalizedAge); + float c = cos(rot); + float s = sin(rot); + mat2 rotation = mat2(c, -s, s, c); + corner = rotation * corner; + center += renderer_SizeScale.xzy * (corner.x * sideVector + corner.y * upVector); + } + #else + if (renderer_ThreeDStartRotation) { + center += renderer_SizeScale.xzy * rotationByEuler(corner.x * sideVector + corner.y * upVector, a_StartRotation0); + } else { + float c = cos(a_StartRotation0.x); + float s = sin(a_StartRotation0.x); + mat2 rotation = mat2(c, -s, s, c); + corner = rotation * corner; + center += renderer_SizeScale.xzy * (corner.x * sideVector + corner.y * upVector); + } + #endif +#endif \ No newline at end of file diff --git a/packages/core/src/shaderlib/particle/stretched_billboard.glsl b/packages/core/src/shaderlib/particle/stretched_billboard.glsl new file mode 100644 index 0000000000..44c27d1d5d --- /dev/null +++ b/packages/core/src/shaderlib/particle/stretched_billboard.glsl @@ -0,0 +1,32 @@ +#ifdef RENDERER_MODE_STRETCHED_BILLBOARD + vec2 corner = a_CornerTextureCoordinate.xy + renderer_PivotOffset.xy; + vec3 velocity; + #if defined(RENDERER_VOL_CONSTANT) || defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CONSTANT) || defined(RENDERER_VOL_RANDOM_CURVE) + if (renderer_VOLSpace == 0){ + velocity = rotationByQuaternions(renderer_SizeScale * (startVelocity + lifeVelocity), + worldRotation) + + gravityVelocity; + } + else{ + velocity = rotationByQuaternions(renderer_SizeScale * startVelocity, worldRotation) + lifeVelocity + gravityVelocity; + } + #else + velocity = rotationByQuaternions(renderer_SizeScale * startVelocity, worldRotation) + gravityVelocity; + #endif + vec3 cameraUpVector = normalize(velocity); + vec3 direction = normalize(center - camera_Position); + vec3 sideVector = normalize(cross(direction, normalize(velocity))); + + sideVector = renderer_SizeScale.xzy * sideVector; + cameraUpVector = length(vec3(renderer_SizeScale.x, 0.0, 0.0)) * cameraUpVector; + + vec2 size = computeParticleSizeBillboard(a_StartSize.xy, normalizedAge); + + const mat2 rotationZHalfPI = mat2(0.0, -1.0, 1.0, 0.0); + corner = rotationZHalfPI * corner; + corner.y = corner.y - abs(corner.y); + + float speed = length(velocity); // TODO: + center += sign(renderer_SizeScale.x) * (sign(renderer_StretchedBillboardLengthScale) * size.x * corner.x * sideVector + + (speed * renderer_StretchedBillboardSpeedScale + size.y * renderer_StretchedBillboardLengthScale) * corner.y * cameraUpVector); +#endif \ No newline at end of file diff --git a/packages/core/src/shaderlib/particle/texture_sheet_animation_module.glsl b/packages/core/src/shaderlib/particle/texture_sheet_animation_module.glsl new file mode 100644 index 0000000000..37b458d585 --- /dev/null +++ b/packages/core/src/shaderlib/particle/texture_sheet_animation_module.glsl @@ -0,0 +1,30 @@ +#if defined(RENDERER_TSA_FRAME_CURVE) || defined(RENDERER_TSA_FRAME_RANDOM_CURVES) + uniform float renderer_TSACycles; + uniform vec3 renderer_TSATillingParams; // x:subU y:subV z:tileCount + uniform vec2 renderer_TSAFrameMaxCurve[4]; // x:time y:value +#endif + +#ifdef RENDERER_TSA_FRAME_RANDOM_CURVES + uniform vec2 renderer_TSAFrameMinCurve[4]; // x:time y:value +#endif + +vec2 computeParticleUV(in vec2 uv, in float normalizedAge) { + #if defined(RENDERER_TSA_FRAME_CURVE) || defined(RENDERER_TSA_FRAME_RANDOM_CURVES) + float scaledNormalizedAge = normalizedAge * renderer_TSACycles; + float cycleNormalizedAge = scaledNormalizedAge - floor(scaledNormalizedAge); + float normalizedFrame = evaluateParticleCurve(renderer_TSAFrameMaxCurve, cycleNormalizedAge); + + #ifdef RENDERER_TSA_FRAME_RANDOM_CURVES + normalizedFrame = mix(evaluateParticleCurve(renderer_TSAFrameMinCurve, cycleNormalizedAge), normalizedFrame, a_Random1.x); + #endif + + float frame = floor(normalizedFrame * renderer_TSATillingParams.z); + + float totalULength = frame * renderer_TSATillingParams.x; + float floorTotalULength = floor(totalULength); + uv.x += totalULength - floorTotalULength; + uv.y += floorTotalULength * renderer_TSATillingParams.y; + #endif + + return uv; +} diff --git a/packages/core/src/shaderlib/particle/velocity_over_lifetime_module.glsl b/packages/core/src/shaderlib/particle/velocity_over_lifetime_module.glsl new file mode 100644 index 0000000000..dbcb7d4f45 --- /dev/null +++ b/packages/core/src/shaderlib/particle/velocity_over_lifetime_module.glsl @@ -0,0 +1,102 @@ +#if defined(RENDERER_VOL_CONSTANT) || defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CONSTANT) || defined(RENDERER_VOL_RANDOM_CURVE) + uniform int renderer_VOLSpace; + + #if defined(RENDERER_VOL_CONSTANT) || defined(RENDERER_VOL_RANDOM_CONSTANT) + uniform vec3 renderer_VOLMaxConst; + + #ifdef RENDERER_VOL_RANDOM_CONSTANT + uniform vec3 renderer_VOLMinConst; + #endif + #endif + + #if defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CURVE) + uniform vec2 renderer_VOLMaxGradientX[4]; // x:time y:value + uniform vec2 renderer_VOLMaxGradientY[4]; // x:time y:value + uniform vec2 renderer_VOLMaxGradientZ[4]; // x:time y:value + + #ifdef RENDERER_VOL_RANDOM_CURVE + uniform vec2 renderer_VOLMinGradientX[4]; // x:time y:value + uniform vec2 renderer_VOLMinGradientY[4]; // x:time y:value + uniform vec2 renderer_VOLMinGradientZ[4]; // x:time y:value + #endif + #endif +#endif + + +#if defined(RENDERER_VOL_CONSTANT) || defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CONSTANT) || defined(RENDERER_VOL_RANDOM_CURVE) + vec3 computeParticleLifeVelocity(in float normalizedAge) { + vec3 velocity; + #if defined(RENDERER_VOL_CONSTANT) || defined(RENDERER_VOL_RANDOM_CONSTANT) + velocity = renderer_VOLMaxConst; + #ifdef RENDERER_VOL_RANDOM_CONSTANT + velocity = mix(renderer_VOLMinConst, velocity, vec3(a_Random1.y, a_Random1.z, a_Random1.w)); + #endif + #endif + + #if defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CURVE) + velocity = vec3(evaluateParticleCurve(renderer_VOLMaxGradientX, normalizedAge), evaluateParticleCurve(renderer_VOLMaxGradientY, normalizedAge), evaluateParticleCurve(renderer_VOLMaxGradientZ, normalizedAge)); + #endif + + #ifdef RENDERER_VOL_RANDOM_CURVE + velocity = vec3( + mix(velocity.x, evaluateParticleCurve(renderer_VOLMinGradientX, normalizedAge), a_Random1.y), + mix(velocity.y, evaluateParticleCurve(renderer_VOLMinGradientY, normalizedAge), a_Random1.z), + mix(velocity.z, evaluateParticleCurve(renderer_VOLMinGradientZ, normalizedAge), a_Random1.w)); + #endif + + return velocity; + } +#endif + +vec3 getStartPosition(vec3 startVelocity, float age, vec3 dragData) { + vec3 startPosition; + float lastTime = min(startVelocity.x / dragData.x, age); // todo 0/0 + startPosition = lastTime * (startVelocity - 0.5 * dragData * lastTime); + return startPosition; +} + +vec3 computeParticlePosition(in vec3 startVelocity, in vec3 lifeVelocity, in float age, in float normalizedAge, vec3 gravityVelocity, vec4 worldRotation, vec3 dragData) { + vec3 startPosition = getStartPosition(startVelocity, age, dragData); + vec3 lifePosition; + #if defined(RENDERER_VOL_CONSTANT) || defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CONSTANT) || defined(RENDERER_VOL_RANDOM_CURVE) + #if defined(RENDERER_VOL_CONSTANT)|| defined(RENDERER_VOL_RANDOM_CONSTANT) + // @todo:just RENDERER_VOL_CONSTANT and RENDERER_VOL_RANDOM_CONSTANT need `lifeVelocity` + lifePosition = lifeVelocity * age; + #endif + + #if defined(RENDERER_VOL_CURVE) || defined(RENDERER_VOL_RANDOM_CURVE) + lifePosition = vec3( + evaluateParticleCurveCumulative(renderer_VOLMaxGradientX, normalizedAge) + evaluateParticleCurveCumulative(renderer_VOLMaxGradientY, normalizedAge), + evaluateParticleCurveCumulative(renderer_VOLMaxGradientZ, normalizedAge)); + + #ifdef RENDERER_VOL_RANDOM_CURVE + lifePosition = vec3( + mix(evaluateParticleCurveCumulative(renderer_VOLMinGradientX, normalizedAge), lifePosition.x, a_Random1.y), + mix(evaluateParticleCurveCumulative(renderer_VOLMinGradientY, normalizedAge), lifePosition.y, a_Random1.z), + mix(evaluateParticleCurveCumulative(renderer_VOLMinGradientZ, normalizedAge), lifePosition.z, a_Random1.w)); + #endif + + lifePosition *= vec3(a_ShapePositionStartLifeTime.w); + #endif + + vec3 finalPosition; + if (renderer_VOLSpace == 0) { + finalPosition = rotationByQuaternions(a_ShapePositionStartLifeTime.xyz + startPosition + lifePosition, worldRotation); + } else { + finalPosition = rotationByQuaternions(a_ShapePositionStartLifeTime.xyz + startPosition, worldRotation) + lifePosition; + } + #else + vec3 finalPosition = rotationByQuaternions(a_ShapePositionStartLifeTime.xyz + startPosition, worldRotation); + #endif + + if (renderer_SimulationSpace == 0) { + finalPosition = finalPosition + renderer_WorldPosition; + } else if (renderer_SimulationSpace == 1) { + finalPosition = finalPosition + a_SimulationWorldPosition; + } + + finalPosition += 0.5 * gravityVelocity * age; + + return finalPosition; +} diff --git a/packages/core/src/shaderlib/particle/vertical_billboard.glsl b/packages/core/src/shaderlib/particle/vertical_billboard.glsl new file mode 100644 index 0000000000..6366d89f0f --- /dev/null +++ b/packages/core/src/shaderlib/particle/vertical_billboard.glsl @@ -0,0 +1,13 @@ +#ifdef RENDERER_MODE_VERTICAL_BILLBOARD + vec2 corner = a_CornerTextureCoordinate.xy + renderer_PivotOffset.xy; // Billboard模式z轴无效 + const vec3 cameraUpVector = vec3(0.0, 1.0, 0.0); + vec3 sideVector = normalize(cross(camera_Forward, cameraUpVector)); + + float rot = computeParticleRotationFloat(a_StartRotation0.x, age, normalizedAge); + float c = cos(rot); + float s = sin(rot); + mat2 rotation = mat2(c, -s, s, c); + corner = rotation * corner * cos(0.78539816339744830961566084581988); // TODO:临时缩小cos45,不确定U3D原因 + corner *= computeParticleSizeBillboard(a_StartSize.xy, normalizedAge); + center += renderer_SizeScale.xzy * (corner.x * sideVector + corner.y * cameraUpVector); +#endif \ No newline at end of file diff --git a/packages/core/src/texture/RenderTarget.ts b/packages/core/src/texture/RenderTarget.ts index 0ae785b5ac..cca09bc2ce 100644 --- a/packages/core/src/texture/RenderTarget.ts +++ b/packages/core/src/texture/RenderTarget.ts @@ -229,6 +229,7 @@ export class RenderTarget extends GraphicsResource { */ _setRenderTargetInfo(faceIndex: TextureCubeFace, mipLevel: number): void { this._platformRenderTarget.setRenderTargetInfo(faceIndex, mipLevel); + this._isContentLost = false; } /** diff --git a/packages/core/src/texture/Texture.ts b/packages/core/src/texture/Texture.ts index 78a722704e..10503bcd78 100644 --- a/packages/core/src/texture/Texture.ts +++ b/packages/core/src/texture/Texture.ts @@ -185,8 +185,10 @@ export abstract class Texture extends GraphicsResource { platformTexture.wrapModeV = this._wrapModeV; platformTexture.filterMode = this._filterMode; platformTexture.anisoLevel = this._anisoLevel; - platformTexture.depthCompareFunction = this._depthCompareFunction; - platformTexture.setUseDepthCompareMode(this._useDepthCompareMode); + if (this._engine._hardwareRenderer._isWebGL2) { + platformTexture.depthCompareFunction = this._depthCompareFunction; + platformTexture.setUseDepthCompareMode(this._useDepthCompareMode); + } } /** diff --git a/packages/core/src/texture/Texture2D.ts b/packages/core/src/texture/Texture2D.ts index 1c5068f7ef..8bb28ca768 100644 --- a/packages/core/src/texture/Texture2D.ts +++ b/packages/core/src/texture/Texture2D.ts @@ -69,6 +69,7 @@ export class Texture2D extends Texture { height?: number ): void { (this._platformTexture as IPlatformTexture2D).setPixelBuffer(colorBuffer, mipLevel, x, y, width, height); + this._isContentLost = false; } /** @@ -89,6 +90,7 @@ export class Texture2D extends Texture { y: number = 0 ): void { (this._platformTexture as IPlatformTexture2D).setImageSource(imageSource, mipLevel, flipY, premultiplyAlpha, x, y); + this._isContentLost = false; } /** diff --git a/packages/core/src/texture/Texture2DArray.ts b/packages/core/src/texture/Texture2DArray.ts index 382d09d678..ec9df45403 100644 --- a/packages/core/src/texture/Texture2DArray.ts +++ b/packages/core/src/texture/Texture2DArray.ts @@ -81,6 +81,7 @@ export class Texture2DArray extends Texture { height, length ); + this._isContentLost = false; } /** @@ -111,6 +112,7 @@ export class Texture2DArray extends Texture { x, y ); + this._isContentLost = false; } /** diff --git a/packages/core/src/texture/TextureCube.ts b/packages/core/src/texture/TextureCube.ts index 741f19d699..0461fccb86 100644 --- a/packages/core/src/texture/TextureCube.ts +++ b/packages/core/src/texture/TextureCube.ts @@ -53,6 +53,7 @@ export class TextureCube extends Texture { height?: number ): void { (this._platformTexture as IPlatformTextureCube).setPixelBuffer(face, colorBuffer, mipLevel, x, y, width, height); + this._isContentLost = false; } /** @@ -83,6 +84,7 @@ export class TextureCube extends Texture { x, y ); + this._isContentLost = false; } /** diff --git a/packages/design/src/renderingHardwareInterface/IPlatformPrimitive.ts b/packages/design/src/renderingHardwareInterface/IPlatformPrimitive.ts index 9b48b0d008..ffda94e7a5 100644 --- a/packages/design/src/renderingHardwareInterface/IPlatformPrimitive.ts +++ b/packages/design/src/renderingHardwareInterface/IPlatformPrimitive.ts @@ -1,16 +1,4 @@ -/** - * Platform primitive interface. - */ export interface IPlatformPrimitive { - /** - * Draw primitive. - * @param tech - Shader - * @param subPrimitive - Sub primitive - */ draw(tech: any, subPrimitive: any): void; - - /** - * Destroy. - */ destroy(): void; } diff --git a/packages/math/src/Rand.ts b/packages/math/src/Rand.ts new file mode 100644 index 0000000000..85490c393a --- /dev/null +++ b/packages/math/src/Rand.ts @@ -0,0 +1,50 @@ +/** + * Random number generator based on the xorshift128+ algorithm. + * https://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf + */ +export class Rand { + private _state0: number; + private _state1: number; + + /** + * Create a random number generator. + * @param seed0 - Seed 0 used to initialize the generator + * @param seed1 - Seed 1 used to initialize the generator + */ + constructor(seed0: number, seed1: number) { + this.reset(seed0, seed1); + } + + /** + * Generate a integer 32bit random number. + * @returns - A random number + */ + randomInt32(): number { + let x = this._state0; + const y = this._state1; + this._state0 = y; + x ^= x << 23; + x ^= x >>> 17; + x ^= y ^ (y >>> 26); + this._state1 = x; + return (this._state0 + this._state1) >>> 0; + } + + /** + * Generate a number between 0 and 1. + * @returns - A random number + */ + random(): number { + return this.randomInt32() / 0xffffffff; // 2^32 - 1 + } + + /** + * Reset the generator by new seeds. + * @param seed0 - Random seed0 + * @param seed1 - Random seed1 + */ + reset(seed0: number, seed1: number): void { + this._state0 = seed0 >>> 0; + this._state1 = seed1 >>> 0; + } +} diff --git a/packages/math/src/index.ts b/packages/math/src/index.ts index cf6d9d6a07..fb44f4f74a 100755 --- a/packages/math/src/index.ts +++ b/packages/math/src/index.ts @@ -17,3 +17,4 @@ export { Plane } from "./Plane"; export { Color } from "./Color"; export { Rect } from "./Rect"; export { SphericalHarmonics3 } from "./SphericalHarmonics3"; +export { Rand } from "./Rand"; diff --git a/packages/rhi-webgl/src/GLPrimitive.ts b/packages/rhi-webgl/src/GLPrimitive.ts index 69f90dc92e..9b4222ca2e 100644 --- a/packages/rhi-webgl/src/GLPrimitive.ts +++ b/packages/rhi-webgl/src/GLPrimitive.ts @@ -1,4 +1,5 @@ -import { GLCapabilityType, Logger, Mesh, SubMesh } from "@galacean/engine-core"; +import { GLCapabilityType, Logger, Primitive } from "@galacean/engine-core"; +import { SubPrimitive } from "@galacean/engine-core/types/graphic/SubPrimitive"; import { IPlatformPrimitive } from "@galacean/engine-design"; import { WebGLGraphicDevice } from "./WebGLGraphicDevice"; import { WebGLExtension } from "./type"; @@ -14,31 +15,29 @@ import { WebGLExtension } from "./type"; */ export class GLPrimitive implements IPlatformPrimitive { private _attribLocArray: number[] = []; - private readonly _primitive: Mesh; + private readonly _primitive: Primitive; private readonly _canUseInstancedArrays: boolean; private _gl: (WebGLRenderingContext & WebGLExtension) | WebGL2RenderingContext; private _vaoMap: Map = new Map(); - private readonly _useVao: boolean; + private readonly _isSupportVAO: boolean; - constructor(rhi: WebGLGraphicDevice, primitive: Mesh) { + constructor(rhi: WebGLGraphicDevice, primitive: Primitive) { this._primitive = primitive; this._canUseInstancedArrays = rhi.canIUse(GLCapabilityType.instancedArrays); - this._useVao = rhi.canIUse(GLCapabilityType.vertexArrayObject); + this._isSupportVAO = rhi.canIUse(GLCapabilityType.vertexArrayObject); this._gl = rhi.gl; } /** * Draw the primitive. */ - draw(shaderProgram: any, subMesh: SubMesh): void { + draw(shaderProgram: any, subMesh: SubPrimitive): void { const gl = this._gl; const primitive = this._primitive; - // @ts-ignore - const useVao = this._useVao && primitive._enableVAO; + const useVao = this._isSupportVAO && primitive.enableVAO; if (useVao) { - // @ts-ignore if (primitive._bufferStructChanged) { this._clearVAO(); } @@ -51,16 +50,15 @@ export class GLPrimitive implements IPlatformPrimitive { this._bindBufferAndAttrib(shaderProgram); } - // @ts-ignore - const { _indexBufferBinding, _instanceCount, _glIndexType, _glIndexByteCount } = primitive; + const { indexBufferBinding, instanceCount, _glIndexType, _glIndexByteCount } = primitive; const { topology, start, count } = subMesh; - if (!_instanceCount) { - if (_indexBufferBinding) { + if (!instanceCount) { + if (indexBufferBinding) { if (useVao) { gl.drawElements(topology, count, _glIndexType, start * _glIndexByteCount); } else { - const { _glBuffer } = _indexBufferBinding.buffer._platformBuffer; + const { _glBuffer } = indexBufferBinding.buffer._platformBuffer; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, _glBuffer); gl.drawElements(topology, count, _glIndexType, start * _glIndexByteCount); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); @@ -70,17 +68,17 @@ export class GLPrimitive implements IPlatformPrimitive { } } else { if (this._canUseInstancedArrays) { - if (_indexBufferBinding) { + if (indexBufferBinding) { if (useVao) { - gl.drawElementsInstanced(topology, count, _glIndexType, start * _glIndexByteCount, _instanceCount); + gl.drawElementsInstanced(topology, count, _glIndexType, start * _glIndexByteCount, instanceCount); } else { - const { _glBuffer } = _indexBufferBinding.buffer._platformBuffer; + const { _glBuffer } = indexBufferBinding.buffer._platformBuffer; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, _glBuffer); - gl.drawElementsInstanced(topology, count, _glIndexType, start * _glIndexByteCount, _instanceCount); + gl.drawElementsInstanced(topology, count, _glIndexType, start * _glIndexByteCount, instanceCount); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); } } else { - gl.drawArraysInstanced(topology, start, count, _instanceCount); + gl.drawArraysInstanced(topology, start, count, instanceCount); } } else { Logger.error("ANGLE_instanced_arrays extension is not supported"); @@ -96,7 +94,7 @@ export class GLPrimitive implements IPlatformPrimitive { } destroy(): void { - this._useVao && this._clearVAO(); + this._isSupportVAO && this._clearVAO(); } /** @@ -105,12 +103,10 @@ export class GLPrimitive implements IPlatformPrimitive { private _bindBufferAndAttrib(shaderProgram: any): void { const gl = this._gl; const primitive = this._primitive; - // @ts-ignore - const vertexBufferBindings = primitive._vertexBufferBindings; + const vertexBufferBindings = primitive.vertexBufferBindings; this._attribLocArray.length = 0; const attributeLocation = shaderProgram.attributeLocation; - // @ts-ignore const attributes = primitive._vertexElementMap; let vbo: WebGLBuffer; @@ -159,10 +155,9 @@ export class GLPrimitive implements IPlatformPrimitive { /** register VAO */ gl.bindVertexArray(vao); - // @ts-ignore - const { _indexBufferBinding } = this._primitive; - if (_indexBufferBinding) { - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, _indexBufferBinding.buffer._platformBuffer._glBuffer); + const { indexBufferBinding } = this._primitive; + if (indexBufferBinding) { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferBinding.buffer._platformBuffer._glBuffer); } this._bindBufferAndAttrib(shaderProgram); diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index e826b90458..c44b542d7f 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -313,11 +313,10 @@ export class WebGLGraphicDevice implements IHardwareRenderer { gl.clear(clearFlag); } - drawPrimitive(primitive: Mesh, subPrimitive: SubMesh, shaderProgram: any) { + drawPrimitive(primitive: GLPrimitive, subPrimitive: SubMesh, shaderProgram: any) { // todo: VAO not support morph animation if (primitive) { - //@ts-ignore - primitive._draw(shaderProgram, subPrimitive); + primitive.draw(shaderProgram, subPrimitive); } else { Logger.error("draw primitive failed."); }