English | 简体中文
一个 WebGPU-first、TypeScript-first,并提供生产级 WebGL 2 兼容后端的 3D 引擎。
Hilo3d vNext 围绕 WebGPU-shaped RHI、显式 render pass、可复用 GPU 资源、GLSL ES 3.00、PBR 与 glTF 设计。WebGPU 保留原生 pipeline/bind-group/command 模型;WebGL 2 则通过带状态差分缓存的即时 GL 执行实现同一可移植子集。
finish()/submit() 只是归属边界,不做延迟回放。auto 按能力选择;显式请求 webgpu 或 webgl2 时绝不会静默切换。npm install hilo3d
包只提供一个 ESM 入口,面向现代 bundler 与浏览器原生 ESM。WebGL 1、CommonJS、UMD 和全局脚本构建不属于 vNext 契约。
Stage.create() 默认使用
backend: 'auto',存在兼容 adapter 时优先 WebGPU,并等待最终选中的后端完成初始化;返回的 stage 可以直接渲染。
import * as Hilo3d from 'hilo3d';
const camera = new Hilo3d.PerspectiveCamera({ aspect: innerWidth / innerHeight, z: 4 });
const stage = await Hilo3d.Stage.create({
backend: 'webgpu',
container: document.querySelector('#app')!,
camera,
width: innerWidth,
height: innerHeight
});
const box = new Hilo3d.Mesh({
geometry: new Hilo3d.BoxGeometry(),
material: new Hilo3d.PBRMaterial({
baseColor: new Hilo3d.Color(0.832, 0.119, 0.093)
})
}).addTo(stage);
stage.addChild(new Hilo3d.AmbientLight({ amount: 1 }));
const ticker = new Hilo3d.Ticker(60);
ticker.addTick(stage);
ticker.start();
省略 backend 等同于 backend: 'auto'。auto 会先调用
Renderer.isBackendSupported('webgpu', options):这个轻量探测只请求 adapter,并校验 fallback-adapter 策略、required
feature、required limit 和 Hilo3d 的最低 adapter limits;不会请求 device、获取 canvas
context、初始化 Naga、创建 pipeline 或分配 GPU 资源。adapter 兼容时选择 WebGPU,否则直接创建 WebGL
2。传入 WebGL 2-only 的 preserveDrawingBuffer,或请求 alpha: true, premultipliedAlpha: false
的 straight-alpha canvas 合成时,auto 也会直接选择 WebGL 2。
探测选中 WebGPU 后,正式 WebGPU 初始化只执行一次。device 或 canvas
context 创建错误、shader 编译器失败、pipeline/资源初始化错误以及之后的任何错误都会让
Stage.create() reject,绝不会被捕获后当作回退理由。显式请求 backend: 'webgpu'
会跳过 auto 探测,同样绝不回退。
应用也可以在不创建 renderer 的情况下复用同一不创建 device 或 GPU 资源的探测:
const webgpuSupported = await Hilo3d.Renderer.isBackendSupported('webgpu', {
powerPreference: 'high-performance'
});
无需修改 scene、material、render target 或 GLSL 代码即可使用兼容后端:
const stage = await Hilo3d.Stage.create({
backend: 'webgl2',
container: document.querySelector('#app')!,
camera
});
Stage 只通过异步 Stage.create() 工厂创建,包括显式选择 WebGL 2 的场景。Hilo3d 永远不会创建 WebGL
1 上下文。
Renderer 是唯一公开的 renderer 类,并且只通过异步 Renderer.create() 工厂创建:
const webglRenderer = await Hilo3d.Renderer.create({
backend: 'webgl2',
domElement: document.querySelector('canvas')!
});
const autoRenderer = await Hilo3d.Renderer.create({
backend: 'auto',
domElement: document.createElement('canvas')
});
const webgpuRenderer = await Hilo3d.Renderer.create({
backend: 'webgpu',
domElement: document.createElement('canvas')
});
所有 Renderer 都创建同一公开 RenderTarget 契约。可以通过 renderer.backend
观察最终后端,但 scene、material、target 与 shader API 不随之变化。
| 能力 | WebGPU | WebGL 2 |
|---|---|---|
| RHI 执行 | 薄封装 native encoder/pass/queue | encoder/pass 语义下的即时 GL 执行 |
| Shader module 输入 | Renderer 准备好的 WGSL | Renderer 准备好的 GLSL ES 3.00 |
多 pass renderFrame |
资源已就绪的 renderer pass 使用一个 encoder/submit | 按顺序即时执行;submit 绝不回放命令 |
| Device 对象复用 | 有界 pipeline、layout 与 sampler cache | 有界 pipeline、layout、sampler、framebuffer 与 VAO cache |
| 增量上传 | UBO/geometry dirty range;texture revision | UBO/geometry dirty range;texture revision |
| Render target | MRT、1×/4× MSAA、可采样 attachment、异步回读 | 相同引擎契约 |
| RHI 不支持能力 | 通过 features/limits 声明,请求时拒绝 |
compute/storage/1D/异步 buffer mapping 明确不支持 |
| 丢失处理 | 重新获取 device 并恢复资源 | 恢复 context 与资源 |
| 后端选择 | 显式请求失败即 reject;auto 只做 adapter 探测 |
auto 探测不支持时直接选择;不回退 WebGL 1 |
应用自主管理 frame graph 时使用
renderFrame()。WebGPU 中,回调内资源已就绪的 scene、target 与 present 调用共享一个应用 command
encoder,最终最多产生一次应用提交;WebGL 2 则通过相同的后端中立 facade 按顺序执行同一组命令。
const reflectionTarget = renderer.createRenderTarget({
width: renderer.width,
height: renderer.height
});
const sceneTarget = renderer.createRenderTarget({ width: renderer.width, height: renderer.height });
renderer.renderFrame(frame => {
frame.renderToTarget(reflectionTarget, stage, reflectionCamera);
frame.renderToTarget(sceneTarget, stage, camera, true);
frame.present(sceneTarget);
});
renderer 尺寸变化时需要 resize 应用持有的 target。请在自定义 tick 中调用该 frame
callback,不要同时让 Stage
执行默认渲染。callback 必须同步执行,不能返回 Promise,也不能在返回后继续持有 frame
facade。进入 callback 前应确定 scene transform、material、GeometryData
与 texture 更新;同一帧首次使用后不能再改变 geometry 或 texture 内容。冷启动 texture
mipmap 准备和显式 readback 属于独立 GPU 工作,不计入应用 pass 的单次提交承诺。renderer 的 resize、setRenderTarget()、资源释放/销毁,以及 render
target 的 resize、readback、destroy 必须在 callback 外执行;WebGPU 录制期间尝试这些操作会中止整帧,且不会提交部分 command。
可以在 Renderer.create() 或 Stage.create() 中传入可复用的 renderPipeline
factory 来完整替换帧编排,也可以通过 ForwardRenderPipelineFactory
feature 在 shadow、opaque、transparent、post-process 和 output 阶段之间注入工作。每个 Renderer 都获得独立 pipeline/feature
runtime;record 保持同步,并与普通 renderer 命令写入同一个事务化 Render
Graph。默认空 feature 集继续走原有 direct forward 快路径,不创建中间 scene target,也不增加 present
pass。
图采样与纹理 copy 使用不同声明:fullscreen 输入必须支持线性过滤;copy pass 必须声明精确的 source/destination pair,并在 backend frame 开始前验证解析后的 RHI texture。
可脚本化管线示例展示了使用 retained fullscreen feature 采样 scene color; SRP 架构文档说明资源所有权、失败回滚、性能门禁,以及 storage buffer/compute 的 capability-gated 扩展路线。当前 compute/storage capability 会明确失败,不会在 WebGL 2 上提供不完整模拟。
src/render 负责唯一公开的 Renderer、scene traversal、frame planning、render target 契约、std140
uniform 数据、shader 接口准备和确定性引擎资源归属。src/shader 负责 GLSL 预处理和引擎 shader variant;src/render/shader
负责反射 binding 与 GLSL→WGSL 编译;RHI 不知道 shader variant 或 material。src/render/rhi/RHI.ts 定义 WebGPU-shaped 的 device、resource、pipeline、bind group、render
pass、encoder、queue、surface、feature 与 limit 契约。src/render/rhi/RHIFactory.ts
是唯一硬件组合根:只构造一次具体 RHI 并负责后端能力探测,不在每条 command 外再包一层 facade。src/render/rhi/backends/webgpu 直接包装 native WebGPU;src/render/rhi/backends/webgl2
包含 WebGL 2 语义模拟、state cache、framebuffer/VAO 归属和 context
recovery。两个 RHI 都不引用引擎 scene 类型。抽象边界刻意采用 WebGPU 模型,而不是 WebGL 状态机。WebGPU render pass 与 native pass 一对一,command
encoder 直接持有 native encoder。WebGL2RHI 只在 pipeline 或 bind
group 状态变化时应用 GL 状态,draw/copy 在编码期间已执行;返回的 command buffer 只是一次性 submit
token。生产 WebGPU 路径直接使用同一个 concrete device 上的一跳 native fast path,主 draw
loop 保留 native handle,不承担逐 draw wrapper 或虚调用开销。WebGL 2 路径则在 frame-scoped
session 中执行 Program/VAO,并共享 RHI 唯一的 context、canonical state
differential、lifecycle 与 device-owned sampler
cache;Program、VAO、framebuffer 仍属于 render 层 cache,不会创建并行 context 或可回放命令列表。无法在 WebGL
2 中正确实现的 compute pipeline、storage texture/buffer、1D texture、异步 buffer
mapping、base-vertex 与 first-instance draw 会从 features 中缺席或以 0
limit 暴露,请求时明确报错。RHI 契约内部会保守声明格式相关的采样、过滤、attachment、storage 和 MSAA 能力,包括 extension/tier 导致的差异。
全部引擎 shader 都以 GLSL ES 3.00 为起点。WebGL 2 直接编译;WebGPU 先解析 shader variant,把生效接口改写成 Vulkan GLSL 4.50,再交给 Naga WASM frontend 生成 WGSL。引擎内部 utility pass 也使用相同路径,不维护手写 fallback WGSL shader 集。
Shader variant 使用结构化、带类型与长度边界的双通道 64-bit hash,不生成中间序列化 key;同时保留精确字段用于碰撞检查,发生碰撞时会得到确定性 bucket key,不会错误复用另一个 shader。Cache 归属只保留一层:每个 RHI device 持有有界的 immutable sampler、bind-group-layout、pipeline-layout 与 render-pipeline cache;Renderer 持有 material、Mesh、shader variant、binding set 和 upload revision cache。RHI 不按 descriptor 去重 buffer、texture、shader module 或 bind group,因为它们的 identity 与生命周期属于应用数据;label 不参与 device cache key。Device lost/context restore 和显式 destroy 会清空所有 device cache。
Texture identity 保持后端中立:共享对象只保存 CPU 内容、不可变 update snapshot 与单调 revision;每个 WebGL context 和 WebGPU device 分别持有 native allocation 与 upload cursor。WebGL descriptor snapshot 让 framebuffer resize/reset 可稳定复用 native object;WebGPU 会延迟销毁仍被待提交 command 引用的 buffer 与 texture。不可取消的内部生命周期 observer 会先释放所有后端 allocation,再执行可被用户取消的公开事件,context/device lost 与显式资源释放也遵循同一规则。WebGL sampler variant 是不可变、有界且按 texture unit 绑定的,因此同一张 depth texture 可以在一次 draw 中同时用于数值读取和 comparison sampling,不需要改写 texture 全局状态。
两个后端的 render target owner 都会跟踪 attachment allocation generation。Texture target 变化、上传失败或显式销毁 attachment 时会使旧 allocation 失效;target 会在再次使用前重建资源或重新挂接,并拒绝陈旧的 native handle。
Uniform buffer、动态 geometry 与 texture 都携带后端本地 revision;allocation shape 稳定时,两个后端只上传 UBO 与 geometry 合并后的 dirty range,texture 则从所需 revision 重放不可变的 subresource update snapshot。WebGPU command-state cache 还会在单个 pass 内消除重复的 pipeline、bind group、vertex/index buffer、viewport 与 stencil 命令。
数值 shader 数据必须放入已注册的 std140 block;sampler 是唯一允许放在 block 外的 uniform。
Hilo3d.registerUniformBlockBinding('EffectBlock');
const effectLayout = Hilo3d.createStd140Layout({ tint: 'vec4' });
const effectBlock = Hilo3d.UniformBuffer.fromSchema(effectLayout, {
tint: [0.6, 0.8, 1, 1]
});
const material = new Hilo3d.ShaderMaterial({
attributes: { a_position: 'POSITION' },
uniformBlocks: { EffectBlock: effectBlock },
vs: `#version 300 es
layout(std140) uniform EffectBlock { vec4 tint; };
in vec3 a_position; out vec4 v_tint;
void main() { v_tint = tint; gl_Position = vec4(a_position, 1.0); }`,
fs: `#version 300 es
precision highp float;
in vec4 v_tint; layout(location = 0) out vec4 outColor;
void main() { outColor = v_tint; }`
});
effectBlock.set('tint', [1, 0.5, 0.2, 1]);
使用 in/out、texture() 和显式 fragment output。每个 custom
block 必须在首次使用前注册;跨 stage 的同名 block
layout 必须相同;schema 只使用 scalar、vector、matrix 或定长数组组成的扁平结构。
WebGPU device lost 后先触发
webgpuDeviceLost,使用冻结的要求重新获取等价 adapter/device,复核 feature 与 limit,重建 device-owned
manager/cache,在不改变公开 target 对象 identity 的前提下恢复 render-target 资源,最后触发
webgpuDeviceRestored。恢复期间安全跳帧;最终失败会触发
webgpuDeviceRecoveryFailed,之后的 render 显式抛错,并且绝不切换到 WebGL
2。releaseGPUResources() 会清理自有 GPU 状态,但 renderer 仍可继续使用。
应用需要后端中立的完成 fence 时使用 await renderer.waitForIdle()。Native 互操作只能显式通过
renderer.getExtension('webgl2-native') 或 renderer.getExtension('webgpu-native')
获取,公开 Renderer 不直接暴露 context 或 device 字段。使用前必须检查 extension 是否存在,常规渲染继续使用共享的 Renderer/RenderTarget
API。