0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WebGPUを試してみる:線画と面描画を別パイプラインで混在させて立方体を回してみた

0
Last updated at Posted at 2026-08-25

WebGPUを試してみる:線画と面描画を別パイプラインで混在させて立方体を回してみた

はじめに

こんにちは。今回は、自作の3D計算・タイマー管理ライブラリ(nas6lib)をベースに、モダンブラウザの標準となりつつある WebGPU を使って、線と面が混在する立方体の3Dアニメーションを描画するデモを作成しました。

デモページはこちらです:
🔗 3Dプログラミング講座・その33:WEBGPUテスト (nas6.net)

perthWEBGPU000.gif

WebGPUの仕様上、1回のドローコールで異なるプリミティブトポロジ(line-listtriangle-list)を直接混在させられない制限があるため、「2つのパイプラインを切り替えて描画する」というアプローチをとっています。その実装のポイントを備忘録としてシェアします。


デモの概要

  • 描画内容: ワイヤーフレーム(緑色)の立方体を描きつつ、上面だけを三角形2枚(薄い黄緑)で塗りつぶす。
  • アニメーション: 自作のタイマーマネージャー(N6LTimerMan)と回転行列・透視射影関数を使い、毎フレーム座標を計算して描画を更新。
  • WebGPU特有の工夫:
  • 線描画用(line-list)と面描画用(triangle-list)の2つの GPURenderPipeline を用意。
  • 1つのレンダーパス内でパイプラインを切り替えながら、頂点バッファを共有して効率的に描画。

注釈:簡略化された透視射影の概念図

frustum000.png


実装のポイント:線と面をどう共存させるか?

WebGPUでは、パイプライン作成時に primitive.topologyline-listtriangle-list など)を固定します。そのため、同じオブジェクト内で「ワイヤーフレーム」と「塗りつぶし面」を両立させるには、レンダーパス内でパイプラインを切り替える必要があります。

実際の描画ループ(GLoop)のコードの骨子は以下のようになっています。

function GLoop(id) {
  time += Math.PI / 180;

  // 1. 3D座標の回転・透視投影計算(自作関数を使用)
  // ... (省略: 頂点座標の計算&NDC変換) ...

  const encoder = device.createCommandEncoder();
  const textureView = context.getCurrentTexture().createView();

  const pass = encoder.beginRenderPass({
    colorAttachments: [{
      view: textureView,
      loadOp: 'clear',
      storeOp: 'store',
      clearValue: { r: 0, g: 0, b: 0, a: 1 }, // 背景:黒
    }],
  });

  // --- 2. 上面の塗りつぶし(三角形描画) ---
  {
    const tverts = [];
    // 三角形1・2の頂点を構築してバッファに書き込む
    // ...
    device.queue.writeBuffer(vertexBuffer, 0, tdata.buffer);

    pass.setPipeline(trianglePipeline); // 面用パイプライン
    pass.setVertexBuffer(0, vertexBuffer);
    pass.draw(6, 1, 0, 0);  // 三角形2枚(6頂点)
  }

  // --- 3. 輪郭線の描画(線描画) ---
  {
    // 線の頂点データを構築してバッファに書き込む
    // ...
    device.queue.writeBuffer(vertexBuffer, 0, data.buffer);

    pass.setPipeline(linePipeline);     // 線用パイプライン
    pass.setVertexBuffer(0, vertexBuffer);
    
    for (let i = 0; i < edges.length; i++) {
      pass.draw(2, 1, i * 2, 0);        // 1本ずつ線を描画
    }
  }

  pass.end();
  device.queue.submit([encoder.finish()]);

  // 次フレームの予約
  TMan.timer[0].setalerm(() => GLoop(0), 50);
}

工夫した点

頂点バッファの動的更新: フレームごとに計算される頂点データを device.queue.writeBuffer を使って効率よくGPU側へ転送しています。


シェーダーコード(WGSL)

頂点シェーダーは共通(位置をそのまま受け渡す)、フラグメントシェーダーでそれぞれ「緑の線」「薄い黄緑の面」の色を返しています。

// 線描画用シェーダーモジュール
const shaderCode = `
@vertex
fn vs_main(@location(0) inPos : vec2<f32>) -> @builtin(position) vec4<f32> {
  return vec4<f32>(inPos, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4<f32> {
  return vec4<f32>(0.0, 1.0, 0.0, 1.0); // 緑
}
`;

// 面描画用シェーダーモジュール
const triangleshaderCode = `
@vertex
fn vs_main(@location(0) inPos : vec2<f32>) -> @builtin(position) vec4<f32> {
  return vec4<f32>(inPos, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4<f32> {
  return vec4<f32>(0.7, 1.0, 0.7, 1.0); // 薄い黄緑
}
`;


おわりに

WebGPUへ移行するにあたり、パイプラインやレンダーパスの概念をしっかり理解する必要がありましたが、このように自前の数学ルーチンと組み合わせることで、クリーンかつモダンな描画環境を構築することができました。

実際の動きや全ソースコードは以下のページから確認できますので、興味のある方はぜひ触ってみてください!

🔗 デモページを見る

perthWEBGPU000.gif


作者

GitHub: https://github.com/NAS6mixfoolv
X(旧Twitter): https://x.com/NAS6_oxo
作者HP: https://nas6.net

気に入っていただけたら GitHub に ⭐ をいただけると嬉しいです!


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?