mcpField
T
typegpu

typegpu

github.com/boraoztuncクリエイティブ・メディア434185 回インストール11 週間前に更新
リポジトリ

typegpu について

HyperFrames 向けに TypeGPU と WebGPU のコンポジション制作を支援するスキルです。タイムライン同期や GPU 描画、視覚効果の実装方法を解説します。

主な機能

  1. 01HyperFrames 時刻との描画同期
  2. 02hf-seek イベントへの対応
  3. 03GSAP タイムラインの同期登録
  4. 04複数パスのすりガラスぼかし
  5. 05透明・不透明キャンバスの設定

活用シーン

  1. 01動画タイムラインの正確なフレーム描画
  2. 02HTML コンテンツ上への GPU アニメーション配置
  3. 03テクスチャを使ったすりガラス効果の作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/boraoztunc/skills --skill typegpu

インストール後、Claude Code で/typegpuを入力して読み込まれたか確認します。

詳細

TypeGPU / HyperFrames 向け WebGPU

HyperFrames は、typegpu ランタイムアダプターを通じて TypeGPU と生の WebGPU をサポートします。このアダプターはパイプラインを管理しません。HyperFrames の時刻を公開し、シークイベントをディスパッチすることで、コンポジションが正確な GPU フレームをレンダリングできるようにします。

契約

  • WebGPU は非同期で初期化します(await navigator.gpu.requestAdapter())。ただし、すべての GSAP トゥイーンは await より前に 同期的に 登録してください。HyperFrames プレーヤーはページ読み込み時にタイムラインを直ちに読み取ります。
  • performance.now() ではなく、HyperFrames の時刻を基準にレンダリングします。
  • hf-seek イベントをリッスンし、その時刻に正確に再レンダリングします。
  • WebGPU を利用できない環境に備えて、ガード処理を追加してください。アダプター側ではチェックしません。
  • 動画をレンダリングする場合は、GPU 処理を送信した後に await device.queue.onSubmittedWorkDone() を呼び出し、フレームのキャプチャ前にキャンバスがフラッシュされるようにします。

アダプターはシークのたびに window.__hfTypegpuTime を設定し、new CustomEvent("hf-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("hf-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);
})();

動画を使ったエフェクト(リキッドグラス、歪み)

GPU 入力テクスチャとして <video> を使用するには:

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 パスのガウシアンカーネルでは、ガラスのようなすりガラス風ぼかしには不十分です。2 パス方式を使用します。

  1. パス 1 — ダウンサンプル: フル解像度のテクスチャを小さなテクスチャ(解像度は 1/6)にレンダリングします。ダウンサンプル時のバイリニアフィルタリングによって、ピクセルが自然に平均化されます。
  2. パス 2 — ガラスの合成: すりガラス部分には小さなテクスチャ(バイリニアでの拡大により強く滑らかなぼかし)を、シャープな部分や色収差による屈折にはフル解像度のテクスチャをサンプリングします。

これは、ミップマップを生成せずに TypeGPU の textureSampleBias によるミップレベル指定方式を再現します。

透明キャンバスと不透明キャンバス

  • alphaMode: 'opaque' — GPU キャンバスがフレーム全体(動画+エフェクト)をレンダリングします。GPU パイプラインがすべての視覚コンテンツを処理する場合に使用します。
  • alphaMode: 'premultiplied' — アルファ値が 0 の部分で GPU キャンバスが透明になり、下にある 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) で描画します。1 つの三角形でビューポート全体を覆います。

角丸長方形の 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 は使用せず、hf-seek に応じてのみレンダリングします。
  • アニメーション時刻に performance.now() は使用せず、window.__hfTypegpuTime または e.detail.time を読み取ります。
  • GPU 処理の送信後、レンダーモードでのフレームキャプチャに備えて await device.queue.onSubmittedWorkDone() を呼び出します。

コメント