# gauss-viewer **Repository Path**: simplifer/gauss-viewer ## Basic Information - **Project Name**: gauss-viewer - **Description**: 3dgs 渲染,基于supersplat - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-06-24 - **Last Updated**: 2026-07-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GSplat Viewer 桌面端 3D Gaussian Splat 编辑器,基于 [PlayCanvas SuperSplat](https://github.com/playcanvas/supersplat) (MIT) 改写,针对**大文件加载**与**可视化体验**做了深度优化。 > 当前版本:**v1.1.9** --- ## ✨ 核心特性 ### 📦 支持的文件格式 | 格式 | 扩展名 | 加载 | 导出 | 备注 | |------|--------|------|------|------| | PLY | `.ply` / `.compressed.ply` | ✅ | ✅ | PlayCanvas KSplat 中间格式 | | SPLAT | `.splat` | ✅ | ✅ | 32 字节/splat,Inria 抗锯齿格式 | | KSplat | `.ksplat` | ✅ | ❌ | PlayCanvas 高效格式 | | **SPZ v2** | `.spz` | ✅ | ✅ | first-three 四元数(3 bytes) | | **SPZ v3** | `.spz` | ✅ | ✅ | smallest-three 四元数(4 bytes),含 gzip 包装 | | **SPZ v4** | `.spz` | ✅ | ✅ | TOC + per-stream zstd(6 streams) | | SOG | `.sog` | ✅ | ✅ | WebP 纹理 + meta.json | | **LCC** | `.lcc` + `.bin` | ✅ | ❌ | XGRIDS LCC splat container | | **LCC2** | `.lcc2` + `.sog`/`.spz` | ✅ | ❌ | XGRIDS LCC2 octree splat container | | SuperSplat | `.ssproj` | ✅ | ✅ | 项目文档 | ### 🚀 大文件加载优化(v1.0.28+) | 场景 | 默认 | 实际验证 | |------|------|----------| | 1.07 GB `.splat` | ❌ OOM | ✅ 流畅(Blob 流式 + 4MB 块) | | 585 MB `.spz` v4 | ❌ OOM | ✅ 流畅(per-stream zstd) | | 570 MB `.spz` v3 | ❌ 解码错 | ✅ 流畅(smallest-three + gzip) | | 35 M 高斯点 | 内存爆炸 | ✅ 16 GB V8 堆上限 | **核心机制:** 1. **Blob 流式传输** — 大文件(≥ 256 MB)通过 `Blob.slice()` 分块传给 Worker,避免主线程一次分配 1 GB+ ArrayBuffer 触发 `RangeError` 2. **.splat 自实现解析器** — 4 MB 块边界跨越、rolling buffer、14 float 列直接写出,绕过 splat-transform 的 `readAll()` 预分配陷阱 3. **SPZ 自我实现解析器** — v2/v3 直接按 numPoints 计算块大小,v4 读 TOC + per-stream zstd 解压 4. **V8 堆自动调节** — 默认 `50% 系统内存`,clamp 到 `[4 GB, 24 GB]`,env 变量可覆盖 ### 🎨 可视化增强(v1.1.x) #### 点云 ↔ 高斯 Splat 渲染模式切换 借鉴 [Reall3dViewer](https://github.com/reall3d-com/Reall3dViewer)(MIT)的过渡机制,单击工具栏图标或按 `K` 键在两种渲染模式间切换,并伴随**4 种过渡动画**: | 效果 | 视觉 | 适用场景 | |------|------|----------| | 模型中心光圈 | 白色/自定义色环从模型中心向外扩散 | 小到中型场景 | | 屏幕中心光圈 | 环从屏幕中心向边缘扩散 | 任意 | | 中央向左右擦除 | 屏幕中央分裂向左右两侧推开 | 任意 | | 中央向上下擦除 | 屏幕中央分裂向上下两侧推开 | 任意 | **4 种过渡效果全部上线了可见光圈环带**(v1.1.9 之前的屏幕变体只是二元切换)。 **核心机制:** - **Shader chunk 注入** — 通过 PlayCanvas 的 `material.shaderChunks.glsl.set('gsplatVS', ...)` 覆盖默认 vertex shader - **JS 单 rAF 驱动** — `src/splat-render-mode-controller.ts` 每个 frame 把 `currentLightRadius` / `pointMode` / `irisColor` 等 uniform 推到 GPU - **自适应动画时长**(v1.1.8) — 动画总时长 ≈ 1.2 秒(按 60fps 折算 72 帧),无论 maxRadius=1 还是 100 都跑同样久 - **自适应环带厚度**(v1.1.8) — controller 在每次 `startIrisTransition` 时按 `maxRadius` 重新计算 `irisBandRatio` 并推 GPU: | maxRadius | bandRatio | 场景 | |---|---|---| | < 5 | 0.30 | 桌面小物体(粗环) | | < 20 | 0.20 | 人物 / 雕塑 | | < 100 | 0.10 | 室内 / 中型建筑 | | ≥ 100 | 0.05 | 大型室外 | - **Iris 环颜色自定义**(v1.1.4) — view-panel 中 ColorPicker 选任意 RGBA 颜色持久化到 electron-store(key=`irisColor`),默认白色 0.4 alpha - **首次加载点云生长** — 新 splat 进入场景时自动从中心生长点云 → iris 翻转到高斯 #### 3DGS 数据处理(v1.1.x) - **Process 对话框**:选中 splat → Tools 菜单 → Process,可执行 - **删除浮动点**(floaters)— 移除离群孤立 splat - **孔洞填补**(hole-fill)— 在选中区域内加密 - **密度提升**(densify)— 增加局部点数 - **并行处理**(v1.1.0)— Worker 池多 splat 并行 Process,避免阻塞 UI #### 快捷键 | 键 | 功能 | |------|------| | `K` | 切换点云 / 高斯渲染模式 | | `= / -` | 调整点云 disc 大小(0.1 ~ 50,步长 0.5) | | `F` | 框选 / 重置视角 | | `M` | 切换控制模式 | | `V` | 切换飞行 / 环绕 | | `Tab` | 切换点云/高斯 overlay | | `H` | 隐藏选择(`Shift+H` 取消隐藏) | | `D` | 删除选中 | | `A` | 全选(`Ctrl+Shift+A` 反选) | ### 🛠️ 开发与调试 - **DevTools 自动启动**:环境变量 `SPLAT_VIEWER_DEVTOOLS=1` 或 View 菜单 → Toggle Developer Tools(F12),偏好跨会话持久化 - **HEAP 限额覆盖**:`SPLAT_VIEWER_HEAP_MB=8192 electron .` - **自动更新**:通过 Gitee Releases + `electron-updater`(`generic` provider) - **CDP 远程调试**:`electron . --remote-debugging-port=9229` 启动后用 Chrome DevTools 连接 `chrome://inspect` --- ## 🚀 快速开始 ### 环境要求 - Node.js **>= 20.19.0** - npm >= 10 - Windows 10/11(其它平台 CI 未覆盖,但代码本身跨平台) ### 安装与构建 ```bash # 1) 安装依赖 npm install # 2) 开发模式:rollup 监听 + Electron 自动启动 + DevTools 自动打开 SPLAT_VIEWER_DEVTOOLS=1 npm run dev # 3) 生产构建 npm run build # 4) 启动(需先 build) npm start # 5) 打包 npm run dist:portable # 便携版(单 exe) npm run dist:win # NSIS 安装包 + 便携版 ``` 产物路径:`release/GSplat Viewer 1.1.9.exe` --- ## ⚙️ 配置与调试 ### 环境变量 | 变量 | 用途 | 默认 | |------|------|------| | `SPLAT_VIEWER_HEAP_MB` | V8 堆上限(MB) | 50% RAM,clamp [4 GB, 24 GB] | | `SPLAT_VIEWER_DEVTOOLS` | 启动时自动打开 DevTools | `0`(关闭) | | `ELECTRON_DEV` | 启用 dev 模式(preload 热重载) | `0` | | `ELECTRON_DEV_URL` | 从远程 URL 加载(hot reload 配合远程 rollup) | 未设 | | `CRASH_REPORT_URL` | 启用 crash reporter | 未设 | ### V8 堆限额算法 ```ts // electron/main.ts const HEAP_MIN_MB = 4 * 1024; // 4 GB 下限 const HEAP_MAX_MB = 24 * 1024; // 24 GB 上限 const HEAP_RATIO = 0.5; // 默认占系统内存 50% const resolveHeapMb = () => { const envValue = process.env.SPLAT_VIEWER_HEAP_MB; if (envValue !== undefined) { const parsed = parseInt(envValue, 10); if (Number.isFinite(parsed) && parsed > 0) return parsed; } const totalMb = Math.floor(os.totalmem() / (1024 * 1024)); return Math.max(HEAP_MIN_MB, Math.min(HEAP_MAX_MB, Math.floor(totalMb * HEAP_RATIO))); }; ``` > 50% 留出另一半给 OS、GPU、其它 app;上限避免 V8 GC-pause 退化。 ### 流式加载触发条件 ```ts // src/asset-loader.ts const STREAMING_THRESHOLD_BYTES = 256 * 1024 * 1024; // 256 MB if (fileSystem instanceof MappedReadFileSystem) { const blob = fileSystem.getBlob(filename); if (blob && blob.size >= STREAMING_THRESHOLD_BYTES) { // 走 Blob 流式路径 → Worker } } ``` ### CDP 调试示例 ```bash # 启动带 CDP 的 Electron electron . --remote-debugging-port=9229 # 在 Chrome 中连接 # chrome://inspect → Configure → localhost:9229 ``` 调试脚本位于 `scripts/debug/`: - `probe-shaders.js` — 检查 gsplatVS / gsplatCenterVS chunks 是否注入,uniform 值类型 - `probe-shader-runtime.js` — 触发 toggle 后采样每帧 shader uniform - `trace-toggle.js` — 多次 toggle 采样 controller 状态序列 --- ## 🏗️ 架构 ### 进程模型 ``` ┌──────────────────┐ ┌──────────────────┐ │ Electron Main │ │ BrowserWindow │ │ (electron/main) │◀──▶│ (Renderer) │ │ • V8 heap flag │ │ • PlayCanvas │ │ • DevTools pref │ │ • splat-transform│ │ • Menu/Updater │ │ • Web Worker │ └──────────────────┘ └────────┬─────────┘ │ postMessage ▼ ┌──────────────────┐ │ parse-worker.ts │ │ • .splat parser │ │ • SPZ v2/v3/v4 │ │ • zstd-wasm │ │ • Morton reorder│ └──────────────────┘ ``` ### 关键文件 ``` electron/ ├── main.ts # 入口:heap、菜单、窗口、DevTools 持久化、CDP ├── preload.ts # contextBridge 暴露 window.api ├── menu.ts # 应用程序菜单 + Toggle DevTools (F12) └── ipc/ # 文件系统 / store / updater src/ ├── asset-loader.ts # 文件加载入口,大文件路由到 Blob 流式 ├── editor.ts # 编辑器主控 ├── splat.ts # Splat 数据模型(state/transform/storage) + onPreRender ├── splat-render-mode-controller.ts # 点云/高斯切换 + iris 过渡控制器 ├── splat-overlay.ts # Centers / Rings 模式 ├── selection.ts # 多选管理 ├── merge-splats.ts # 数据合并 ├── worker/ │ ├── parse-worker.ts # Worker:.splat + SPZ v2/v3/v4 流式解析 │ ├── parse-client.ts # 主线程 ↔ Worker 通信 + 进度上报 │ └── worker-types.ts # ParseRequest/Success/Progress 类型 ├── io/ │ ├── read/ # 读路径 │ └── write/ # 写路径 │ ├── spz-codec.ts # SPZ v4 编码 │ └── zstd-loader.ts # zstd-wasm lazy init ├── shaders/ │ └── splat-shader.ts # 自定义 gsplatVS / gsplatPS / gsplatCenterVS └── ui/ ├── status-bar.ts # 状态栏 ├── splat-list.ts # 数据列表 ├── view-panel.ts # 视图设置(含渲染模式 / 过渡效果 / 点尺寸 / iris 颜色) └── right-toolbar.ts # 右工具栏(含 renderModeToggle 按钮) static/ ├── lib/ # 第三方 wasm(zstd.wasm / webp / lodepng) └── locales/ # 9 种语言本地化(zh-CN, en, de, es, fr, ja, ko, pt-BR, ru) ``` ### 点云/高斯切换数据流 ``` 用户点击工具栏按钮 / 按 K 键 ↓ events.fire('view.toggleRenderMode') ↓ SplatRenderModeController.setRenderMode('splat' | 'pointcloud') ↓ cancelInFlight() 取消进行中的 rAF ↓ _renderModeTarget = mode ↓ if (effect == 1) startIrisTransition() else startScreenTransition() ↓ rAF 循环每帧: r += maxRadius * stepRate // stepRate 自适应 (v1.1.8) this._currentLightRadius = r scene.forceRender = true ↓ Splat.onPreRender (下一帧) ↓ material.setParameter('pointMode', ...) material.setParameter('transitionEffect', ...) // int, v1.1.7 fix material.setParameter('currentLightRadius', r) material.setParameter('irisBandRatio', ...) // v1.1.8 material.setParameter('irisColor', ...) ↓ GPU shader (splat-shader.ts 中的自定义 gsplatVS): if (transitionEffect == 1) { // ModelCenterCircle: 按 worldPos 距 modelCenterUniform 比较 if (currentRadius < currentLightRadius) { if (currentRadius > currentLightRadius * (1 - irisBandRatio)) { isLightColor = 1.0 // 环带 iris tint } else { renderAsPoint = !pointMode // 环内已翻转 } } } else { // 屏幕变体:按 NDC 距离比较,同理有环带 } ``` ### SPZ 解析路径 ``` .spz 文件 │ ├─ magic 0x1f 0x8b? ─ yes ─▶ DecompressionStream('gzip') ─▶ gunzip 后版本分发 │ │ │ ├─ v >= 4 ─▶ wasm readSpz │ └─ v2/v3 ─▶ JS 流式解析 │ └─ 原始 NGSP ─▶ streamParseSpzInner │ ├─ v4 (32 字节 header) ─▶ 读 TOC ─▶ per-stream zstd └─ v2/v3 (16 字节 header) ─▶ 按 numPoints 计算块大小 ─▶ 直接读 最终统一走 decodeSpzBlocks: • positions: 9 bytes/splat (24-bit signed fixed-point × 3) • alphas: 1 byte/splat (sigmoid → logit) • colors: 3 bytes/splat (SH-DC × 3,0.15 系数) • scales: 3 bytes/splat (log-scale + 10, × 16) • rotations: 3/4 bytes/splat (v2 first-three | v3+ smallest-three) • sh: shDim × 3 bytes (shDegree >= 1) ``` ### Morton Reorder 策略 ```ts // src/worker/parse-worker.ts const SKIP_MORTON_THRESHOLD_ROWS = 50 * 1024 * 1024; // 50M splats // > 50M splats 时跳过 Morton 重排 — 重排会把源列 cache 住再构建 permuted // 副本,峰值内存翻倍,在碎片化堆上极易 OOM。牺牲渲染性能换取稳定性。 ``` --- ## 🚢 发布流程 更新通过 Gitee Releases + `electron-updater`(`generic` provider)分发。 CI 由 **Gitee Go** 驱动:`.gitee-pipeline.yml` 在 push 到 `main` 时自动构建并发布。 ### 一次性配置 1. Gitee 账号 → 设置 → 私人令牌 → 生成新令牌,scope 勾选 `projects` + `releases` 2. 仓库页面 → **Gitee Go** → 开通(免费版即可) 3. 流水线 → 新建 → 「代码仓库内流水线」→ 选 `.gitee-pipeline.yml` 4. 流水线 → 变量 → 新增 `GITEE_TOKEN` = 上面生成的令牌 ### 发版本 ```bash # 1) 改 package.json 里的 "version" # 2) 提交并推 main git commit -am "Release vX.Y.Z" git push origin main # 3) Gitee Go 自动:build → 出包 → 创建 Gitee Release → 上传 latest.yml + 两个 exe # 4) 用户机器下次启动会自动弹出更新提示 ``` --- ## 📊 版本历史 | 版本 | 主要变更 | |------|----------| | **v1.1.9** | 屏幕变体(2/3/4)添加可见光圈环带 | | **v1.1.8** | 自适应动画时长 + 自适应环带厚度(按 maxRadius 分档) | | **v1.1.7** | **修复**:transitionEffect uniform 字符串 → int(SelectInput 缺 type:'number') | | **v1.1.6** | 加粗 iris 环带 + 慢启动步进 | | **v1.1.5** | 修复 transitionEffect 切换导致 iris 消失 | | **v1.1.4** | iris 环颜色自定义(ColorPicker 持久化) | | **v1.1.3** | 点云/高斯 Splat 渲染模式切换 + 4 种过渡动画 | | **v1.1.2** | About popup 显示作者 | | **v1.1.1** | File→Open 进度条 | | **v1.1.0** | 3DGS 并行处理 + File→Open 流式加载 | | **v1.0.32** | 3DGS 处理:删除浮动点 / 孔洞填补 / 密度提升 | | **v1.0.31** | LCC / LCC2 容器格式支持 | | **v1.0.30** | SPZ v2/v3/v4 完整支持 + 删除数据时点数同步 | | v1.0.29 | 大文件加载(V8 16GB + Blob 流式 + .splat 自实现 + SPZ v4) | | v1.0.28 | 数据合并功能 + 深度内存释放 | | v1.0.27 | 基础编辑 / 导出 / 多语言 | --- ## 🐛 已知限制 - **SPZ SH 系数**:v2/v3/v4 解析器对 `shDegree >= 1` 抛错(暂未实现 SH 解码),需要 SH 的文件应回退到 wasm `readSpz`(v4 路径) - **v2 first-three 旋转**:实现,但尚未在 v2 文件上充分测试 - **Morton reorder**:> 50M splats 跳过重排,渲染端会做非 Morton 排序(性能略降) - **渲染模式切换在 huge 场景**:当 `maxRadius > 100` 时 iris 环带会被自动收窄到 5%(v1.1.8 自适应),但仍可能因相机过远而视觉上看不清——这是屏幕像素密度决定的物理限制 --- ## 📁 项目结构 ``` . ├── PLAN.md # 大文件加载方案 ├── PLAN-RENDERMODE.md # 渲染模式切换方案 ├── src/ # 前端源码(TypeScript) ├── static/ # 静态资源(图片、图标、wasm、本地化) ├── electron/ # Electron 主进程 / preload / IPC │ ├── main.ts # 入口 │ ├── preload.ts # contextBridge 暴露 window.api │ └── ipc/ # 各种 IPC handler ├── build/ # electron-builder 资源(图标等) ├── dist/ # rollup 输出(前端) ├── electron/dist/ # tsc 输出(主进程) ├── release/ # electron-builder 安装包产物 ├── scripts/debug/ # CDP 调试脚本 └── .gitee-pipeline.yml # Gitee Go 传统流水线配置 ``` --- ## 许可 本项目基于 PlayCanvas SuperSplat(MIT License)改写。原始版权见 [`LICENSE-SUPERSPLAT`](./LICENSE-SUPERSPLAT)。 作者:满院蔷薇