关于 typegpu
面向 FrameVideo 的 WebGPU 视觉开发技能,涵盖着色器、GPU 计算,以及与时间轴和帧捕获的同步。
主要功能
- 01WebGPU 着色器与计算模式
- 02基于 FrameVideo seek 的渲染
- 03同步注册 GSAP 时间轴
- 04同步 GPU 帧捕获
- 05确定性视觉渲染
适用场景
- 01创建粒子或物理模拟
- 02构建自定义着色器和后期效果
- 03按 FrameVideo 指定时间渲染效果
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ chanjing-ai/ framevideo --skill typegpu安装后在 Claude Code 中输入/typegpu确认已加载。
详细介绍
FrameVideo 的 TypeGPU / WebGPU
适用场景
以下场景适合使用 TypeGPU / WebGPU:
- GPU 计算着色器 — 粒子系统、物理模拟
- 自定义 WGSL 着色器 — 液态玻璃、元球、光线步进
- 高性能特效 — 复杂片段着色器、后期处理
- 高级视觉特效 — 噪声场、反应扩散、流体模拟
- 计算密集型视觉效果 — 当 Three.js 抽象层级过高时
请勿使用
以下场景应避免使用 TypeGPU / WebGPU:
- 简单的 3D 场景 — 使用
three(更简单、更成熟) - 2D 动画 — 使用
gsap(简单得多) - 标准特效 — WebGPU 的浏览器支持有限,请使用 WebGL 替代方案
- 文本 / UI 动画 — 使用 HTML + GSAP(渲染效果更好)
快速开始
FrameVideo 中的基础 WebGPU 片段着色器:
<canvas id="gpu-layer" width="1920" height="1080"></canvas>
<script>
(async () => {
if (!navigator.gpu) return;
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.getElementById("gpu-layer");
const ctx = canvas.getContext("webgpu");
ctx.configure({
device,
format: navigator.gpu.getPreferredCanvasFormat()
});
// Create time uniform buffer
const timeUniform = new Float32Array([0]);
const timeBuf = device.createBuffer({
size: 16,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
// Listen for FrameVideo seek events
window.addEventListener("fv-seek", async (event) => {
const time = event.detail.time;
// Update time uniform
timeUniform[0] = time;
device.queue.writeBuffer(timeBuf, 0, timeUniform);
// Render your pipeline...
// [render pass code here]
// IMPORTANT: flush GPU work before frame capture
await device.queue.onSubmittedWorkDone();
});
})();
</script>
要点:
- 监听
fv-seek事件 - 使用
event.detail.time更新 uniform - 在捕获帧之前调用
await device.queue.onSubmittedWorkDone() - 对 WebGPU 是否可用进行保护检查
约定
- 异步初始化 WebGPU(
await navigator.gpu.requestAdapter()),但所有 GSAP 补间都必须同步注册 — 在任何await之前。FrameVideo 播放器会在页面加载时立即读取时间线。 - 使用 FrameVideo 的时间进行渲染,而不是
performance.now()。 - 监听
fv-seek事件,并在该精确时间重新渲染。 - 防范 WebGPU 不可用的环境 — 适配器不会替你检查。
- 对于视频渲染,提交 GPU 工作后调用
await device.queue.onSubmittedWorkDone(),确保画布在捕获帧之前完成刷新。
适配器会在每次 seek 时设置 window.__fvTypegpuTime,并派发 new CustomEvent("fv-seek", { detail: { time } })。
基础模式
<canvas id="gpu-layer"></canvas>
<script>
(async () => {
if (!navigator.gpu) return;
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) return;
const device = await adapter.requestDevice();
const canvas = document.getElementById("gpu-layer");
canvas.width = 1920;
canvas.height = 1080;
const ctx = canvas.getContext("webgpu");
const fmt = navigator.gpu.getPreferredCanvasFormat();
ctx.configure({ device, format: fmt, alphaMode: "opaque" });
// Build your pipeline, buffers, bind groups...
const timeUniform = new Float32Array([0]);
const timeBuf = device.createBuffer({
size: 16,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
function render(t) {
timeUniform[0] = t;
device.queue.writeBuffer(timeBuf, 0, timeUniform);
const enc = device.createCommandEncoder();
const pass = enc.beginRenderPass({
colorAttachments: [
{
view: ctx.getCurrentTexture().createView(),
loadOp: "clear",
clearValue: { r: 0, g: 0, b: 0, a: 1 },
storeOp: "store",
},
],
});
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.draw(3);
pass.end();
device.queue.submit([enc.finish()]);
}
render(0);
window.addEventListener("fv-seek", (e) => render(e.detail.time));
})();
</script>
时间线注册
驱动文本、字幕或 HTML 元素的 GSAP 补间必须同步注册 — 在任何 await 之前:
const tl = gsap.timeline({ paused: true });
// Caption tweens: synchronous, added before WebGPU init
gsap.set(".cap", { opacity: 0 });
tl.to("#cap-1", { opacity: 1, duration: 0.3 }, 1.0);
tl.to("#cap-1", { opacity: 0, duration: 0.2 }, 3.5);
window.__timelines["my-comp"] = tl;
// GPU-dependent tweens can go inside the async IIFE
(async () => {
// ... WebGPU init ...
const proxy = { value: 0 };
tl.to(proxy, { value: 1, duration: 2, onUpdate: render }, 0.5);
})();
基于视频的特效(液态玻璃、扭曲)
将 <video> 用作 GPU 输入纹理:
const videoEl = document.getElementById("aroll");
// Wait for video metadata before creating the texture
await new Promise((r) => {
if (videoEl.readyState >= 1) r();
else videoEl.addEventListener("loadedmetadata", r, { once: true });
});
// Create texture at the video's NATIVE resolution
const vw = videoEl.videoWidth,
vh = videoEl.videoHeight;
const bgTex = device.createTexture({
size: [vw, vh],
format: "rgba8unorm",
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
});
function render(t) {
try {
device.queue.copyExternalImageToTexture({ source: videoEl }, { texture: bgTex }, [vw, vh]);
} catch (_) {
/* frame not decoded yet */
}
// ... draw ...
}
**渲染模式注意事项:**无头 Chrome 可能无法对视频元素执行 copyExternalImageToTexture。生产环境渲染时,请先通过 FFmpeg 将关键帧提取为 PNG,再将其作为图像纹理加载。
通过降采样通道实现磨砂模糊
单次通道的高斯核对于玻璃质感的磨砂模糊来说效果太弱。请使用双通道方案:
- **通道 1 — 降采样:**将全分辨率纹理渲染到小尺寸纹理(1/6 分辨率)。降采样过程中的双线性过滤会自然地对像素求平均。
- **通道 2 — 玻璃合成:**对磨砂内部采样小尺寸纹理(双线性放大 = 强烈平滑模糊),对锐利区域和色差折射采样全分辨率纹理。
这与 TypeGPU 的 textureSampleBias mip 层级方案效果相当,无需生成 mipmap。
透明与不透明画布
alphaMode: 'opaque'— GPU 画布会渲染完整画面(视频 + 特效)。当 GPU 渲染管线负责所有视觉内容时使用。alphaMode: 'premultiplied'— GPU 画布在 alpha = 0 的位置透明,让下方的 HTML 元素透显。用于叠加在常规<video>元素之上的效果(粒子、路径动画)。
WGSL 全屏三角形
全屏特效的标准顶点着色器(无需顶点缓冲区):
struct Vo { @builtin(position) pos: vec4f, @location(0) uv: vec2f }
@vertex fn vs(@builtin(vertex_index) vi: u32) -> Vo {
let ps = array<vec2f, 3>(vec2f(-1., -1.), vec2f(3., -1.), vec2f(-1., 3.));
let ts = array<vec2f, 3>(vec2f(0., 1.), vec2f(2., 1.), vec2f(0., -1.));
return Vo(vec4f(ps[vi], 0., 1.), ts[vi]);
}
使用 pass.draw(3) 绘制 — 一个覆盖整个视口的三角形。
圆角矩形 SDF(液态玻璃胶囊)
fn sdf_box(p: vec2f, half_size: vec2f, corner_radius: f32) -> f32 {
let d = abs(p) - half_size + vec2f(corner_radius);
return length(max(d, vec2f(0.))) + min(max(d.x, d.y), 0.) - corner_radius;
}
使用它为玻璃特效定义内部 / 环形 / 外部区域。负值表示位于形状内部。
确定性渲染
- 不使用
Math.random()— 请使用带种子的 PRNG。 - 不使用
requestAnimationFrame驱动渲染循环 — 仅响应fv-seek进行渲染。 - 不使用
performance.now()获取动画时间 — 读取window.__fvTypegpuTime或e.detail.time。 - GPU 提交后,为渲染模式的帧捕获调用
await device.queue.onSubmittedWorkDone()。
评论
支持 Markdown