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の構造の理解にも役立つことでしょう。