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.device 和 gpu.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.Mesh 或 THREE.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 渲染器,而是在应用层实现“复用逻辑”:
- 首个模型:保存加载器返回的渲染器实例。
- 后续模型:将新模型追加到已有的渲染器中,并丢弃加载器返回的多余渲染器。
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();
}
}
}
}