u-space 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/index.cjs +3 -3
  2. package/dist/index.js +594 -533
  3. package/dist/plugins/atmosphere/index.cjs +1 -1
  4. package/dist/plugins/atmosphere/index.js +3255 -129
  5. package/dist/plugins/atmosphere/threeR185Compat.d.ts +2 -0
  6. package/dist/plugins/fire/FireEffect.d.ts +90 -0
  7. package/dist/plugins/fire/index.cjs +1 -0
  8. package/dist/plugins/fire/index.d.ts +1 -0
  9. package/dist/plugins/fire/index.js +433 -0
  10. package/dist/plugins/u-manager/index.cjs +58 -58
  11. package/dist/plugins/u-manager/index.d.ts +1 -0
  12. package/dist/plugins/u-manager/index.js +9691 -9070
  13. package/dist/plugins/u-manager/loaders/SceneInstancedLayer.d.ts +29 -0
  14. package/dist/plugins/u-manager/loaders/SceneLoader.d.ts +5 -5
  15. package/dist/plugins/u-manager/semantics/objects/BuildingGroup.d.ts +3 -0
  16. package/dist/plugins/u-manager/semantics/objects/FacilityInstancedLayer.d.ts +24 -0
  17. package/dist/plugins/u-manager/semantics/objects/FloorMesh.d.ts +2 -1
  18. package/dist/plugins/u-manager/semantics/types.d.ts +2 -0
  19. package/dist/src/tools/AnnotationManager.d.ts +4 -1
  20. package/dist/src/viewers/RenderPipeline.d.ts +6 -5
  21. package/docs/api-effects.md +2 -0
  22. package/docs/api-interactions.md +1 -0
  23. package/docs/api-managers.md +1 -1
  24. package/docs/api-plugin-fire.md +174 -0
  25. package/docs/api-plugin-u-manager.md +178 -15
  26. package/docs/api-tools.md +1 -1
  27. package/docs/changelog.md +38 -0
  28. package/docs/examples-guide.md +13 -1
  29. package/docs/getting-started.md +1 -1
  30. package/docs/index.md +1 -0
  31. package/docs/mcp.md +27 -1
  32. package/package.json +4 -4
@@ -0,0 +1,29 @@
1
+ import { Box3, Color, Group, Object3D, type ColorRepresentation } from 'three/webgpu';
2
+ import { BaseGroup } from 'u-space';
3
+ export declare class SceneInstanceObject extends Group {
4
+ #private;
5
+ isSceneInstanceObject: boolean;
6
+ type: string;
7
+ setSceneInstanceBounds(bounds: Box3): this;
8
+ getSceneInstanceBoundingBox(target?: Box3, world?: boolean): Box3;
9
+ setSceneInstanceVisible(visible: boolean): this;
10
+ setSceneInstanceColor(color: ColorRepresentation): this;
11
+ resetSceneInstanceColor(): this;
12
+ setSceneInstanceOpacity(opacity: number): this;
13
+ getSceneInstanceColor(target?: Color): Color;
14
+ hasSceneInstanceColor(): boolean;
15
+ getSceneInstanceOpacity(): number;
16
+ setSceneInstanceHighlight(color: ColorRepresentation, opacity: number, overwrite?: boolean): this;
17
+ clearSceneInstanceHighlight(): this;
18
+ getSceneInstanceColorMode(): number;
19
+ onSceneInstanceRenderDirty(callback: () => void): () => boolean;
20
+ markSceneInstanceRenderDirty(): this;
21
+ updateMatrixWorld(force?: boolean): void;
22
+ }
23
+ export declare class SceneInstancedLayer extends BaseGroup {
24
+ #private;
25
+ isSceneInstancedLayer: boolean;
26
+ type: string;
27
+ constructor();
28
+ addSceneBatch(url: string, template: Object3D, instances: SceneInstanceObject[]): boolean;
29
+ }
@@ -1,9 +1,7 @@
1
1
  import { Group, Loader } from 'three/webgpu';
2
- import { type Viewer } from 'u-space';
2
+ import { Model, type Viewer } from 'u-space';
3
3
  import type { ITreeData } from '../types';
4
- export interface SceneLoaderOptions {
5
- }
6
- export declare class SceneLoader extends Loader<unknown, SceneLoaderOptions> {
4
+ export declare class SceneLoader extends Loader {
7
5
  #private;
8
6
  viewer: Viewer;
9
7
  key: string;
@@ -12,13 +10,15 @@ export declare class SceneLoader extends Loader<unknown, SceneLoaderOptions> {
12
10
  protected _treeData: ITreeData[];
13
11
  constructor(viewer: Viewer);
14
12
  setKey(key: string): this;
13
+ protected loadModel(url: string): Promise<Model>;
15
14
  protected _preareTreeData(): Promise<void>;
15
+ protected _prepareSemanticIds(): Promise<ReadonlySet<string>>;
16
16
  /**
17
17
  * 加载场景
18
18
  * @param options
19
19
  * @returns
20
20
  */
21
- loadAsync(options?: SceneLoaderOptions): Promise<Group>;
21
+ loadAsync(): Promise<Group>;
22
22
  clearCache(): void;
23
23
  dispose(): void;
24
24
  }
@@ -16,12 +16,15 @@ export declare class BuildingGroup extends BaseGroup {
16
16
  addFloor(mesh: FloorMesh): this;
17
17
  hasFloor(mesh: FloorMesh): boolean;
18
18
  getFloorAt(index: number): FloorMesh;
19
+ getFloorById(id: string): FloorMesh | undefined;
19
20
  removeFloor(mesh: FloorMesh): this;
20
21
  addElevator(mesh: ElevatorMesh): this;
21
22
  getElevatorAt(index: number): ElevatorMesh;
23
+ getElevatorById(id: string): ElevatorMesh | undefined;
22
24
  removeElevator(mesh: ElevatorMesh): this;
23
25
  addVent(mesh: VentMesh): this;
24
26
  getVentAt(index: number): VentMesh;
27
+ getVentById(id: string): VentMesh | undefined;
25
28
  removeVent(mesh: VentMesh): this;
26
29
  isolateFloor(floor: BuildingFloorReference | BuildingFloorReference[]): this;
27
30
  isolateFloors(floors: BuildingFloorReference[]): this;
@@ -1,10 +1,16 @@
1
1
  import { Box3, Color, Group, Object3D, type ColorRepresentation } from 'three/webgpu';
2
2
  import { BaseGroup } from 'u-space';
3
+ export interface FacilityInstanceCullingOptions {
4
+ enabled?: boolean;
5
+ frustum?: boolean;
6
+ minScreenRadius?: number;
7
+ }
3
8
  export declare class FacilityInstanceObject extends Group {
4
9
  #private;
5
10
  isFacilityInstanceObject: boolean;
6
11
  type: string;
7
12
  setLocalBounds(bounds: Box3): this;
13
+ getFacilityBoundingBox(target?: Box3, world?: boolean): Box3;
8
14
  setFacilityVisible(visible: boolean): this;
9
15
  setFacilityColor(color: ColorRepresentation): this;
10
16
  resetFacilityColor(): this;
@@ -12,11 +18,29 @@ export declare class FacilityInstanceObject extends Group {
12
18
  getFacilityColor(target?: Color): Color;
13
19
  hasFacilityColor(): boolean;
14
20
  getFacilityOpacity(): number;
21
+ setFacilityHighlight(color: ColorRepresentation, opacity: number, overwrite?: boolean): this;
22
+ clearFacilityHighlight(): this;
23
+ getFacilityColorMode(): number;
24
+ onFacilityRenderDirty(callback: () => void): () => boolean;
25
+ markFacilityRenderDirty(): this;
15
26
  }
16
27
  export declare class FacilityInstancedLayer extends BaseGroup {
17
28
  #private;
18
29
  isFacilityInstancedLayer: boolean;
19
30
  type: string;
20
31
  constructor();
32
+ getFacilities(): FacilityInstanceObject[];
33
+ getInstanceCulling(): {
34
+ enabled: boolean;
35
+ frustum: boolean;
36
+ minScreenRadius: number;
37
+ };
38
+ setInstanceCulling(options?: FacilityInstanceCullingOptions): this;
39
+ setFacilityVisible(visible: boolean): this;
40
+ setFacilityColor(color: ColorRepresentation): this;
41
+ resetFacilityColor(): this;
42
+ setFacilityOpacity(opacity: number): this;
43
+ setFacilityHighlight(color: ColorRepresentation, opacity: number, overwrite?: boolean): this;
44
+ clearFacilityHighlight(): this;
21
45
  addFacilityBatch(url: string, template: Object3D, facilities: FacilityInstanceObject[]): boolean;
22
46
  }
@@ -3,6 +3,7 @@ import { BaseMesh, type InteractionEvent, type InteractionEventMap } from 'u-spa
3
3
  import type { SemanticColor, SemanticGeometryEntry, SemanticKind } from '../types';
4
4
  type FacilityReference = Object3D | string;
5
5
  type FacilitySemanticKind = 'Facilities';
6
+ type FloorMeshMaterial = MeshStandardNodeMaterial | MeshStandardNodeMaterial[];
6
7
  export type FloorSemanticKind = SemanticKind | FacilitySemanticKind;
7
8
  export interface FloorGeometrySemanticEntity {
8
9
  type: 'geometry';
@@ -43,7 +44,7 @@ export type FloorMeshInteractionEvent = Omit<InteractionEvent, 'target' | 'curre
43
44
  export type FloorMeshEventMap = {
44
45
  [K in keyof InteractionEventMap]: FloorMeshEventPayload<InteractionEventMap[K]>;
45
46
  };
46
- export declare class FloorMesh extends BaseMesh<BufferGeometry, MeshStandardNodeMaterial, FloorMeshEventMap> {
47
+ export declare class FloorMesh extends BaseMesh<BufferGeometry, FloorMeshMaterial, FloorMeshEventMap> {
47
48
  #private;
48
49
  isFloorMergedSemanticMesh: boolean;
49
50
  type: string;
@@ -13,6 +13,8 @@ export interface SemanticObjectState {
13
13
  kind: SemanticKind;
14
14
  name?: string;
15
15
  index: number;
16
+ indexStart: number;
17
+ indexCount: number;
16
18
  baseMatrix: Matrix4;
17
19
  matrix: Matrix4;
18
20
  finalMatrix: Matrix4;
@@ -1,4 +1,7 @@
1
1
  import { Line, type Scene, type ColorRepresentation, Group } from 'three/webgpu';
2
+ export type AnnotationLabelStyle = {
3
+ [K in keyof CSSStyleDeclaration as CSSStyleDeclaration[K] extends string ? K : never]?: CSSStyleDeclaration[K];
4
+ };
2
5
  export interface AnnotationOptions {
3
6
  /** Position of the annotation anchor in 3D space */
4
7
  position: {
@@ -19,7 +22,7 @@ export interface AnnotationOptions {
19
22
  /** Leader line color */
20
23
  lineColor?: ColorRepresentation;
21
24
  /** CSS styles for the label element */
22
- labelStyle?: Partial<CSSStyleDeclaration>;
25
+ labelStyle?: AnnotationLabelStyle;
23
26
  }
24
27
  export interface Annotation {
25
28
  id: string;
@@ -12,8 +12,9 @@ export interface SSGIConfig {
12
12
  radius?: number;
13
13
  thickness?: number;
14
14
  }
15
- export type OutputNodeComposer = (scenePass: PassNode) => Node;
16
- export type OutputNodeEffect = ((scenePass: PassNode, inputNode: Node) => Node) & {
15
+ export type ColorOutputNode = Node<'vec4'>;
16
+ export type OutputNodeComposer = (scenePass: PassNode) => ColorOutputNode;
17
+ export type OutputNodeEffect = ((scenePass: PassNode, inputNode: ColorOutputNode) => ColorOutputNode) & {
17
18
  includesTemporalAntialias?: boolean;
18
19
  includesOutputTransform?: boolean;
19
20
  };
@@ -27,11 +28,11 @@ export declare class RenderPipeline extends RenderPipelineBase {
27
28
  constructor(...args: Args);
28
29
  /** @deprecated Use `scenePass` instead */
29
30
  get currentOutputNode(): PassNode;
30
- addOverlayPass(pass: Node): void;
31
- removeOverlayPass(pass: Node): void;
31
+ addOverlayPass(pass: ColorOutputNode): void;
32
+ removeOverlayPass(pass: ColorOutputNode): void;
32
33
  setCamera(camera: Camera): void;
33
34
  /** @deprecated Use `setOutputComposer` instead */
34
- setCurrentOutputNode(node: Node): void;
35
+ setCurrentOutputNode(node: ColorOutputNode): void;
35
36
  /** @deprecated Use `setOutputComposer(null)` instead */
36
37
  resetCurrentOutputNode(): void;
37
38
  setOutputComposer(composer: OutputNodeComposer | null): void;
@@ -5,6 +5,8 @@
5
5
  ## `MaterialEffects`
6
6
 
7
7
  静态工具类,将基于 TSL 的视觉特效直接应用于对象的材质。适用于任何 `Object3D`(支持单个或数组),会自动遍历所有子网格。
8
+ 当对象是 `SceneLoader` 自动批处理生成的 `SceneInstanceObject` 时,`highlightColor()` / `removeHighlightColor()` 会改写该实例的 per-instance color / opacity,而不是改动共享 batch 材质。
9
+ `SceneInstanceObject` 会支持 `overwrite` 的替换/染色语义;`depthWrite` 属于共享 batch 材质状态,不能按单个实例设置,因此对该类对象不会生效。
8
10
 
9
11
  特性:
10
12
  - **效果可叠加**:高亮和呼吸效果可同时作用于同一对象,呼吸在高亮结果之上混合
@@ -16,6 +16,7 @@ viewer.interactionManager.pointerMoveEventsEnabled = true;
16
16
  ### 目标对象
17
17
 
18
18
  可以限制射线检测所针对的对象范围。默认值为 `scene.children`(即整个场景的直接子级,含递归嵌套)。
19
+ `BaseMesh` / `BaseGroup` 默认会在自身 `visible === false` 时让 `raycast()` 返回 `false`,从而阻止自身和子级继续参与递归射线检测;如需隐藏但仍可交互,可将对象的 `ignoreInvisibleWhenRaycast` 设为 `false`。`setInteractable(object, false)` / `addIgnore(object)` 会在命中后过滤该对象及其子对象。
19
20
 
20
21
  ```typescript
21
22
  // 仅检测指定对象
@@ -129,7 +129,7 @@ viewer.objectManager.showAll();
129
129
 
130
130
  #### `setOpacity(id: string, opacity: number)`
131
131
 
132
- 设置指定对象所有材质的透明度。
132
+ 设置指定对象所有材质的透明度。对于 `SceneLoader` 自动批处理生成的 `SceneInstanceObject`,会写入该实例的 per-instance opacity,而不会影响同一模型 path 的其他实例。
133
133
 
134
134
  ```typescript
135
135
  viewer.objectManager.setOpacity('building-01', 0.3);
@@ -0,0 +1,174 @@
1
+ # fire
2
+
3
+ 体积火焰插件,基于 Three.js 官方 `webgpu_volume_fire` 示例封装。插件在 GPU 上运行 3D 流体模拟,并通过独立的 volumetric pass 叠加到 `Viewer.renderPipeline` output effect 链中,提供火焰、烟雾、扰动、阴影和动态点光源效果。
4
+
5
+ ```typescript
6
+ import { Vector3 } from 'three/webgpu';
7
+ import { FireEffect } from 'u-space/plugins/fire';
8
+
9
+ const fire = new FireEffect(viewer, {
10
+ position: new Vector3(0, 0, 0),
11
+ size: new Vector3(12, 12, 24),
12
+ fireIntensity: 40,
13
+ smokeLifespan: 3.5,
14
+ });
15
+
16
+ fire.enable();
17
+ ```
18
+
19
+ ## 能力
20
+
21
+ - 按官方示例的 semi-Lagrangian advection、curl noise、buoyancy、Jacobi pressure projection 流程模拟体积火焰和烟雾。
22
+ - 使用 3D storage texture 保存速度、密度、温度、压力和 curl noise 数据。
23
+ - 默认使用 `TeapotGeometry(0.8, 28)` 作为 emitter 采样几何;也可以传入业务对象和自定义 `BufferGeometry`。
24
+ - 通过 `RenderPipeline.addOutputEffect()` 叠加体积结果,不占用业务侧的 `setOutputComposer()`。
25
+ - 内置 spot light 和 point light:spot light 负责体积阴影方向,point light 跟随火焰核心投射暖色动态光照。
26
+ - 监听 `cameraChange`,相机对象切换后会重建 fire 自己的 volumetric pass。
27
+
28
+ ## API
29
+
30
+ ### `new FireEffect(viewer, options?)`
31
+
32
+ 创建火焰效果实例。构造函数不会立即往场景写入模拟资源;调用 `enable()` 后才会创建体积盒、storage texture、compute pass、灯光和 output effect。
33
+
34
+ ### `enable(options?)`
35
+
36
+ 开启火焰效果。重复调用时等同于 `update(options)`。启用期间会把 `viewer.frameloop` 临时切到 `always`,关闭时恢复启用前的值。
37
+
38
+ ```typescript
39
+ fire.enable({
40
+ position: new Vector3(0, 0, 0),
41
+ size: new Vector3(12, 12, 24),
42
+ renderResolution: 0.5,
43
+ });
44
+ ```
45
+
46
+ ### `update(options?)`
47
+
48
+ 更新火焰参数。大多数视觉和模拟强度参数会通过 uniform 直接生效。`gridSize`、`emitterGeometry`、`volumeLayer` 或 `raymarchSteps` 改变时会重建相关资源。
49
+
50
+ ```typescript
51
+ fire.update({
52
+ emitTemperature: 6,
53
+ emitDensity: 8,
54
+ emitterRadius: 1.4,
55
+ smokeLifespan: 6,
56
+ });
57
+ ```
58
+
59
+ ### `setPosition(position)`
60
+
61
+ 更新体积盒底部中心位置。等同于 `update({ position })`。
62
+
63
+ ### `setEmitter(emitter, geometry?)`
64
+
65
+ 设置驱动 emitter 的对象和采样几何。`emitter` 的 `matrixWorld` 会在每帧写入模拟;如果只想使用默认内部 emitter,可以传入 `null`。
66
+
67
+ ### `reset()`
68
+
69
+ 清空当前模拟状态并重新创建模拟资源。
70
+
71
+ ### `disable()`
72
+
73
+ 关闭火焰效果,移除 `beforeRender` / `cameraChange` 监听、output effect、体积盒、灯光、storage texture 和 compute pass,并恢复启用前的 `viewer.frameloop`。
74
+
75
+ ### `dispose()`
76
+
77
+ 关闭效果并释放插件持有的默认 emitter geometry。
78
+
79
+ ## Options
80
+
81
+ | 参数 | 类型 | 默认值 | 说明 |
82
+ | --- | --- | --- | --- |
83
+ | `position` | `Vector3` | `(0, 0, 0)` | 体积盒底部中心世界坐标 |
84
+ | `size` | `Vector3` | `(12, 12, 24)` | 体积盒世界尺寸,也是烟雾扩散的约束范围 |
85
+ | `gridSize` | `{ x?: number; y?: number; z?: number }` | `{ x: 100, y: 100, z: 200 }` | 3D 模拟网格尺寸,越大越细腻也越耗显存 |
86
+ | `volumeLayer` | `number` | `10` | 体积盒所在 layer,用于隔离 volumetric pass |
87
+ | `emitterGeometry` | `BufferGeometry \| null` | `TeapotGeometry(0.8, 28)` | emit pass 采样的几何顶点 |
88
+ | `emitter` | `Object3D \| null` | 内部 `Object3D` | 驱动 emitter matrix 和扰动速度的对象 |
89
+ | `emitterRadius` | `number` | `1` | emitter 移动时影响流场的半径,作为 uniform 更新 |
90
+ | `simulate` | `boolean` | `true` | 是否推进流体模拟 |
91
+ | `simSpeed` | `number` | `1.2` | 模拟速度倍率 |
92
+ | `pressureIterations` | `number` | `2` | Jacobi pressure projection 次数,会自动向上限制为正偶数 |
93
+ | `raymarchSteps` | `number` | `16` | 体积 raymarch 步数,改变后会重建体积材质 |
94
+ | `renderResolution` | `number` | `0.5` | volumetric pass 分辨率倍率,范围 `0.1..1` |
95
+ | `denoise` | `boolean` | `true` | 是否对体积 pass 做 Gaussian blur |
96
+ | `denoiseStrength` | `number` | `0.5` | 降噪强度 |
97
+ | `bloom` | `boolean` | `true` | 是否对火焰输出追加 bloom |
98
+ | `bloomStrength` | `number` | `0.1` | bloom 强度 |
99
+ | `bloomRadius` | `number` | `1` | bloom 半径 |
100
+ | `bloomThreshold` | `number` | `0.5` | bloom 阈值 |
101
+ | `smokeLifespan` | `number` | `3.5` | 烟雾寿命,越大烟雾保留越久 |
102
+ | `smokeIntensity` | `number` | `1` | 烟雾散射可见度倍率 |
103
+ | `smokeColor` | `ColorRepresentation` | `0xffffff` | 烟雾散射颜色 |
104
+ | `fireLifespan` | `number` | `1.3` | 火焰温度衰减时间 |
105
+ | `turbulence` | `number` | `3.2` | 火焰和烟雾湍流强度 |
106
+ | `turbulenceDecay` | `number` | `0.1` | 湍流随年龄衰减速度 |
107
+ | `turbulenceFrequency` | `number` | `10` | curl noise 频率 |
108
+ | `buoyancy` | `number` | `3` | 热浮力 |
109
+ | `velocityDamping` | `number` | `0.25` | 速度阻尼 |
110
+ | `emitDensity` | `number` | `7` | emitter 注入密度 |
111
+ | `emitTemperature` | `number` | `5.5` | emitter 注入温度 |
112
+ | `motionBoost` | `number` | `0.25` | emitter 移动带来的额外发射量 |
113
+ | `windStrength` | `number` | `6.5` | emitter 移动对流场的扰动强度 |
114
+ | `fireIntensity` | `number` | `40` | 火焰发光强度 |
115
+ | `glowSpread` | `number` | `5` | 火焰亮度扩散范围 |
116
+ | `fireHue` | `number` | `0` | 火焰色相偏移,单位为度 |
117
+ | `saturation` | `number` | `1.1` | 输出饱和度 |
118
+ | `fireStartColor` | `ColorRepresentation` | `0xffe68c` | 高温火焰颜色 |
119
+ | `fireMidColor` | `ColorRepresentation` | `0xff7305` | 中段火焰颜色 |
120
+ | `fireEndColor` | `ColorRepresentation` | `0xff0000` | 低温火焰颜色 |
121
+ | `phaseAsymmetry` | `number` | `0` | Henyey-Greenstein 相函数 g 值 |
122
+ | `powderStrength` | `number` | `0.59` | powder effect 强度 |
123
+ | `multiScattering` | `number` | `1` | 多次散射近似强度 |
124
+ | `shadowAbsorption` | `number` | `2` | 体积阴影吸收系数 |
125
+ | `shadowAmbient` | `number` | `0.5` | 阴影环境补光 |
126
+ | `keyLight` | `boolean` | `true` | 是否创建体积阴影 spot light |
127
+ | `pointLight` | `boolean` | `true` | 是否创建火焰动态点光源 |
128
+ | `pointLightIntensity` | `number` | `1` | 动态点光源强度 |
129
+ | `pointLightDistance` | `number` | `40` | 动态点光源距离 |
130
+
131
+ ## Emitter
132
+
133
+ 如果不设置 `emitterGeometry`,插件会创建一个内部 `TeapotGeometry(0.8, 28)`,这和官方示例保持一致。emit pass 会遍历该几何的 position 顶点,把密度和温度写入体积纹理。传入自定义 `emitterGeometry` 时,该几何必须包含 `position` attribute。
134
+
135
+ 如果不设置 `emitter`,插件会使用内部 `Object3D`,并把它放在 `position` 位置。传入业务对象后,插件每帧读取该对象的 `matrixWorld` 和世界位置变化,移动速度会影响流场扰动和额外发射量。
136
+
137
+ ```typescript
138
+ const fire = new FireEffect(viewer, {
139
+ emitter: torchMesh,
140
+ emitterGeometry: torchFlameGeometry,
141
+ emitterRadius: 0.8,
142
+ });
143
+ ```
144
+
145
+ ## 光源与体积范围
146
+
147
+ 火焰本体不是场景里的普通透明 mesh 直接混合,而是先放在 `volumeLayer` 的体积盒里单独渲染,再通过 output effect 叠加到主场景。烟雾和火焰的可见范围由 `position + size` 定义的体积盒限制,超出盒子的密度会在边界处淡出。要让烟雾扩散范围更大,优先增大 `size`;要提升细节,再同步增大 `gridSize`。
148
+
149
+ 插件会按需创建两个光源:
150
+
151
+ - `FireEffect.keyLight`:`SpotLight`,用于体积阴影方向和烟雾散射计算。
152
+ - `FireEffect.pointLight`:`PointLight`,跟随 emitter / 火焰核心移动,并通过 TSL `colorNode` 产生随温度、密度和噪声变化的暖色照明。
153
+
154
+ 如果业务场景已经有自己的灯光,可以通过 `keyLight: false` 或 `pointLight: false` 关闭插件内置光源。
155
+
156
+ ## 与 RenderPipeline 的关系
157
+
158
+ `FireEffect` 使用 `viewer.renderPipeline.addOutputEffect()` 和 `removeOutputEffect()` 接入输出链。它不会调用 `setOutputComposer()`,因此不会覆盖业务或其他插件的 output effects。注意如果业务主动设置自定义 `setOutputComposer()`,RenderPipeline 会按自定义 composer 的语义跳过 output effects,火焰叠加也会被跳过。
159
+
160
+ `renderResolution` 只影响独立 volumetric pass 的分辨率;主场景分辨率仍由 `Viewer` 和 renderer 控制。启用 `denoise` 和 `bloom` 时,插件会在 output effect 内创建并释放对应节点。
161
+
162
+ ## 示例
163
+
164
+ 示例页见 `examples/test_fire.html`。本地运行时先构建插件:
165
+
166
+ ```bash
167
+ pnpm build:plugins -- plugins/fire
168
+ ```
169
+
170
+ 然后通过本地静态服务访问:
171
+
172
+ ```text
173
+ http://127.0.0.1:5174/examples/test_fire.html
174
+ ```