1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WebGLとWebGPUで同じ図形を描画して分かったシェーダーの違い

Last updated at Posted at 2025-12-09

概要

前回の記事はWebGLとWebGPUでcanvasの背景を塗りつぶす処理を比較しました。

今回は、以前書いたこの記事の内容をWebGPUで書き直してみて、両者の違いを理解しようと思います。

完成物

見た目はこのようになります。

黒い背景の中心に赤い矩形を配置しているだけです。

WebGPU版の実装

まずは、WebGPUでの実装を見ていきます。

完全なコード

async function initWebGPU() {
  // Canvas要素の取得
  const canvas = document.getElementById('webgl-canvas') as HTMLCanvasElement;

  // GPUアダプターとデバイスの取得
  const adapter = await navigator.gpu.requestAdapter() as GPUAdapter;
  const device = await adapter.requestDevice();

  // Canvasコンテキストの取得と設定
  const context = canvas.getContext('webgpu') as GPUCanvasContext;
  const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
  context.configure({
    device: device,
    format: canvasFormat,
  });

  // 頂点データの定義(四角形の4つの頂点)
  const vertices = new Float32Array([
    -0.5, -0.5, // 左下
     0.5, -0.5, // 右下
    -0.5,  0.5, // 左上
     0.5,  0.5, // 右上
  ]);

  // 頂点バッファの作成
  const vertexBuffer = device.createBuffer({
    size: vertices.byteLength,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  device.queue.writeBuffer(vertexBuffer, 0, vertices);

  // WGSLシェーダーの定義
  const shaderModule = device.createShaderModule({
    code: `
      @vertex
      fn vertexMain(@location(0) position: vec2f) -> @builtin(position) vec4f {
        return vec4f(position, 0.0, 1.0);
      }

      @fragment
      fn fragmentMain() -> @location(0) vec4f {
        return vec4f(1.0, 0.0, 0.0, 1.0); // 赤色
      }
    `,
  });

  // レンダーパイプラインの構築
  const pipeline = device.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: shaderModule,
      entryPoint: 'vertexMain',
      buffers: [
        {
          arrayStride: 8, // 2 floats * 4 bytes
          attributes: [
            {
              shaderLocation: 0,
              offset: 0,
              format: 'float32x2',
            },
          ],
        },
      ],
    },
    fragment: {
      module: shaderModule,
      entryPoint: 'fragmentMain',
      targets: [
        {
          format: canvasFormat,
        },
      ],
    },
    primitive: {
      topology: 'triangle-strip',
    },
  });

  // 描画コマンドの記録と実行
  const encoder = device.createCommandEncoder();
  const textureView = context.getCurrentTexture().createView();
  const renderPass = encoder.beginRenderPass({
    colorAttachments: [
      {
        view: textureView,
        loadOp: 'clear',
        clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, // 黒色
        storeOp: 'store',
      },
    ],
  });
  renderPass.setPipeline(pipeline);
  renderPass.setVertexBuffer(0, vertexBuffer);
  renderPass.draw(4); // 4つの頂点を描画
  renderPass.end();
  device.queue.submit([encoder.finish()]);
}

initWebGPU();

実装の流れ

WebGPUでの図形描画は、以下のステップで進みます。

  1. GPUデバイスの取得
  2. Canvasコンテキストの設定
  3. 頂点データの準備とバッファ作成
  4. シェーダーモジュールの作成
  5. レンダーパイプラインの構築
  6. コマンドエンコーダーでの描画命令の記録
  7. コマンドバッファの送信

重要なポイント

前回の記事で説明したコマンドバッファの仕組みに加え、今回は明示的なパイプライン設定が必要になります。
createRenderPipeline()では、頂点バッファのレイアウト、シェーダーのエントリーポイント、プリミティブのトポロジーなど、描画に必要な全ての情報を事前に設定します。

WebGL版の実装

次に、同じ処理をWebGLで実装した場合を見ていきます。

完全なコード

function initWebGL() {
  const canvas = document.getElementById('webgl-canvas') as HTMLCanvasElement;
  const gl = canvas.getContext('webgl2') as WebGL2RenderingContext;

  // 頂点シェーダーのソースコード
  const vertexShaderSource = `#version 300 es
    in vec2 aPosition;
    void main() {
      gl_Position = vec4(aPosition, 0.0, 1.0);
    }
  `;

  // フラグメントシェーダーのソースコード
  const fragmentShaderSource = `#version 300 es
    precision mediump float;
    out vec4 fragColor;
    void main() {
      fragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
  `;

  // シェーダーのコンパイル
  const vertexShader = gl.createShader(gl.VERTEX_SHADER)!;
  gl.shaderSource(vertexShader, vertexShaderSource);
  gl.compileShader(vertexShader);

  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)!;
  gl.shaderSource(fragmentShader, fragmentShaderSource);
  gl.compileShader(fragmentShader);

  // プログラムの作成とシェーダーのリンク
  const program = gl.createProgram()!;
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  gl.useProgram(program);

  // 頂点データ(四角形の4つの頂点)
  const vertices = new Float32Array([
    -0.5, -0.5, // 左下
     0.5, -0.5, // 右下
    -0.5,  0.5, // 左上
     0.5,  0.5, // 右上
  ]);

  // バッファの作成とデータの転送
  const buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  // 属性の設定
  const aPosition = gl.getAttribLocation(program, 'aPosition');
  gl.enableVertexAttribArray(aPosition);
  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  // 背景色の設定とクリア
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // 描画
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

initWebGL();

実装の流れ

WebGLでの図形描画は、以下のステップで進みます。

  1. シェーダーの作成とコンパイル
  2. プログラムの作成とリンク
  3. 頂点データの準備
  4. バッファへのデータ転送
  5. 属性の設定
  6. 描画コマンドの実行

重要なポイント

前回の記事で説明した状態マシンモデルに従い、gl.bindBuffer()でバッファをバインドすると、その後の操作は全てそのバッファに対して実行されます。
シェーダーは個別にコンパイルし、プログラムとしてリンクする必要があります。

シェーダー言語の詳細な違い

今回の実装ではシェーダーを書きました。
WebGLとWebGPUでは異なるシェーダー言語を使用します。

WebGLはGLSL(OpenGL Shading Language)を使用します。
GLSLはOpenGLから派生したシェーダー言語で、C言語に似た構文を持ちます。

WebGPUはWGSL(WebGPU Shading Language)を使用します。
WGSLはWebGPU専用に設計された新しいシェーダー言語で、Rustに似た構文を持ちます。

バージョン宣言とエントリーポイント

GLSL(WebGL)では、ファイルの先頭でバージョンを宣言します。

#version 300 es

WGSL(WebGPU)では、バージョン宣言はなく、代わりに関数にアトリビュートを付けてエントリーポイントを指定します。

@vertex
fn vertexMain() { ... }

型の表記

GLSLでは型名と変数名の間にスペースを入れます。

vec2 aPosition
vec4 fragColor

WGSLでは変数名の後にコロンと型名を記述します。

position: vec2f

また、WGSLの型名には精度が含まれます。
vec2fは32ビット浮動小数点のvec2を意味し、正確にはvec2<f32>の簡略記法(エイリアス)です。
同様にvec4fvec4<f32>のエイリアスになります。

入出力の指定

GLSLではinoutキーワードで入出力を指定します。

in vec2 aPosition;  // 頂点シェーダーの入力
out vec4 fragColor; // フラグメントシェーダーの出力

WGSLでは関数の引数と戻り値で表現し、アトリビュートで詳細を指定します。

fn vertexMain(@location(0) position: vec2f) -> @builtin(position) vec4f
fn fragmentMain() -> @location(0) vec4f

@location(0)は頂点バッファの0番目の属性から値を受け取ることを意味します。
@builtin(position)はGPUの組み込み変数(頂点の位置)に書き込むことを意味します。

ベクトルのコンストラクタ

GLSLでは型名をそのまま使います。

vec4(aPosition, 0.0, 1.0)
vec4(1.0, 0.0, 0.0, 1.0)

WGSLでは型名の後にfを付けます。

vec4f(position, 0.0, 1.0)
vec4f(1.0, 0.0, 0.0, 1.0)

精度修飾子

GLSLでは、フラグメントシェーダーで精度を明示的に指定する必要があります。

precision mediump float;

WGSLでは、型名自体に精度が含まれているため(vec2ffは32ビット浮動小数点を表す)このような精度宣言は不要です。

main関数

GLSLでは必ずmain()という名前の関数がエントリーポイントになります。

void main() { ... }

WGSLでは任意の関数名を使用でき、パイプライン設定でentryPointとして指定します。

vertex: {
  entryPoint: 'vertexMain',
  ...
}

この柔軟性により、1つのシェーダーモジュールに複数のエントリーポイントを定義できます。

その他の違い

コード量

WebGL版は約60行、WebGPU版は約100行と、WebGPUの方が多くのコードを必要とします。
これは、WebGPUが明示的な設定を重視する設計になっているためです。

バッファ管理

WebGLではbindBuffer()bufferData()を使ってバッファを作成・転送します。
WebGPUではcreateBuffer()でバッファを作成し、writeBuffer()でデータを転送します。
WebGPUではバッファの用途(GPUBufferUsage.VERTEXGPUBufferUsage.COPY_DST)を明示的に指定する必要があります。

特にGPUBufferUsage.COPY_DSTフラグは重要です。
writeBuffer()でCPUからデータを書き込む際は、転送先として使えるようにこのフラグを指定する必要があります。
WebGPUはこのような用途の指定が厳密で、フラグが不足しているとエラーになります。

属性の接続

WebGLではgetAttribLocation()で属性の場所を取得し、vertexAttribPointer()で接続します。

const aPosition = gl.getAttribLocation(program, 'aPosition');
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

WebGPUではパイプライン構築時に全て宣言的に記述します。

buffers: [
  {
    arrayStride: 8,
    attributes: [
      {
        shaderLocation: 0,
        offset: 0,
        format: 'float32x2',
      },
    ],
  },
]

クリップ空間の違い

今回は2D描画(Z=0)なので影響しませんが、WebGLとWebGPUではクリップ空間のZ軸の範囲が異なります。

  • WebGL: -1.0 ~ 1.0
  • WebGPU: 0.0 ~ 1.0

3D描画を行う際は、この違いに注意が必要です。
特に既存のWebGLコードをWebGPUに移植する場合、深度計算やプロジェクション行列の調整が必要になります。

まとめ

赤い四角形を描画するというシンプルな内容でも、WebGLとWebGPUではコード
が大きく異なりました。

  • シェーダー言語(GLSLとWGSL)は構文が異なり、WGSLの方が型安全性が高く現代的な設計
  • WebGPUは明示的な設定が必要でコード量は多いが、最適化できる余地が大きい
  • WebGLはステートマシン、WebGPUはコマンドバッファと設計思想が異なる
  • WebGPU対応ブラウザが使える環境であれば、新規プロジェクトではWebGPUを選ぶメリットが大きい
    • ただし、学習コストはWebGPUの方が高いため、シンプルな用途ならWebGLも十分選択肢になる
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?