UNPKG

@realsee/five

Version:

Realsee Renderer SDK

481 lines (389 loc) • 15 kB
# Flowing Light 3D Pass (流光 3D 通道) - **Summary**: `FlowingLight3DPass` 基于 3D 世界坐标绘制流光效果,使用 InstancedMesh 实例化渲染优化性能,自动投影到屏幕空间,适合沿模型表面、路径或空间轨迹演示动态流动光带。 - **Schema**: `FlowingLight3DPass` 类及 Line 数据结构。 - **Concepts**: 基于相机投影的 3D 光带、InstancedMesh 实例化渲染、世界坐标到屏幕坐标映射、头尾追逐动画。 - **Configuration**: 3D 路径点、颜色、不透明度、线宽、动画周期、延迟。 - **Examples**: 空间轨迹演示、结合相机动画、多条 3D 路径、高 DPR 场景优化。 ## Schema > **Definition**: [FlowingLight3DPass](../../five/renderer/postprocessing/passes/flowing-light-3d-pass.d.ts) `FlowingLight3DPass` 继承自 `FivePass`,支持投影到当前相机视锥。核心接口如下: ```ts import * as THREE from 'three'; import { FivePass } from './pass'; import { type Camera } from '../../../core/camera'; type Line = { id?: string; // 可选的唯一标识符,不传则自动生成 UUID points: THREE.Vector3[]; // 世界坐标系路径点 totalLength: number; // 路径总长度(世界单位) color: THREE.Color; // 光带颜色 opacity?: number; // 光带不透明度(0-1) duration?: number; // 动画周期(毫秒) startTime?: number; // 线条开始时间(毫秒),默认使用当前内部时间 lineWidth?: number; // 线宽(像素) tailLengthRatio?: number; // 尾巴长度比例(0-1),默认 0.2 }; export class FlowingLight3DPass extends FivePass { constructor(camera: Camera); // 批量设置路径列表(替换所有现有线条) public setLines(lines: Line[]): void; // 追加一条或多条线,返回生成的 id 数组 public addLine(...lines: Line[]): string[]; // 按 id 移除一条或多条线,返回实际移除的数量 public removeLine(...ids: string[]): number; // 修改单条线的参数(通过 id),支持部分更新 public setLine(params: { id: string } & Partial<Omit<Line, 'id'>>): void; public render( renderer: THREE.WebGLRenderer, writeBuffer: THREE.WebGLRenderTarget, readBuffer: THREE.WebGLRenderTarget, deltaTime: number, maskActive?: boolean, ): void; public dispose(): void; } ``` ## Concepts ### InstancedMesh 实例化渲染 (Instanced Rendering) 为了优化高 DPR(Device Pixel Ratio)场景下的性能,该 Pass 使用 InstancedMesh 代替全屏着色器。每个线段作为一个实例,只渲染线段覆盖的区域,避免了对每个像素的全屏检查,显著提升了渲染效率。 ### 世界空间路径与投影 (World-space Projection) 路径点定义在 3D 世界坐标系中。Pass 内部自动根据当前相机投影矩阵将 3D 点映射到屏幕空间,从而实现"随相机变化"的动态光带效果。 ### 头尾追逐动画 (Head-Tail Chase Animation) 流光效果采用"头尾追逐"模式: - **头部(Head)**: 光流的前端,沿路径循环移动 - **尾部(Tail)**: 光流的后端,跟随头部移动 - **渐变**: 从尾部(透明度 0)到头部(透明度 1)线性渐变 - **尾长**: 尾部长度由 `tailLengthRatio` 参数控制(默认 0.2,即路径总长的 20%) 光头在路径上循环流动,通过 `gl_FragCoord` 投影到线段上计算每个像素的渐变位置,确保渐变效果准确。尾巴长度会在动画开始时逐渐增长,直到达到 `tailLengthRatio * totalLength`。 ### 相机同步 Pass 自动同步相机的投影矩阵和视图矩阵,无需手动管理。支持相机运动时光带跟随。 ### DPR 处理 自动处理高 DPR 场景,渲染目标尺寸根据 `readBuffer` 自动调整,确保在 Retina 等高分辨率屏幕上位置准确且性能优化。 ## Configuration ### Line 配置参数 | Parameter | Type | Required | Default | Description | | :--- | :--- | :--- | :--- | :--- | | `id` | `string` | | 自动生成 UUID | 唯一标识符,用于 `setLine` 方法更新单条线。 | | `points` | `THREE.Vector3[]` | ✓ | - | 世界坐标系路径点数组。至少 2 个点。 | | `totalLength` | `number` | ✓ | - | 路径总长度(世界单位)。通常为所有相邻点间距离之和。 | | `color` | `THREE.Color` | ✓ | - | 光带颜色(RGB)。 | | `opacity` | `number` | | `1.0` | 光带不透明度(0-1)。控制与背景的混合强度。 | | `duration` | `number` | | `1000` | 动画周期(毫秒)。光头完成一个循环所需时长。 | | `startTime` | `number` | | 当前内部时间 | 线条开始时间(毫秒)。控制线条动画从何时开始播放,可用于多条线错开起始时间。 | | `lineWidth` | `number` | | `3.0` | 线宽(像素)。控制光带在屏幕上的宽度。 | | `tailLengthRatio` | `number` | | `0.2` | 尾巴长度比例(0-1)。控制流光尾巴相对于路径总长的比例。 | > **注意**: `totalLength` 应为世界坐标单位。若不准确,流速会偏差。 ## Instance Methods ### `constructor(camera: Camera)` 初始化 Pass,需传入相机以获取投影矩阵和视图矩阵。 ### `setLines(lines: Line[])` 批量设置路径列表,替换所有现有线条。支持运行时动态更改。 **自动生成 ID**: 如果 line 没有提供 `id`,会自动生成 UUID。 **自动设置 startTime**: 如果 line 没有提供 `startTime`,会使用当前内部时间。 **示例**: ```ts const pathLine: Line = { id: 'my-3d-line', // 可选,不传则自动生成 points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(5, 2, 3), new THREE.Vector3(10, 1, 6), ], totalLength: 12, // 世界坐标单位 color: new THREE.Color(0xff6600), opacity: 0.85, duration: 3000, startTime: 500, lineWidth: 4.0, }; pass.setLines([pathLine]); ``` ### `addLine(...lines: Line[]): string[]` 追加一条或多条线到现有列表中,不影响已有线条。返回新增线条的 id 数组。 **示例**: ```ts // 追加单条线 const [id1] = pass.addLine({ points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(10, 5, 5), ], totalLength: 12, color: new THREE.Color(0xff0000), duration: 2000, }); // 追加多条线 const ids = pass.addLine( { points: [...], totalLength: 8, color: new THREE.Color(0x00ff00), duration: 1500, }, { points: [...], totalLength: 15, color: new THREE.Color(0x0000ff), duration: 1800, }, ); // ids = ['uuid-1', 'uuid-2'] ``` ### `removeLine(...ids: string[]): number` 按 id 移除一条或多条线,返回实际移除的数量。如果 id 不存在,会在控制台输出警告。 **示例**: ```ts // 移除单条线 pass.removeLine(id1); // 移除多条线 const removedCount = pass.removeLine(id1, id2, id3); console.log(`移除了 ${removedCount} 条线`); ``` ### `setLine(params: { id: string } & Partial<Omit<Line, 'id'>>)` 通过 `id` 修改单条线的参数。支持部分更新,只更新传入的字段。 **性能优化**: - 如果 `points` 数量不变,只更新 GPU 的 instance attributes,性能极高 - 如果 `points` 数量改变,会重建整个 mesh,性能开销较大 **示例**: ```ts // 只修改颜色 - 高性能 pass.setLine({ id: 'my-3d-line', color: new THREE.Color(0xff0000), }); // 修改多个属性 - 高性能 pass.setLine({ id: 'my-3d-line', color: new THREE.Color(0x00ff00), opacity: 0.5, duration: 3000, lineWidth: 5.0, }); // 修改 points (相同数量) - 高性能 pass.setLine({ id: 'my-3d-line', points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(6, 3, 4), new THREE.Vector3(12, 2, 8), ], // 仍是 3 个点 totalLength: 15, }); // 修改 points (不同数量) - 会重建 mesh pass.setLine({ id: 'my-3d-line', points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(10, 5, 5), ], // 从 3 个点变为 2 个点 totalLength: 12, }); ``` **注意事项**: - 如果 `id` 不存在,会在控制台输出警告 - 动画时间(`uTime`)不会被重置,流光继续从当前位置播放 - 建议修改 `points` 时同时更新 `totalLength` ### `render(...)` 由 EffectComposer 自动调用,无需手动调用。自动同步相机矩阵和渲染目标尺寸。 ### `dispose()` 释放 InstancedMesh、着色材质和渲染目标资源,销毁 Pass 时必须调用。 ## Examples ### 快速上手 (Quick Example) ```typescript import { Five } from '@realsee/five'; import { FlowingLight3DPass } from '../../lib/five/renderer/postprocessing'; import * as THREE from 'three'; const five = new Five(); // 创建 3D 流光通道 const flowing3D = new FlowingLight3DPass(five.camera); // 定义一条 3D 路径(世界坐标) const path = [ new THREE.Vector3(-5, 0, 0), new THREE.Vector3(-2, 3, 2), new THREE.Vector3(2, 3, -2), new THREE.Vector3(5, 0, 0), ]; const totalLen = 15; // 世界单位 flowing3D.setLines([{ points: path, totalLength: totalLen, color: new THREE.Color(0x00ff88), opacity: 0.8, duration: 4000, lineWidth: 3.0, }]); five.addPass(flowing3D); function animate() { requestAnimationFrame(animate); five.render(); } animate(); ``` ### 多条 3D 路径 ```typescript const pass = new FlowingLight3DPass(camera); const paths = [ { points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(10, 5, 0), ], totalLength: 11, color: new THREE.Color(0xff0000), duration: 2000, lineWidth: 3.0, }, { points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 10, 5), ], totalLength: 12, color: new THREE.Color(0x00ff00), duration: 2500, startTime: 500, lineWidth: 4.0, }, { points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(5, 0, 10), ], totalLength: 11, color: new THREE.Color(0x0000ff), duration: 2200, startTime: 1000, lineWidth: 2.5, }, ]; pass.setLines(paths); ``` ### 计算 3D 路径长度 ```typescript function calculatePath3DLength(points: THREE.Vector3[]): number { let length = 0; for (let i = 1; i < points.length; i++) { length += points[i].distanceTo(points[i - 1]); } return length; } const points = [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(5, 2, 3), new THREE.Vector3(10, 1, 6), ]; const pathLine = { points: points, totalLength: calculatePath3DLength(points), color: new THREE.Color(0x00ccff), duration: 3000, lineWidth: 3.0, }; ``` ### 结合相机动画 ```typescript // 光带路径随相机移动自动投影 const pass = new FlowingLight3DPass(camera); pass.setLines([{ points: [ new THREE.Vector3(-10, 0, 0), new THREE.Vector3(0, 5, 0), new THREE.Vector3(10, 0, 0), ], totalLength: 20, color: new THREE.Color(0xffff00), duration: 3000, lineWidth: 4.0, }]); // 相机动画过程中光带自动跟随投影 five.setState({ mode: Five.Mode.Model, // ... 其他参数 }); ``` ### 高 DPR 场景优化 ```typescript // InstancedMesh 实现自动优化高 DPR 性能 // 在 DPR=2 的 Retina 屏幕上,性能显著优于全屏着色器方案 const pass = new FlowingLight3DPass(camera); // 多条路径在高 DPR 下仍能保持流畅 const paths = Array.from({ length: 10 }, (_, i) => ({ points: [ new THREE.Vector3(i * 2, 0, 0), new THREE.Vector3(i * 2, 5, 5), ], totalLength: 7, color: new THREE.Color(Math.random() * 0xffffff), duration: 2000 + i * 100, startTime: i * 200, lineWidth: 3.0, })); pass.setLines(paths); ``` ## Debugging - **光带不可见**: 检查路径点是否在相机视锥内;验证 `color` 和 `opacity` 非全透明。 - **光带抖动或消失**: 确认路径在相机背面时自动丢弃(3D 投影的预期行为)。 - **流速不对**: 检查 `totalLength` 计算是否为世界单位;调整 `duration` 以改变流速。 - **位置偏移**: 确认在高 DPR 场景下渲染目标尺寸正确,Pass 会自动同步 `readBuffer` 尺寸。 - **性能问题**: InstancedMesh 实现已优化,但过多路径点仍会影响性能。建议单条路径不超过 20 个点。 ## Performance ### 与全屏着色器对比 - **全屏着色器**: 每帧检查所有像素,DPR=2 时像素数增加 4 倍,性能下降明显 - **InstancedMesh**: 仅渲染线段覆盖区域,DPR=2 时性能影响较小 - **实测**: 在 DPR=2 场景下,InstancedMesh 方案 FPS 提升约 3-5 倍 ### setLine 性能优化 `setLine` 方法根据修改类型自动选择最优更新策略: | 修改类型 | 性能 | 说明 | |---------|------|------| | 只修改颜色/透明度 | ⚡️ 极快 (~100x) | 只更新 instanceColor attribute | | 只修改 duration/startTime | ⚡️ 极快 (~100x) | 只更新 instanceMeta attribute | | 只修改 lineWidth | ⚡️ 极快 (~100x) | 只更新 instanceData attribute | | 修改 points (相同数量) | ⚡️ 快 (~50x) | 只更新 instanceStart/End attributes | | 修改 points (不同数量) | ⚠️ 慢 | 重建整个 mesh | **最佳实践**: ```ts // ✅ 高性能 - 频繁修改颜色 requestAnimationFrame(() => { pass.setLine({ id: lineId, color: new THREE.Color(Math.random(), Math.random(), Math.random()) }); }); // ✅ 高性能 - 动画路径(保持点数) function animatePath(t: number) { pass.setLine({ id: lineId, points: [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(5 + Math.sin(t) * 2, 2, 3), new THREE.Vector3(10, 0, 0), ] // 始终 3 个点 }); } // ⚠️ 低性能 - 避免频繁改变点数 setInterval(() => { const pointCount = Math.floor(Math.random() * 5) + 2; pass.setLine({ id: lineId, points: generateRandomPoints(pointCount) // 点数不固定 }); }, 100); ``` ### 优化建议 1. 减少路径点数量,使用更简化的路径 2. 降低 `lineWidth`,减少渲染区域 3. 控制同时渲染的路径数量 4. 使用 `setLine` 而非 `setLines` 来更新单条线 5. 保持 points 数量不变,只修改坐标 6. 在低端设备上可通过 `pass.enabled = false` 禁用 ## Common Pitfalls 1. **totalLength 单位混淆**: 必须使用世界坐标单位,不能混用屏幕像素或其他单位。 2. **投影失败**: 确保相机矩阵有效,相机在构造时已初始化。 3. **路径在背面**: 相机背后的点自动投影到屏幕外,无需手动处理。 4. **未调用 dispose()**: 销毁 Pass 时必须调用 `dispose()`,避免 GPU 资源泄漏。 5. **DPR 不匹配**: 确保渲染目标尺寸与实际屏幕 DPR 匹配,Pass 会自动处理。 ## Related * [Postprocessing](./postprocessing.md): 后处理通用概念与 EffectComposer 使用指南。 * [Flowing Light 2D Pass](./flowing-light-2d-pass.md): 屏幕空间的流光效果。 * [Flowing Fireworks 3D Pass](./flowing-fireworks-3d-pass.md): 在流光尾部喷射烟花粒子的配套特效。 --- tags: [流光, 光带, 空间轨迹, 模型表面, 特效, postprocessing, effect, flowing, light, pass, rendering, worldspace, projection, instanced, performance, 3d, world-space]