如视 Five SDK
    Preparing search index...

    Flowing Light 2D Pass (流光 2D 通道)

    • Summary: FlowingLight2DPass 提供屏幕空间的流光(光带)特效,使用 InstancedMesh 实例化渲染优化性能,沿着 2D 归一化坐标路径绘制动态光流,适用于突出导航路径、指示线或动态路径演示。
    • Schema: FlowingLight2DPass 类及 Line 数据结构。
    • Concepts: 后处理通道、屏幕空间光带、InstancedMesh 实例化渲染、归一化坐标系统、头尾追逐动画。
    • Configuration: 归一化路径点、颜色、不透明度、线宽、动画时长、延迟。
    • Examples: 基础集成、多条路径、动态更新、高 DPR 场景优化。

    Definition: FlowingLight2DPass

    FlowingLight2DPassFivePass 的子类,核心接口如下:

    import * as THREE from 'three';
    import { FivePass } from './pass';
    import { type Camera } from '../../../core/camera';

    type Line = {
    id?: string; // 可选的唯一标识符,不传则自动生成 UUID
    points: THREE.Vector2[]; // 归一化坐标路径点 [0, 1]
    totalLength: number; // 路径总长度(归一化单位)
    color: THREE.Color; // 光带颜色
    opacity?: number; // 光带不透明度(0-1)
    duration?: number; // 动画时长(毫秒)
    delay?: number; // 动画延迟(毫秒)
    lineWidth?: number; // 线宽(归一化单位)
    tailLengthRatio?: number; // 尾巴长度比例(0-1),默认 0.2
    };

    export class FlowingLight2DPass extends FivePass {
    constructor(camera: Camera);

    // 设置要渲染的路径列表
    public setLines(lines: Line[]): void;

    // 修改单条线的参数(通过 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;
    }

    为了优化高 DPR(Device Pixel Ratio)场景下的性能,该 Pass 使用 InstancedMesh 代替全屏着色器。每个线段作为一个实例,只渲染线段覆盖的区域,避免了对每个像素的全屏检查,显著提升了渲染效率。

    路径点使用归一化坐标 [0, 1] 定义:

    • (0, 0) 表示屏幕左上角
    • (1, 1) 表示屏幕右下角
    • (0.5, 0.5) 表示屏幕中心

    这种坐标系统与屏幕分辨率无关,自动适配不同尺寸和 DPR。

    流光效果采用"头尾追逐"模式:

    • 头部(Head): 光流的前端,沿路径循环移动
    • 尾部(Tail): 光流的后端,跟随头部移动
    • 渐变: 从尾部(透明度 0)到头部(透明度 1)线性渐变
    • 尾长: 尾部长度由 tailLengthRatio 参数控制(默认 0.2,即路径总长的 20%)

    光头在路径上循环流动,通过像素空间投影计算每个像素的渐变位置,确保渐变效果准确。尾巴长度会在动画开始时逐渐增长,直到达到 tailLengthRatio * totalLength

    内部实现中,归一化坐标 [0, 1] 会转换为 NDC(Normalized Device Coordinates) [-1, 1],与 WebGL 标准坐标系统一致,确保投影计算准确。

    自动处理高 DPR 场景和不同屏幕宽高比:

    • DPR 补偿: 通过 uPixelRatio uniform 自动补偿高 DPR 设备的光线宽度,确保在 Retina 等高分辨率屏幕上视觉一致
    • 像素空间计算: 所有距离计算在像素空间进行,而非 NDC 空间,确保横向和纵向光线宽度在任何宽高比下都保持一致
    • 各向同性渲染: 无论屏幕是横屏、竖屏还是方形,光线的扩散效果在各个方向上都保持相同
    Parameter Type Required Default Description
    id string 自动生成 UUID 唯一标识符,用于 setLine 方法更新单条线。
    points THREE.Vector2[] - 归一化坐标路径点数组 [0, 1]。至少 2 个点。
    totalLength number - 路径总长度(归一化单位)。通常为所有相邻点间距离之和。
    color THREE.Color - 光带颜色(RGB)。
    opacity number 1.0 光带不透明度(0-1)。控制与背景的混合强度。
    duration number 1000 动画周期(毫秒)。光头从路径起点完成一个周期的时长。
    delay number 0 动画延迟(毫秒)。延迟后才开始播放动画。
    lineWidth number 0.01 线宽(归一化单位)。控制光带在屏幕上的宽度。
    tailLengthRatio number 0.2 尾巴长度比例(0-1)。控制流光尾巴相对于路径总长的比例。

    注意: totalLengthlineWidth 使用归一化单位,与屏幕实际像素无关。

    初始化 Pass,需传入相机以获取屏幕分辨率。

    更新要渲染的路径列表。可随时调用以修改或新增路径。

    自动生成 ID: 如果 line 没有提供 id,会自动生成 UUID。

    示例:

    const pathLine: Line = {
    id: 'my-line-1', // 可选,不传则自动生成
    points: [
    new THREE.Vector2(0.3, 0.7),
    new THREE.Vector2(0.7, 0.7),
    new THREE.Vector2(0.7, 0.3),
    ],
    totalLength: 0.8, // 归一化单位
    color: new THREE.Color(0x00c2ff),
    opacity: 0.8,
    duration: 2000,
    delay: 500,
    lineWidth: 0.01,
    };
    pass.setLines([pathLine]);

    通过 id 修改单条线的参数。支持部分更新,只更新传入的字段。

    性能优化:

    • 如果 points 数量不变,只更新 GPU 的 instance attributes,性能极高
    • 如果 points 数量改变,会重建整个 mesh,性能开销较大

    示例:

    // 只修改颜色 - 高性能
    pass.setLine({
    id: 'my-line-1',
    color: new THREE.Color(0xff0000),
    });

    // 修改多个属性 - 高性能
    pass.setLine({
    id: 'my-line-1',
    color: new THREE.Color(0x00ff00),
    opacity: 0.5,
    duration: 3000,
    });

    // 修改 points (相同数量) - 高性能
    pass.setLine({
    id: 'my-line-1',
    points: [
    new THREE.Vector2(0.2, 0.2),
    new THREE.Vector2(0.8, 0.8),
    new THREE.Vector2(0.8, 0.2),
    ], // 仍是 3 个点
    totalLength: 1.2,
    });

    // 修改 points (不同数量) - 会重建 mesh
    pass.setLine({
    id: 'my-line-1',
    points: [
    new THREE.Vector2(0.2, 0.2),
    new THREE.Vector2(0.8, 0.8),
    ], // 从 3 个点变为 2 个点
    totalLength: 0.85,
    });

    注意事项:

    • 如果 id 不存在,会在控制台输出警告
    • 动画时间(uTime)不会被重置,流光继续从当前位置播放
    • 建议修改 points 时同时更新 totalLength

    由 EffectComposer 自动调用,无需手动调用。自动同步渲染目标尺寸。

    释放 InstancedMesh、着色材质和渲染目标资源,避免内存泄漏。销毁 Pass 时必须调用。

    import { Five } from '@realsee/five';
    import { FlowingLight2DPass } from '../../lib/five/renderer/postprocessing';
    import * as THREE from 'three';

    const five = new Five();

    // 创建 2D 流光通道
    const flowing2D = new FlowingLight2DPass(five.camera);

    // 定义一条屏幕空间的路径(归一化坐标)
    const path = [
    new THREE.Vector2(0.2, 0.2),
    new THREE.Vector2(0.5, 0.8),
    new THREE.Vector2(0.8, 0.2),
    ];
    const totalLen = 1.2; // 归一化单位

    flowing2D.setLines([{
    points: path,
    totalLength: totalLen,
    color: new THREE.Color(0x00ff00),
    opacity: 0.9,
    duration: 2000,
    lineWidth: 0.01,
    }]);

    five.addPass(flowing2D);

    function animate() {
    requestAnimationFrame(animate);
    five.render();
    }
    animate();
    const pass = new FlowingLight2DPass(five.camera);

    // 初始路径集合
    const paths = [
    {
    points: [
    new THREE.Vector2(0.1, 0.1),
    new THREE.Vector2(0.9, 0.1),
    ],
    totalLength: 0.8,
    color: new THREE.Color(0xff6600),
    duration: 1500,
    lineWidth: 0.01,
    },
    {
    points: [
    new THREE.Vector2(0.1, 0.5),
    new THREE.Vector2(0.9, 0.5),
    ],
    totalLength: 0.8,
    color: new THREE.Color(0x0066ff),
    opacity: 0.7,
    duration: 2000,
    delay: 500,
    lineWidth: 0.015,
    },
    ];

    pass.setLines(paths);
    five.addPass(pass);

    // 响应用户交互动态更新路径
    document.addEventListener('click', (e) => {
    const rect = five.canvas.getBoundingClientRect();
    const x = (e.clientX - rect.left) / rect.width;
    const y = (e.clientY - rect.top) / rect.height; // 左上角为原点,无需翻转

    const newPath = {
    points: [
    new THREE.Vector2(0.5, 0.5),
    new THREE.Vector2(x, y),
    ],
    totalLength: Math.hypot(x - 0.5, y - 0.5),
    color: new THREE.Color(Math.random() * 0xffffff),
    duration: 1200,
    lineWidth: 0.01,
    };
    pass.setLines([...paths, newPath]);
    });
    function calculatePathLength(points: THREE.Vector2[]): 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.Vector2(0.3, 0.7),
    new THREE.Vector2(0.7, 0.7),
    new THREE.Vector2(0.7, 0.3),
    new THREE.Vector2(0.3, 0.3),
    new THREE.Vector2(0.3, 0.7),
    ];

    const pathLine = {
    points: points,
    totalLength: calculatePathLength(points),
    color: new THREE.Color(0x00ffff),
    duration: 2000,
    lineWidth: 0.01,
    };
    const pass = new FlowingLight2DPass(five.camera);

    // 创建一个矩形路径(注意:左上角为原点)
    const rectPath = {
    points: [
    new THREE.Vector2(0.3, 0.3), // 左上
    new THREE.Vector2(0.7, 0.3), // 右上
    new THREE.Vector2(0.7, 0.7), // 右下
    new THREE.Vector2(0.3, 0.7), // 左下
    new THREE.Vector2(0.3, 0.3), // 闭合路径
    ],
    totalLength: 1.6, // 矩形周长
    color: new THREE.Color(1.0, 1.0, 1.0),
    opacity: 1.0,
    duration: 4000,
    delay: 2000,
    lineWidth: 0.01,
    };

    pass.setLines([rectPath]);
    // InstancedMesh 实现自动优化高 DPR 性能
    // 在 DPR=2 的 Retina 屏幕上,性能显著优于全屏着色器方案

    const pass = new FlowingLight2DPass(camera);

    // 多条路径在高 DPR 下仍能保持流畅
    const paths = Array.from({ length: 10 }, (_, i) => ({
    points: [
    new THREE.Vector2(0.1, 0.1 + i * 0.08),
    new THREE.Vector2(0.9, 0.1 + i * 0.08),
    ],
    totalLength: 0.8,
    color: new THREE.Color(Math.random() * 0xffffff),
    duration: 2000 + i * 100,
    delay: i * 200,
    lineWidth: 0.01,
    }));

    pass.setLines(paths);
    • 光带不出现: 检查 coloropacity,确保非全透明;验证 points 至少 2 个;确认坐标在 [0, 1] 范围内。
    • 流速不对: 检查 totalLength 计算是否准确;若流速过快/过慢,调整 duration
    • 位置偏移: 确认坐标系统使用归一化坐标 [0, 1],不是像素坐标;注意坐标系统以左上角为原点,Y 轴方向为 0(上) 到 1(下)。
    • 性能问题: InstancedMesh 实现已优化,但过多路径点仍会影响性能。建议单条路径不超过 20 个点。
    • 全屏着色器: 每帧检查所有像素,DPR=2 时像素数增加 4 倍,性能下降明显
    • InstancedMesh: 仅渲染线段覆盖区域,DPR=2 时性能影响较小
    • 实测: 在 DPR=2 场景下,InstancedMesh 方案 FPS 提升约 3-5 倍

    setLine 方法根据修改类型自动选择最优更新策略:

    修改类型 性能 说明
    只修改颜色/透明度 ⚡️ 极快 (~100x) 只更新 instanceColor attribute
    只修改 duration/delay ⚡️ 极快 (~100x) 只更新 instanceMeta attribute
    只修改 lineWidth ⚡️ 极快 (~100x) 只更新 instanceData attribute
    修改 points (相同数量) ⚡️ 快 (~50x) 只更新 instanceStart/End attributes
    修改 points (不同数量) ⚠️ 慢 重建整个 mesh

    最佳实践:

    // ✅ 高性能 - 频繁修改颜色
    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.Vector2(0.1, 0.1),
    new THREE.Vector2(0.5 + Math.sin(t) * 0.2, 0.5),
    new THREE.Vector2(0.9, 0.9),
    ] // 始终 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 禁用
    1. 坐标系统混淆: 必须使用归一化坐标 [0, 1],不能使用像素坐标。坐标系统以左上角为原点,Y 轴方向为 0(上) 到 1(下)。
    2. totalLength 计算错误: 光流速度直接依赖 totalLength。手动计算时务必精确,使用归一化单位。
    3. lineWidth 过大: 归一化单位下,0.01 约为屏幕高度的 1%。过大的值会导致光带过宽。
    4. 未及时 dispose(): 移除 Pass 时必须调用 dispose(),否则 GPU 资源泄漏。
    5. DPR 不匹配: 确保渲染目标尺寸与实际屏幕 DPR 匹配,Pass 会自动处理。

    tags: [流光, 光带, 导航路径, 指示线, 动态路径, 特效, postprocessing, effect, flowing, light, pass, rendering, screenspace, instanced, performance, 2d, screen-space]