WebGPUのシェーダー言語WGSL

WebGPUのシェーダー言語はWGSLという、一風Rust言語にも似た独特な文法の言語です。

今回のサンプルでは、頂点シェーダー、フラグメントシェーダーそれぞれ、次のようなシェーダーコードになります。

// 頂点シェーダー @vertex fn main( @builtin(vertex_index) VertexIndex : u32 ) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(0.0, 0.5), vec2<f32>(-0.5, -0.5), vec2<f32>(0.5, -0.5) ); return vec4<f32>(pos[VertexIndex], 0.0, 1.0); }
// フラグメントシェーダー @fragment fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.5, 0.0, 0.0, 0.5); }

GLSLとはだいぶ異なっておりギョッとしますが、1つづつみていきましょう。

頂点シェーダー

最初の@vertexは、これが頂点シェーダーであることを意味します。

@vertex

次にfnというのはfunctionの略です。Rust言語でも関数宣言をfnと書きますので、非常にrustっぽいですね。

fn main(

次の

@builtin(vertex_index) VertexIndex : u32

ですが、@builtinはシェーダー言語WGSLにあらかじめ組み込まれている変数を利用することを意味します。つまり、WebGPU側で特に設定しなくても標準で使える内部変数ということです。ここではvertex_indexという内部変数をVertexIndexという変数名で利用することを宣言しています。: u32はVertexIndexの型です。unsignedの32bitであることを意味します。ここら辺もrustっぽいですね。

次の

) -> @builtin(position) vec4<f32> {

ですが、この->はmain関数の戻り値の型を意味するものです。これもrust風の記法ですね。
見るとその型は@builtin(position) vec4<f32>とあります。@bultin(position)positionというWGSLの内部変数であることを意味しています。これはGLSLでいうところのgl_Positionに相当します。vec4はそのposition内部変数の型です。32bit floatの4要素ベクター型であることを意味しています。

次の、

var pos = array<vec2<f32>, 3>( vec2<f32>(0.0, 0.5), vec2<f32>(-0.5, -0.5), vec2<f32>(0.5, -0.5) );

は、2要素ベクター3つ分の配列を宣言しています。これは3頂点分の2次元頂点座標ですね。ここで宣言した頂点座標の配列を、次の

return vec4<f32>(pos[VertexIndex], 0.0, 1.0);

でアクセスしています。VertexIndexは先ほどの内部変数です。つまり描画した際の頂点インデックスです。
前ページまでのWebGPUコードを振り返っていただくとわかるかと思いますが、特に頂点データの設定などは行なっていませんでした。それにもかかわらず三角形が描画できていたのは、このシェーダー内での頂点座標の配列がまさに三角形の頂点座標リストであり、それをVertexIndexで添え字アクセスしていたのです。

フラグメントシェーダー

最初の@fragmentはこれがフラグメントシェーダーであることを意味しています。

@fragment
fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.5, 0.0, 0.0, 0.5); }

-> @location(0) vec4<f32>ですが、これはmain関数の戻り値を示しています。つまり、フラグメントシェーダーの出力ですね。
この@location(0)はなんでしょうか?

これは、WebGPUコードのRenderPipelineの設定コードの以下に対応します。

// create a render pipeline const pipeline = g_device.createRenderPipeline({ layout: 'auto', vertex: { module: g_device.createShaderModule({ code: triangleVertWGSL, }), entryPoint: 'main', }, fragment: { module: g_device.createShaderModule({ code: redFragWGSL, }), entryPoint: 'main', targets: [ // 0 { // @location(0) in fragment shader format: presentationFormat, }, ], }, primitive: { topology: 'triangle-list', }, });

つまり、fragment.targets配列の0番目のformatに対応します。

また、renderPassDescriptor.colorAttachments配列の0番目の指定に対応します。

const textureView = context.getCurrentTexture().createView(); const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [ { view: textureView, clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear', storeOp: 'store', }, ], };

ということは、フラグメントシェーダーの@location(0) context.getCurrentTexture().createView();つまりcanvasのバックバッファを意味することになります。

最終的なコード

最終的なコードを以下に示します。

まとめ

WebGPUでのHelloWorld、三角形描画を解説しました。
WebGPUは必要な初期や指定がWebGLより多く、最初は大変かもしれませんが、使いこなした先にはハイパフォーマンスな描画体験が待っています。また、WebGLよりもより現在のGPUの作りにマッチした設計思想になっているため、現代のGPUの構造の理解にも役立つことでしょう。

前のページ