- 计算机视觉
- 图形学
- 深度学习
【免费下载链接】brush
3D Reconstruction for all
Brush 是一个面向全平台的 3D 重建与高斯泼溅(Gaussian Splatting)项目,其桌面与移动端界面由brush-app(基于 egui/eframe 的 Rust 应用)承载。本文介绍的是同一套查看器代码如何通过 WebAssembly 编译到浏览器:brush-app的 egui 查看器在浏览器中以 WASM 形式运行,前端使用 Vite + React 负责页面装配与 URL 参数解析,二者通过wasm-bindgen桥接层(EmbeddedApp)通信。读完本文,你将掌握 WASM 版查看器的本地开发与生产构建流程、全部 URL 参数(数据加载、全屏泼溅模式、相机初始位姿与漫游限制)的用法,以及这些参数在前端解析后最终如何落到 Rust 侧相机控制实现的调用链。
一、架构总览:egui 查看器如何跑进浏览器
整个 web demo 由两部分组成:
- Rust 侧:
apps/brush-app以cdylib形式编译成 WASM(crate 名为brush_app,见 Cargo.toml),把原本运行在桌面/移动端的 egui 界面封装成可供 JavaScript 直接调用的对象; - 前端侧:web/README.md 所在目录
apps/brush-app/web是一个 Vite + React 19 工程,负责创建<canvas>、实例化 WASM 中的EmbeddedApp、把 URL 查询参数翻译成对 Rust 侧的调用。
前端并不绘制任何 3D 内容——所有渲染都在 Rust/egui/wgpu 内部完成,画布由 eframe 的WebRunner接管。three.js之所以出现在依赖中,是因为 Rust 侧wasm.rs直接绑定并读取 THREE.js 的Vector3对象(x/y/zgetter),从而让前端可以复用 three 的向量工具来构造相机参数(见 src/wasm.rs)。React 只负责生命周期与参数传递。
核心桥接类型是 src/wasm.rs 中导出的EmbeddedApp:
new():安装 panic hook,debug 构建下还会把日志转发到浏览器 console;start(canvas):初始化 burn 后端后,在给定<canvas>上启动 eframe 应用;load_url(url)、set_ui_mode(mode)、set_cam_settings(settings)、set_cam_fov(fov)、set_cam_transform(pos, rot)、set_focal_point(point, distance, rot):分别对应数据加载、界面模式与相机控制。
二、快速开始:安装与本地开发
WASM demo 的前端工程依赖 npm,仓库根目录另有一份 workspace 级的 package.json,但 demo 自身的脚本与依赖都声明在 apps/brush-app/web/package.json 中。进入apps/brush-app/web后执行:
npm install npm run dev # localhost:5173 — auto-rebuilds wasm before startingnpm run dev并不是直接启动 Vite:它先执行build:wasm-dev,即用wasm-pack以dev profile编译上一级目录(..,即apps/brush-app)的 Rust 代码,输出到web/pkg,然后再启动 Vite dev server(见 package.json):
"build:wasm-dev": "wasm-pack build .. --dev --target bundler --out-dir web/pkg", "dev": "npm run build:wasm-dev && vite"其中--target bundler表明生成的是面向打包器(Vite)的 ES 模块形式 WASM;dev profile 下 Cargo.toml 还会开启dwarf-debug-info,便于调试。
Vite 侧的配置集中在 vite.config.ts:
- 端口固定为
5173且strictPort: true; server.fs.allow被放宽到上一级目录('..'),否则 Vite 会拒绝读取web/pkg下由wasm-pack生成的 WASM 产物;- 内置
vite-plugin-wasm与vite-plugin-top-level-await,让浏览器可以原生加载并初始化 WASM 模块。
页面骨架在 index.html:#root挂载点、深色背景,入口脚本是 src/main.tsx,它用createRoot渲染App组件。加载 WASM 期间,src/App.tsx 通过React.lazy+<Suspense>展示一个 "Loading Brush WASM..." 的全屏占位。
三、生产构建与部署
npm run build # static build under dist/, basepath /brush-demonpm run build先以release profile重新编译 WASM(build:wasm-release),再执行 Vite 静态构建(见 package.json):
"build": "npm run build:wasm-release && cross-env BRUSH_BASE_PATH=/brush-demo vite build", "preview": "vite preview"两个要点:
BRUSH_BASE_PATH环境变量:构建脚本通过cross-env把它设为/brush-demo,vite.config.ts 据此设置base,使产物的资源路径都以该子路径为前缀。这样部署到类似 GitHub Pages 的子目录时无需硬编码路径;不设置时默认使用/。release WASM 的激进优化:release 构建走
wasm-pack的wasm-opt阶段,使用-Oz --converge并显式开启bulk-memory与nontrapping-float-to-int两个特性(见 apps/brush-app/Cargo.toml),在保证性能的前提下压缩产物体积。
构建产物输出在dist/目录,可用npm run preview本地预览。
四、完整 URL 参数参考
原文档给出的一组 URL 参数全部可选,通过查询字符串传入,例如:
https://your-host/?url=https://example.com/model.ply&fullsplat=true&focal_point=0,0,0&focus_distance=3&speed_scale=0.8| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
url=… | string | 启动时加载的.ply/ 数据集 URL | 无(不自动加载) |
fullsplat=true | bool | 全屏泼溅(嵌入式查看器)模式;旧别名zen=true仍兼容 | false |
focal_point=x,y,z | Vector3 | 相机注视点(世界坐标) | (0,0,0) |
camera_rotation=x,y,z | Vector3 | 相机欧拉角(XYZ 顺序,与 THREE.js 一致) | (0,0,0) |
focus_distance | float | 相机到注视点的距离 | 2.5 |
min_focus_distance | float | 注视距离下界(漫游限制) | 不限制 |
max_focus_distance | float | 注视距离上界(漫游限制) | 不限制 |
speed_scale | float | 相机漫游速度倍率 | 不覆盖(用场景默认) |
参数解析发生在 src/App.tsx,逻辑如下:
url直接读取查询串;fullsplat同时兼容新名fullsplat与旧名zen(大小写不敏感,均为'true'时生效);- 数值参数用
getFloat解析,非法或缺失时返回undefined(保持 Rust 侧默认); - 三维向量参数用
getVector3解析,按逗号切分、逐项parseFloat并去空格,只有恰好 3 个有效分量时才构造THREE.Vector3。
随后这些值被作为 props 传给 src/BrushViewer.tsx,由它调用 WASM 侧 API:加载完成前先new EmbeddedApp()并await app.start(canvas),然后按 props 变化依次执行app.load_url(url)、app.set_ui_mode(...)、app.set_cam_settings(new CameraSettings(...))与app.set_focal_point(focalPoint, focusDistance, cameraRotation)。
五、参数背后的 Rust 实现:从 URL 到相机位姿
5.1 注视点与相机位姿
set_focal_point是最值得展开的参数之一。前端传入focal_point、focus_distance、camera_rotation三个值后,WASM 绑定层把它们组装成Quat与Vec3,最终在 src/ui/ui_process.rs 中完成真正的数学计算:
fn set_focal_point(&mut self, focal_point: Vec3, focus_distance: f32, rotation: Quat) { let position = focal_point - rotation * Vec3::Z * focus_distance; self.set_camera_transform(position, rotation); self.controls.focus_distance = focus_distance; }即相机位置由focal_point - 旋转后的 -Z 方向 × focus_distance反推得到,这样前端无需同时给出位置与朝向,只要指定"看哪里 + 离多远 + 朝向",即可唯一定位相机。注意 src/wasm.rs 中的欧拉角使用EulerRot::XYZ顺序构造四元数,并注释明确说明"与 THREE.js 默认顺序一致",因此 URL 里写的camera_rotation语义与 three.js 的欧拉角一致。
5.2 漫游限制与速度
min_focus_distance/max_focus_distance属于CameraClamping(注视距离上下界),speed_scale属于speed_scale字段,二者共同组成 Rust 侧的CameraSettings结构体(见 src/ui/app.rs)。该结构体还包含未在 URL 中暴露但可用于二次开发的字段:splat_scale、background(背景色)、grid_enabled(网格开关)以及俯仰/偏航角限制min_pitch/max_pitch/min_yaw/max_yaw(见 src/ui/camera_controls.rs)。WASM 绑定层的CameraSettings构造函数完整接受这些可选项(见 src/wasm.rs),如果你要扩展 URL 参数,把对应字段从App.tsx传进构造函数即可。
关于速度还有一个值得注意的实现细节:CameraController的注释指出漫游速度会按场景的世界尺度(scene_scale)缩放,"一个房间和一条街区穿越耗时大致相同"(见 src/ui/camera_controls.rs),所以speed_scale本质上是一个相对倍率,而不是绝对速度。
5.3 全屏泼溅 / 嵌入式查看器模式
fullsplat=true(或旧别名zen=true)在前端被翻译为UiMode.FullScreenSplat。UiMode枚举定义在 src/ui/mod.rs,共有三态:
Default:带数据加载、统计等面板的默认 UI;FullScreenSplat:全屏展示泼溅,但保留切换与控制面板;EmbeddedViewer:纯粹的嵌入渲染,不渲染任何其他内容。
App.tsx只把fullsplat映射到FullScreenSplat;需要更纯粹的嵌入行为时,可在前端直接调用app.set_ui_mode(UiMode.EmbeddedViewer)。
5.4 数据加载链路
url=…参数最终走到 src/wasm.rs 的load_url:它通过brush_process::create_process(DataSource::Url(url), ...)创建一个数据加载进程,并交给应用的上下文connect_to_process。也就是说,浏览器端的数据加载复用了brush-processcrate 的进程模型,.ply文件与完整数据集(如 NerfStudio 导出的场景)均通过该入口读取,而不仅是简单的文件解析。
六、常见问题与排查要点
以下要点均可从当前仓库源码确认:
- WASM 启动失败时的报错展示:BrushViewer.tsx 捕获
start()的异常并展示在画布区域,注意注释指出 eframe/wasm 的错误常常是原始JsValue(字符串)而非Error实例,因此代码同时处理了Error、字符串与兜底String(err)三种形态。 - 随机数后端:WASM 目标需要显式启用
getrandom的wasm_js后端(0.4 与 0.3 两个版本都要开,后者经由ahash→burn-core传递引入),否则某些依赖随机数的 burn 初始化会在浏览器中失败,见 apps/brush-app/Cargo.toml。 - Vite 读不到 pkg 目录:如果 dev server 报文件系统权限错误,检查 vite.config.ts 的
server.fs.allow是否包含'..'。 - 部署子路径资源 404:构建时必须设置与目标部署路径一致的
BRUSH_BASE_PATH(默认脚本是/brush-demo),否则dist/内资源路径会指向错误根目录。
七、小结
apps/brush-app/web是 Brush 全平台故事的浏览器一角:Rust 侧EmbeddedApp把 eframe/egui 的 3D 查看器完整编译进 WASM,前端 Vite + React 只负责画布、生命周期与 URL 参数翻译。本文覆盖的三条命令(npm install/npm run dev/npm run build)与全部可选 URL 参数均可直接落地使用;在此基础上,set_focal_point的注视点反推公式、CameraClamping的漫游限制、UiMode的三态设计以及wasm-pack的 release 优化配置,为你在嵌入式页面、展示链接或自定义相机入场动画等场景中做二次开发提供了源码级的参考。如需继续深入,可依次阅读 apps/brush-app/web/src/BrushViewer.tsx、apps/brush-app/src/wasm.rs 与 apps/brush-app/src/ui/ui_process.rs。
- 计算机视觉
- 图形学
- 深度学习
【免费下载链接】brush
3D Reconstruction for all
相关推荐
3D高斯泼溅技术终极指南:如何在浏览器中实现实时3D渲染
3D高斯泼溅技术终极指南:如何在浏览器中实现实时3D渲染 3D高斯泼溅技术正在彻底改变我们体验3D内容的方式!GaussianSplats3D是一个基于Thre
3D渲染图形学5个关键步骤:在浏览器中实现百万级3D高斯泼溅渲染
5个关键步骤:在浏览器中实现百万级3D高斯泼溅渲染 想要在浏览器中流畅渲染百万级3D点云数据?3D高斯泼溅技术正是你需要的解决方案。这个基于Three.js的创
3D渲染图形学3D高斯泼溅终极指南:在浏览器中实现百万级点云实时渲染
3D高斯泼溅终极指南:在浏览器中实现百万级点云实时渲染 你是否曾在浏览器中渲染大型3D点云时遇到性能瓶颈?当数据量达到百万级别时,传统WebGL渲染往往在帧率和
3D渲染图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考