第10章:二次开发实战与生态集成
本章把前面所学落到实处:基于 SuperSplat 开源代码做二次开发——新增工具、自定义编辑操作、扩展 UI、对接 iframe 通信、增加语言,并将其集成到自己的产品中。本章基于第 09 章的架构理解,给出可操作的开发范式(请以仓库当前源码为准)。
1. 二次开发的总体思路
由于 SuperSplat 采用事件总线 + 模块化注册架构,二次开发的核心范式是:
- 写一个
registerMyFeatureEvents(events, ...)函数,在其中用events.function/events.on注册逻辑; - 在
main.ts的装配流程中调用它; - 如需界面,在
ui/中新增面板/按钮,通过events.fire/events.invoke与逻辑通信; - 如需可撤销,把修改封装成
EditOp交给EditHistory。
牢记“UI 只发事件、逻辑只挂事件”的解耦原则,新增功能就不会侵入既有代码。
2. 实战一:新增一个自定义选择工具
所有工具实现统一接口(src/tools/tool-manager.ts):
interface Tool {
activate: () => void;
deactivate: () => void;
}
新增工具的步骤:
- 在
src/tools/下新建my-selection.ts,实现一个类,构造时接收events、场景/相机等依赖,实现activate()(绑定鼠标事件、显示辅助图形)与deactivate()(解绑、清理); - 在
activate的交互回调里,计算被选中的高斯点索引区间,然后通过编辑操作修改selected状态位(复用edit-ops.ts中的SelectOp/StateOp模式),并压入历史; - 在
main.ts注册:toolManager.register('mySelection', new MySelection(events, ...)); - 在
src/shortcut-manager.ts的defaultShortcuts增加绑定,例如'tool.mySelection': { keys: ['k'] },并在工具激活逻辑里响应该事件; - 在右侧工具栏(
src/ui/right-toolbar.ts)添加按钮,点击时events.fire('tool.mySelection')。
参考实现:rect-selection.ts(2D 屏幕选择)或 sphere-selection.ts(3D 体积选择)是最佳模板。
3. 实战二:自定义可撤销编辑操作
要新增一种“数据编辑”(例如“把选中点的不透明度乘以 0.5”),实现 EditOp 接口:
class ScaleOpacityOp implements EditOp {
name = 'scaleOpacity';
// 保存撤销所需的原始数据
constructor(/* splat, ranges, factor, 备份... */) {}
do() { /* 修改选中点 opacity,并更新 GPU 数据 */ }
undo() { /* 用备份恢复 */ }
destroy() { /* 释放备份 */ }
}
然后通过历史执行:
events.fire('edit.add', new ScaleOpacityOp(/* ... */));
实现要点:
do()/undo()必须严格互逆;备份原始值以便回退;- 修改高斯数据后要触发对应的 GPU 数据更新(参考
splat.ts的updateState/相关更新方法); - 大批量修改用
IndexRanges(src/index-ranges.ts)按区间操作以提升性能; - 组合多个操作可用
MultiOp。
这样新功能自动获得 Ctrl+Z/Ctrl+Shift+Z 支持。
4. 实战三:扩展 UI 面板
以新增一个“统计面板”为例:
- 在
src/ui/下新建stats-panel.ts,用 PCUI 的Container/Label构建; - 通过
events.invoke('selection')、自定义的events.invoke('splat.count')等查询数据并显示; - 监听相关事件(如
events.on('edit.changed', refresh))在数据变化时刷新; - 在
src/ui/editor.ts的EditorUI中实例化并挂载到布局; - 所有文案用
localize('panel.stats.xxx')并在语言文件中补充键值。
PCUI 提供 SliderInput、SelectInput、BooleanInput、ColorPicker、TextInput、Button 等控件,足以构建复杂面板。务必坚持“面板不直接持有业务对象,只经由 events 通信”。
5. 实战四:对接 iframe API(嵌入到你的网站)
SuperSplat 提供了一个最小的 iframe 通信 API(src/iframe-api.ts),便于把编辑器嵌入你自己的页面并查询状态。当前内置的查询是“场景是否有未保存改动”:
const IS_SCENE_DIRTY = 'supersplat:is-scene-dirty';
const registerIframeApi = (events: Events) => {
window.addEventListener('message', (event) => {
const source = event.source as Window | null;
if (!source) return;
if (event.data?.type === IS_SCENE_DIRTY) {
source.postMessage({
type: IS_SCENE_DIRTY,
result: events.invoke('scene.dirty') as boolean
}, event.origin);
}
});
};
在宿主页面中查询:
// 宿主页面
const iframe = document.getElementById('supersplat');
window.addEventListener('message', (e) => {
if (e.data?.type === 'supersplat:is-scene-dirty') {
console.log('未保存改动:', e.data.result);
}
});
iframe.contentWindow.postMessage({ type: 'supersplat:is-scene-dirty' }, '*');
扩展自己的消息类型:在 iframe-api.ts 中仿照 IS_SCENE_DIRTY 增加新的 type,在 message 监听里识别并通过 events.invoke/events.fire 桥接到内部逻辑,再 postMessage 回传结果。例如增加“加载指定 URL 的模型”“触发导出”等命令,即可让宿主页面远程控制编辑器。
6. 实战五:增加一种界面语言
参照 README 的本地化指南:
- 在
static/locales/下新增<locale>.json(复制en.json后逐项翻译); - 在
src/ui/localization.ts的supportedLngs数组中加入新语言代码; npm run develop后用http://localhost:3000/?lng=<locale>测试。
localize(key) 会在缺失键时回退到英文(fallbackLng: 'en'),因此可以渐进翻译。注意保持 JSON 结构与键名和英文版完全一致。
7. 实战六:私有化部署与品牌定制
把 SuperSplat 改造成自有产品的常见定制点:
- 品牌:替换 logo(
src/ui/playcanvas-logo.png)、关于弹窗(src/ui/about-popup.ts)、标题与图标(src/index.html、manifest.json); - 默认配置:调整
src/scene-config.ts的背景、相机、网格、色调默认值; - 裁剪功能:通过删除工具注册/菜单项,精简为面向特定用户的轻量版本;
- 发布后端:
src/publish.ts的上传/发布走user.apiServer,若要发布到自有服务,需要实现兼容的后端接口(/api/id、/upload/*、/splats/publish等)或改造该模块对接你的存储; - 构建部署:
npm run build产出dist/,作为纯静态站点部署到任意 CDN / 对象存储 / Nginx 即可。
注意许可证:SuperSplat 采用 MIT 许可证,二次开发与商用需保留版权与许可证声明,并遵守其条款。
8. 生态集成:把成果用起来
二次开发往往是“编辑器 + 查看器”的组合。导出/发布后(第 08 章),在下游消费:
- PlayCanvas Engine:用
GSplat组件加载.ply/.sog,集成进 Web 应用或游戏; - SuperSplat Viewer(
playcanvas/supersplat-viewer):轻量查看器,发布链接即由它驱动,可自托管; - Three.js:
@mkkellogg/gaussian-splats-3d加载.ply/.splat/.ksplat; - 批处理流水线:用
@playcanvas/splat-transform(与 SuperSplat 共享数据处理内核)做命令行转换/压缩,纳入 CI/自动化管线; - iframe 嵌入:把编辑器或查看器嵌入 CMS、电商、数字孪生平台。
一个典型产品形态:用户上传 .ply → 嵌入的 SuperSplat(定制版)清理编辑 → 发布到你的后端 → 前端用 viewer 展示。
9. 开发建议与排错
- 改动后看不到效果:多半是 Service Worker 缓存,按第 09 章关闭缓存;
- 事件不触发:检查
function名称是否重复(重复会抛错)、on/fire名称是否一致、焦点是否在画布(快捷键仅document.body聚焦时生效); - 撤销异常:确认
EditOp的do/undo互逆且正确备份; - 性能问题:批量修改用
IndexRanges,避免逐点 JS 循环;调色等用hasTint思路做“无改动跳过”; - 提交前:跑
npm run lint,遵循@playcanvas/eslint-config规范;参与上游贡献时遵循仓库 CONTRIBUTING 流程。
10. 本章小结
本章给出了 SuperSplat 二次开发的完整范式:
- 遵循“注册函数/事件 + 在 main.ts 装配 + UI 经事件通信”的模式扩展功能;
- 新增工具实现
Tool接口并注册到 ToolManager、绑定快捷键与工具栏按钮; - 自定义编辑实现
EditOp接口交给 EditHistory,自动获得撤销/重做; - 扩展 UI 用 PCUI 并保持与逻辑解耦;
- 通过 iframe API(
postMessage)让宿主页面查询/控制编辑器,可自行扩展消息类型; - 增加语言只需新增 locale JSON 并登记到
supportedLngs; - 可做品牌定制、私有化部署,并与 PlayCanvas/Three.js/viewer/splat-transform 等生态集成。
至此,本教程从原理、使用到源码与二次开发的完整链路结束。建议结合官方仓库源码与用户手册边读边练,真正把 SuperSplat 变成你工具箱里得心应手的一件利器。