Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,613 @@
# 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` 导入,内部使用统一的 SceneIRThree.js 实时显示。
3. 支持基础对象操作:选择、移动、旋转、缩放、复制、删除、集合管理。
4. 支持基础网格编辑:顶点/边/面数据读取、局部修改、法线重算、三角化、基础变换。
5. 支持基础材质、贴图、灯光、相机和关键帧动画。
6. 通过 OPFS 自动保存,刷新页面后恢复项目;支持导出 `.blend``.glb` 和贴图包。
7. 在没有网络的情况下继续工作,网络只用于首次加载和可选的协作/同步功能。
8. 三角面简化由 Blender WASM 的 Decimate/BMesh 求值,支持 Collapse、Un-Subdivide、Planar/Dissolve、属性保护和多级 LODThree.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、KTX2Three.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` bufferStorageWorker 写入临时 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底部 TimelineViewport 自带 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/ClangNode.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 0Web 视口和存储 Spike2~4 周)
- React + Three.js + Vite 工程。
- 直接加载一个 glTF/GLB完成相机、选择、变换和导出。
- StorageWorker + IndexedDB 元数据 + OPFS 文件表。
- 做浏览器能力探测WebGL2、OffscreenCanvas、SharedArrayBuffer、OPFS、存储配额。
- 交付:可以离线打开、编辑和保存一个 Three.js 项目。
### Phase 1Blender 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. 验收标准
以 Chromium、Firefox、Safari 最近稳定版各一台桌面设备作为参考环境,至少验证:
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)