JavaScript

【超入門】WebGPUでブラウザの限界を超える!3D描画も機械学習も“GPU直叩き”で実現する方法

目次

📖 目次

  1. WebGPUとは?何ができるの?
  2. 事前準備:ブラウザと開発環境を整えよう
  3. Hello WebGPU:Canvasをクリアしてみよう
  4. Compute Shader入門:GPUでデータを扱う基礎
  5. 3D描画の基本:点をつないで三角形を描画
  6. 粒子システム応用:たくさんの点を動かしてみる
  7. 機械学習推論応用:TensorFlow.js × WebGPU
  8. 性能チューニング:速くするコツと注意点
  9. 実際のフレームワークとPolyfill
  10. まとめ:どこから学び、次に何をする?

1️⃣ WebGPUとは?何ができるの?

WebGLとの違い

  • WebGL: 古いAPI、主に3D描画向け。Compute Shaderは無し
  • WebGPU: 描画計算もできる。最新の“低レベル”GPU制御

具体例

  • 3Dゲーム:より複雑なライティングやエフェクト
  • 機械学習:ブラウザで画像認識などを高速に実行
  • 物理シミュレーション:流体や剛体計算をリアルタイム

🔍 ポイント:WebGPUは“ブラウザ向けのVulkan/DirectX12”と覚えよう


2️⃣ 事前準備:ブラウザと開発環境を整えよう

2.1 Chrome Canaryで試す

  1. Chrome Canaryをダウンロード
  2. アドレスバーに chrome://flags/#enable-unsafe-webgpu と入力
  3. 「Enabled」に切り替えて再起動

🎯 なぜCanary?WebGPUはまだ実験段階なので、安定版には入っていないため

2.2 Node.jsでローカル開発(Optional)

# 新規フォルダを作成
mkdir webgpu-demo && cd webgpu-demo
npm init -y
npm install @webgpu/types wgpu-native
  • @webgpu/types → TypeScriptで補完を効かせる
  • wgpu-native → Node上でWebGPUを動かす実験用

3️⃣ Hello WebGPU:Canvasをクリアしてみよう

  1. HTML<canvas>タグを準備
  2. JavaScriptでGPUアダプタデバイスを取得
  3. クリアカラーでCanvasを塗りつぶす

HTML

<!-- index.html -->
<canvas id="gpu-canvas" width="640" height="360"></canvas>
<script type="module" src="main.js"></script>

main.js

async function init() {
  // 1. Canvas要素を取得
  const canvas = document.getElementById('gpu-canvas');
  
  // 2. GPUアダプタ(物理GPU情報)をリクエスト
  const adapter = await navigator.gpu.requestAdapter();
  if (!adapter) return alert('GPUアダプタが見つかりません');

  // 3. アダプタから論理デバイスを取得
  const device = await adapter.requestDevice();

  // 4. CanvasコンテキストをWebGPUに設定
  const context = canvas.getContext('webgpu');
  const format = navigator.gpu.getPreferredCanvasFormat();
  context.configure({ device, format });

  // 5. コマンドバッファを作成
  const encoder = device.createCommandEncoder();

  // 6. 現在のテクスチャ(キャンバス)を取得
  const view = context.getCurrentTexture().createView();

  // 7. レンダーパス(描画処理)の設定
  const pass = encoder.beginRenderPass({
    colorAttachments: [{
      view,
      clearValue: { r: 0.1, g: 0.2, b: 0.3, a: 1.0 }, // 背景色
      loadOp: 'clear', storeOp: 'store'
    }]
  });
  pass.end(); // 描画コマンドを終了

  // 8. GPUキューに命令を送信
  device.queue.submit([encoder.finish()]);
}

// ページ読み込み後に実行
init();

解説(初心者向け)

  • adapter: コンピューター内のGPU性能を扱う「窓口」
  • device: GPUに描画や計算を依頼する「電話機」
  • context.configure: CanvasをWebGPU用に初期化
  • CommandEncoder: やることリスト(レンダリング命令)を蓄積する箱
  • RenderPass: クリアや描画の具体的な手順をラッピング
  • queue.submit: リストをGPUに渡して実行

🔑 完全に「Canvasを好きな色で塗りつぶす」だけの最小構成です


4️⃣ Compute Shader入門:GPUでデータを扱う基礎

GPUはピクセルだけでなく、数値配列を並列処理できます。

4.1 JavaScript側:バッファを作る

// 1. 倍にしたいデータの例
const array = new Float32Array([1, 2, 3, 4]);

// 2. GPUに置くバッファを作成
const buffer = device.createBuffer({
  size: array.byteLength,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
});

// 3. CPU上のデータをGPUバッファにコピー
device.queue.writeBuffer(buffer, 0, array.buffer);

4.2 WGSL(シェーダー言語)

// data[0..3] を2倍にするシェーダー
@group(0) @binding(0) var<storage, read_write> data : array<f32>;

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id : vec3<u32>) {
  let i = id.x;        // GPU内の「仕事番号」
  data[i] = data[i] * 2.0; // 各要素を処理
}

4.3 JSで実行パイプラインを組む

// 1. シェーダーモジュールを生成
const module = device.createShaderModule({ code: csCode });

// 2. ComputePipelineを作成
const pipeline = device.createComputePipeline({
  compute: { module, entryPoint: 'main' }
});

// 3. バッファをbindGroupでシェーダーに渡す
const bindGroup = device.createBindGroup({
  layout: pipeline.getBindGroupLayout(0),
  entries: [{ binding: 0, resource: { buffer } }]
});

// 4. コマンドバッファに処理を記録
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(array.length / 64));
pass.end();

// 5. 実行
device.queue.submit([encoder.finish()]);

解説(初心者向け)

  1. ShaderModule: GPUで動くプログラムを登録する
  2. ComputePipeline: 計算手順をまとめた設計図
  3. BindGroup: パイプラインにデータ(バッファ)を紐付け
  4. dispatchWorkgroups: GPUに「64並列で何周すればいい?」と伝える
  5. 最後にsubmitで実行完了!

💡 GPUは一度に大量の数値を処理するのが得意。画像処理や機械学習の基盤になります。


5️⃣ 3D描画の基本:点をつないで三角形を描画

WebGPUで最も基本的な描画は“三角形を1つ画面に描く”ことです。初心者向けにステップバイステップで解説します。

5.1 頂点データの準備

// main.js の初めに追加
// 1. 三角形の頂点座標 (x, y, z)
const vertices = new Float32Array([
  -0.5, -0.5, 0.0, // 左下
   0.5, -0.5, 0.0, // 右下
   0.0,  0.5, 0.0  // 上
]);

// 2. GPU バッファ作成: VERTEX と COPY_DST の用途宣言
const vertexBuffer = device.createBuffer({
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

// 3. CPU 配列を GPU バッファに書き込む
device.queue.writeBuffer(vertexBuffer, 0, vertices.buffer);

解説:

  • Float32Array は JavaScript 上で扱う数値配列
  • GPUBufferUsage.VERTEX は「頂点データ用」
  • COPY_DST は「CPU→GPU コピー許可」

5.2 WGSL シェーダーの記述

vertex.wgsl

@vertex
fn vs_main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> {
  // 画面座標に変換して返却
  return vec4<f32>(pos, 1.0);
}

fragment.wgsl

@fragment
fn fs_main() -> @location(0) vec4<f32> {
  // 赤色で塗る
  return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

解説:

  • @vertex / @fragment はそれぞれ頂点/断片シェーダーを示すアノテーション
  • @location(0) はシェーダー間の入出力スロット番号を指定
  • @builtin(position) はクリップ空間座標を意味する

5.3 レンダーパイプラインの構築

// ShaderModule 作成
const vsModule = device.createShaderModule({ code: await fetch('vertex.wgsl').then(r=>r.text()) });
const fsModule = device.createShaderModule({ code: await fetch('fragment.wgsl').then(r=>r.text()) });

// パイプライン生成
const pipeline = device.createRenderPipeline({
  layout: 'auto',
  vertex: {
    module: vsModule,
    entryPoint: 'vs_main',
    buffers: [{
      arrayStride: 3 * 4, // vec3<f32> = 4バイト×3
      attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }]
    }]
  },
  fragment: {
    module: fsModule,
    entryPoint: 'fs_main',
    targets: [{ format }]
  },
  primitive: { topology: 'triangle-list', cullMode: 'back' }
});

解説:

  • layout: 'auto' で自動バインディングレイアウト
  • buffers で頂点バッファのレイアウトを指定
  • triangle-list は三角形描画モード

5.4 描画コマンドの発行とループ

function draw() {
  const encoder = device.createCommandEncoder();
  const view = context.getCurrentTexture().createView();
  const pass = encoder.beginRenderPass({
    colorAttachments: [{ view, clearValue: {r:0,g:0,b:0,a:1}, loadOp:'clear', storeOp:'store' }]
  });

  pass.setPipeline(pipeline);
  pass.setVertexBuffer(0, vertexBuffer);
  pass.draw(3, 1); // 頂点数=3, インスタンス数=1
  pass.end();

  device.queue.submit([encoder.finish()]);
  requestAnimationFrame(draw);
}
// 初回呼び出し
draw();

解説:

  1. CommandEncoder: 描画命令をためる箱
  2. beginRenderPass: 描画範囲の指定・クリア処理
  3. setPipeline / setVertexBuffer: シェーダー&頂点データ指定
  4. draw: 指定頂点数で三角形描画
  5. submit: GPUへ命令を送信
  6. requestAnimationFrame: 連続描画 (ループ)

🎉 これで画面に赤い三角形が表示されるはずです!

6️⃣ 粒子システム応用:たくさんの点を動かしてみる

この章では、多数の粒子(点)をGPUだけで高速に動かし描画する方法を学びます。粒子システムはCGやゲームに欠かせない技術ですが、CPUでは重い処理もGPUならリアルタイムで実現できます。

6.1 粒子データの初期化

まずは JavaScript で粒子の位置データを作成し、GPUバッファに転送します。

// 粒子数
const numParticles = 100000;
// 2D 座標 x,y の配列 (float32)
const positions = new Float32Array(numParticles * 2);
for (let i = 0; i < numParticles; i++) {
  // ランダムに [-1,1] の範囲で初期配置
  positions[2 * i]     = Math.random() * 2 - 1; // x
  positions[2 * i + 1] = Math.random() * 2 - 1; // y
}

// GPU用バッファを作成
const posBuffer = device.createBuffer({
  size: positions.byteLength,
  usage: GPUBufferUsage.STORAGE    // Computeで read/write
       | GPUBufferUsage.VERTEX     // VertexShaderで利用
       | GPUBufferUsage.COPY_DST    // CPU→GPU コピー
});
// データを書き込む
device.queue.writeBuffer(posBuffer, 0, positions);

解説:

  • numParticles は描画する点の数
  • positions 配列は [x0, y0, x1, y1, ...] の形式
  • バッファは STORAGE (計算用) と VERTEX (描画用) を両方指定

6.2 Compute Shader で位置を更新

次に、Compute Shader(WGSL)を使って各粒子に動きを加えます。ここではノイズを利用した揺らぎを与えます。

update.wgsl

@group(0) @binding(0) var<storage, read_write> pos : array<vec2<f32>>;

// シンプルなノイズ関数
fn rand(v: vec2<f32>) -> f32 {
  let dotv = dot(v, vec2<f32>(12.9898, 78.233));
  return fract(sin(dotv) * 43758.5453);
}

@compute @workgroup_size(256)
fn cs_main(@builtin(global_invocation_id) gid : vec3<u32>) {
  let i = gid.x;
  if (i >= arrayLength(&pos)) { return; }
  var p = pos[i];
  // ノイズ量を計算
  let n = rand(p) * 2.0 - 1.0;
  // y方向に少しずつ動かす
  p.y += n * 0.002;
  // 端を超えたら反対側へループ
  if (p.y > 1.1) { p.y = -1.1; }
  pos[i] = p;
}

解説:

  • @builtin(global_invocation_id) は各スレッドのID
  • workgroup_size(256)256粒子ずつ並列処理
  • rand 関数で擬似ノイズを生成
  • pos[i]y 値を更新し、範囲外はループさせる

6.3 描画用シェーダー

粒子を点として可視化するためのシンプルな Vertex/Fragmentシェーダーです。

particle_vertex.wgsl

struct VSOut { @builtin(position) position : vec4<f32>; @location(0) color : vec4<f32>; };

@vertex
fn vs_main(@location(0) p : vec2<f32>) -> VSOut {
  var out : VSOut;
  // vec2 -> vec4 へ変換 (x,y,0,1)
  out.position = vec4<f32>(p, 0.0, 1.0);
  // 位置に応じて色を変化 (例: y でグラデーション)
  out.color = vec4<f32>(0.5 + p.y * 0.5, 0.8, 1.0 - p.y * 0.5, 1.0);
  return out;
}

particle_fragment.wgsl

@fragment
fn fs_main(@location(0) color : vec4<f32>) -> @location(0) vec4<f32> {
  return color;
}

解説:

  • Vertex で座標 p をクリップ空間に変換
  • @location(0) color で色データを渡す
  • Fragment はそのまま色を出力

6.4 GPU パイプラインとループ処理

// Compute パイプライン
const csModule = device.createShaderModule({ code: await fetch('update.wgsl').then(r=>r.text()) });
const csPipeline = device.createComputePipeline({ compute: { module: csModule, entryPoint: 'cs_main' } });
const csBindGroup = device.createBindGroup({ layout: csPipeline.getBindGroupLayout(0), entries: [{ binding:0, resource:{ buffer: posBuffer } }] });

// Render パイプライン
const vsModule = device.createShaderModule({ code: await fetch('particle_vertex.wgsl').then(r=>r.text()) });
const fsModule = device.createShaderModule({ code: await fetch('particle_fragment.wgsl').then(r=>r.text()) });
const renderPipeline = device.createRenderPipeline({
  layout:'auto',
  vertex:{ module:vsModule, entryPoint:'vs_main', buffers:[{ arrayStride:2*4, attributes:[{ shaderLocation:0, offset:0, format:'float32x2' }] }] },
  fragment:{ module:fsModule, entryPoint:'fs_main', targets:[{ format }] },
  primitive:{ topology:'point-list' }
});

// 描画ループ
function frame() {
  const commandEncoder = device.createCommandEncoder();

  // ① Compute Pass: 位置更新
  const computePass = commandEncoder.beginComputePass();
  computePass.setPipeline(csPipeline);
  computePass.setBindGroup(0, csBindGroup);
  computePass.dispatchWorkgroups(Math.ceil(numParticles / 256));
  computePass.end();

  // ② Render Pass: 点描画
  const view = context.getCurrentTexture().createView();
  const renderPass = commandEncoder.beginRenderPass({ colorAttachments:[{ view, loadOp:'clear', clearValue:{r:0,g:0,b:0,a:1}, storeOp:'store' }] });
  renderPass.setPipeline(renderPipeline);
  renderPass.setVertexBuffer(0, posBuffer);
  renderPass.draw(numParticles, 1);
  renderPass.end();

  // GPU に送信
  device.queue.submit([commandEncoder.finish()]);
  // 次のフレームを予約
  requestAnimationFrame(frame);
}
frame();

解説:

  1. Compute Pass で粒子位置を更新
  2. Render Pass で更新された位置を点として描画
  3. dispatchWorkgroups で並列数を指定、draw(numParticles) で全粒子描画
  4. requestAnimationFrame でループ実行

🚀 GPUだけで 10万粒子のアニメーションがリアルタイム表示可能に!


これで粒子システムの基本が完成です。次は「7️⃣ 機械学習推論応用:TensorFlow.js × WebGPU」に進みます。

7️⃣ 機械学習推論応用:TensorFlow.js × WebGPU

ブラウザ上で機械学習モデルを動かす際、CPU処理だけでは遅いことがあります。ここでは TensorFlow.js の WebGPU バックエンドを利用し、GPUの並列処理を活かした推論を行います。

7.1 環境セットアップ

npm install @tensorflow/tfjs @tensorflow/tfjs-backend-webgpu

HTML にスクリプトを読み込み:

<script type="module">
  import '@tensorflow/tfjs-backend-webgpu';
  import * as tf from '@tensorflow/tfjs';
  // 以下 7.2 以降のコードをここに記述
</script>

7.2 WebGPU バックエンドの有効化

// どのバックエンドを使うか設定
await tf.setBackend('webgpu');
await tf.ready();
console.log('Backend:', tf.getBackend()); // 'webgpu' と表示されればOK

解説:

  • setBackend('webgpu') で GPU 実行モードに切替
  • await tf.ready() で内部の WebGPU 初期化完了を待機

7.3 モデルのロード

今回は事前学習済み MobileNetV2(小型画像分類モデル)を使用します。

const MODEL_URL = 'https://tfhub.dev/google/tfjs-model/imagenet/mobilenet_v2_100_224/classification/3/default/1';
const model = await tf.loadGraphModel(MODEL_URL, { fromTFHub: true });
console.log('Model loaded');

解説:

  • loadGraphModel で標準化されたTFHubモデルを取得
  • WebGPU へ自動的に最適化されたグラフが内部生成される

7.4 画像前処理

Canvas や画像要素から Tensor を作成し、リサイズ・正規化します。

// 例: <img id="input-img" src="cat.jpg" /> がある場合
const img = document.getElementById('input-img');
let tensor = tf.browser.fromPixels(img)
  .resizeNearestNeighbor([224, 224]) // モデル入力サイズ
  .toFloat()
  .div(255.0)                      // 0-1に正規化
  .expandDims();                   // バッチ次元を追加

解説:

  • fromPixels で HTML の画像データを取得
  • resizeNearestNeighbor で指定サイズに変更
  • div(255) で 0〜255 のピクセル値を 0〜1 にマップ
  • expandDims[height, width, channels][1, h, w, c]

7.5 推論実行 & 結果表示

// 推論開始
const predictions = await model.predict(tensor).data();

// 結果上位5件を取得
const top5 = Array.from(predictions)
  .map((p, i) => ({ probability: p, className: IMAGENET_CLASSES[i] }))
  .sort((a, b) => b.probability - a.probability)
  .slice(0, 5);

console.log('Top-5 predictions:');
top5.forEach((p, i) => console.log(`${i+1}. ${p.className}: ${ (p.probability*100).toFixed(2) }%`));

解説:

  • model.predict は内部で GPU コンピュートパスを構築
  • .data() で結果配列を取得
  • クラスラベル (IMAGENET_CLASSES) は標準リストを参照

7.6 実装まとめとUIサンプル

<input type="file" id="file-input" accept="image/*" />
<img id="input-img" style="max-width:200px;" />
<button id="run-btn">推論実行</button>
<pre id="result"></pre>
<script type="module">
  import '@tensorflow/tfjs-backend-webgpu';
  import * as tf from '@tensorflow/tfjs';

  await tf.setBackend('webgpu'); await tf.ready();
  const model = await tf.loadGraphModel(MODEL_URL, { fromTFHub: true });

  document.getElementById('file-input').onchange = e => {
    const file = e.target.files[0];
    const url = URL.createObjectURL(file);
    document.getElementById('input-img').src = url;
  };

  document.getElementById('run-btn').onclick = async () => {
    const imgEl = document.getElementById('input-img');
    const tensor = tf.browser.fromPixels(imgEl).resizeNearestNeighbor([224,224]).toFloat().div(255).expandDims();
    const preds = await model.predict(tensor).data();
    const top5 = Array.from(preds).map((p,i)=>({p,i}))
      .sort((a,b)=>b.p-a.p).slice(0,5);
    document.getElementById('result').textContent = top5.map((x,idx)=>
      `${idx+1}. ${IMAGENET_CLASSES[x.i]}: ${(x.p*100).toFixed(2)}%`).join('
');
  };
</script>

解説: ユーザーが画像を選択し、ボタンクリックで 即時 GPU 推論 結果を表示する UI。初心者でも組み合わせ例としてコピペOK!


8️⃣ 性能チューニング:速くするコツと注意点

WebGPUを使うと高速処理が可能ですが、より速く・安定して動かすにはいくつか注意点があります。本章では代表的なチューニング手法を初心者向けに解説します。

8.1 バッファのマッピングとメモリ同期

問題:CPUとGPU間のデータコピーがボトルネックに

// 悪い例:毎フレームreadBufferで結果を戻すと遅い
const readBuffer = device.createBuffer({
  size: dataSize,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
});
// ... computePass 終了後
device.queue.copyBufferToBuffer(resultBuffer, 0, readBuffer, 0, dataSize);
await readBuffer.mapAsync(GPUMapMode.READ);
const array = new Float32Array(readBuffer.getMappedRange());
  • mapAsync はGPU同期を誘発し、数ミリ秒〜数十ミリ秒かかることも

対策:SharedArrayBuffer を使わないケースは最小限に

  • 必要なときだけ mapAsync を呼ぶ
  • GPU結果はなるべく画面描画で完結させ、CPUに戻さない設計にする
    • 例:粒子位置は次のComputeで再利用し、画面にのみ反映する

8.2 ワークグループサイズの最適化

デフォルト256では足りない?多すぎる?

@compute @workgroup_size(256)
fn cs_main(@builtin(global_invocation_id) gid : vec3<u32>) { /* ... */ }
  • ワークグループはGPUの並列単位
  • 小さいとGPUリソースを持て余し、大きいと割り当て不足

対策:ベンチマークで適切な値を探す

  1. [64,128,256,512] などで時間計測
  2. 最速かつ使用メモリが少ない設定を採用
for (const size of [64,128,256,512]) {
  // 再コンパイル、再測定コード...
}

8.3 メモリアラインメントとバイトサイズ

256バイト境界に注意

  • WebGPUではバインドグループのオフセットが256バイト単位でアラインされる必要がある
// NG: サイズが250バイト
const badBuffer = device.createBuffer({ size:250, usage:GPUBufferUsage.UNIFORM });
// OK: 256の倍数
const goodBuffer = device.createBuffer({ size:256, usage:GPUBufferUsage.UNIFORM });
  • アラインメント違反すると実行時エラーになることも

8.4 リソースの再利用

毎フレームcreateではなく再利用を

// NG: フレームごとにバッファを作る
function draw() {
  const buf = device.createBuffer({ /* ... */ });
  // 描画処理...
}

// OK: 外で一度だけ作る
const buf = device.createBuffer({ /* ... */ });
function draw() {
  // 更新のみ + 描画処理
}
  • オブジェクト生成コストを削減し、GC影響も防ぐ

8.5 プロファイリングツールの活用

  • Chrome DevTools → Performance タブ → GPU スレッドを見る
  • timestampQuery 機能で GPU 処理時間を取得
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 });
encoder.writeTimestamp(querySet, 0);
// compute / render pass
encoder.writeTimestamp(querySet, 1);
device.queue.submit([encoder.finish()]);
// 結果読み取りは mapAsync で同期
  • 経過時間を詳細可視化し、どのパスが重いか特定する

8.6 まとめ:速く動かすためのチェックリスト

  • mapAsync や readBuffer は最小限に
  • ワークグループサイズをベンチマークで調整
  • バッファサイズは256バイト境界に揃え、再利用
  • プロファイリングでボトルネックを可視化

9️⃣ 実際のフレームワークとPolyfill

WebGPUは低レベルAPIのため、高レベルのラッパーライブラリ互換性を補うPolyfillを活用すると開発がスムーズです。

9.1 wgpu-rs (Rust → WASM)

  • 概要:Rust製のWebGPU実装「WGPU」をWASMでブラウザにバインド
  • 導入手順: # Rustツールチェインをインストール curl https://sh.rustup.rs -sSf | sh rustup target add wasm32-unknown-unknown # Cargo.tomlに追記

[dependencies]

wgpu = “0.14” wasm-bindgen = “0.2” # WASMビルド wasm-pack build –target web メリット:型安全・自動メモリ管理・Rustエコシステムと親和性

9.2 Babylon.js の WebGPU レンダラー

概要:Babylon.js v5 以降に実験的WebGPUバックエンドをサポート

<script src="https://cdn.babylonjs.com/babylon.js"></script>
<canvas id="canvas"></canvas>
<script>
  const canvas = document.getElementById('canvas');
  const engine = new BABYLON.Engine(canvas, true, { adaptToDeviceRatio: true });
  engine.enableWebGPU(); // WebGPU を有効化
  const scene = new BABYLON.Scene(engine);
  engine.runRenderLoop(() => scene.render());
</script>

9.3 Three.js の WebGPURenderer

概要:Three.js r134 以降に実験的WebGPUレンダラーが追加

import { WebGPURenderer } from 'three/examples/jsm/renderers/WebGPURenderer.js';
const renderer = new WebGPURenderer({ canvas, antialias: true });
await renderer.initAdapter();
function animate() { renderer.render(scene, camera); requestAnimationFrame(animate); }
animate();

9.4 Polyfill: @shaderfrog/webgpu-polyfill

  • 概要:WebGPU未対応環境でWebGL2を代替動作させるPolyfill
  • インストール: npm install @shaderfrog/webgpu-polyfill

利用例:

import { polyfill } from '@shaderfrog/webgpu-polyfill'; 
await polyfill(); if (!navigator.gpu) {     console.warn('WebGPU非対応、WebGL2へフォールバックします'); 
} 
initWebGPU();

🔟 まとめ:どこから学び、次に何をする?

本記事では以下を解説しました:

  1. Hello WebGPU:Canvasクリアの最小構成
  2. Compute Shader:GPUで数値を並列計算
  3. 3D描画:頂点→フラグメントパイプライン
  4. 粒子システム:10万点をリアルタイム更新
  5. 機械学習推論:TensorFlow.js × WebGPU
  6. 性能チューニング:メモリ同期・ワークグループ・バッファ再利用
  7. ライブラリ/Polyfill:wgpu-rs, Babylon.js, Three.js, Polyfill

次に学ぶべきテーマ:

  • WebXR × WebGPU:VR/ARコンテンツ制作
  • NeRF実装:ブラウザでNeural Radiance Fields
  • サーバーサイドGPU:Node.jsでバッチ計算&レンダリング