# WebGPU 渲染引擎

::: warning 版本要求
`webgpu` 引擎、`app.whenReady()`、`exportRenderTargetAsync` / `renderTargetToDataUrlAsync` 均为 **v2.0.0 起**的 API——旧版 `vjmap3d@1.0.23`（webgl-only，peer `three@^0.165`）的 dist 中**没有**这些符号。npm 当前最新版 `vjmap3d@2.0.0`（peer `three@>=0.185`）已包含全部本节能力；若你装的是 1.x 请升级或改用 `webgl` 引擎。
:::

`vjmap3d` v2 起支持 `WebGPU` 渲染引擎：创建应用时通过渲染参数 `render.engine` 即可在 `webgl`（默认，`WebGLRenderer`）与 `webgpu`（`WebGPURenderer`，基于 `three/webgpu` 与 `TSL` 节点材质体系）之间选择。业务代码（实体、模块、事件、内置材质、后期处理、粒子等）在两种引擎下保持同一套写法，SDK 内部会按引擎自动选择对应实现。

::: tip 为什么用 WebGPU
`WebGPU` 是新一代 Web 图形标准，相比 `WebGL` 具有更低的驱动开销、更好的多线程与计算能力（Compute Shader）。在大场景、大量实例、复杂后期处理的情况下有更好的性能上限；同时 three.js 的 `TSL`（Three Shading Language）节点材质可同时编译到 `WGSL`（WebGPU）与 `GLSL`（WebGL2 后端），一份材质两端可用。
:::

## 浏览器要求

| 环境 | 支持情况 |
|:---|:---|
| Chrome / Edge 113+ | 默认开启（需 `HTTPS` 或 `localhost` 环境） |
| Firefox / Safari 较新版本 | 逐步支持中，部分版本需在设置中开启实验特性 |
| 不支持 WebGPU 的环境 | three 的 `WebGPURenderer` 自动降级为 WebGL2 后端（仍属 `webgpu` 引擎：`app.renderer.isWebGPURenderer===true`、仍走 TSL 材质，GLSL `ShaderMaterial` 依旧不可用） |

可通过 `navigator.gpu` 判断浏览器是否支持 WebGPU：

```js
const supportWebGPU = typeof navigator !== "undefined" && !!navigator.gpu;
```

## 启用 WebGPU 引擎

创建 `App` 时在 `render`（渲染引擎模块参数）中设置 `engine: "webgpu"`：

<mapFrame>
<pre>
// 创建一个服务类，与服务连接
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    render: {
        engine: "webgpu", // 渲染引擎: 'webgl'(默认) 或 'webgpu'
        antialias: true,
        clearColor: 0x10141c
    },
    camera: {
        viewHelper: { enable: true }
    }
})
// webgpu 引擎的渲染器需要异步初始化：访问 app.renderer 前先等待就绪
await app.whenReady();
app.logInfo("当前渲染引擎: " + (app.renderer && app.renderer.isWebGPURenderer
    ? (app.renderer.backend?.isWebGPUBackend ? "WebGPU" : "WebGL2 后端(WebGPURenderer)")
    : "WebGL(webgl 引擎)"), "success", 5000);
//
// 之后的业务代码与 webgl 引擎完全一致
app.addDefaultLights();
let box = new THREE.Mesh(
    new THREE.BoxGeometry(2, 2, 2),
    new THREE.MeshStandardMaterial({ color: 0x4488ff, roughness: 0.35, metalness: 0.15 })
);
box.position.set(0, 1, 0);
vjmap3d.Entity.fromObject3d(box).addTo(app);
app.signal.onAppUpdate.add(() => { box.rotation.y += 0.01; });
// 等价通道：app.on("onAppUpdate", cb) 或 Module.onAppUpdate —— 三者同源于 callModules 扇出（见 core/app.md）
</pre>
</mapFrame>

### 渲染参数

在 `render` 中与 WebGPU 相关的参数：

|参数|类型|默认|说明|
|:---|:---|:---|:---|
|engine|`"webgl"` \| `"webgpu"`|`"webgl"`|渲染引擎。`webgpu` 时使用 `three/webgpu` 的 `WebGPURenderer`|
|forceWebGL|`boolean`|`false`|`webgpu` 引擎时强制使用 `WebGL2` 后端（`WebGPURenderer + WebGLBackend`）：保留 TSL 新材质体系，同时兼容不支持 WebGPU 的环境|

其余 `render` 参数（`antialias` / `clearColor` / `pixelRatio` / `autoClear` 等）两种引擎通用。另外 `webgpu` 引擎的 `powerPreference` 只接受 `"high-performance"` 与 `"low-power"`（`"default"` 会被忽略）。

## 异步就绪 whenReady

`webgl` 引擎的渲染器是同步创建的；`webgpu` 引擎的渲染器需要异步初始化（内部 `await renderer.init()`，并动态加载 `three/webgpu`、`three/tsl` 与 TSL 材质实现，`webgl` 用户零开销）。

因此**创建 `App` 后如需立即访问 `app.renderer`，应先 `await app.whenReady()`**：

```js
const app = new vjmap3d.App(svc, { container: "map", render: { engine: "webgpu" } });
await app.whenReady(); // webgl 引擎下立即 resolve，两种引擎通用
console.log(app.renderer.isWebGPURenderer); // true
```

渲染器就绪前，SDK 会自动跳过每帧的渲染阶段（更新阶段照常执行），不会产生报错。

::: warning 建议在 whenReady 之后再创建场景内容
内置材质的自动切换发生在材质**创建时**。`webgpu` 的 TSL 材质实现是异步按需加载的，若在渲染器就绪之前创建这些材质，会回退到 GLSL 实现并在控制台告警（`webgpu` 下无法正确渲染）。因此 `webgpu` 引擎下建议把创建实体/材质的业务代码统一放在 `await app.whenReady()` 之后。
:::

## 自动回退

浏览器不支持 WebGPU 本身**不会**回退到 `webgl` 引擎：three 的 `WebGPURenderer` 会在内部换用 WebGL2 后端（控制台告警 `WebGPU is not available, running under WebGL2 backend`），此时仍是 `webgpu` 引擎（`WebGPURenderer` + TSL，GLSL `ShaderMaterial` 不可用）。

以下情况才会回退到 `webgl` 引擎（`WebGLRenderer`），并在控制台输出提示：

- `three/webgpu`/`three/tsl` 动态加载失败（如 importmap 缺映射）或渲染器 `init()` 抛错；
- 做为唯杰地图三维图层使用时（`map2d` 模式，共享 2D 地图的 WebGL 上下文）：该模式不支持 `webgpu` 引擎。

判断当前实际使用的引擎：

```js
await app.whenReady();
if (app.renderer.isWebGPURenderer) {
    // WebGPURenderer：可能是 WebGPU 后端，也可能是 WebGL2 后端（forceWebGL，或浏览器不支持 WebGPU 时 three 自动降级）
    console.log(app.renderer.backend?.isWebGPUBackend ? "WebGPU 后端" : "WebGL2 后端");
} else {
    console.log("WebGLRenderer（已回退 webgl 引擎）");
}
```

## 内置能力的自动切换

`webgpu` 引擎下，SDK 内部所有基于 GLSL 的内置实现都会自动切换为对应的 TSL / WebGPU 实现，**用法与 webgl 引擎完全一致**：

| 类别 | 内容 |
|:---|:---|
| 特效材质 | `FlowMaterial` 流动纹理（含 `createTextFlowMaterial` 文本滚动 / `createRenderElementFlowMaterial`）、`RadarMaterial` 雷达扫描、`createFlowGradientMaterial` 建筑流光渐变、`AnimatorFrameMaterial` 序列帧、`SpriteAnimator` 精灵动画、`HolographicMaterial` 全息投影、`HighlightMaterial` 高亮、`FixedSizeMeshMaterial` 固定像素大小、`GridMaterial` 网格 |
| 反射 / 透射 | `createReflectorShaderMesh`（`ReflectorShaderMaterial` / `ReflectorDudvMaterial`）、`MeshReflectorMaterial`、`MeshTransmissionMaterial` |
| 线 / 文本 / 标注 | `PolylinesEntity` 线（`Line2`，含逐段线宽/透明度/虚线）、`MeshLine`、`Text` 文本（SDF，含描边）、`Label` 标签、`SymbolEntity` 符号点、`Outlines` 描边对象 |
| 内置对象 | `Heatmap` 热力图（二维/三维）、`createFlyline` 飞线、`TrailLine` 轨迹线、`ScreenQuad` 屏幕四边形（⚠️ 例外：自定义 GLSL `fragmentShader`/`vertexShader`/`uniforms` 选项在 webgpu 下被丢弃、仅显示 `map` 纹理；自定义着色请改传 `material`）、体积渲染（`MeshEntity.setHeatmapDataMaterial` 的体纹理盒渲染路径） |
| 地图 / 地形 | `MapView` / `MapViewEntity` 底图瓦片与地形高程（`HEIGHT_SHADER`） |
| 粒子系统 | `ParticleRenderModule` 全部粒子模式（billboard / stretched / mesh / trail 拖尾 / 软粒子） |
| Shadertoy | `ShadertoyMaterial` / `ShadertoyTexture`（GLSL 自动转译为 TSL，见下文） |
| 后期处理 | 辉光（bloom）、选中/描边（selected/outline）、`FXAA` / `SMAA` / `AO` / 景深 / 色调映射 / 色深 / 像素化 / 故障 / 模糊，以及自定义效果（见下文） |
| 其他 | 天空盒 / 环境贴图（`PMREMGenerator`、`getEnvmapFromScene`）、截图与图像导出、深度拾取（`unprojectAsync`）等 |

## 双引擎统一的 uniforms 用法

`webgpu` 下的 TSL 内置材质同样暴露 `uniforms` 字典与同名属性存取器，`uniforms.xxx.value` 的运行期改值写法两种引擎相同。⚠️ 但 webgl 下**只有** `shaderMaterial()` 工厂生成的材质（`FlowMaterial` 及 `createTextFlowMaterial`/`createRenderElementFlowMaterial`、`AnimatorFrameMaterial`、`RadarMaterial`）才有同名属性存取器；`HolographicMaterial`、`FixedSizeMeshMaterial`、`createFlowGradientMaterial`、`ShadertoyMaterial`、反射材质在 webgl 下写同名属性会被静默忽略——跨引擎代码请统一用 `uniforms.xxx.value`：

```js
let mat = new vjmap3d.RadarMaterial(
    { uRadius: 3, uColor: new THREE.Color("#00ffff"), uSpeed: 2 },
    { uTime: app.commonUniforms.iTime }
);
// 以下写法两种引擎通用：
mat.uniforms.uColor.value = new THREE.Color("#ff0000"); // uniforms 字典
mat.uSpeed = 3;                                          // 同名属性存取器（仅工厂材质，见上）
mat.opacity = 0.8;                                       // three 内建属性（⚠️ webgl 下雷达着色器不读 opacity，无效果）
```

## Shadertoy：GLSL 自动转译

`webgpu` 引擎下，`ShadertoyMaterial` 与 `ShadertoyTexture` 会在运行时把 shadertoy 的 GLSL（`mainImage`）**自动转译**为 TSL 节点，再由 three 编译到 WGSL 运行，无需修改 shader 代码：

- 支持 `iTime` / `iResolution` / `iMouse`（实时鼠标交互）/ `iChannel0-3` 输入通道 / 自定义 `uniforms`，以及 `#define` 宏与 `#ifdef` 条件编译；
- webgl 版的通用选项（`opacity` / `brightOpacity` / `hsbOffset` / `uvRotateAngle` / `uvScale` / `repeat` / 背景色与背景纹理混合 / `sizeAttenuation` 等）同样生效；
- 少数复杂 GLSL 语法可能转译失败：此时自动回退为基础材质并在控制台告警（按 shader 去重），不会报错中断；
- 已知差异：`iTime` 在 `webgpu` 下使用渲染器时钟，不随动画 `timeScale` 缩放与暂停。

## 自定义材质

`webgpu` 引擎（含 `forceWebGL` 及自动降级的 WebGL2 后端）**不支持直接使用 GLSL 编写的 `THREE.ShaderMaterial` / `RawShaderMaterial` / `onBeforeCompile` 注入**。自定义材质有三种选择：

1. **TSL 节点材质（推荐）**：使用 three.js 的 `three/tsl` `NodeMaterial` 体系编写，可同时运行于 WebGPU 与 WebGL2 后端；
2. **`CustomShaderMaterial` 的 `tslNodes` 节点注入**：与 GLSL 注入并存，按引擎自动选择（见下）；
3. **过渡方案**：直接使用默认的 `webgl` 引擎。`forceWebGL: true` 仍是 `WebGPURenderer`（仅换 WebGL2 后端），同样只认 NodeMaterial，GLSL `ShaderMaterial`/`RawShaderMaterial`/`onBeforeCompile` 依旧不可用（three 报 `Material "ShaderMaterial" is not compatible` 并以默认节点材质渲染）。

### CustomShaderMaterial 的 TSL 节点注入

`webgl` 引擎下 `CustomShaderMaterial` 通过 GLSL 字符串（`vertexShader` / `fragmentShader`）注入；`webgpu` 引擎下改用 `tslNodes` 提供 TSL 节点（`colorNode` / `positionNode` / `opacityNode` 等）。两者可同时提供，SDK 按当前引擎自动选择，做到一份代码双引擎兼容：

```js
const mat = new vjmap3d.CustomShaderMaterial({
    baseMaterial: THREE.MeshStandardMaterial,
    color: 0x4488ff,
    uniforms: { uProgress: { value: 0 } },
    // webgl 引擎: GLSL 注入
    fragmentShader: /* glsl */`
        uniform float uProgress;
        void main() {
            csm_DiffuseColor.rgb = mix(csm_DiffuseColor.rgb, vec3(1.0, 0.0, 0.0), uProgress);
        }
    `,
    // webgpu 引擎: TSL 节点注入(tsl 为 three/tsl 导出集合, uniforms 为对应的 TSL uniform 节点)
    tslNodes: (tsl, uniforms) => ({
        colorNode: tsl.mix(tsl.materialColor.rgb, tsl.vec3(1, 0, 0), uniforms.uProgress)
    })
});
// 运行期改 uniform，两种引擎写法一致
mat.uniforms.uProgress.value = 0.5;
```

`tslNodes` 可以是对象，也可以是函数 `(tsl, uniformNodes) => nodes` 延迟构建。`webgpu` 下仅提供 GLSL 而没有 `tslNodes` 时，会在控制台告警并返回未注入自定义效果的基础材质（不会崩溃）。

## 后期处理

`webgpu` 引擎下，后期处理由 WebGPU 后处理模块（基于 three 的 `RenderPipeline` + TSL 节点）自动接管，对外 API 与 `webgl` 引擎保持一致：

- `entity.selected` / `entity.bloom` / `entity.outline` 用法不变；`postProcess` 模块参数仅部分生效（`enable`/`renderUpdate`/`useFXAA`/`useSMAA`/`autoAdd{Selected,Outline,Bloom}Pass`/`bloomPass`/`outlinePass`/`selectedPass`，其余如 `disableMergeEffect`/`renderTargetOptions`/`depthPickingPass` 被忽略；`bloomPass.radius` 语义不同——是 mip 权重，默认 0）；
- `app.composer` 是兼容外壳（`passes` 恒为空数组）；`app.bloomEffect` 只有 `selection`/`intensity`/`luminanceThreshold`，`app.outlineEffect`/`app.selectedEffect` 只有 `selection`/`edgeStrength`/`visibleEdgeColor`/`hiddenEdgeColor`/`pulseSpeed`；`app.getDepth` 改为场景射线求交实现（不依赖 `postProcess.enable`）；
- `addEffectRenderPass` / `removeEffectRenderPass` / `setEffectRenderPassEnable` / `setEffectRenderPassOrder` 继续可用：内置 pmndrs 效果中**只有** `KawaseBlurPass`、`PixelationEffect`、`ColorDepthEffect`、`ToneMappingEffect`（固定 Reinhard）、`DepthOfFieldEffect`（固定参数）、`SSAOEffect`（GTAO）、`TextureEffect` 映射到 TSL，`FXAAEffect`/`SMAAEffect` 在此路径上为空操作（抗锯齿请用 `postProcess.useFXAA/useSMAA`）；其余含 `mainImage` 的效果（含自定义 GLSL `Effect`）会尝试自动转译为 TSL，只有 `mainUv` 的效果（如 `GlitchEffect`）直接告警跳过——故障效果请用便捷方法 `addGlitchEffect()`（混入在 `app` 上）；也可直接传 TSL 效果函数（原生写法）；
- `addSSAOEffect` / `addDepthOfFieldEffect` / `addKawaseBlurPass` / `addToneMappingEffect` / `addColorDepthEffect` / `addPixelationEffect` / `addGlitchEffect` 等便捷方法均有 `webgpu` 等价实现（可传参数，如 `addPixelationEffect(granularity)`、`addKawaseBlurPass({ sigma })`；两种引擎都不返回句柄）。WebGPU 后处理模块在渲染器就绪后才挂载，**须在 `await app.whenReady()` 之后调用**——之前调用会落到 webgl 版 mixin（无 composer）被静默丢弃。

详见 [后期处理](../postprocessing/postprocessing.md) 文档中的 WebGPU 部分。

## 截图与图像导出

- `app.getScreenshot({})` 截图两种引擎通用（参数对象不可省略：`getScreenshot()` 无参时内部读 `params.download` 抛 TypeError、Promise reject）；
- `webgpu` 下 GPU 读回是**异步**的：同步的 `exportRenderTarget` 在 `webgpu` 引擎下无法工作（会告警并返回空），请改用异步版本 `exportRenderTargetAsync` / `renderTargetToDataUrlAsync`（`webgl` 引擎下这些异步版本同样可用，内部自动分流）。

```js
// 两种引擎通用的渲染目标导出
const blob = await vjmap3d.exportRenderTargetAsync(app.renderer, renderTarget, "image/png");
const dataUrl = await vjmap3d.renderTargetToDataUrlAsync(app.renderer, renderTarget);
```

## 注意事项与已知限制

- **裁剪平面**：材质级 `clippingPlanes` 在 `webgpu` 后端不生效，这是 three r185 的行为约定，需改用 three 的 `ClippingGroup` 把受裁剪的对象包起来（仅 `webgl` 引擎的 `WebGLRenderer` 支持材质级 `clippingPlanes`；`forceWebGL` 仍是 `WebGPURenderer`，同样需 `ClippingGroup`）；
- **动态环境反射**：基于 `CubeCamera` 的动态环境（如 `DynamicEnvironment`）在 `webgpu` 引擎下暂不支持，可改用 `getEnvmapFromScene` 对场景做一次性 PMREM 环境贴图烘焙（功能对等，但失去逐帧动态反射）；
- **加载动画**：内置 loading 动画的 shadertoy 特效在 `webgpu` 下可能转译失败并自动回退，如遇到可在 loading 选项中设置 `showShadertoy: false, showHtml: true` 改用 HTML 样式；
- **单页面引擎混用**：同一页面创建多个 `App` 时建议使用同一渲染引擎；两个不同引擎的 `App` 并发存活期间交错创建材质可能导致分流错误（控制台有明确告警），先销毁再创建的顺序用法不受影响；
- **TypeScript 类型**：`@types/three` 版本与所装 three 主版本对齐（npm 版 vjmap3d@2.0.0 对应 `three@>=0.185` / `@types/three@^0.185`）；工程 `tsconfig` 的 `moduleResolution` 建议 `bundler` 或 `node16`（`three/webgpu`、`three/tsl` 为子路径导出），否则需开启 `skipLibCheck`。

### 已知视觉差异

`webgpu` 与 `webgl` 引擎渲染结果整体一致，个别效果存在轻微、可接受的观感差异：

- 选中/描边效果的边缘略粗、略亮（three `OutlineNode` 与 pmndrs 算法差异，SDK 已做强度对齐补偿）；
- 辉光（bloom）强度换算语义略有不同，相同参数下观感可能略偏弱/偏强，可微调 `intensity`；
- 雷达/飞线/轨迹线/粒子等特效的中间调在 `webgpu` 下更亮：`webgpu` 统一走 sRGB 输出转换（色彩上更正确），webgl 旧实现是直写线性值；
- 透射材质（`MeshTransmissionMaterial`）的折射偏移垂直方向与 webgl 呈镜像（两端各自自洽）。

## 相关示例

- [创建WebGPU引擎应用](https://vjmap.com/map3d/demo/#/demo/map/webgpu/01webgpubase)
- [WebGPU特性检测与回退](https://vjmap.com/map3d/demo/#/demo/map/webgpu/02webgpufallback)
- [WebGPU内置特效材质](https://vjmap.com/map3d/demo/#/demo/map/webgpu/03webgpumaterials)
- [WebGPU后期处理](https://vjmap.com/map3d/demo/#/demo/map/webgpu/04webgpupostprocess)
