「WebGPU」でシェーダーを使って三角形を1つだけ描画してみよう

2025年7月17日(木)
大西 武 (オオニシ タケシ)
第5回の今回は、WebGPUでシンプルなシェーダーを使って三角形を描画する解説をします。

はじめに

今回は、やっと三角形を描画してみます。しかも1つだけです。「たったこれだけ?」と思われるかもしれませんが、第1回でも解説したように、何千何万個の三角形が組み合わさって3Dモデルが形成されるのです。つまり、三角形の描き方さえ分かれば、ほとんどできたようなものです。

あとは「マテリアル(色や模様などの材質)」を「法線(面の向き)」を考慮してグラデーションをかけるなどして色を塗るだけです。図1のようにその描画を決める部分を「シェーダー」が担当します。

図1:シェーダーとパイプラインと頂点データ

シェーダーを準備する

図2のように、ここではまだ三角形のデータをシェーダーに渡していないため、まだ三角形は何も描画されず真っ黒な<canvas>画面です。頂点データを用意したら三角形を描画するために、ここでシェーダーと「パイプライン」を準備します。

図2:まだシェーダーを準備しただけで真っ黒な画面

頂点シェーダーでは三角形の頂点を決めます。フラグメントシェーダーでは頂点で囲まれたポリゴン面の内側の1ピクセルずつの色を決めます。頂点は三角形だけでなくポイントやラインの場合もあります。

「index.html」から「lib」→「WGSL.js」ファイルを読み込む

次のサンプルコードをコーディングして、JavaScriptファイル「lib」→「WGSL.js」ファイルを読み込みます。WGSL.jsファイルにはWGSL言語で記述したシェーダーを記述します。まだ「init」関数は何もしませんが、宣言だけします。

・サンプルコード「index.html」
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>UltraMotion3D</title>
    <script src="lib/WGSL.js"></script>
    <script src="lib/UltraMotion3D.js"></script>
    <script type="text/javascript">
function init() {
}
    </script>
  </head>
  <body onload='initWebGPU("CanvasAnimation");'>
    <canvas id="CanvasAnimation" width="1000" height="900"></canvas>
  </body>
</html>

シェーダーを読み込む

「レンダーパイプライン」を使ってシェーダーを読み込み、受け取った頂点データや変換行列などをシェーダーに渡します。シェーダーに渡された頂点データなどで頂点を描画する位置を決めたり、描画する色を決めたりします。

・サンプルコード「lib」→「UltraMotion3D.js」
var _passEncoder = null;

async function initWebGPU(canvas) {
  if (!navigator.gpu) {
    throw Error('WebGPU not supported.');
  }
  const adapter = await navigator.gpu.requestAdapter();
  if (!adapter) {
    throw Error('Could not request WebGPU adapter.');
  }
  _device = await adapter.requestDevice();
  _canvas = document.getElementById(canvas);
  _context = _canvas.getContext('webgpu');
  _presentationFormat = navigator.gpu.getPreferredCanvasFormat();
  _context.configure({
    device: _device,
    format: _presentationFormat,
    alphaMode: 'premultiplied',
  });
  _pipeline = setPipeline(vertWGSL,fragWGSL);
  window.addEventListener("resize", resize, false);
  resize();
  init();
  everyFrame();
}

function setPipeline(vertexWGSL,fragmentWGSL) {
  const pipeline = _device.createRenderPipeline({
  layout: 'auto',
  vertex: {
    module: _device.createShaderModule({
      code: vertexWGSL,
    }),
    entryPoint: 'main',
    buffers: [{
      attributes: [
        {
          shaderLocation: 0,
          offset: 0,
          format: 'float32x4',
        },
      ],
      arrayStride: 4*4, // 4byte(float32) × 4個
      stepMode: "vertex",
    },],
  },
  fragment: {
    module: _device.createShaderModule({
      code: fragmentWGSL,
    }),
    entryPoint: 'main',
    targets: [
      {
        format: _presentationFormat,
      },
    ],
  },
  primitive: {
    topology: 'triangle-list',
  },
  depthStencil: {
    depthWriteEnabled: true,
    depthCompare: 'less',
    format: 'depth24plus',
  },
});
  return pipeline;
}

function everyFrame() {
(中略)
}

function resize() {
(中略)
}

【サンプルコードの解説】
「initWebGPU」関数で「setPipeline」関数を呼び出して取得したパイプラインを「_pipeline」変数に代入します。頭文字に「_」がある変数はグローバル変数です。
「setPipeline」関数で頂点シェーダーとフラグメントシェーダーを読み込み、パイプラインを作成してそれを戻り値で返します。
「createShaderModule」メソッドで作る頂点シェーダーは頂点の座標を決めます。attributesのフォーマットがfloat32(4byte)x(X,Y,Z,W)の4個なので、arrayStride(配列の歩)は4*4になります。
「createShaderModule」メソッドで作るフラグメントシェーダーはポリゴンピクセルの色を決めます。ターゲットのフォーマットは「_presentationFormat」に指定します。
ポリゴンは「三角形リスト(triangle-list)」に、デプスバッファを有効にします。

シェーダーを記述する

次のサンプルコードをコーディングしたら、Google Chromeからindex.htmlを開いてください。ここで初めてシェーダーを実装したWebGPUアプリを実行しました。まだ三角形の頂点データを渡していないので、真っ黒な<canvas>画面のままです。

・サンプルコード「lib」→「WGSL.js」
const vertWGSL = `
struct VertexOutput {
  @builtin(position) position : vec4f,
}

@vertex
fn main(
  @location(0) position: vec4f,
) -> VertexOutput {
  var output : VertexOutput;
  output.position =  position;
  return output;
}
`;
const fragWGSL = `
struct VertexOutput {
  @builtin(position) position : vec4f,
}
@fragment
fn main(fragData: VertexOutput) -> @location(0) vec4f {
  return vec4f(0.0,0.0,0.0,1.0);
}
`;

【サンプルコードの解説】
「vertWGSL」定数を頂点シェーダー(@vertex)としパイプラインで決めた「entryPoint」の'main'関数をエントリーポイントにします。パイプラインで決めた「shaderLocation」の0が@location(0)になり、「float32x4」が「vec4f」になります。戻り値で「VertexOutput」構造体を返します。
「fragWGSL」定数をフラグメントシェーダー(@fragment)としパイプラインで決めた「entryPoint」の'main'関数をエントリーポイントにします。「VertexOutput」構造体を頂点シェーダーから受け取り、戻り値で黒色vec4f(0.0,0.0,0.0,1.0)を返します。

著者
大西 武 (オオニシ タケシ)
1975年香川県生まれ。大阪大学経済学部経営学科中退。プログラミング入門書など30冊以上を商業出版する作家。Microsoftで大賞やNTTドコモでグランプリなど20回以上全国区のコンテストに入賞するアーティスト。オリジナルの間違い探し「3Dクイズ」が全国放送のTVで約10回出題。
https://profile.vixar.jp

連載バックナンバー

開発言語技術解説
第5回

「WebGPU」でシェーダーを使って三角形を1つだけ描画してみよう

2025/7/17
第5回の今回は、WebGPUでシンプルなシェーダーを使って三角形を描画する解説をします。
開発言語技術解説
第4回

はじめての「WebGPU」で背景色を塗りつぶしてみよう

2025/6/25
第4回の今回は、WebGPUを初期化してタグを真っ黒な背景色で塗りつぶしてみる解説をします。
開発言語技術解説
第3回

WebGPUライブラリ「UltraMotion3D」で3DのサンプルWebコンテンツを動かしてみよう

2025/6/5
第3回の今回は、WebGPUライブラリ「UltraMotion3D」を使った3DのサンプルWebコンテンツの動かし方と「XAMPP」の使い方を解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています