feat(particle): add NoiseModule for simplex noise turbulence#2953
feat(particle): add NoiseModule for simplex noise turbulence#2953hhhhkrx wants to merge 1 commit intogalacean:dev/2.0from
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughAdded a new NoiseModule for particle noise control, integrated it into ParticleGenerator (property, shader updates, bounds expansion), exported the module from the particle package, and registered a new GLSL noise module in the particle shader library. Changes
Sequence DiagramsequenceDiagram
participant PG as ParticleGenerator
participant NM as NoiseModule
participant Renderer as Renderer
participant ShaderData as ShaderData
participant Shader as Shader
PG->>NM: construct NoiseModule(this)
PG->>NM: set properties (strength, freq, scroll, octaves, damping)
NM->>Renderer: _onGeneratorParamsChanged()
PG->>ShaderData: _updateShaderData(shaderData)
PG->>NM: NM._updateShaderData(shaderData)
alt Noise enabled
NM->>ShaderData: write strengthVec, freq, scroll, octaveVec
NM->>Shader: _enableMacro("NOISE_ENABLED", true)
alt damping enabled
NM->>Shader: _enableMacro("NOISE_DAMPING", true)
else damping disabled
NM->>Shader: _enableMacro("NOISE_DAMPING", false)
end
else Noise disabled
NM->>Shader: _enableMacro("NOISE_ENABLED", false)
end
PG->>PG: _calculateTransformedBounds()
PG->>PG: expand bounds by noise axis-aligned margin (maxAmplitude * strength)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Add GPU-computed simplex noise displacement to particles, referencing Unity's Noise Module. Supports per-axis strength, frequency, scroll speed, damping, and up to 3 octaves. Reuses existing noise_common and noise_simplex_3D shader libraries. No instance buffer changes needed.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev/2.0 #2953 +/- ##
===========================================
- Coverage 77.73% 77.70% -0.04%
===========================================
Files 898 899 +1
Lines 98310 98552 +242
Branches 9806 9806
===========================================
+ Hits 76417 76575 +158
- Misses 21728 21812 +84
Partials 165 165
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/src/particle/modules/NoiseModule.ts`:
- Around line 146-150: The octaveMultiplier setter currently allows negative
values which can invert bounds; update set octaveMultiplier(value: number) to
validate and reject or clamp values to a safe non-negative range (e.g., clamp to
>= 0.0 or a chosen min like 0.0/1.0), only assign to this._octaveMultiplier and
call this._generator._renderer._onGeneratorParamsChanged() when the
validated/clamped value differs from the current value; ensure any invalid
inputs are normalized (or throw a clear error) so downstream bounds calculations
in ParticleGenerator no longer receive negative multipliers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: bd9486dd-e2cd-4d0b-91a3-d78d7fd9eb2b
⛔ Files ignored due to path filters (2)
packages/core/src/shaderlib/extra/particle.vs.glslis excluded by!**/*.glslpackages/core/src/shaderlib/particle/noise_over_lifetime_module.glslis excluded by!**/*.glsl
📒 Files selected for processing (4)
packages/core/src/particle/ParticleGenerator.tspackages/core/src/particle/index.tspackages/core/src/particle/modules/NoiseModule.tspackages/core/src/shaderlib/particle/index.ts
| set octaveMultiplier(value: number) { | ||
| if (value !== this._octaveMultiplier) { | ||
| this._octaveMultiplier = value; | ||
| this._generator._renderer._onGeneratorParamsChanged(); | ||
| } |
There was a problem hiding this comment.
Validate octaveMultiplier to avoid invalid bounds shrinkage.
Line 146 currently accepts negative values. That propagates into transformed-bounds expansion (packages/core/src/particle/ParticleGenerator.ts, Line 1397 onward) where signed amplitude accumulation can under-estimate or invert expected expansion, causing culling artifacts.
Proposed fix
set octaveMultiplier(value: number) {
- if (value !== this._octaveMultiplier) {
- this._octaveMultiplier = value;
+ const nextValue = Number.isFinite(value) ? Math.max(0, value) : 0;
+ if (nextValue !== this._octaveMultiplier) {
+ this._octaveMultiplier = nextValue;
this._generator._renderer._onGeneratorParamsChanged();
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| set octaveMultiplier(value: number) { | |
| if (value !== this._octaveMultiplier) { | |
| this._octaveMultiplier = value; | |
| this._generator._renderer._onGeneratorParamsChanged(); | |
| } | |
| set octaveMultiplier(value: number) { | |
| const nextValue = Number.isFinite(value) ? Math.max(0, value) : 0; | |
| if (nextValue !== this._octaveMultiplier) { | |
| this._octaveMultiplier = nextValue; | |
| this._generator._renderer._onGeneratorParamsChanged(); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/particle/modules/NoiseModule.ts` around lines 146 - 150,
The octaveMultiplier setter currently allows negative values which can invert
bounds; update set octaveMultiplier(value: number) to validate and reject or
clamp values to a safe non-negative range (e.g., clamp to >= 0.0 or a chosen min
like 0.0/1.0), only assign to this._octaveMultiplier and call
this._generator._renderer._onGeneratorParamsChanged() when the validated/clamped
value differs from the current value; ensure any invalid inputs are normalized
(or throw a clear error) so downstream bounds calculations in ParticleGenerator
no longer receive negative multipliers.
Code Review: PR #2953 — NoiseModule for Particle System总结PR 为粒子系统新增 Simplex Noise 模块,整体方向正确。主要问题集中在:噪声采样坐标空间错误、多八度噪声缺少归一化、damping 语义实现错误、包围盒计算偏大、API 类型/命名不够清晰。以下逐条说明。 问题1. [Bug] 噪声采样坐标空间错误 + 缺少仿真空间变换原代码 ( #ifdef RENDERER_NOISE_MODULE_ENABLED
center += computeNoisePositionOffset(a_ShapePositionStartLifeTime.xyz, normalizedAge, age);
#endif问题:
修正: #ifdef RENDERER_NOISE_MODULE_ENABLED
vec3 noiseOffset = computeNoisePositionOffset(a_FeedbackPosition);
if (renderer_SimulationSpace == 0) {
noiseOffset = rotationByQuaternions(noiseOffset, worldRotation);
}
center += noiseOffset;
#endif2. [Bug] 多八度噪声未归一化原代码 ( float amplitude = 1.0;
float frequency = 1.0;
vec3 noiseValue = sampleNoise3D(coord);
int octaves = int(renderer_NoiseOctaveInfo.x);
if (octaves >= 2) {
amplitude *= renderer_NoiseOctaveInfo.y;
frequency *= renderer_NoiseOctaveInfo.z;
noiseValue += amplitude * sampleNoise3D(coord * frequency);
}
if (octaves >= 3) {
amplitude *= renderer_NoiseOctaveInfo.y;
frequency *= renderer_NoiseOctaveInfo.z;
noiseValue += amplitude * sampleNoise3D(coord * frequency);
}
vec3 offset = noiseValue * renderer_NoiseStrength;问题:
修正: vec3 noiseValue = sampleSimplexNoise3D(coord);
int octaveCount = int(renderer_NoiseOctaveInfo.x);
if (octaveCount >= 2) {
float intensity = renderer_NoiseOctaveInfo.y;
float frequencyScale = renderer_NoiseOctaveInfo.z;
float range = 1.0 + intensity;
noiseValue += intensity * sampleSimplexNoise3D(coord * frequencyScale);
if (octaveCount >= 3) {
intensity *= renderer_NoiseOctaveInfo.y;
frequencyScale *= renderer_NoiseOctaveInfo.z;
range += intensity;
noiseValue += intensity * sampleSimplexNoise3D(coord * frequencyScale);
}
noiseValue /= range;
}3. [Bug] damping 语义实现错误原代码 ( #ifdef RENDERER_NOISE_DAMPING
offset *= (1.0 - normalizedAge);
#endif原代码 ( /** Whether noise strength diminishes with particle age. */
get damping(): boolean { ... }
// _updateShaderData 中:
if (this._damping) {
dampingMacro = NoiseModule._dampingMacro;
}问题:
修正:删除 const dampingScale = 1.0 / this._frequency;
params.set(
this._strengthX.constantMax * dampingScale,
this._strengthY.constantMax * dampingScale,
this._strengthZ.constantMax * dampingScale,
this._frequency
);4. [Bug] 包围盒计算偏大 + 缺少 frequency 补偿原代码 ( let maxAmplitude = 1.0;
let amp = 1.0;
for (let i = 1; i < noise.octaves; i++) {
amp *= noise.octaveMultiplier;
maxAmplitude += amp;
}
const noiseMaxX = Math.abs(noise.strengthX) * maxAmplitude;
const noiseMaxY = Math.abs(noise.strengthY) * maxAmplitude;
const noiseMaxZ = Math.abs(noise.strengthZ) * maxAmplitude;问题:
修正: // Noise offset is computed in simulation space then rotated to world.
// Adding axis-aligned max offset in world space is a conservative bound.
const { noise } = this;
if (noise.enabled) {
// Noise output is normalized to [-1, 1] after octave accumulation,
// then multiplied by strength / frequency on CPU side.
const invFrequency = 1.0 / noise.frequency;
const noiseMaxX = Math.abs(noise.strengthX.constantMax) * invFrequency;
const noiseMaxY = Math.abs(noise.strengthY.constantMax) * invFrequency;
const noiseMaxZ = Math.abs(noise.strengthZ.constantMax) * invFrequency;
min.set(min.x - noiseMaxX, min.y - noiseMaxY, min.z - noiseMaxZ);
max.set(max.x + noiseMaxX, max.y + noiseMaxY, max.z + noiseMaxZ);
}5. [API 类型] strengthX/Y/Z、scrollSpeed 应为 ParticleCompositeCurve原代码: private _strengthX: number = 1.0;
private _strengthY: number = 1.0;
private _strengthZ: number = 1.0;
private _scrollSpeed: number = 0.0;
set strengthX(value: number) {
if (value !== this._strengthX) {
this._strengthX = value;
this._generator._renderer._onGeneratorParamsChanged();
}
}问题:Galacean 粒子系统中 MinMaxCurve 对应的类型是 修正: @deepClone private _strengthX: ParticleCompositeCurve;
@deepClone private _strengthY: ParticleCompositeCurve;
@deepClone private _strengthZ: ParticleCompositeCurve;
@deepClone private _scrollSpeed: ParticleCompositeCurve;
set strengthX(value: ParticleCompositeCurve) {
const lastValue = this._strengthX;
if (value !== lastValue) {
this._strengthX = value;
this._onCompositeCurveChange(lastValue, value);
}
}
// strengthY, strengthZ, scrollSpeed 同理
constructor(generator: ParticleGenerator) {
super(generator);
this.strengthX = new ParticleCompositeCurve(1.0);
this.strengthY = new ParticleCompositeCurve(1.0);
this.strengthZ = new ParticleCompositeCurve(1.0);
this.scrollSpeed = new ParticleCompositeCurve(0.0);
}6. [API 命名] 多处命名改良6a.
6b. 原名看不出是什么的 multiplier。改后明确表示"每层强度的倍率"。 /**
* Intensity multiplier for each successive octave.
* Each layer's contribution is scaled by this factor relative to the previous layer, range [0, 1].
*/
get octaveIntensityMultiplier(): number6c. 原名看不出和 frequency 的关系。改后明确表示"每层频率的倍率",与 /**
* Frequency multiplier for each successive octave.
* Each layer samples at this multiple of the previous layer's frequency, range [1, 4].
*/
get octaveFrequencyMultiplier(): number6d. shader 函数名 明确标注噪声算法类型,可读性更好。 7. [API 缺失] 参数范围约束原代码: 修正: set frequency(value: number) {
value = Math.max(1e-6, value); // 防止 1/frequency 除零
// ...
}
set octaveIntensityMultiplier(value: number) {
value = Math.max(0, Math.min(1, value));
// ...
}
set octaveFrequencyMultiplier(value: number) {
value = Math.max(1, Math.min(4, value));
// ...
}8. [Bug] enabled 未触发 Transform Feedback 状态更新原代码: override set enabled(value: boolean) {
if (value !== this._enabled) {
this._enabled = value;
this._generator._renderer._onGeneratorParamsChanged();
}
}问题:噪声模块依赖 Transform Feedback,启用/禁用时需要调用 修正: override set enabled(value: boolean) {
if (value !== this._enabled) {
if (value && !this._generator._renderer.engine._hardwareRenderer.isWebGL2) {
return;
}
this._enabled = value;
this._generator._updateTransformFeedbackState();
this._generator._renderer._onGeneratorParamsChanged();
}
}9. [性能] 4 个 uniform 合并为 2 个 vec4原代码: uniform vec3 renderer_NoiseStrength;
uniform float renderer_NoiseFrequency;
uniform float renderer_NoiseScrollSpeed;
uniform vec3 renderer_NoiseOctaveInfo;4 个 uniform 可以打包为 2 个 vec4,减少 uniform 槽位占用。 修正: uniform vec4 renderer_NoiseParams; // xyz=strength, w=frequency
uniform vec4 renderer_NoiseOctaveInfo; // x=octaveCount, y=octaveIntensityMultiplier, z=octaveFrequencyMultiplier, w=scrollSpeedTS 侧对应改为 10. [改进] 噪声三轴采样改为分量重排原代码: vec3 sampleNoise3D(vec3 coord) {
return vec3(
simplex(coord),
simplex(coord + vec3(17.0, 31.0, 47.0)),
simplex(coord + vec3(67.0, 89.0, 113.0))
);
}问题:用魔法数偏移来让三轴采到不同噪声值。分量重排的方式不依赖随意选的常数,通过交换坐标轴本身就保证了各轴采样的独立性,更有保证。 修正: vec3 sampleSimplexNoise3D(vec3 coord) {
float d = 100.0;
return vec3(
simplex(vec3(coord.z, coord.y, coord.x)),
simplex(vec3(coord.x + d, coord.z, coord.y)),
simplex(vec3(coord.y, coord.x + d, coord.z))
);
}11. [辅助]
|
Summary
NoiseModuleto the particle system, referencing Unity's Noise Modulenoise_common+noise_simplex_3Dshader librariesUsage
Test plan
npm run buildpassesSummary by CodeRabbit