Files
workinf_Blender_Wasm/WEB_BLENDER_TECHNICAL_ROADMAP.md
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

765 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Blender Web 化技术路线(React + Three.js + WebAssembly + OPFS/IndexedDB)
> 历史技术背景:本文不参与当前任务领取。当前任务只认 `docs/EXECUTION_QUEUE.md` 和最小上下文工具。
> **存储决策(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. 模块化开发与 6,620+ 操作符验证规范
本节把“完整移植”落实为可执行的模块合同。完整移植的长期目标是覆盖 Blender 的
数据模型、操作语义、编辑器工作流和文件生态;发布仍按能力等级逐步交付,不把
“已经注册”或“Three.js 能显示”误认为 Blender 语义已经完成。当前能力、任务顺序和
证据以 `docs/EXECUTION_QUEUE.md`、任务 manifest/status 和可复验命令为准。
### 16.1 分层架构和模块边界
所有能力必须落在四层之一,并通过版本化协议连接。浏览器 UI 不直接修改 WASM 堆,
Three.js 不承担 Blender 语义求值。
| 层 | 责任 | 首批模块 | 权威状态 |
| --- | --- | --- | --- |
| 核心层 | DNA/RNA 对齐、数据块生命周期、数学库、Main、revision 和 depsgraph | `core`、`data`、`math`、`depsgraph` | Blender WASM Main |
| 操作层 | 面向用户的 operator、共享算法、参数校验、撤销/重做 | `mesh`、`object`、`material`、`animation`、`render` | EngineWorker |
| UI 层 | Workspace/Area/Region/Editor、Context、Keymap、属性面板和视口交互 | `viewport`、`outliner`、`properties`、`timeline`、`node-editor` | React UI state + Engine revision |
| 系统层 | `.blend` 文件、导入导出、Worker、插件能力声明、缓存、性能和安全 | `file-io`、`storage`、`workers`、`plugin`、`performance` | StorageWorker/manifest |
每个模块必须声明 `moduleId`、owner、输入/输出 schema、依赖模块、生产入口、测试入口、
资源预算和回滚点。模块只通过 `EngineClient`、SceneIR 或协议类型访问其他模块;禁止
在 React 组件中复制 Blender 数据结构,禁止在 checker 中构造生产结果。
推荐的实现分工如下:TypeScript 负责协议、注册表、UI 和测试编排;Blender C/C++
以 WASM 负责 DNA/RNA、Main、依赖图和需要保持语义一致的算法;Three.js 只负责
SceneIR 的实时渲染;React(或等价的 Vue 层)负责 Blender 风格的编辑器组合和输入路由。
### 16.2 操作符注册和统一执行上下文
操作符以稳定 ID 注册,按模块和类别批量装载。一个 operator 不是一个独立生产任务,
而是共享算法引擎的参数化声明。建议的最小模型如下:
```ts
type OperatorDefinition<Args, Result> = {
id: string; // e.g. "mesh.extrude_region"
moduleId: string;
category: "mesh" | "object" | "material" | "animation" | "render";
dependsOn: string[]; // operator or module IDs
poll(ctx: OperatorContext, args: Args): PollResult;
execute(ctx: OperatorContext, args: Args): Result;
serialize?(result: Result): SceneDelta;
};
type OperatorContext = {
main: MainHandle;
sceneRevision: number;
sceneId: string;
mode: "OBJECT" | "EDIT" | "POSE" | "SCULPT";
selection: StableId[];
depsgraph: DepsgraphSnapshot;
budget: { timeMs: number; memoryBytes: number };
signal: AbortSignal;
};
```
注册流程固定为:加载模块 manifest -> 校验 schema/hash -> 按依赖拓扑排序 -> 批量注册
定义 -> 冻结 registry snapshot -> 执行命令。`poll` 只做上下文和参数检查;`execute`
只能通过 `MainHandle` 提交事务,返回新的 revision、SceneDelta 和结构化 report。依赖
缺失、schema 不兼容、取消、超时、超预算和重复提交都必须 fail closed,不能修改已提交
的 Main 或 revision。
共享算法应通过参数配置覆盖操作符变体,例如 mesh 的选择集、法线、拓扑和属性保护
策略由一个 mesh transaction engine 实现,`extrude`、`inset`、`bevel` 等只提供不同
参数和上下文约束。只有当操作符具有不可共享的 Blender 语义时,才允许新增专用 engine,
并在模块报告中说明原因。
### 16.3 批量开发协议
开发以模块为最小交付单元,禁止按单个操作符拆分生产任务或逐条手工提交。每个模块
按以下顺序完成:
1. **契约门**:冻结 operator schema、错误码、稳定 ID、依赖、预算、revision 和 Main 回滚规则。
2. **生产门**:实现共享算法、模块 registry、EngineWorker 入口和 React/Three.js 所需的最小接线。
3. **负例门**:覆盖 malformed、unsupported、poll 失败、取消、迟到、重复、超预算和 hash drift;失败时保持 `in_progress`。
4. **批量门**:在同一份不可变 catalog 快照上运行该模块的全部映射 cases,并按确定性 shard 分片。
5. **证据门**:生成 desktop/WASM 对照、save/reopen、报告、artifact SHA-256、manifest、status 和回滚说明。
模块合并前必须运行受影响模块的全量 matrix;模块依赖闭包变化时,扩展到依赖模块。生产
提交可以一次包含多个 operator 定义,但只能对应一个模块行为或一个独立证据变化,避免
把“注册完成”提前当成“能力完成”。
### 16.4 6,620+ 操作符验证矩阵
操作符目录从固定版本的 Blender 文档/RNA/源码生成,并写入带 source hash 的机器 catalog。
目录规模以 pinned catalog 为准,当前目标是覆盖 **6,620+** 条操作符记录;文档中的数量
不是可手工修改的完成度指标。每条记录至少包含:
- `operatorId`、`moduleId`、Blender 版本、来源文件和 `behaviorClass`;
- 所需 `poll` context、参数 schema、默认值、前置数据块和能力等级;
- 正例 fixture、边界输入、随机种子和预期 invariants;
- desktop/WASM runner、save/reopen 策略、资源预算和已知阻断原因。
每个模块 runner 对目录执行三类用例:
| 用例 | 目的 | 最小断言 |
| --- | --- | --- |
| 基础用例 | 证明正常上下文和典型参数 | operator 返回成功,Main/revision/SceneIR 改变符合 schema |
| 边界用例 | 证明空选择、退化拓扑、缺失引用、非法参数和预算边界 | 稳定错误码,旧 Main、revision 和文件保持不变 |
| 随机用例 | 发现参数组合和依赖顺序问题 | 固定 seed 可重放;不变量、内存和时间预算不漂移 |
正例必须比较 Blender desktop golden 与 WASM/Main 结果,并在需要时保存、关闭、重新打开
后再次比较。Three.js 的像素或对象存在性只能作为呈现证据,不能替代 Blender 语义结果。
不支持或证据不足的 operator 保持 `BLOCKED`/`METADATA_ONLY`,不得伪造 `PASS`。
### 16.5 Worker 并行、缓存和增量验证
批量验证由 Node 编排器生成确定性 shard,并在 Chromium-only 约束下将独立 case 分发到
Web Workers。每个 shard 使用独立的临时目录和 WASM 实例;worker 返回结构化结果,不共享
可变 Main。聚合器按 `operatorId` 排序后生成报告,保证并行度变化不会改变 artifact hash。
缓存键至少包括:Blender/WASM 构件 hash、catalog source hash、module registry snapshot、
fixture hash、runner 版本、浏览器能力和随机 seed。缓存命中只能跳过完全相同的 case;
schema、算法、依赖、fixture 或浏览器能力变化时必须失效。
增量验证按“变更模块 + 依赖闭包 + 受影响 catalog 行”选择 case;模块未变化且缓存有效的
行可以复用,但每次模块合并仍要执行一次全量模块 matrix。报告应同时给出总量、执行量、
缓存命中量、`PASS`、`FAIL`、`BLOCKED`、`RETAINED_NEGATIVE`、耗时、峰值内存和覆盖率,
不能只输出一个百分比。
### 16.6 完成门和报告格式
模块只有在以下条件全部满足时才可进入 `done`:
1. registry snapshot、schema hash 和 catalog source hash 稳定。
2. desktop/WASM 正例、边界/随机负例和 save/reopen 结果齐全。
3. Worker shard 与串行重跑结果一致,缓存命中不会掩盖失败。
4. Main revision、文件 hash、SceneIR invariants 和资源预算均无未解释漂移。
5. 报告、manifest、status 和聚焦命令可复验;失败或环境缺失时保持 `in_progress`/`blocked`。
建议的聚合报告字段:
```json
{
"schemaVersion": 1,
"moduleId": "mesh",
"catalogSourceHash": "sha256:...",
"registryHash": "sha256:...",
"counts": { "total": 0, "executed": 0, "cached": 0, "pass": 0, "fail": 0, "blocked": 0 },
"coverage": { "registered": 0, "withFixture": 0, "withDesktopGolden": 0 },
"cases": [],
"artifacts": []
}
```
报告只描述实际运行结果;不能通过修改 Markdown checkbox、手工推进 `nextTask` 或删除
失败 case 来提高覆盖率。任务交接继续使用 `node tools/web/check-task-context.mjs --task
<task-id> --write`,并遵守当前队列的最小上下文和 Chromium 验收规则。
## 17. 参考资料
- [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)