跳转至

1. 核心基础 (Foundations)

Visionary 的运行依赖 WebGPU 环境ONNX Runtime (ORT)。本章节介绍如何正确初始化环境并加载资源,构建基础的渲染管线。

1.1 环境初始化

1.1.1 配置 ONNX Runtime (ORT)

Visionary 使用 onnxruntime-web (ORT) 来处理 Gaussian Splatting 的解码及后处理。由于 ORT 依赖 .wasm 文件(如 ort-wasm.wasm, ort-wasm-simd.wasm)进行高性能计算,必须在加载模型前明确指定这些静态资源的路径。

接口来源: src/config/ort-config

/**
 * 初始化 ONNX Runtime 环境配置
 * @param wasmPaths - (可选) WASM 文件的存放路径。默认为 '/src/ort/'。
 *                    可以是单个字符串路径,也可以是路径数组。
 */
function initOrtEnvironment(wasmPaths?: string | string[]): void

使用示例: 建议在应用入口(如 main.ts 或组件 mounted 阶段)调用:

import { initOrtEnvironment } from 'src/config/ort-config';

// 1. 定义静态资源目录 (构建后需确保该目录包含 ort-wasm.wasm 等文件)
const wasmPath = '/static/ort/'; 

// 2. 初始化环境
initOrtEnvironment(wasmPath);

特性说明: initOrtEnvironment 具有容错性。如果调用时 window.ort 尚未加载,它会启动轮询机制(每 50ms 检查一次),直到 ORT 就绪后自动应用配置。


1.1.2 创建 WebGPU 渲染上下文

initThreeContext 是启动引擎的核心。它负责初始化 THREE.WebGPURenderer,并同步拉起底层的 WebGPU Device,使其在渲染引擎和推理引擎之间共享。

接口来源: src/app/three-context

⚠️ 关键前置条件 (Prerequisites): 项目 public 目录下必须存在文件 /models/onnx_dummy.onnx。这是一个极小的 ONNX 文件,用于触发 ORT WebGPU 后端的初始化流程。

/**
 * 初始化 WebGPU 渲染器
 * @param canvasElement - 用于渲染的 Canvas DOM 元素
 * @returns Promise<THREE.WebGPURenderer | null>
 */
async function initThreeContext(canvasElement: HTMLCanvasElement): Promise<THREE.WebGPURenderer | null>

默认配置行为: * Device 共享: 强制共享 gpu.devicegpu.context,实现渲染引擎和推理引擎的零拷贝交互。 * 性能模式: adapterPowerPreference 设为 'high-performance'。 * 清除颜色: 默认为灰色 (#808080),建议初始化后按需修改。 * 像素比率: 自动限制最大为 2。

使用示例:

import { initThreeContext } from 'src/app/three-context';

const canvas = document.getElementById('canvas') as HTMLCanvasElement;

try {
    const renderer = await initThreeContext(canvas);

    if (!renderer) {
        throw new Error("WebGPU 初始化失败,请检查浏览器兼容性或 onnx_dummy.onnx 路径");
    }

    // 设置透明背景以便与 UI 融合
    renderer.setClearColor(0x000000, 0);

    // 开始渲染循环...
} catch (e) {
    console.error("Context Error:", e);
}

1.2 统一资源加载 (Unified Asset Loading)

Visionary 封装了智能加载器 loadUnifiedModel,支持自动识别并加载 Mesh 和 Gaussian Splatting 模型。

接口来源: src/app/unified-model-loader

1.2.1 核心接口定义

// 支持的模型类型字符串
type ModelType = 'ply' | 'onnx' | 'gltf' | 'glb' | 'obj' | 'fbx' | 'gaussian';

interface UnifiedLoadOptions {
    /** 显式指定类型。若不指定,将通过后缀或文件头嗅探自动检测 */
    type?: ModelType;

    /** 模型名称 */
    name?: string;

    /** Gaussian 模型标记 */
    isGaussian?: boolean;

    /** [Gaussian专用] 初始相机视图矩阵 (用于初始粒子排序) */
    cameraMatrix?: Float32Array;

    /** [Gaussian专用] 初始投影矩阵 */
    projectionMatrix?: Float32Array;

    /** 进度回调 (0.0 - 1.0) */
    onProgress?: (progress: number) => void;
}

interface LoadResult {
    /** 
     * 加载完成的模型对象列表。
     * - 普通模型: Mesh 或 Group
     * - Gaussian: 包含 Splat 数据的代理 Object3D
     */
    models: THREE.Object3D[];

    /** 
     * [关键] Gaussian 专用渲染器实例。
     * 仅当加载内容包含 Gaussian Splatting 时存在。
     */
    gaussianRenderer?: GaussianThreeJSRenderer;

    info: {
        type: ModelType;
        name: string;
        isGaussian: boolean;
    };
}

1.2.2 智能检测逻辑

加载器内置了针对 .ply 文件的二义性检测机制: 1. 后缀检测: .compressed.ply, .splat, .onnx 等直接视为 Gaussian。 2. 内容嗅探 (Sniffing): * 本地文件: 读取前 4KB,检查是否包含 rot_0, scale_0 等特征字段。 * 在线 URL: 发起 Range: bytes=0-4095 请求预读取文件头,避免下载大文件即可判断类型。

1.2.3 使用示例

场景 A: 加载网格 3D 模型

加载器会将网格 3D 模型(如 .glb, .gltf, .fbx, .obj)解析为标准的 THREE.Object3D(通常是 THREE.MeshTHREE.Group),并自动添加到场景中。

import { loadUnifiedModel } from 'src/app/unified-model-loader';

const result = await loadUnifiedModel(renderer, scene, '/models/your_model.glb');
const mesh = result.models[0]; 
mesh.position.set(0, 0, 0);

场景 B: 加载 Gaussian Splatting

加载 Gaussian 模型时,建议传入当前相机矩阵以保证首帧排序正确。

import { loadUnifiedModel } from 'src/app/unified-model-loader';

// 1. 准备矩阵
camera.updateMatrixWorld();
const camMat = new Float32Array(camera.matrixWorldInverse.elements);
const projMat = new Float32Array(camera.projectionMatrix.elements);

// 2. 加载模型
const result = await loadUnifiedModel(renderer, scene, '/models/splat_model.ply', {
    cameraMatrix: camMat,
    projectionMatrix: projMat,
    name: 'MySplat'
});

// 3. 处理渲染器引用
if (result.gaussianRenderer) {
    // 必须保存此引用,在动画循环中调用其 drawSplats 方法
    this.gaussianThreeJSRenderer = result.gaussianRenderer;

    // 如果是首个模型,通常会自动 init;若是追加模型,需注意渲染器复用逻辑
    console.log('Gaussian Renderer Ready:', this.gaussianThreeJSRenderer.uuid);
}

1.2.4 高斯渲染器管理与复用 (Renderer Management)

与普通的 Mesh 模型不同,Gaussian Splatting 模型需要通过 GaussianThreeJSRenderer 进行特殊的排序与光栅化。该渲染器占用大量 GPU 资源(缓冲区、Worker 线程)。

loadUnifiedModel 的设计原则是无状态的——它每次被调用都会返回一个新的 gaussianRenderer 实例。

为了避免性能崩溃,请不要在场景中同时运行多个 Gaussian 渲染器,而是在应用层实现“复用逻辑”:

  1. 首个模型:保存加载器返回的渲染器实例。
  2. 后续模型:将新模型追加到已有的渲染器中,并丢弃加载器返回的多余渲染器。
import { loadUnifiedModel } from 'src/app/unified-model-loader';
import { GaussianThreeJSRenderer } from 'src/app/GaussianThreeJSRenderer';

// 定义一个类成员变量来持有唯一的渲染器引用
private activeGaussianRenderer: GaussianThreeJSRenderer | null = null;

async function handleModelLoad(url: string) {
    const result = await loadUnifiedModel(renderer, scene, url);

    // 1. 处理普通 Mesh 模型
    // ...

    // 2. 处理 Gaussian 模型
    if (result.gaussianRenderer && result.models.length > 0) {
        const newSplatModel = result.models[0];

        if (!this.activeGaussianRenderer) {
            // [场景 A: 初始化] 
            // 当前没有活跃的渲染器,直接使用加载器返回的实例
            this.activeGaussianRenderer = result.gaussianRenderer;

        } else {
            // [场景 B: 追加] 
            // 场景中已有渲染器,将新模型“合并”进去
            this.activeGaussianRenderer.appendGaussianModel(newSplatModel);

            // 关键:销毁加载器创建的那个多余的临时渲染器,释放 WebGPU 资源
            // (假设渲染器有 dispose 方法,如果没有,则忽略引用让 GC 回收)
            if (typeof result.gaussianRenderer.dispose === 'function') {
                result.gaussianRenderer.dispose();
            }
        }
    }
}

模块索引

  • Config 模块 (17-config) – 对应章节 1.1.1 中的 ORT 环境初始化,详见 概览API 参考
  • App 模块 (01-app) – 提供 initThreeContextloadUnifiedModel 等应用级入口(章节 1.1.2、1.2),参阅 概览API 参考
  • Three.js Integration 模块 (12-three-integration) – 负责 GaussianThreeJSRenderer 及其复用策略(章节 1.2.4),可查阅 概览架构