# 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-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GSplat Viewer 桌面端 3D Gaussian Splat 编辑器,基于 [PlayCanvas SuperSplat](https://github.com/playcanvas/supersplat) (MIT) 改写,针对**大文件加载**做了深度优化。 > 当前版本:**v1.0.30** --- ## ✨ 核心特性 ### 📦 支持的文件格式 | 格式 | 扩展名 | 加载 | 导出 | 备注 | |------|--------|------|------|------| | 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 | | SuperSplat | `.ssproj` | ✅ | ✅ | 项目文档 | > **SPZ v2/v3/v4 全部支持**(v1.0.30)。v3 包含 Niantic 工具链常见 gzip 包装格式,v4 由 wasm 加速。 ### 🚀 大文件加载优化(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: Array buffer allocation failed` 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 变量可覆盖 ### 🛠️ 开发与调试 - **DevTools 自动启动**:环境变量 `SPLAT_VIEWER_DEVTOOLS=1` 或 View 菜单 → Toggle Developer Tools(F12),偏好跨会话持久化 - **HEAP 限额覆盖**:`SPLAT_VIEWER_HEAP_MB=8192 electron .` - **自动更新**:通过 Gitee Releases + `electron-updater`(`generic` provider) --- ## 🚀 快速开始 ### 环境要求 - 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.0.30.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 } } ``` --- ## 🏗️ 架构 ### 进程模型 ``` ┌──────────────────┐ ┌──────────────────┐ │ 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 持久化 ├── 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) ├── selection.ts # 多选管理(Set 维护) ├── 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/ # 读路径(loader、progress-reader、file-systems) │ └── write/ # 写路径 │ ├── spz-codec.ts # SPZ v4 编码(per-stream zstd + 24-bit fixed-point) │ └── zstd-loader.ts # zstd-wasm lazy init + 512 MB 堆预热 └── ui/ ├── status-bar.ts # 状态栏(点数 / 选中 / 锁定 / 删除) ├── splat-list.ts # 数据列表(添加 / 移除) └── view-panel.ts # 视图设置 static/ ├── lib/ # 第三方 wasm(zstd.wasm / webp / lodepng) └── locales/ # 9 种语言本地化(zh-CN, en, de, es, fr, ja, ko, pt-BR, ru) ``` ### 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.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 排序(性能略降) --- ## 📁 项目结构 ``` . ├── PLAN.md # 大文件加载方案(详细见此文件) ├── PLAN-MULTIWINDOW.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 安装包产物 └── .gitee-pipeline.yml # Gitee Go 传统流水线配置 ``` --- ## 许可 本项目基于 PlayCanvas SuperSplat(MIT License)改写。原始版权见 [`LICENSE-SUPERSPLAT`](./LICENSE-SUPERSPLAT)。