615 lines
37 KiB
Markdown
615 lines
37 KiB
Markdown
# Blender Web 化技术路线(React + Three.js + WebAssembly + OPFS/IndexedDB)
|
||
|
||
> **存储决策(2026-08):** SQLite WASM 不是项目首期必需依赖,已从当前实现和发布资源中移除。小型元数据使用 Worker 内 IndexedDB,大型二进制使用 OPFS;未来只有在查询/事务需求超过 IndexedDB 时,才单独评估 SQLite WASM。
|
||
|
||
## 1. 结论先行
|
||
|
||
将 Blender 的“数据模型和建模能力”搬到浏览器中是可行的;将桌面版 Blender 的全部界面、渲染后端、Python 生态和所有插件原样搬到浏览器,短期内不现实。
|
||
|
||
推荐的产品形态是:
|
||
|
||
> **React 负责应用界面,Three.js 负责全部浏览器端实时渲染,Blender 的 C/C++ 核心以 WASM 运行,OPFS 保存二进制资源,IndexedDB 保存项目元数据和轻量状态。**
|
||
|
||
这不是把 Blender 的窗口系统直接“截图搬到网页”,而是复用 Blender 的文件解析、数据块、依赖图和部分编辑算法,建立一个面向 Web 的场景协议(SceneIR),再由 Three.js 渲染。
|
||
|
||
### 可行性分级
|
||
|
||
| 目标 | 可行性 | 建议 |
|
||
| --- | --- | --- |
|
||
| Web 端浏览/编辑网格、材质、相机、灯光、动画 | 高 | 作为 MVP |
|
||
| Blender 风格工作区、编辑器和常用交互 | 中高 | React 复刻 Workspace/Area/Region/Editor 语义和默认布局,不移植桌面窗口后端 |
|
||
| 读写 `.blend`,支持本地离线保存 | 中高 | 使用 Blender 内存读写 API + OPFS |
|
||
| Blender 常用建模操作和部分 Modifier | 中 | 按操作逐项移植/暴露;Decimate 必须复用 Blender WASM 求值 |
|
||
| Blender 三角面简化、属性保护和多级 LOD | 中高 | 以 Decimate 三模式、三角形 ratio、误差预算和原始/求值 mesh 分离为验收基准 |
|
||
| Blender 对象父子、骨骼层级、蒙皮和关节姿态 | 中高 | 保留 Object、EditBone、Bone、PoseBone、Armature modifier 和约束语义 |
|
||
| Blender 节点材质完全一致 | 中低 | 先支持 Principled BSDF 子集 |
|
||
| Eevee/Cycles 与桌面版像素级一致 | 低 | 浏览器端固定使用 Three.js,服务端 Blender 渲染作为可选补充 |
|
||
| 完整 Blender 桌面 UI 后端、原生窗口、多屏和全部编辑器 | 低 | 用户可见 UI 尽量对标;C++ WindowManager/GHOST 和桌面插件体系不作为首期目标 |
|
||
|
||
## 2. 目标和非目标
|
||
|
||
### 首期目标
|
||
|
||
1. 浏览器中创建、打开、修改和导出一个 3D 项目。
|
||
2. 支持 `.blend` 导入,内部使用统一的 SceneIR,Three.js 实时显示。
|
||
3. 支持基础对象操作:选择、移动、旋转、缩放、复制、删除、集合管理。
|
||
4. 支持基础网格编辑:顶点/边/面数据读取、局部修改、法线重算、三角化、基础变换。
|
||
5. 支持基础材质、贴图、灯光、相机和关键帧动画。
|
||
6. 通过 OPFS 自动保存,刷新页面后恢复项目;支持导出 `.blend`、`.glb` 和贴图包。
|
||
7. 在没有网络的情况下继续工作,网络只用于首次加载和可选的协作/同步功能。
|
||
8. 三角面简化由 Blender WASM 的 Decimate/BMesh 求值,支持 Collapse、Un-Subdivide、Planar/Dissolve、属性保护和多级 LOD;Three.js 只选择和渲染 LOD。
|
||
9. 层级和关节定义对齐 Blender:对象父子、Rest 骨架、PoseBone、Armature modifier、顶点组权重、逆绑定矩阵和基础约束均进入版本化 SceneIR。
|
||
10. 提供 Blender 风格默认工作区:Topbar/Workspace tabs、3D Viewport、Outliner、Properties、Timeline、Editor Header、Toolbar、Sidebar、Status Bar、Operator Search 和模式/快捷键上下文。
|
||
|
||
### 首期明确不做
|
||
|
||
- 不编译 Blender 的 C++ WindowManager、GHOST、多窗口和原生桌面主题系统;但 React 必须提供等价的 Workspace/Area/Region/Editor 用户界面语义。
|
||
- 不在浏览器内运行任意 `.blend` 携带的 Python 脚本。
|
||
- 不把完整 `.blend` 文件拆成大量数据库行作为唯一真相。
|
||
- 不承诺 Cycles、OSL、Embree、OpenVDB、FFmpeg 等桌面依赖在浏览器中等价可用。
|
||
|
||
## 3. 总体架构
|
||
|
||
```text
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Browser │
|
||
│ │
|
||
│ React Application (main thread) │
|
||
│ ├─ Blender-style Topbar / Workspace tabs / menus │
|
||
│ ├─ Area layout / Region frame / Editor Header │
|
||
│ ├─ 3D Viewport / Outliner / Properties / Timeline │
|
||
│ ├─ Toolbar / Sidebar / Operator Search / settings │
|
||
│ └─ Viewport input proxy │
|
||
│ │ versioned commands/events │
|
||
│ ▼ │
|
||
│ EngineWorker │
|
||
│ ├─ Blender WASM core │
|
||
│ │ ├─ BLO/BKE/BMesh/Depsgraph subset │
|
||
│ │ ├─ SceneIR exporter/importer │
|
||
│ │ └─ memory-based .blend reader/writer │
|
||
│ ├─ mesh/material/animation operations │
|
||
│ └─ optional Python-disabled runtime │
|
||
│ │
|
||
│ Three.js Viewport │
|
||
│ ├─ WebGLRenderer (WebGL2) │
|
||
│ ├─ BufferGeometry / InstancedMesh / materials │
|
||
│ ├─ selection, raycast, gizmos, camera controls │
|
||
│ └─ optional OffscreenCanvas renderer worker │
|
||
│ │
|
||
│ StorageWorker │
|
||
│ ├─ IndexedDB metadata store │
|
||
│ ├─ OPFS file handles │
|
||
│ └─ project metadata / snapshots / operation summaries │
|
||
│ │
|
||
│ OPFS │
|
||
│ ├─ projects/<id>/scene.blend │
|
||
│ ├─ projects/<id>/assets/<sha256> │
|
||
│ ├─ projects/<id>/snapshots/<revision>.blend │
|
||
│ └─ projects/<id>/thumbs/*.webp │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 关键原则
|
||
|
||
- **Blender WASM 是场景操作引擎,不是 React 组件。** React 不应直接读写 WASM 堆,也不应持有完整几何数据副本。
|
||
- **Three.js 是浏览器端唯一渲染器。** 先把 Blender 数据转换为 Three.js 对象;不要在浏览器中调用 Blender 的 Eevee/Cycles,也不要一开始移植 Blender GPU 后端。
|
||
- **EngineWorker 和 StorageWorker 分离。** IndexedDB 请求、OPFS 锁和大文件 I/O 不阻塞 React。
|
||
- **所有跨边界调用都有版本号和 revision。** 防止异步命令返回乱序后覆盖新状态。
|
||
- **`.blend` 是兼容性主文件,IndexedDB 是应用元数据索引。** 它保存项目元数据、快照索引和轻量操作摘要,不替代 Blender 的 DNA 数据结构。
|
||
- **React UI 对标 Blender 的用户可见语义。** 采用 Window/Screen/Workspace/Area/Region/Editor 的等价 `WorkspaceIR`,保留编辑器类型、上下文、区域尺寸、活动区域和布局持久化;不把页面做成不可拆分的自定义 Dashboard。
|
||
- **三角面简化是 Blender 语义的强制 P0/P1 能力。** 简化参数、三角形计数、属性边界和 modifier stack 必须由 Blender WASM 求值;Three.js 不提供第二套权威简化算法。
|
||
- **层级和关节是数据契约而不是显示效果。** Object parent、EditBone、Bone、PoseBone、Armature modifier、权重和约束必须分别建模,稳定 ID 不能只依赖名称或数组下标。
|
||
|
||
## 4. Blender WASM 的实现边界
|
||
|
||
### 4.1 推荐的核心裁剪
|
||
|
||
Blender 5.2 源码已经把文件加载、BMesh、依赖图、几何节点、GPU 抽象和编辑器拆成相对清晰的模块。首期建议按下表裁剪:
|
||
|
||
| 保留 | 首期状态 |
|
||
| --- | --- |
|
||
| `blenloader` (`BLO_*`) | 必须保留,用于 `.blend` 内存读写 |
|
||
| `blenkernel` (`BKE_*`) | 保留场景、对象、材质、动画等数据块 |
|
||
| `bmesh` | 保留基础网格操作 |
|
||
| `depsgraph` | 保留对象依赖和变换求值,先关闭复杂模拟 |
|
||
| `makesdna/makesrna` | 保留数据结构和属性访问 |
|
||
| 部分 `modifiers` | 按需打开,逐个做 WASM 回归测试 |
|
||
| `draw/gpu` | 首期不编译 Blender 视口渲染路径 |
|
||
| `editors/windowmanager` | 首期不编译桌面 UI |
|
||
| `intern/ghost` | 首期不移植;Three.js 处理窗口和输入 |
|
||
| Python、OSL、Embree、CUDA/OptiX、OpenVDB | 首期关闭或改为服务端能力 |
|
||
|
||
源码中已有 `BLO_read_from_memory()`,适合从 JavaScript 传入 `ArrayBuffer` 后加载 `.blend`;写回时可以使用 Blender 的内存写入流程,或增加一个导出内存缓冲区的 C API。相关接口见:
|
||
|
||
- `blender-5.2.0/source/blender/blenloader/BLO_readfile.hh`
|
||
- `blender-5.2.0/source/blender/blenloader/BLO_writefile.hh`
|
||
- `blender-5.2.0/source/blender/blenkernel/BKE_blendfile.hh`
|
||
|
||
### 4.2 稳定的 C ABI
|
||
|
||
不要把 C++ 类、STL 容器或 Embind 类型直接暴露给 React。建议增加 `source/blender/web_api/`,只导出 C ABI:
|
||
|
||
```cpp
|
||
extern "C" {
|
||
WebEngine *web_engine_create(const WebEngineConfig *config);
|
||
int web_engine_open_blend(WebEngine *, const uint8_t *data, size_t size);
|
||
int web_engine_apply_command(WebEngine *, const uint8_t *data, size_t size);
|
||
int web_engine_get_scene_delta(WebEngine *, uint64_t since_revision,
|
||
uint8_t **data, size_t *size);
|
||
int web_engine_save_blend(WebEngine *, uint8_t **data, size_t *size);
|
||
void web_engine_free_buffer(uint8_t *data, size_t size);
|
||
void web_engine_destroy(WebEngine *);
|
||
}
|
||
```
|
||
|
||
建议协议分两层:
|
||
|
||
- 低频命令使用版本化 JSON/CBOR,例如 `object.transform`, `mesh.extrude`。
|
||
- 顶点、索引、权重、贴图使用 `ArrayBuffer`/TypedArray,禁止 JSON 编码大数组。
|
||
|
||
每个命令包含 `requestId`、`expectedRevision`、`operation` 和 `payload`。引擎返回新 revision、受影响对象 ID、场景增量和错误报告。
|
||
|
||
### 4.3 SceneIR 场景中间表示
|
||
|
||
SceneIR 是 Blender 和 Three.js 的解耦层。建议第一版定义为:
|
||
|
||
```ts
|
||
type SceneIR = {
|
||
version: 1;
|
||
revision: number;
|
||
coordinateSystem: "blender-z-up";
|
||
units: { scale: number; length: "meter" | "centimeter" | "unknown" };
|
||
nodes: NodeIR[];
|
||
meshes: MeshIR[];
|
||
materials: MaterialIR[];
|
||
images: ImageIR[];
|
||
cameras: CameraIR[];
|
||
lights: LightIR[];
|
||
animations: AnimationIR[];
|
||
};
|
||
```
|
||
|
||
`MeshIR` 至少包含 position、normal、tangent、uv、color、index、material slot 和 object-local bounds;`NodeIR` 使用稳定 UUID,而不是数组下标。Blender 的 Z-up 和 Three.js 的 Y-up 必须在适配层一次性转换,不能在各个组件中分别修正。
|
||
|
||
SceneIR 采用“完整快照 + 增量更新”:打开文件返回完整快照,变换对象只返回节点变换,编辑网格只返回受影响的 buffer range 或新 mesh ID。这样 React 和 Three.js 不需要每次操作都重建整个场景。
|
||
|
||
## 5. Three.js 视口实现
|
||
|
||
### 5.1 首期渲染路径
|
||
|
||
首期使用 Three.js `WebGLRenderer`,目标是 WebGL2。Three.js 当前的 `WebGLRenderer` 已以 WebGL2 为基础,适合把 Blender 的网格、贴图、灯光和基础 PBR 材质转换到浏览器中。浏览器端的所有实时帧、选择高亮、阴影、后处理和动画显示均由 Three.js 完成;Blender WASM 不创建浏览器 GPU 上下文。
|
||
|
||
```ts
|
||
const renderer = new THREE.WebGLRenderer({
|
||
canvas,
|
||
antialias: true,
|
||
powerPreference: "high-performance",
|
||
});
|
||
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||
```
|
||
|
||
### 5.2 SceneIR 到 Three.js
|
||
|
||
适配器职责:
|
||
|
||
1. `MeshIR` -> `BufferGeometry`,按属性是否存在动态设置 attribute。
|
||
2. index 使用 `Uint16Array` 或 `Uint32Array`,超过 16 位时检测 WebGL2 能力。
|
||
3. 重复对象优先使用 `InstancedMesh`,避免创建大量相同 Geometry。
|
||
4. Blender 的 Principled BSDF 子集映射到 `MeshStandardMaterial`/`MeshPhysicalMaterial`。
|
||
5. 图片从 OPFS 读取为 Blob/`ImageBitmap`,交给 `TextureLoader` 或 `ImageBitmapLoader`;不把大图转成 base64。
|
||
6. 相机、灯光、雾、世界颜色和曝光在一个 `RenderSettings` 适配器中统一处理。
|
||
7. Node UUID 与 `Object3D.userData.blenderId` 一一对应,选中、撤销和增量更新都通过 UUID 定位。
|
||
|
||
复杂节点材质不应静默伪造结果。建议返回 `materialWarnings`,对不支持的节点显示近似材质。若产品需要桌面版一致的最终图像,可以把 `.blend` 或规范化场景发送到服务端 Blender 渲染,但这属于独立的离线渲染路径,不改变浏览器端由 Three.js 负责实时渲染的约束。
|
||
|
||
### 5.3 交互和编辑
|
||
|
||
- 相机:`OrbitControls`,后续增加平移、缩放、第一人称模式。
|
||
- 选择:射线拾取得到 Three.js 对象,再映射到 Blender UUID。
|
||
- 操作器:Three.js `TransformControls` 只产生变换命令,最终状态由 EngineWorker 确认。
|
||
- 取消/撤销:拖动过程发送预览命令,鼠标释放发送提交命令;撤销由引擎操作栈处理。
|
||
- 多选、集合、可见性、锁定、隐藏等 UI 状态必须与引擎场景状态分开管理。
|
||
- 视口只消费 `SceneDelta`;不要在 React render 中遍历几何数组。
|
||
|
||
### 5.4 大场景优化
|
||
|
||
- 几何按对象或 collection 分块加载,视口先显示包围盒或低模。
|
||
- 只在对象 revision 变化时更新 GPU buffer。
|
||
- 使用 frustum culling、LOD、实例化和合批减少 draw call。
|
||
- 导入/导出 glTF 时可启用 Draco、Meshopt、KTX2;Three.js `GLTFLoader` 已有这些扩展的加载入口。
|
||
- 贴图使用 mipmap、压缩纹理和按需解码;释放对象时同时调用 Geometry、Material、Texture 的 `dispose()`。
|
||
- 对动画和骨骼使用 Three.js `AnimationMixer`,复杂约束先在 WASM 中求值再传输矩阵。
|
||
|
||
### 5.5 OffscreenCanvas 的使用时机
|
||
|
||
先在主线程渲染,保证交互和调试简单;场景规模达到瓶颈后再把 Three.js 渲染移到 RenderWorker。Three.js 官方提供了 OffscreenCanvas + Worker 的模式,但 Worker 不能访问 DOM,鼠标和键盘必须通过事件代理转发,因此要把输入代理设计成独立模块,不要让 `OrbitControls` 直接依赖 DOM。
|
||
|
||
推荐模式:
|
||
|
||
```text
|
||
React/main thread
|
||
canvas DOM + pointer/keyboard events
|
||
│ transferable OffscreenCanvas + input messages
|
||
▼
|
||
RenderWorker
|
||
Three.js renderer + SceneIR cache
|
||
```
|
||
|
||
Blender EngineWorker 可以和 RenderWorker 合并,也可以分开。首个可交付版本建议合并为一个 Worker,减少跨 Worker 的 buffer 复制;当建模操作和渲染互相阻塞时再拆开。
|
||
|
||
## 6. OPFS 和 IndexedDB 存储设计
|
||
|
||
### 6.1 存储职责
|
||
|
||
| 数据 | 存储位置 | 原因 |
|
||
| --- | --- | --- |
|
||
| 当前 `.blend` | OPFS 文件 | 保留 Blender 原生兼容性,适合大二进制 |
|
||
| 自动保存快照 | OPFS 文件 | 避免把大文件塞进 SQL BLOB |
|
||
| 贴图、HDR、缓存 | OPFS 文件,按 SHA-256 命名 | 去重和按需加载 |
|
||
| 项目列表、路径、版本、校验和 | IndexedDB | 浏览器原生、无需额外 WASM 依赖 |
|
||
| 操作日志和撤销元数据 | IndexedDB object store | 轻量命令摘要可恢复;大 payload 另存 OPFS |
|
||
| 缩略图索引 | IndexedDB;图像本体在 OPFS | 快速显示项目列表 |
|
||
|
||
### 6.2 IndexedDB StorageWorker
|
||
|
||
IndexedDB 和 OPFS 都通过 StorageWorker 访问。OPFS 只用于大文件和快照,IndexedDB 只保存可序列化的小对象;这避免把 Blender 数据块拆成数据库行,也避免首期引入额外数据库 WASM。
|
||
|
||
StorageWorker 对请求排队,并在升级事件中创建版本化 object store:
|
||
|
||
```ts
|
||
// storage.worker.ts, 示意
|
||
const request = indexedDB.open("blender-web-metadata", 1);
|
||
request.onupgradeneeded = () => {
|
||
request.result.createObjectStore("project", { keyPath: "id" });
|
||
};
|
||
```
|
||
|
||
部署到多标签页时,增加 BroadcastChannel 锁和 project revision 检查;IndexedDB 事务不替代产品层面的冲突策略。
|
||
|
||
### 6.3 推荐表结构
|
||
|
||
```sql
|
||
const project = {
|
||
id: "project-1",
|
||
name: "Untitled",
|
||
currentRevision: 0,
|
||
blendPath: "projects/project-1/scene.blend",
|
||
updatedAt: Date.now(),
|
||
};
|
||
await put("project", project);
|
||
```
|
||
|
||
不要把每个顶点作为一条记录。对于编辑操作,记录可重放的命令摘要和必要的二进制差异;每 20~100 个命令或达到大小阈值后生成一个新的 `.blend` 快照,再清理旧日志。
|
||
|
||
### 6.4 文件导入、保存和恢复
|
||
|
||
1. `<input type=file>` 得到 `File`,使用 `arrayBuffer()` 传入 EngineWorker。
|
||
2. 引擎调用 `BLO_read_from_memory()`,建立 Blender 数据块和 SceneIR。
|
||
3. SceneIR 首屏增量传给 Three.js,贴图通过 asset hash 延迟加载。
|
||
4. 保存时引擎生成内存 `.blend` buffer,StorageWorker 写入临时 OPFS 文件。
|
||
5. 写完并校验 SHA-256 后,IndexedDB 在一个事务中更新 `currentRevision` 和 `snapshot` 元数据。
|
||
6. 写入采用 `scene.blend.tmp` -> `scene.blend` 的替换策略,页面崩溃时根据 manifest 恢复最近一个完整文件。
|
||
7. 用户显式导出时,从 OPFS 读取文件并创建 Blob 下载;浏览器没有“写任意本地路径”的权限,不能模拟桌面 Save As。
|
||
|
||
## 7. React 前端设计
|
||
|
||
### 7.1 状态分层
|
||
|
||
```text
|
||
UI state tab、面板开关、当前工具、快捷键状态
|
||
View state 相机、选择高亮、显示模式、临时拖动状态
|
||
Engine state 节点、网格、材质、动画、revision(以 WASM 为准)
|
||
Persistence 保存状态、OPFS quota、快照、同步错误
|
||
Workspace 当前 Workspace、Area/Region 布局、Editor 类型、Context、活动区域
|
||
```
|
||
|
||
React store 只存可序列化的轻量对象摘要,例如 `ObjectSummary`、`MaterialSummary` 和 `SceneStats`。大型顶点数组和纹理永远留在 Worker、GPU 或 OPFS 中。复杂 UI 可以使用 reducer/context 或 Zustand/Redux,但引擎命令必须通过同一个 `EngineClient` 发出。
|
||
|
||
### 7.2 组件建议
|
||
|
||
- `AppShell`:项目、保存、导出、设置。
|
||
- `ViewportPanel`:Three.js canvas、选择和工具状态。
|
||
- `OutlinerPanel`:对象树、集合、可见性和锁定。
|
||
- `InspectorPanel`:变换、材质、对象属性。
|
||
- `TimelinePanel`:帧范围、播放、关键帧。
|
||
- `AssetPanel`:OPFS 资源、缩略图、导入。
|
||
- `StatusBar`:WASM 内存、GPU draw calls、保存状态和错误。
|
||
- `Topbar`:文件菜单、Workspace tabs、场景/视图层、全局搜索。
|
||
- `AreaFrame`:可拆分/合并的 Blender Area 容器,负责 Header、Editor 和 Region 生命周期。
|
||
- `EditorHeader`:编辑器类型选择、模式、视图、覆盖层和操作菜单。
|
||
- `Toolbar` / `Sidebar`:工具、属性面板和 N-panel 的 Blender 风格容器。
|
||
- `OperatorSearch` / `KeymapManager`:F3 操作搜索、按 Area/Editor/Mode 分发快捷键。
|
||
|
||
所有面板通过命令调用引擎,不要在组件内直接调用 C API。
|
||
|
||
### 7.3 Blender 界面对标原则
|
||
|
||
Blender 的 UI 不是一组固定面板,而是“工作区 + 区域 + 编辑器 + 区域子区”的可组合布局。Web 版采用同样的用户模型:
|
||
|
||
| Blender 概念 | Web 等价物 | 必须保留的行为 |
|
||
| --- | --- | --- |
|
||
| Window/Screen | Browser project window / `ScreenIR` | 项目级 UI 状态、布局版本、当前窗口上下文 |
|
||
| Workspace | `WorkspaceIR` + 顶部 tab | Layout/Modeling/Sculpting/Animation 等工作区切换、复制、重命名、持久化 |
|
||
| Area | `AreaFrame` | 编辑器类型、边界拖拽、split/join、最大化、上下文焦点 |
|
||
| Region | `RegionFrame` | Header、Main、Toolbar、Sidebar、Asset/Status 等区域的显示和折叠 |
|
||
| Editor/Space | `EditorHost` | 3D Viewport、Outliner、Properties、Timeline、Dope Sheet、Node Editor 等编辑器状态 |
|
||
| Context | `UIContextIR` | active object、mode、selection、active area、scene、view layer、pinned data |
|
||
| Operator/Keymap | `OperatorRegistry` + `KeymapManager` | F3 搜索、快捷键优先级、鼠标所在 Area/Editor/Mode 的上下文分发 |
|
||
|
||
实现要求:
|
||
|
||
1. 默认启动布局接近 Blender Layout:顶部 Workspace tabs,中央 3D Viewport,右上 Outliner,右侧 Properties,底部 Timeline,Viewport 自带 Header/Toolbar/Sidebar。
|
||
2. Area 边界可以拖动调整大小;支持 split、join、最大化当前 Area、切换 Editor 类型。移动端采用可折叠抽屉,但不改变编辑器和命令语义。
|
||
3. 每个 Editor 都有独立 Header、Context 和快捷键范围。鼠标/键盘事件先由 `UIContextIR` 路由,再决定是 UI 操作、Three.js 视口预览还是 Blender WASM 命令。
|
||
4. UI 布局和用户偏好存入 IndexedDB/OPFS;项目 `.blend` 中可表达的 Workspace 信息要保留,Web 专属面板状态使用版本化 `WebWorkspaceState` 扩展,不能覆盖 Blender 数据块。
|
||
5. 主题、字号、DPI、图标和颜色采用 Blender 风格的密度和层级,但不复制 Blender 的版权素材;所有图标由现有图标库或项目自有资源提供。
|
||
6. 面板显示的是 Blender RNA/SceneIR 摘要,提交通过 `EngineClient`;React 不直接改变对象、网格、骨骼或材质数据。
|
||
|
||
UI 对标验收不是“截图颜色相似”,而是同一操作在对应的 Editor/Mode/Context 下可完成、快捷键作用域一致、布局刷新后可恢复、命令结果与 Blender 数据一致。桌面 Blender 的 UI 组织可参考官方对 [Workspaces](https://docs.blender.org/manual/en/5.0/interface/window_system/workspaces.html)、[Areas](https://docs.blender.org/manual/en/2.82/interface/window_system/areas.html)、[Tabs & Panels](https://docs.blender.org/manual/en/5.0/interface/window_system/tabs_panels.html) 的定义。
|
||
|
||
## 8. Worker 消息协议
|
||
|
||
```ts
|
||
type EngineRequest = {
|
||
requestId: string;
|
||
projectId: string;
|
||
expectedRevision: number;
|
||
command:
|
||
| { type: "openBlend"; bytes: ArrayBuffer }
|
||
| { type: "setTransform"; objectId: string; matrix: Float32Array }
|
||
| { type: "meshEdit"; objectId: string; edit: MeshEdit }
|
||
| { type: "saveBlend" }
|
||
| { type: "exportGlb"; options: ExportOptions };
|
||
};
|
||
|
||
type EngineResponse = {
|
||
requestId: string;
|
||
ok: boolean;
|
||
revision: number;
|
||
delta?: SceneDelta;
|
||
buffer?: ArrayBuffer;
|
||
reports?: Report[];
|
||
};
|
||
```
|
||
|
||
必须处理以下情况:
|
||
|
||
- `expectedRevision` 过期时拒绝命令,React 重新拉取 delta。
|
||
- Worker 发生异常时返回可显示的 report,并保留最近一个 OPFS 快照。
|
||
- 大 buffer 使用 transferable list;开启跨源隔离后可为高频数据使用 SharedArrayBuffer。
|
||
- 所有命令都带 schema 版本,便于以后协作同步和回放。
|
||
|
||
## 9. 编译和打包路线
|
||
|
||
### 9.1 环境
|
||
|
||
- Blender 5.2 源码作为上游基线,单独建立 `web` 分支或 patch 集合。
|
||
- Emscripten SDK + LLVM/Clang,Node.js 仅用于前端构建和测试。
|
||
- CMake + Ninja,使用 `emcmake`/`em++`,不要把原生 `build_blender_5.2.0` 直接改成 WASM 构建目录。
|
||
- Vite 或 Rsbuild 打包 React、Three.js、WASM glue JS 和资源 manifest。
|
||
|
||
### 9.2 需要增加的 CMake 选项
|
||
|
||
Blender 当前 CMake 主要按桌面平台选择 GHOST、OpenGL/Vulkan 和外部库,因此建议增加 Web 平台开关,而不是伪装成 Linux:
|
||
|
||
```cmake
|
||
option(WITH_WEB "Build the Blender Web engine" OFF)
|
||
|
||
if(WITH_WEB)
|
||
set(WITH_GHOST_X11 OFF CACHE BOOL "" FORCE)
|
||
set(WITH_GHOST_WAYLAND OFF CACHE BOOL "" FORCE)
|
||
set(WITH_GHOST_SDL OFF CACHE BOOL "" FORCE)
|
||
set(WITH_VULKAN_BACKEND OFF CACHE BOOL "" FORCE)
|
||
set(WITH_PYTHON OFF CACHE BOOL "" FORCE)
|
||
set(WITH_CYCLES OFF CACHE BOOL "" FORCE)
|
||
set(WITH_OPENVDB OFF CACHE BOOL "" FORCE)
|
||
set(WITH_USD OFF CACHE BOOL "" FORCE)
|
||
set(WITH_CODEC_FFMPEG OFF CACHE BOOL "" FORCE)
|
||
add_definitions(-DWITH_WEB)
|
||
endif()
|
||
```
|
||
|
||
首期构建的是 `web_engine` 静态库或最小可执行入口,不编译桌面 Blender 的 `screen/editor/windowmanager`。后续如果确实需要原生 Blender 编辑器,再增加 `GHOST_SystemWeb`、`GHOST_WindowWeb` 和 WebGL context 适配层。
|
||
|
||
### 9.3 Emscripten 链接参数(示意)
|
||
|
||
以下参数需要根据最终依赖逐项验证,不能直接视为现成的 Blender CMake 配置:
|
||
|
||
```text
|
||
-O3 -flto
|
||
-sMODULARIZE=1 -sEXPORT_ES6=1
|
||
-sENVIRONMENT=web,worker
|
||
-sALLOW_MEMORY_GROWTH=1
|
||
-sINITIAL_MEMORY=536870912
|
||
-sMAXIMUM_MEMORY=4294967296
|
||
-sUSE_PTHREADS=1
|
||
-sPTHREAD_POOL_SIZE=4
|
||
-sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2
|
||
-sOFFSCREENCANVAS_SUPPORT=1
|
||
-sFILESYSTEM=1
|
||
```
|
||
|
||
说明:
|
||
|
||
- 开启 pthreads 后,生产服务器必须发送 COOP/COEP,浏览器才能启用 SharedArrayBuffer;不具备跨源隔离时提供单线程构建。
|
||
- `ALLOW_MEMORY_GROWTH` 方便早期验证,但要监控增长造成的暂停;正式版本应根据场景规模设置初始/上限内存。
|
||
- 启用 SIMD 时必须准备不支持 SIMD 的降级包或明确浏览器门槛。
|
||
- Emscripten 的 POSIX 文件系统是内存/虚拟文件系统,不等于 OPFS;主文件仍通过显式 memory buffer 与 OPFS 交换。
|
||
|
||
### 9.4 前端资源拆分
|
||
|
||
建议拆成:
|
||
|
||
```text
|
||
app.js React shell + Three.js
|
||
engine.wasm/js Blender core + C ABI
|
||
engine-manifest.json 版本、能力和资源哈希
|
||
startup-assets/* 默认材质、图标、HDR 缩略资源
|
||
```
|
||
|
||
WASM 文件随发布包放在同源的版本化静态目录,版本号和 SHA-256 写入
|
||
manifest;浏览器运行时禁止从 CDN、远程 ESM 或第三方脚本地址加载。不要把
|
||
`.blend`、字体、HDR 和所有材质预置无条件打进 WASM;它们应作为发布包内的
|
||
可缓存资源或按需写入 OPFS 文件。
|
||
|
||
## 10. 分阶段实施计划
|
||
|
||
### Phase 0:Web 视口和存储 Spike(2~4 周)
|
||
|
||
- React + Three.js + Vite 工程。
|
||
- 直接加载一个 glTF/GLB,完成相机、选择、变换和导出。
|
||
- StorageWorker + IndexedDB 元数据 + OPFS 文件表。
|
||
- 做浏览器能力探测:WebGL2、OffscreenCanvas、SharedArrayBuffer、OPFS、存储配额。
|
||
- 交付:可以离线打开、编辑和保存一个 Three.js 项目。
|
||
|
||
### Phase 1:Blender WASM 解析闭环(4~8 周)
|
||
|
||
- 建立 Blender Web patch 和最小 CMake target。
|
||
- 关闭 Python、Cycles、桌面 GHOST 和不可移植外部依赖。
|
||
- 暴露 `open_blend`、`get_scene_snapshot`、`save_blend`。
|
||
- 用官方/测试 `.blend` 覆盖 mesh、object、material、camera、light、animation。
|
||
- 交付:`.blend` -> WASM -> SceneIR -> Three.js,编辑后可重新保存 `.blend`。
|
||
|
||
### Phase 2:基础建模和增量同步(6~12 周)
|
||
|
||
- BMesh 基础命令:添加 primitive、extrude、inset、bevel、merge、delete、normals。
|
||
- 引擎 revision、SceneDelta、操作日志和撤销/重做。
|
||
- 网格按对象/属性增量更新,不重建整个场景。
|
||
- 交付:可完成简单建模任务,并在刷新页面后恢复。
|
||
|
||
### Phase 3:生产可用子集(3~6 个月)
|
||
|
||
- Modifier 子集、骨骼和关键帧、集合/链接、材质节点子集。
|
||
- glTF/GLB、OBJ、STL、PLY 导入导出;Draco/Meshopt/KTX2。
|
||
- 大场景 LOD、实例化、Worker 渲染、崩溃恢复和配额管理。
|
||
- 可选:把渲染/模拟重任务下沉到服务器 Blender。
|
||
|
||
### Phase 4:高级能力(长期)
|
||
|
||
- 自定义 Blender GHOST Web 后端。
|
||
- Three.js WebGPU renderer、后处理和材质节点适配;不把 Blender GPU backend 纳入主路线。
|
||
- 沙箱化 Python 子集或服务器执行,不在默认浏览器上下文执行任意脚本。
|
||
- 多人协作、CRDT/操作日志同步和服务端渲染。
|
||
|
||
## 11. 主要风险和规避策略
|
||
|
||
| 风险 | 影响 | 规避 |
|
||
| --- | --- | --- |
|
||
| Blender C++ 依赖链很大 | WASM 构建失败、包过大 | 先做 `web_engine`,按模块启用;不要追求全量构建 |
|
||
| `.blend` 数据块直接暴露复杂 | ABI 不稳定 | SceneIR + 版本化 C ABI;只暴露稳定命令 |
|
||
| 三角网格和贴图占用内存 | 标签页崩溃 | 流式加载、分块、压缩、LOD、及时 dispose |
|
||
| WebGL 与 Blender shader 不一致 | 材质效果偏差 | 先支持 PBR 子集;复杂材质交给服务端或离线烘焙 |
|
||
| OPFS 只在 Worker 可用 | 主线程调用失败 | 单独 StorageWorker;所有数据库访问排队 |
|
||
| pthreads 需要 COOP/COEP | 部署后线程失效 | 提供单线程包;服务器配置跨源隔离和资源 CORP |
|
||
| 多标签页同时写项目 | 数据覆盖/锁冲突 | 单写者、BroadcastChannel 锁、revision 检查、临时文件替换 |
|
||
| 任意 Python/插件执行 | 安全风险和不可移植 | 默认关闭;服务器沙箱执行或白名单 API |
|
||
| 浏览器存储配额不足 | 保存失败 | `navigator.storage.estimate()`、持久化请求、导出备份、清理策略 |
|
||
| Three.js 与 Blender 坐标/色彩差异 | 视觉和动画错误 | SceneIR 适配层统一处理,并建立 golden scene 回归测试 |
|
||
|
||
## 12. 验收标准
|
||
|
||
V1 以 Chromium 桌面设备作为发布参考环境,并同时覆盖主线程和 OffscreenCanvas Worker;
|
||
Firefox、Safari/WebKit 留作发布后兼容矩阵。至少验证:
|
||
|
||
1. 空白项目、基础 `.blend` 和含贴图项目可以打开、编辑、保存、刷新恢复。
|
||
2. 保存失败、页面刷新、Worker 异常后不会破坏上一个完整快照。
|
||
3. 100,000 三角形场景中的选择和变换不会重建无关对象。
|
||
4. 变换、网格编辑、材质修改具有可重放的 operation log,撤销/重做结果一致。
|
||
5. `.blend` 读写后通过 Blender 桌面版重新打开,关键对象、材质、动画数据没有丢失。
|
||
6. Three.js 视口在 WebGL2 不可用时给出明确的能力提示;未启用跨源隔离时自动降级单线程。
|
||
7. IndexedDB schema migration、OPFS quota error、损坏快照和多标签页冲突都有自动化测试。
|
||
8. 对同一组 golden `.blend` 生成 SceneIR 和截图,作为跨浏览器回归基线。
|
||
|
||
## 13. 推荐的第一批代码目录
|
||
|
||
```text
|
||
web/
|
||
app/ React + Vite
|
||
components/ Outliner/Inspector/Timeline/Viewport
|
||
engine-client/ TypeScript EngineClient + protocol types
|
||
workers/
|
||
engine.worker.ts
|
||
storage.worker.ts
|
||
render.worker.ts # Phase 3 再启用
|
||
three-adapter/ SceneIR -> Three.js
|
||
storage/ OPFS files + IndexedDB schema/migrations
|
||
protocol/ versioned commands/events
|
||
blender-5.2.0/
|
||
source/blender/web_api/
|
||
build_files/cmake/platform/web.cmake
|
||
intern/ghost/web/ # Phase 4
|
||
```
|
||
|
||
## 14. 最终建议
|
||
|
||
先实现“Web 端 Blender 数据编辑器”,不要以“浏览器里完整复刻桌面 Blender”作为第一里程碑。具体决策如下:
|
||
|
||
1. **Three.js WebGL2 作为首期唯一客户端渲染路径。** WebGPU 可作为后续 Three.js renderer 增强,不阻塞 MVP;浏览器端不启用 Blender Eevee/Cycles。
|
||
2. **Blender WASM 只负责 `.blend`、SceneIR 和建模命令;React 负责 Blender 风格 UI。** 不移植桌面 C++ UI/GHOST/GPU 后端,但保留 Window/Screen/Workspace/Area/Region/Editor、上下文和快捷键的用户可见语义。
|
||
3. **OPFS 文件保存大对象,IndexedDB 保存索引和操作摘要。** 不使用数据库行存储顶点。
|
||
4. **单线程优先交付,pthreads 作为可选构建。** 生产环境需要 COOP/COEP 时再打开多线程。
|
||
5. **用 golden `.blend` 建立回归测试。** 坐标、单位、颜色管理、动画和材质映射是最容易出现隐性错误的地方。
|
||
6. **复杂最终渲染和完整 Python 采用“浏览器轻量编辑 + Blender 服务端”混合架构。** 浏览器交互仍由 Three.js 负责,不会把客户端实时渲染切回 Blender。
|
||
|
||
## 15. 连续执行计划和功能对标
|
||
|
||
详细的连续任务、原子工作包(A-01~K-04)、前置依赖、交付物、验证命令、回滚规则、验收标准、P0/P1/P2 功能矩阵和首个 Sprint 见:[WEB_BLENDER_MIGRATION_EXECUTION_PLAN.md](/home/mes123456/workinf_Blender_Wasm/WEB_BLENDER_MIGRATION_EXECUTION_PLAN.md)
|
||
|
||
执行顺序固定为:
|
||
|
||
```text
|
||
基线 -> Web 壳 -> Blender WASM 最小引擎 -> SceneIR
|
||
-> OPFS/IndexedDB 持久化 -> Three.js 视口
|
||
-> 核心建模 -> 材质/动画 -> 导入导出
|
||
-> 性能/安全/发布 -> P2 高级能力
|
||
```
|
||
|
||
每个阶段都必须先形成可运行版本,再进入下一阶段;P2 功能不能阻塞 P0/P1 的可用编辑器。
|
||
|
||
### 15.1 三角面简化、轻量化和层级专项门槛
|
||
|
||
这两个专项不能被当作“显示优化”或“Three.js 适配细节”,而是 Blender 文件可恢复性和模型可用性的核心契约:
|
||
|
||
| 专项 | Blender 权威来源 | Web 实现要求 | 发布门槛 |
|
||
| --- | --- | --- | --- |
|
||
| 三角面简化 | `MOD_decimate.cc`、`bmesh_decimate_*`、Decimate RNA | Blender WASM 负责 Collapse/Un-Subdivide/Planar 求值;保留 ratio、iterations、angle、vertex group、symmetry、triangulate、delimit 和属性保护;Three.js 只消费 LOD | W-079 golden 对照、误差预算、1M 三角形内存/性能基准通过 |
|
||
| 轻量化 LOD | evaluated mesh、modifier stack、导出 manifest | 原始 mesh 不变;按 mesh revision/profile hash 缓存;按屏幕空间误差选择 LOD;记录三角形、顶点、材质、纹理和显存预算 | P0 可预览/导出,P1 多级 LOD 和 GLB manifest 可追踪 |
|
||
| 对象层级 | `DNA_object_types.h`、depsgraph | 保留 parent、parent type、parent bone、parent inverse、local/world matrix、collection 和 keep-world 重定父级语义 | W-089/W-097 父子、矩阵、循环和负缩放回归通过 |
|
||
| 关节和蒙皮 | `DNA_armature_types.h`、Armature modifier、PoseBone | 分离 EditBone、Bone、PoseBone;保留 rest/pose 矩阵、约束、顶点组权重、逆绑定和 joint remap | W-098/W-099 骨架编辑、姿态、蒙皮、LOD 和 GLB 往返通过 |
|
||
|
||
详细执行顺序、SceneIR 字段、任务依赖和测试样例见 [WEB_BLENDER_MIGRATION_EXECUTION_PLAN.md](/home/mes123456/workinf_Blender_Wasm/WEB_BLENDER_MIGRATION_EXECUTION_PLAN.md) 的 W-073~W-099。
|
||
|
||
### 15.2 Blender 界面对标门槛
|
||
|
||
| UI 能力 | 首期范围 | 验收方式 |
|
||
| --- | --- | --- |
|
||
| Workspace/Area/Region/Editor | P0 | 默认 Layout、Layout/Modeling/Animation 切换、Area resize/split/join/maximize、Editor 切换和布局恢复 |
|
||
| Header/Toolbar/Sidebar/Properties/Outliner/Timeline | P0 | 面板上下文、折叠、选中同步、模式显示和操作结果与 Blender 数据一致 |
|
||
| Context 和快捷键 | P0/P1 | 鼠标所在 Area -> Editor -> Mode -> 全局的路由顺序正确;Tab、模式切换、视图快捷键和面板快捷键互不抢占 |
|
||
| Operator Search 和菜单 | P0/P1 | F3 能按当前 Context 过滤操作;命令通过 `EngineClient` 提交并产生可撤销 revision |
|
||
| UI 持久化和响应式布局 | P0 | IndexedDB/OPFS 保存 WorkspaceIR 和 WebWorkspaceState;桌面分栏与移动端抽屉共享同一协议 |
|
||
| UI golden 和可访问性 | P1 | Playwright 截图、键盘操作、焦点、缩放、窄屏和错误状态均有回归;颜色相似不能代替行为验收 |
|
||
|
||
详细任务为 W-018/W-019,视口交互和操作注册分别由 W-050~W-058、W-069~W-070 接续完成。
|
||
|
||
## 16. 参考资料
|
||
|
||
- [Emscripten pthreads](https://emscripten.org/docs/porting/pthreads.html)
|
||
- [Emscripten File System API / WasmFS](https://emscripten.org/docs/api_reference/Filesystem-API.html)
|
||
- [Emscripten WebGL support](https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html)
|
||
- [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
|
||
- [Origin Private File System](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system)
|
||
- [Three.js WebGLRenderer](https://threejs.org/docs/pages/WebGLRenderer.html)
|
||
- [Three.js GLTFLoader](https://threejs.org/docs/pages/GLTFLoader.html)
|
||
- [Three.js OffscreenCanvas](https://threejs.org/manual/en/offscreencanvas.html)
|
||
- [React state management](https://react.dev/learn/managing-state)
|
||
- [Blender Decimate Modifier](https://docs.blender.org/manual/en/5.0/modeling/modifiers/generate/decimate.html)
|
||
- [Blender Armature Structure](https://docs.blender.org/manual/en/5.0/animation/armatures/structure.html)
|
||
- [Blender Armature Skinning Introduction](https://docs.blender.org/manual/en/3.2/animation/armatures/skinning/introduction.html)
|
||
- [Blender Workspaces](https://docs.blender.org/manual/en/5.0/interface/window_system/workspaces.html)
|
||
- [Blender Areas](https://docs.blender.org/manual/en/2.82/interface/window_system/areas.html)
|
||
- [Blender Tabs and Panels](https://docs.blender.org/manual/en/5.0/interface/window_system/tabs_panels.html)
|
||
- [Blender Outliner](https://docs.blender.org/manual/en/5.0/editors/outliner/introduction.html)
|