Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
64f726c
feat(particle): add NoiseModule for simplex noise turbulence
hhhhkrx Apr 3, 2026
6202615
fix(particle): use current position for noise sampling and handle sim…
hhhhkrx Apr 7, 2026
8a5ed7c
fix(particle): normalize noise octave accumulation to [-1,1]
hhhhkrx Apr 7, 2026
bc0ffc4
fix(particle): replace damping toggle with strength/frequency ratio
hhhhkrx Apr 7, 2026
78cd0cf
refactor(particle): use ParticleCompositeCurve for noise strength and…
hhhhkrx Apr 7, 2026
5b5c4e5
refactor(particle): rename noise octave API for clarity
hhhhkrx Apr 7, 2026
9937579
fix(particle): clamp NoiseModule parameter ranges
hhhhkrx Apr 7, 2026
68a1596
perf(particle): pack noise uniforms from 4 slots to 2 vec4s
hhhhkrx Apr 7, 2026
fe1c044
refactor(particle): use component swizzle instead of magic offsets fo…
hhhhkrx Apr 7, 2026
cb1f42d
test(particle): add e2e test for NoiseModule
hhhhkrx Apr 7, 2026
5f7c3ff
fix(particle): initialize NoiseModule in constructor like other modules
hhhhkrx Apr 7, 2026
a964cca
test(particle): add NoiseModule e2e baseline screenshot
hhhhkrx Apr 7, 2026
a080689
feat(particle): add noise damping over particle lifetime
hhhhkrx Apr 7, 2026
40dad06
test(particle): adjust noise e2e for damping visibility
hhhhkrx Apr 7, 2026
7fee4a9
test(particle): update noise e2e baseline screenshot
hhhhkrx Apr 7, 2026
f81187a
refactor(particle): switch noise from VS position offset to TF veloci…
hhhhkrx Apr 7, 2026
2fed01c
fix(particle): use analytical base position for noise sampling
hhhhkrx Apr 7, 2026
83d8d64
test(particle): update noise e2e baseline for analytical sampling
hhhhkrx Apr 8, 2026
647ab5e
feat(particle): support curve over lifetime for noise strength
hhhhkrx Apr 8, 2026
052bb81
fix(particle): use dedicated noise random for TwoConstants and rename…
hhhhkrx Apr 8, 2026
841653e
fix(particle): unify TF lifecycle to prevent modules from disabling e…
hhhhkrx Apr 8, 2026
97f489b
fix(particle): reduce noise spatial offset to preserve field-like coh…
hhhhkrx Apr 8, 2026
d316ac4
refactor(particle): downgrade noise scrollSpeed from ParticleComposit…
hhhhkrx Apr 8, 2026
bb4b531
test(particle): update noise e2e baseline for reduced spatial offset
hhhhkrx Apr 8, 2026
0d1c4b4
refactor(particle): remove redundant scrollSpeed assignment in NoiseM…
hhhhkrx Apr 9, 2026
f8a22c4
refactor(particle): rename noise_over_lifetime_module to noise_module
GuoLei1990 Apr 9, 2026
a2182b6
perf(particle): optimize TF noise integration
GuoLei1990 Apr 9, 2026
72567ac
refactor(particle): rename computeNoiseVelocity to computeNoiseDispla…
GuoLei1990 Apr 9, 2026
95c225b
refactor(particle): improve noise shader readability
GuoLei1990 Apr 9, 2026
0a3a1c3
refactor(particle): remove per-particle noise random offset
GuoLei1990 Apr 9, 2026
9b74bc7
refactor(particle): rename octave params to self-descriptive names
GuoLei1990 Apr 9, 2026
7b985f0
refactor(particle): rename noise octave weight to amplitude
GuoLei1990 Apr 9, 2026
335e053
refactor(particle): remove redundant type annotations in NoiseModule
GuoLei1990 Apr 9, 2026
42702f0
style(particle): fix lint formatting in NoiseModule
GuoLei1990 Apr 9, 2026
3331fbd
test(particle): update noise e2e baseline for sin-hash offset
hhhhkrx Apr 9, 2026
f3dff19
refactor(particle): consolidate noise velocity into single ifdef bloc…
hhhhkrx Apr 9, 2026
531eb56
style(particle): remove extra blank line in particle vertex shader
hhhhkrx Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions e2e/case/particleRenderer-noise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @title Particle Noise
* @category Particle
*/
import {
AssetType,
BlendMode,
Camera,
Color,
Engine,
Entity,
ParticleCompositeCurve,
ParticleCurveMode,
ParticleGradientMode,
ParticleMaterial,
ParticleRenderer,
ParticleSimulationSpace,
ConeShape,
Texture2D,
WebGLEngine
} from "@galacean/engine";
import { initScreenshot, updateForE2E } from "./.mockForE2E";

WebGLEngine.create({
canvas: "canvas"
}).then((engine) => {
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();
scene.background.solidColor = new Color(0, 0, 0, 1);

const cameraEntity = rootEntity.createChild("camera");
cameraEntity.transform.setPosition(0, 0, 0);
const camera = cameraEntity.addComponent(Camera);
camera.fieldOfView = 60;

engine.resourceManager
.load({
url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original",
type: AssetType.Texture
})
.then((texture) => {
createNoiseParticle(engine, rootEntity, <Texture2D>texture);

updateForE2E(engine, 200);
initScreenshot(engine, camera);
});
});

function createNoiseParticle(engine: Engine, rootEntity: Entity, texture: Texture2D): void {
const particleEntity = new Entity(engine, "Noise");
particleEntity.transform.setPosition(0, 0, -2);

const particleRenderer = particleEntity.addComponent(ParticleRenderer);
const generator = particleRenderer.generator;
generator.useAutoRandomSeed = false;

const material = new ParticleMaterial(engine);
material.baseColor = new Color(0.4, 0.8, 1.0, 1.0);
material.blendMode = BlendMode.Additive;
material.baseTexture = texture;
particleRenderer.setMaterial(material);

const { main, emission, noise, colorOverLifetime } = generator;

// Main
main.duration = 3;
main.isLoop = true;
main.startLifetime.constantMin = 0.3;
main.startLifetime.constantMax = 0.6;
main.startLifetime.mode = ParticleCurveMode.TwoConstants;
main.startSpeed.constantMin = 4;
main.startSpeed.constantMax = 4;
main.startSpeed.mode = ParticleCurveMode.TwoConstants;
main.startSize.constantMin = 0.05;
main.startSize.constantMax = 0.1;
main.startSize.mode = ParticleCurveMode.TwoConstants;
main.gravityModifier.constant = -0.5;
main.simulationSpace = ParticleSimulationSpace.Local;
main.maxParticles = 200;

// Emission
emission.rateOverTime.constant = 40;
const coneShape = new ConeShape();
coneShape.angle = 25;
coneShape.radius = 0.00001;
emission.shape = coneShape;

// Color over lifetime
// colorOverLifetime.enabled = true;
// colorOverLifetime.color.mode = ParticleGradientMode.Gradient;
// const gradient = colorOverLifetime.color.gradient;
// gradient.alphaKeys[0].alpha = 0;
// gradient.alphaKeys[1].alpha = 0;
// gradient.addAlphaKey(0.1, 1.0);
// gradient.addAlphaKey(0.8, 1.0);

// Noise
noise.enabled = true;
noise.strengthX = new ParticleCompositeCurve(1);
noise.strengthY = new ParticleCompositeCurve(1);
noise.strengthZ = new ParticleCompositeCurve(1);
noise.frequency = 1;
noise.scrollSpeed = 0;
noise.octaveCount = 1;
noise.octaveIntensityMultiplier = 0.5;
noise.octaveFrequencyMultiplier = 2.0;

rootEntity.addChild(particleEntity);
}
6 changes: 6 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ export const E2E_CONFIG = {
caseFileName: "particleRenderer-horizontal-billboard",
threshold: 0,
diffPercentage: 0.2162
},
noiseModule: {
category: "Particle",
caseFileName: "particleRenderer-noise",
threshold: 0,
diffPercentage: 0
}
},
PostProcess: {
Expand Down
3 changes: 3 additions & 0 deletions e2e/fixtures/originImage/Particle_particleRenderer-noise.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 32 additions & 7 deletions packages/core/src/particle/ParticleGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { ParticleCompositeCurve } from "./modules/ParticleCompositeCurve";
import { RotationOverLifetimeModule } from "./modules/RotationOverLifetimeModule";
import { SizeOverLifetimeModule } from "./modules/SizeOverLifetimeModule";
import { TextureSheetAnimationModule } from "./modules/TextureSheetAnimationModule";
import { NoiseModule } from "./modules/NoiseModule";
import { VelocityOverLifetimeModule } from "./modules/VelocityOverLifetimeModule";

/**
Expand Down Expand Up @@ -83,6 +84,9 @@ export class ParticleGenerator {
/** Texture sheet animation module. */
@deepClone
readonly textureSheetAnimation = new TextureSheetAnimationModule(this);
/** Noise module. */
@deepClone
readonly noise: NoiseModule;

/** @internal */
_currentParticleCount = 0;
Expand Down Expand Up @@ -190,6 +194,7 @@ export class ParticleGenerator {
this.forceOverLifetime = new ForceOverLifetimeModule(this);
this.sizeOverLifetime = new SizeOverLifetimeModule(this);
this.limitVelocityOverLifetime = new LimitVelocityOverLifetimeModule(this);
this.noise = new NoiseModule(this);

this.emission.enabled = true;
}
Expand Down Expand Up @@ -613,6 +618,7 @@ export class ParticleGenerator {
this.sizeOverLifetime._updateShaderData(shaderData);
this.rotationOverLifetime._updateShaderData(shaderData);
this.colorOverLifetime._updateShaderData(shaderData);
this.noise._updateShaderData(shaderData);
}

/**
Expand All @@ -628,20 +634,23 @@ export class ParticleGenerator {
this.limitVelocityOverLifetime._resetRandomSeed(seed);
this.rotationOverLifetime._resetRandomSeed(seed);
this.colorOverLifetime._resetRandomSeed(seed);
this.noise._resetRandomSeed(seed);
}

/**
* @internal
*/
_setTransformFeedback(enabled: boolean): void {
this._useTransformFeedback = enabled;
_setTransformFeedback(): void {
const needed = this.limitVelocityOverLifetime.enabled || this.noise.enabled;
if (needed === this._useTransformFeedback) return;
this._useTransformFeedback = needed;

// Switching TF mode invalidates all active particle state: feedback buffers and instance
// buffer layout are incompatible between the two paths. Clear rather than show a one-frame
// jump; new particles will fill in naturally from the next emit cycle.
this._clearActiveParticles();

if (enabled) {
if (needed) {
if (!this._feedbackSimulator) {
this._feedbackSimulator = new ParticleTransformFeedbackSimulator(this._renderer.engine);
}
Expand Down Expand Up @@ -701,9 +710,7 @@ export class ParticleGenerator {
* @internal
*/
_cloneTo(target: ParticleGenerator): void {
if (target.limitVelocityOverLifetime.enabled) {
target._setTransformFeedback(true);
}
target._setTransformFeedback();
}

/**
Expand Down Expand Up @@ -942,7 +949,9 @@ export class ParticleGenerator {
instanceVertices[offset + 20] = colorOverLifetime._colorGradientRand.random();
}

// instanceVertices[offset + 21] = rand.random();
if (this.noise.enabled) {
instanceVertices[offset + 21] = this.noise._noiseRand.random();
}

const rotationOverLifetime = this.rotationOverLifetime;
if (rotationOverLifetime.enabled && rotationOverLifetime.rotationZ.mode === ParticleCurveMode.TwoConstants) {
Expand Down Expand Up @@ -1385,6 +1394,22 @@ export class ParticleGenerator {
min.add(worldOffsetMin);
max.add(worldOffsetMax);

// Noise module impact: noise output is normalized to [-1, 1],
// max displacement = |strength_max|
const { noise } = this;
if (noise.enabled) {
let noiseMaxX: number, noiseMaxY: number, noiseMaxZ: number;
if (noise.separateAxes) {
noiseMaxX = Math.abs(noise.strengthX._getMax());
noiseMaxY = Math.abs(noise.strengthY._getMax());
noiseMaxZ = Math.abs(noise.strengthZ._getMax());
} else {
noiseMaxX = noiseMaxY = noiseMaxZ = Math.abs(noise.strengthX._getMax());
}
min.set(min.x - noiseMaxX, min.y - noiseMaxY, min.z - noiseMaxZ);
max.set(max.x + noiseMaxX, max.y + noiseMaxY, max.z + noiseMaxZ);
}

min.add(worldPosition);
max.add(worldPosition);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/particle/enums/ParticleRandomSubSeeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export enum ParticleRandomSubSeeds {
Shape = 0xaf502044,
GravityModifier = 0xa47b8c4d,
ForceOverLifetime = 0xe6fb937c,
LimitVelocityOverLifetime = 0xb5a21f7e
LimitVelocityOverLifetime = 0xb5a21f7e,
Noise = 0xf4b2c8a1
}
1 change: 1 addition & 0 deletions packages/core/src/particle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export { SizeOverLifetimeModule } from "./modules/SizeOverLifetimeModule";
export { TextureSheetAnimationModule } from "./modules/TextureSheetAnimationModule";
export { VelocityOverLifetimeModule } from "./modules/VelocityOverLifetimeModule";
export { LimitVelocityOverLifetimeModule } from "./modules/LimitVelocityOverLifetimeModule";
export { NoiseModule } from "./modules/NoiseModule";
export * from "./modules/shape/index";
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export class LimitVelocityOverLifetimeModule extends ParticleGeneratorModule {
return;
}
this._enabled = value;
this._generator._setTransformFeedback(value);
this._generator._setTransformFeedback();
this._generator._renderer._onGeneratorParamsChanged();
}
}
Expand Down
Loading
Loading