# Web Blender PBR 渲染实现与任务分解 更新日期:2026-08-09 ## 1. 目标和声明边界 目标是在浏览器 3D Viewport 中提供与 Blender Principled 工作流一致、可保存、可重开的 PBR 预览。材质参数必须来自 Blender Main,经 SceneIR 传输并由主线程和 OffscreenCanvas Worker 使用相同映射,禁止只修改 Three.js 临时状态。 当前能力称为 `physical-v1`,不是 Cycles、EEVEE 或 Blender Shader Node 的逐像素等价实现。 它保证的是有界 Principled 参数、线性色彩输入、sRGB 输出、ACES Filmic tone mapping、 World exposure、场景灯光和有界 PCF 阴影的一致 WebGL 渲染。未声明节点、谱渲染、光程、体积、 次表面、多次散射和离线 path tracing 必须继续结构化阻断或降级并给出 warning。 ## 2. 权威数据流 ```text Blender Main -> .blend writer/save/reopen -> native blend reader -> SceneIR MaterialIR/LightIR/WorldIR -> transferable mesh buffers -> shared pbr.ts mapping -> main-thread WebGLRenderer -> OffscreenCanvas render Worker ``` 编辑链路为:Inspector 控件 -> `setMaterialPrincipled` -> EngineWorker -> Blender Main 节点 socket -> 新 revision SceneIR -> 视口重建。任何新增参数必须同时通过命令校验、Main 写回、 reader、SceneIR parser、保存重开和双后端验收。 ## 3. Blender 到 Web 的材质映射 | Blender 5.2 Principled 输入 | SceneIR | Three.js | 范围/说明 | | --- | --- | --- | --- | | Base Color | `baseColor` | `color` | 线性 RGB,alpha 独立处理 | | Roughness | `roughness` | `roughness` | `[0,1]` | | Metallic | `metallic` | `metalness` | `[0,1]` | | IOR | `ior` | `ior` | 当前限定 `[1,2.333]` | | Specular IOR Level | `specularIORLevel` | `specularIntensity` | Blender 中性值 0.5 映射到 Three 中性值 1.0;高半区有界截断 | | Transmission Weight | `transmissionWeight` | `transmission` | `[0,1]`,使用物理透射 pass | | Coat Weight | `coatWeight` | `clearcoat` | `[0,1]` | | Coat Roughness | `coatRoughness` | `clearcoatRoughness` | `[0,1]` | | Emission Color | `emissionColor` | `emissive` | 线性 RGB | | Emission Strength | `emissionStrength` | `emissiveIntensity` | `[0,1000000]` | | Alpha | `alpha` | `opacity/transparent/depthWrite` | alpha blend 时关闭 depth write | 材质选择高亮保存原始 emission 和强度,取消选择后恢复,不再破坏自发光材质。所有网格、 实例和 LOD 都启用一致的 cast/receive shadow 标志。 ## 4. 色彩、灯光和阴影 - Blender/SceneIR 数值按线性工作空间进入材质和灯光。 - WebGL 输出固定为 `SRGBColorSpace`,tone mapping 固定为 `ACESFilmicToneMapping`。 - `WorldIR.exposure` 按摄影 stop 转换为 `2 ** exposure`,并限制在 `[-8,8]`。 - 无场景灯时使用低强度中性 Hemisphere fill 和有阴影的 key light,确保空场景可检查。 - POINT、SUN、SPOT、AREA 均从 `LightIR` 创建。SUN/SPOT 根据 Blender 本地 `-Z` 方向创建真实 Three target,不再错误地全部指向世界原点。 - SUN、SPOT、POINT 使用 `PCFShadowMap`,第一质量档为 1024 平方 shadow map,并设置 有界 bias、normal bias、near/far。AREA 受 Three WebGL 能力限制,当前不声明阴影等价。 - 主线程与 Worker 暴露 `data-pbr-profile`、`data-tone-mapping` 和 `data-shadow-map`,供 自动化测试核对真实后端配置。 ## 5. 资产和纹理边界 当前 SceneIR 已表达 UV、Image Texture、Normal Map、packed image 和资源状态,但纹理像素 进入 GPU 的链路必须继续遵守 EngineWorker/StorageWorker 权威模型。浏览器渲染器不得根据 `.blend` 内路径自行访问文件系统或网络。 PBR-007 已建立 `GPUTextureAsset` 只读载荷:`assetId`、`imageId`、SHA-256、MIME、尺寸、 颜色空间、用途、UDIM tile 和 `ArrayBuffer` 必须通过字节数、64 MiB 单图、16384 维度和 SHA-256 校验后才可进入 `ImageBitmap`/Three `Texture`。主线程与 Offscreen Worker 共享同一 `GPUTextureStore` 逻辑,载荷状态和拒绝字节数可观测;路径仍只能由 EngineWorker/StorageWorker 解析,浏览器不得访问 `.blend` 内路径。当前不把 context-loss 自动恢复、mipmap/anisotropy 策略和完整 UV Map 节点映射误报为完成。 ## 6. 质量档和性能预算 | 档位 | 阴影 | DPR | 透射 | 目标 | | --- | --- | --- | --- | --- | | Low | 关闭或 512 | 1 | 关闭 | 低内存移动设备 | | Medium | PCF 1024 | `min(device,1.5)` | 0.5 分辨率 | 默认移动/集显 | | High | PCF 1024/2048 | `min(device,2)` | 全分辨率 | 默认桌面 | `physical-v1` 当前实现 High 的有界子集。后续自动降档的输入包括 GPU tier、draw call、 transmission material 数、shadow caster 数、WASM/GPU 内存压力和帧时间 P95。质量切换只能 改变表现质量,不得改变 Main 或保存语义。 ## 7. 任务分解与执行顺序 ### PBR-001 协议与安全门:done - PBR-001-A:扩展 `MaterialIR` 五个物理参数,保持旧 snapshot 可解析。 - PBR-001-B:对比例参数验证 `[0,1]`,对 emission strength 设硬上限。 - PBR-001-C:扩展 `setMaterialPrincipled`,所有字段保持 optional 兼容旧客户端。 - PBR-001-D:linked material 继续拒绝写回;NaN/Infinity 继续由 native 阻断。 ### PBR-002 Blender Main authoring:done - PBR-002-A:自建 Principled 节点补齐五个 Blender 5.2 socket。 - PBR-002-B:命令写入 Base/Roughness/Metallic/IOR/Specular/Transmission/Coat/Emission。 - PBR-002-C:native reader 从真实 socket 读取并生成 SceneIR。 - PBR-002-D:Main round-trip 和 save/reopen 核对每个物理参数误差小于 `1e-5`。 ### PBR-003 共享物理材质适配器:done - PBR-003-A:用 `MeshPhysicalMaterial` 替代两条路径的 Standard/fixed material。 - PBR-003-B:集中 clamp、默认值、透明深度策略和 selection emission 恢复。 - PBR-003-C:主线程、Worker、实例和 LOD 使用同一映射。 - PBR-003-D:Worker 恢复 UV、corner color 和多材质 triangle group。 ### PBR-004 色彩管理和曝光:done - PBR-004-A:线性输入、sRGB output、ACES Filmic。 - PBR-004-B:World exposure 按 stop 映射并限制极端值。 - PBR-004-C:通过 canvas profile 属性和像素非空门验证双后端。 ### PBR-005 灯光和阴影:done_current_scope - PBR-005-A:POINT/SUN/SPOT/AREA 类型映射。 - PBR-005-B:修复 SUN/SPOT 方向 target。 - PBR-005-C:SUN/SPOT/POINT PCF 阴影、bias 和 shadow camera 边界。 - PBR-005-D:待补能量单位校准 fixture、级联太阳阴影、AREA 阴影替代和 caster budget。 ### PBR-006 材质 Inspector:done_current_scope - PBR-006-A:增加 IOR、Specular、Transmission、Coat、Coat Roughness、Emission Strength。 - PBR-006-B:应用命令保留 alpha 和 emission color。 - PBR-006-C:待补材质槽选择、emission color、alpha mode、双面和 blend mode 面板。 ### PBR-007 纹理像素闭环:in_progress(AssetPayload/解码已完成,GPU 生命周期仍有安全门) - PBR-007-A:done_current_scope;`render-assets.ts` 定义 schema v1、SHA-256、MIME/尺寸/字节预算和稳定错误码,App 从 `requestAsset` 生成载荷并同时写入 OPFS。 - PBR-007-B:done_current_scope;Base Color/Emission 使用 sRGB,Normal/Data 使用 non-color/linear,主线程和 Worker 使用相同 `GPUTextureStore`。 - PBR-007-C:in_progress;ImageBitmap 解码、旧 Texture dispose、状态回传已完成;mipmap、anisotropy、GPU context-loss 恢复和全局显存预算仍未开放。 - PBR-007-D:Normal Map 切线空间、UV Map 选择、wrap/filter 和缺失纹理 fallback。 - PBR-007-E:in_progress;双后端同一载荷正例已覆盖,context-loss/Worker crash 后重新上传仍属于 PBR-011 故障注入门。 ### PBR-008 UDIM 与外部资源:in_progress(manifest/阻断已完成,多 tile 采样未声明) - PBR-008-A:done_current_scope;`UDIMManifest` 校验 1001..1999、u/v 坐标、重复 tile、数量上限和 packed 状态;每个 tile 进入同一 SHA-256 载荷。 - PBR-008-B:in_progress;当前只允许单 tile 安全上传,多 tile 返回 `UDIM_MULTI_TILE_UNAVAILABLE`,尚未实现分页、LRU 和 GPU atlas/array 采样。 - PBR-008-C:done_current_scope;linked library、外部路径和缺 tile 使用结构化门,禁止任意路径读取。 - PBR-008-D:done_current_scope;generated/sequence/movie 不生成静态 GPU 载荷,保持独立 capability。 ### PBR-009 IBL 和 World Nodes:in_progress(有限 raster equirectangular IBL) - PBR-009-A:in_progress;packed PNG/JPEG equirectangular 环境可经 GPU 载荷、PMREMGenerator 生成反射环境;HDR/EXR 解码尚未实现并返回 `IBL_FORMAT_UNSUPPORTED`。 - PBR-009-B:in_progress;WorldIR 已携带 environment image/strength/rotation 字段,当前只消费 strength;旋转、跨场景缓存和完整 HDR 色域仍待安全门。 - PBR-009-C:done_current_scope;`backgroundVisible` 与 `scene.environment` 生命周期分离,旧 PMREM target 会释放。 - PBR-009-D:done_current_scope;任意 World Shader Node 不进入纹理上传,保持结构化阻断。 ### PBR-010 GLB PBR 扩展:done_current_scope - PBR-010-A:已导出 `KHR_materials_ior`、`transmission`、`clearcoat`、`specular`、`emissive_strength`,并保留无完整节点图时的有界 `imageIds` Base Color/Normal 路径。 - PBR-010-B:桌面 Blender 再导入比较覆盖 8x8 packed texture、骨骼、shape key、动作和材质基础值。 - PBR-010-C:不支持的 Blender 图返回 `SHADER_GRAPH_UNMAPPABLE`,禁止静默烘焙。 - PBR-010-D:advanced extension fixture 已验证桌面 Blender 对 IOR、Specular IOR Level、Transmission、Coat 和 Emission Strength 的导入数值。 ### PBR-011 性能、容错和跨浏览器:planned - PBR-011-A:100k/1M 三角形下记录 CPU/GPU 帧时、draw call、shader compile 和显存。 - PBR-011-B:Chromium 主线程/OffscreenCanvas 的 ACES、transmission 与 shadow 差异矩阵。 - PBR-011-C:WebGL context loss、Worker crash、纹理解码失败和 GPU OOM 故障注入。 - PBR-011-D:自动降档必须可观测且不修改权威数据。 ### PBR-012 高级渲染:in_progress(能力查询/阻断协议,运行时家族仍未声明) - PBR-012-A:done_current_scope;`queryRenderCapability` 明确返回 `WEBGPU_RENDERER_UNAVAILABLE`,没有 WebGPU renderer bundle 时不伪装为 WebGPU。 - PBR-012-B:done_current_scope(安全门);BLOOM/SSAO/SSR/TAA/DOF/MOTION_BLUR 未有真实 pass 时返回 `POSTPROCESS_PASS_UNAVAILABLE`,未声明假实现。 - PBR-012-C:done_current_scope(安全门);Volume/SSS 返回独立 `VOLUME_SHADER_UNAVAILABLE`/`SUBSURFACE_SHADER_UNAVAILABLE`,任意 Shader node 返回 `SHADER_NODE_UNSUPPORTED`。 - PBR-012-D:浏览器 path tracer 若引入,作为独立 Render backend,不声称 Cycles 等价。 ## 8. 当前验收 ```bash source tools/web/emscripten-env.sh cmake --build build_web_blender6 --target web_engine -- -j8 bash tools/web/install-web-engine-assets.sh npm --prefix web run typecheck npm --prefix web run lint npm --prefix web test npm --prefix web run test:authoring-roundtrip WEB_TEST_PORT=5209 npm --prefix web run test:e2e -- -g "PBR|Principled|local UV|packed raster|OffscreenCanvas" ``` 发布前还要运行完整 E2E、跨浏览器、GLB Blender round-trip 和离线包门。桌面与移动截图必须 检查 canvas 非空、模型构图、材质高光/阴影可见、控件不遮挡以及页面无异常横向溢出。 ## 9. 完成定义 一个 PBR 子任务只有在以下条件全部满足后才能标记 done:协议字段可验证;Main 可写; 保存重开等价;主线程和 Worker 可渲染;资源可释放;错误稳定且结构化;至少一个正例和一个 边界反例;桌面/移动像素与截图门通过;状态文档同步。仅识别 Blender socket、仅构造 SceneIR 或仅显示 UI 控件都不构成完成。