37 KiB
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. 目标和非目标
首期目标
- 浏览器中创建、打开、修改和导出一个 3D 项目。
- 支持
.blend导入,内部使用统一的 SceneIR,Three.js 实时显示。 - 支持基础对象操作:选择、移动、旋转、缩放、复制、删除、集合管理。
- 支持基础网格编辑:顶点/边/面数据读取、局部修改、法线重算、三角化、基础变换。
- 支持基础材质、贴图、灯光、相机和关键帧动画。
- 通过 OPFS 自动保存,刷新页面后恢复项目;支持导出
.blend、.glb和贴图包。 - 在没有网络的情况下继续工作,网络只用于首次加载和可选的协作/同步功能。
- 三角面简化由 Blender WASM 的 Decimate/BMesh 求值,支持 Collapse、Un-Subdivide、Planar/Dissolve、属性保护和多级 LOD;Three.js 只选择和渲染 LOD。
- 层级和关节定义对齐 Blender:对象父子、Rest 骨架、PoseBone、Armature modifier、顶点组权重、逆绑定矩阵和基础约束均进入版本化 SceneIR。
- 提供 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. 总体架构
┌─────────────────────────────────────────────────────────────────┐
│ 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.hhblender-5.2.0/source/blender/blenloader/BLO_writefile.hhblender-5.2.0/source/blender/blenkernel/BKE_blendfile.hh
4.2 稳定的 C ABI
不要把 C++ 类、STL 容器或 Embind 类型直接暴露给 React。建议增加 source/blender/web_api/,只导出 C ABI:
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 的解耦层。建议第一版定义为:
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 上下文。
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
适配器职责:
MeshIR->BufferGeometry,按属性是否存在动态设置 attribute。- index 使用
Uint16Array或Uint32Array,超过 16 位时检测 WebGL2 能力。 - 重复对象优先使用
InstancedMesh,避免创建大量相同 Geometry。 - Blender 的 Principled BSDF 子集映射到
MeshStandardMaterial/MeshPhysicalMaterial。 - 图片从 OPFS 读取为 Blob/
ImageBitmap,交给TextureLoader或ImageBitmapLoader;不把大图转成 base64。 - 相机、灯光、雾、世界颜色和曝光在一个
RenderSettings适配器中统一处理。 - 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。
推荐模式:
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:
// 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 推荐表结构
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 文件导入、保存和恢复
<input type=file>得到File,使用arrayBuffer()传入 EngineWorker。- 引擎调用
BLO_read_from_memory(),建立 Blender 数据块和 SceneIR。 - SceneIR 首屏增量传给 Three.js,贴图通过 asset hash 延迟加载。
- 保存时引擎生成内存
.blendbuffer,StorageWorker 写入临时 OPFS 文件。 - 写完并校验 SHA-256 后,IndexedDB 在一个事务中更新
currentRevision和snapshot元数据。 - 写入采用
scene.blend.tmp->scene.blend的替换策略,页面崩溃时根据 manifest 恢复最近一个完整文件。 - 用户显式导出时,从 OPFS 读取文件并创建 Blob 下载;浏览器没有“写任意本地路径”的权限,不能模拟桌面 Save As。
7. React 前端设计
7.1 状态分层
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 的上下文分发 |
实现要求:
- 默认启动布局接近 Blender Layout:顶部 Workspace tabs,中央 3D Viewport,右上 Outliner,右侧 Properties,底部 Timeline,Viewport 自带 Header/Toolbar/Sidebar。
- Area 边界可以拖动调整大小;支持 split、join、最大化当前 Area、切换 Editor 类型。移动端采用可折叠抽屉,但不改变编辑器和命令语义。
- 每个 Editor 都有独立 Header、Context 和快捷键范围。鼠标/键盘事件先由
UIContextIR路由,再决定是 UI 操作、Three.js 视口预览还是 Blender WASM 命令。 - UI 布局和用户偏好存入 IndexedDB/OPFS;项目
.blend中可表达的 Workspace 信息要保留,Web 专属面板状态使用版本化WebWorkspaceState扩展,不能覆盖 Blender 数据块。 - 主题、字号、DPI、图标和颜色采用 Blender 风格的密度和层级,但不复制 Blender 的版权素材;所有图标由现有图标库或项目自有资源提供。
- 面板显示的是 Blender RNA/SceneIR 摘要,提交通过
EngineClient;React 不直接改变对象、网格、骨骼或材质数据。
UI 对标验收不是“截图颜色相似”,而是同一操作在对应的 Editor/Mode/Context 下可完成、快捷键作用域一致、布局刷新后可恢复、命令结果与 Blender 数据一致。桌面 Blender 的 UI 组织可参考官方对 Workspaces、Areas、Tabs & Panels 的定义。
8. Worker 消息协议
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:
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 配置:
-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 前端资源拆分
建议拆成:
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. 验收标准
以 Chromium、Firefox、Safari 最近稳定版各一台桌面设备作为参考环境,至少验证:
- 空白项目、基础
.blend和含贴图项目可以打开、编辑、保存、刷新恢复。 - 保存失败、页面刷新、Worker 异常后不会破坏上一个完整快照。
- 100,000 三角形场景中的选择和变换不会重建无关对象。
- 变换、网格编辑、材质修改具有可重放的 operation log,撤销/重做结果一致。
.blend读写后通过 Blender 桌面版重新打开,关键对象、材质、动画数据没有丢失。- Three.js 视口在 WebGL2 不可用时给出明确的能力提示;未启用跨源隔离时自动降级单线程。
- IndexedDB schema migration、OPFS quota error、损坏快照和多标签页冲突都有自动化测试。
- 对同一组 golden
.blend生成 SceneIR 和截图,作为跨浏览器回归基线。
13. 推荐的第一批代码目录
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”作为第一里程碑。具体决策如下:
- Three.js WebGL2 作为首期唯一客户端渲染路径。 WebGPU 可作为后续 Three.js renderer 增强,不阻塞 MVP;浏览器端不启用 Blender Eevee/Cycles。
- Blender WASM 只负责
.blend、SceneIR 和建模命令;React 负责 Blender 风格 UI。 不移植桌面 C++ UI/GHOST/GPU 后端,但保留 Window/Screen/Workspace/Area/Region/Editor、上下文和快捷键的用户可见语义。 - OPFS 文件保存大对象,IndexedDB 保存索引和操作摘要。 不使用数据库行存储顶点。
- 单线程优先交付,pthreads 作为可选构建。 生产环境需要 COOP/COEP 时再打开多线程。
- 用 golden
.blend建立回归测试。 坐标、单位、颜色管理、动画和材质映射是最容易出现隐性错误的地方。 - 复杂最终渲染和完整 Python 采用“浏览器轻量编辑 + Blender 服务端”混合架构。 浏览器交互仍由 Three.js 负责,不会把客户端实时渲染切回 Blender。
15. 连续执行计划和功能对标
详细的连续任务、原子工作包(A-01~K-04)、前置依赖、交付物、验证命令、回滚规则、验收标准、P0/P1/P2 功能矩阵和首个 Sprint 见:WEB_BLENDER_MIGRATION_EXECUTION_PLAN.md
执行顺序固定为:
基线 -> 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 的 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-050W-058、W-069W-070 接续完成。
16. 参考资料
- Emscripten pthreads
- Emscripten File System API / WasmFS
- Emscripten WebGL support
- IndexedDB API
- Origin Private File System
- Three.js WebGLRenderer
- Three.js GLTFLoader
- Three.js OffscreenCanvas
- React state management
- Blender Decimate Modifier
- Blender Armature Structure
- Blender Armature Skinning Introduction
- Blender Workspaces
- Blender Areas
- Blender Tabs and Panels
- Blender Outliner