オブジェクトの頂点変換やピクセルを色付けするシェーダー言語「WGSL」の文法を学ぼう

フラグメントシェーダー
頂点シェーダーは頂点の位置だけでしたが、フラグメントシェーダーでは実際にそこに塗る色を決めます。例えば、図2のように黒い頂点で囲まれた三角形の中に塗る青白いピクセルを決めます。
フラグメントシェーダーの文法
フラグメントシェーダーは次の書式のように記述します。戻り値の型は「@location(0) vec4f」という32ビットずつのRGBA色になることが多いです。
・フラグメントシェーダーの書式@fragment fn main(引数) -> 戻り値の型 { 頂点の色を計算 return 戻り値; }
プログラムは通常上から順に下へ流れて実行されて行きますが、制御構文はその流れを変える働きをします。制御構文の中では「{ }(波括弧)」は必須です。
・条件構文「if文」var i = 1; if (i < 3) { ステートメント } else if (i > 6) { ステートメント } else { ステートメント }・繰り返し構文「for文」
for (var i = 0; i < 8; i++) { ステートメント }・繰り返し構文「while文」
var i = 1; while (i < 5) { i++; }・繰り返し構文「loop文」
var i = 1; loop { i++; if ( i < 10 ) { continue; } if (i >= 15) { break; } }・分岐構文「switch文」
var i = 1; var j: i32; switch i { case 0: { j = 1; } default { j = 2; } }
フラグメントシェーダーのサンプル
フラグメントシェーダーは色を決定しますが、次のサンプルコードではテクスチャ画像を使って色を決定します。法線で各ピクセルの向きに合わせて明るさを決め、反射強度で光る強さを決め、UV座標でテクスチャ画像のどの部分の色を使うかを決めて、それらを計算して1ピクセルずつの色を決めます。
・サンプルコード「lib」→「WGSL.js」@group(0) @binding(1) var mySampler : sampler; @group(0) @binding(2) var myTexture : texture_2d<f32>; struct VertexOutput { @builtin(position) position : vec4f, @location(0) normal : vec3f, @location(1) color : vec4f, @location(2) specular : f32, @location(3) uv : vec2f } @fragment fn main(fragData: VertexOutput) -> @location(0) vec4f { var lightDirection = normalize(vec3f(-1.0,-2.0,-4.0)); var normal = normalize(fragData.normal); var diffuseLightWeighting = (1+dot(normal,lightDirection))/2; var texColor = textureSample(myTexture, mySampler, fragData.uv); var emi = fragData.color.r; var reflect = normalize(2.0*diffuseLightWeighting*normal-lightDirection); var spc = pow(clamp(dot(reflect,lightDirection),0.0,1.0),5.0); var specular = vec4(fragData.specular,fragData.specular,fragData.specular,1); var color = texColor*clamp(emi+diffuseLightWeighting,0.0,1.0)+spc*specular; return vec4f(color.rgb,1.0); }
【サンプルコードの解説】
「sampler(サンプラー)」はテクスチャからサンプリング(色などのデータを取得)する際に使われるオブジェクトです。テクスチャのフィルタリングやアドレッシング(境界の処理)方法を指定します。
「texture_2d<f32>」は1ピクセルが32ビットのテクスチャ画像データです。@groupはデバイスの「createBindGroup」メソッドから送られたバインディングです。
「VertexOutput」構造体は前出の頂点シェーダーの戻り値と全く同じ型でなければなりません。
「lightDirection」変数は正規化した光の向きです。
「normal」変数は引数のnormalプロパティを正規化した法線ベクトルです。
「diffuseLightWeighting」変数は法線ベクトルとライトの向きからピクセルの拡散光(明るさ)を取得します。
「texColor」変数はmyTextureをサンプリングしてUV座標の色を取得します。
「emi」変数は発光色です。
「reflect」変数は反射を取得します。
「spc」は反射から反射強度を取得します。
「specular」変数は引数のspecularプロパティから反射の色を取得します。
「color」変数はテクスチャの色と発光色と拡散光と反射強度から最終的な色を取得します。
「return」で戻り値vec4f(color.rgb,1.0)を返します。見ての通り不透明です。
以前、高校・大学と同期だった知人が筆者のWebサイトを見て「驚いた」とメールしてきました。そのことをメル友に話すと「何で大学まで行ってそんな畑違いのことをしてると思われたの?」と言うので「いや、そんなすごいことはできない、と驚いたらしい」と答えました。ビジネスでクリエイティブなことをやっているのはおかしくなくて、半分趣味でクリエイティブなことをやっているのはおかしなことなのでしょうか?
おわりに
今回は「シェーダー(頂点シェーダーとフラグメントシェーダー)」の基本的な文法解説しました。次回は、Uniformバッファを使った三角形の描画について解説します。