WGSLの詳細な概要については、WGSLツアーを参照してください。また、実際のWGSL仕様もありますが、言語弁護士向けに書かれているため、処理が難しい場合があります😂
この記事は、すでにプログラミングの方法を知っていることを前提としているため、WGSLの例を見るだけで、何が表示されているかを理解できる可能性が高いです。おそらく簡潔すぎますが、WGSLシェーダープログラムの理解と作成に役立つことを願っています。
JavaScriptとは異なり、WGSLでは、すべての変数、構造体フィールド、関数パラメータ、および関数戻り値の型を知る必要があります。typescript、rust、C++、C#、Java、Swift、Kotlinなどを使用したことがある場合は、これに慣れています。
WGSLのプレーン型は次のとおりです。
i32 32ビット符号付き整数u32 32ビット符号なし整数f32 32ビット浮動小数点数bool ブール値f16 16ビット浮動小数点数(これは、確認して要求する必要があるオプション機能です)JavaScriptでは、次のように変数と関数を宣言できます。
var a = 1;
let c = 3;
function d(e) { return e * 2; }
WGSLでは、それらの完全な形式は次のようになります。
var a: f32 = 1;
let c: f32 = 3;
fn d(e: f32) -> f32 { return e * 2; }
上記から注意すべき重要な点は、変数宣言に: f32のような: <type>を追加し、関数宣言に-> <type>を追加する必要があることです。
WGSLには、変数のショートカットがあります。typescriptと同様に、変数の型を宣言しない場合、右側の式の型に自動的になります。
fn foo() -> bool { return false; }
var a = 1; // aはi32です
let b = 2.0; // bはf32です
var c = 3u; // cはu32です
var d = foo(); // dはboolです
さらに、厳密に型付けされているということは、多くの場合、型を変換する必要があることを意味します。
let a = 1; // aはi32です let b = 2.0; // bはf32です *let c = a + b; // エラー:i32をf32に追加できません
修正は、一方を他方に変換することです。
let a = 1; // aはi32です let b = 2.0; // bはf32です let c = f32(a) + b; // ok
しかし、WGSLには「AbstractInt」と「AbstractFloat」と呼ばれるものがあります。これらは、まだ型が決定されていない数値と考えることができます。これらはコンパイル時のみの機能です。
let a = 1; // aはi32です let b = 2.0; // bはf32です *let c = a + b; // エラー:i32をf32に追加できません let d = 1 + 2.0; // dはf32です
2i // i32 3u // u32 4f // f32 4.5f // f32 5h // f16 5.6h // f16 6 // AbstractInt 7.0 // AbstractFloat
let、var、constは、WGSLとJavaScriptで意味が異なります。JavaScriptでは、varは関数スコープを持つ変数です。letはブロックスコープを持つ変数です。constは、ブロックスコープを持つ定数変数(変更不可)です[1]。
WGSLでは、すべての変数はブロックスコープを持ちます。varはストレージを持つ変数であり、したがって変更可能です。letは定数値です。
fn foo() {
let a = 1;
* a = a + 1; // エラー:aは定数式です
var b = 2;
b = b + 1; // ok
}
constは変数ではなく、コンパイル時の定数です。実行時に発生するものにconstを使用することはできません。
const one = 1; // ok
const two = one * 2; // ok
const PI = radians(180.0); // ok
fn add(a: f32, b: f32) -> f32 {
* const result = a + b; // エラー:constはコンパイル時式でのみ使用できます
return result;
}
WGSLには、vec2、vec3、vec4の3つのベクトル型があります。基本的なスタイルはvec?<type>なので、vec2<i32>(2つのi32のベクトル)、vec3<f32>(3つのf32のベクトル)、vec4<u32>(4つのu32のベクトル)、vec3<bool>(3つのブール値のベクトル)です。
例:
let a = vec2<i32>(1, -2); let b = vec3<f32>(3.4, 5.6, 7.8); let c = vec4<u32>(9, 10, 11, 12);
さまざまなアクセサーを使用して、ベクトル内の値にアクセスできます。
let a = vec4<f32>(1, 2, 3, 4); let b = a.z; // x、y、z、w経由 let c = a.b; // r、g、b、a経由 let d = a[2]; // 配列要素アクセサー経由
上記では、b、c、dはすべて同じです。それらはすべてaの3番目の要素にアクセスしています。それらはすべて「3」です。
複数の要素にアクセスすることもできます。
let a = vec4<f32>(1, 2, 3, 4); let b = a.zx; // x、y、z、w経由 let c = a.br; // r、g、b、a経由 let d = vec2<f32>(a[2], a[0]);
上記では、b、c、dはすべて同じです。それらはすべてvec2<f32>(3, 1)です。
要素を繰り返すこともできます。
let a = vec4<f32>(1, 2, 3, 4); let b = vec3<f32>(a.z, a.z, a.y); let c = a.zzy;
上記では、bとcは同じです。どちらも、内容が3、3、2のvec3<f32>です。
基本型にはショートカットがあります。<i32>をiに、<f32>をfに、<u32>をuに、<f16>をhに変更します。
let a = vec4<f32>(1, 2, 3, 4); let b = vec4f(1, 2, 3, 4);
aとbは同じ型です。
ベクトルは、より小さな型で構築できます。
let a = vec4f(1, 2, 3, 4); let b = vec2f(2, 3); let c = vec4f(1, b, 4); let d = vec4f(1, a.yz, 4); let e = vec4f(a.xyz, 4); let f = vec4f(1, a.yzw);
a、c、d、e、fは同じです。
ベクトルで数学を行うことができます。
let a = vec4f(1, 2, 3, 4); let b = vec4f(5, 6, 7, 8); let c = a + b; // cはvec4f(6, 8, 10, 12)です let d = a * b; // dはvec4f(5, 12, 21, 32)です let e = a - b; // eはvec4f(-4, -4, -4, -4)です
多くの関数はベクトルでも機能します。
let a = vec4f(1, 2, 3, 4); let b = vec4f(5, 6, 7, 8); let c = mix(a, b, 0.5); // cはvec4f(3, 4, 5, 6)です let d = mix(a, b, vec4f(0, 0.5, 0.5, 1)); // dはvec4f(1, 4, 5, 8)です
WGSLには多数の行列型があります。行列はベクトルの配列です。形式はmat<numVectors>x<vectorSize><<type>>なので、たとえばmat3x4<f32>は3つのvec4<f32>の配列です。ベクトルと同様に、行列にも同じショートカットがあります。
let a: mat4x4<f32> = ... let b: mat4x4f = ...
aとbは同じ型です。
配列構文を使用して、行列のベクトルを参照できます。
let a = mat4x4f(...); let b = a[2]; // bはaの3番目のベクトルのvec4fです
3D計算で最も一般的な行列型はmat4x4fであり、vec4fと直接乗算して別のvec4fを生成できます。
let a = mat4x4f(....); let b = vec4f(1, 2, 3, 4); let c = a * b; // cはvec4fであり、a * bの結果です
WGSLの配列は、array<type, numElements>構文で宣言されます。
let a = array<f32, 5>; // 5つのf32の配列 let b = array<vec4f, 6>; // 6つのvec4fの配列
しかし、arrayコンストラクタもあります。任意の数の引数を受け取り、配列を返します。引数はすべて同じ型でなければなりません。
let arrOf3Vec3fsA = array(vec3f(1,2,3), vec3f(4,5,6), vec3f(7,8,9)); let arrOf3Vec3fsB = array<vec3f, 3>(vec3f(1,2,3), vec3f(4,5,6), vec3f(7,8,9));
上記では、arrOf3Vec3fsAはarrOf3Vec3fsBと同じです。
残念ながら、WGSLのバージョン1の時点では、固定サイズの配列のサイズを取得する方法はありません。
ルートスコープのストレージ宣言にある配列、またはルートスコープ構造体の最後のフィールドとしてある配列は、サイズなしで指定できる唯一の配列です。
struct Stuff {
color: vec4f,
size: f32,
verts: array<vec3f>,
};
@group(0) @binding(0) var<storage> foo: array<mat4x4f>;
@group(0) @binding(1) var<storage> bar: Stuff;
fooとbar.vertsの要素数は、実行時に使用されるバインドグループの設定によって定義されます。WGSLでarrayLengthを使用してこのサイズを照会できます。
@group(0) @binding(0) var<storage> foo: array<mat4x4f>; @group(0) @binding(1) var<storage> bar: Stuff; ... let numMatrices = arrayLength(&foo); let numVerts = arrayLength(&bar.verts);
WGSLの関数は、fn name(parameters) -> returnType { ..body... }のパターンに従います。
fn add(a: f32, b: f32) -> f32 {
return a + b;
}
WGSLプログラムにはエントリポイントが必要です。エントリポイントは、@vertex、@fragment、または@computeのいずれかで指定されます。
@vertex fn myFunc(a: f32, b: f32) -> @builtin(position): vec4f {
return vec4f(0, 0, 0, 0);
}
@group(0) @binding(0) var<uniforms> uni: vec4f;
vec4f fn foo() {
return uni;
}
@vertex fn vs1(): @builtin(position) vec4f {
return vec4f(0);
}
@vertex fn vs2(): @builtin(position) vec4f {
return foo();
}
上記では、uniはvs1によってアクセスされないため、パイプラインでvs1を使用する場合、必須のバインディングとして表示されません。vs2は、fooを呼び出すことによって間接的にuniを参照するため、パイプラインでvs2を使用する場合、必須のバインディングとして表示されます。
属性という言葉は、WebGPUで2つの意味を持ちます。1つは、頂点バッファに関する記事で説明されている頂点属性です。もう1つは、@で始まるWGSLの属性です。
@location(number)@location(number)は、シェーダーの入力と出力を定義するために使用されます。
頂点シェーダーの場合、入力は頂点シェーダーのエントリポイント関数の@location属性によって定義されます。
@vertex vs1(@location(0) foo: f32, @location(1) bar: vec4f) ...
struct Stuff {
@location(0) foo: f32,
@location(1) bar: vec4f,
};
@vertex vs2(s: Stuff) ...
vs1とvs2の両方が、頂点バッファによって供給される必要がある場所0と1の頂点シェーダーへの入力を宣言します。
ステージ間変数の場合、@location属性は、変数がシェーダー間で渡される場所を定義します。
struct VSOut {
@builtin(position) pos: vec4f,
@location(0) color: vec4f,
@location(1) texcoords: vec2f,
};
struct FSIn {
@location(1) uv: vec2f,
@location(0) diffuse: vec4f,
};
@vertex fn foo(...) -> VSOut { ... }
@fragment fn bar(moo: FSIn) ...
上記では、頂点シェーダーfooはlocation(0)でcolorをvec4fとして、location(1)でtexcoordsをvec2fとして渡します。フラグメントシェーダーbarは、場所が一致するため、それらをuvとdiffuseとして受け取ります。
フラグメントシェーダーの場合、@locationは、結果を格納するGPURenderPassDescriptor.colorAttachmentを指定します。
struct FSOut {
@location(0) albedo: vec4f;
@location(1) normal: vec4f;
}
@fragment fn bar(...) -> FSOut { ... }
@builtin(name)@builtin属性は、特定の変数の値がWebGPUの組み込み機能から取得されることを指定するために使用されます。
@vertex fn vs1(@builtin(vertex_index) foo: u32, @builtin(instance_index) bar: u32) ... {
...
}
上記では、fooは組み込みのvertex_indexから値を取得し、barは組み込みのinstance_indexから値を取得します。
struct Foo {
@builtin(vertex_index) vNdx: u32,
@builtin(instance_index) iNdx: u32,
}
@vertex fn vs1(blap: Foo) ... {
...
}
上記では、blap.vNdxは組み込みのvertex_indexから値を取得し、blap.iNdxは組み込みのinstance_indexから値を取得します。
| 組み込み名 | ステージ | IO | 型 | 説明 |
|---|---|---|---|---|
| vertex_index | 頂点 | 入力 | u32 |
現在のAPIレベルの描画コマンド内の現在の頂点のインデックス。描画インスタンス化とは無関係です。
非インデックス描画の場合、最初の頂点のインデックスは、直接または間接的に提供される描画の インデックス描画の場合、インデックスは、頂点のインデックスバッファエントリに、直接または間接的に提供される描画の |
| instance_index | 頂点 | 入力 | u32 |
現在のAPIレベルの描画コマンド内の現在の頂点のインスタンスインデックス。
最初のインスタンスのインデックスは、直接または間接的に提供される描画の |
| position | 頂点 | 出力 | vec4<f32> | 同次座標を使用した現在の頂点の出力位置。 同次正規化(x、y、zの各成分がw成分で除算される)の後、位置はWebGPU正規化デバイス座標空間にあります。 WebGPU § 3.3 座標系を参照してください。 |
| フラグメント | 入力 | vec4<f32> | フレームバッファ空間内の現在のフラグメントのフレームバッファ位置。 (x、y、zの成分は、wが1になるようにすでにスケーリングされています。) WebGPU § 3.3 座標系を参照してください。 | |
| front_facing | フラグメント | 入力 | bool | 現在のフラグメントが前面プリミティブ上にある場合はtrue。 それ以外の場合はfalse。 |
| frag_depth | フラグメント | 出力 | f32 | ビューポート深度範囲内のフラグメントの更新された深度。 WebGPU § 3.3 座標系を参照してください。 |
| local_invocation_id | コンピュート | 入力 | vec3<u32> | 現在の呼び出しのローカル呼び出しID、 つまり、ワークグループグリッド内の位置。 |
| local_invocation_index | コンピュート | 入力 | u32 | 現在の呼び出しのローカル呼び出しインデックス、ワークグループグリッド内の呼び出しの位置の線形化されたインデックス。 |
| global_invocation_id | コンピュート | 入力 | vec3<u32> | 現在の呼び出しのグローバル呼び出しID、 つまり、コンピュートシェーダーグリッド内の位置。 |
| workgroup_id | コンピュート | 入力 | vec3<u32> | 現在の呼び出しのワークグループID、 つまり、コンピュートシェーダーグリッド内のワークグループの位置。 |
| num_workgroups | コンピュート | 入力 | vec3<u32> | ディスパッチサイズ、vec<u32>(group_count_x, group_count_y, group_count_z)、APIによってディスパッチされたコンピュートシェーダーの。 |
| sample_index | フラグメント | 入力 | u32 | 現在のフラグメントのサンプルインデックス。
値は少なくとも0で、最大でsampleCount-1です。ここで、sampleCountは、GPUレンダーパイプラインに指定されたMSAAサンプルcountです。WebGPU § 10.3 GPURenderPipelineを参照してください。 |
| sample_mask | フラグメント | 入力 | u32 | 現在のフラグメントのサンプルカバレッジマスク。
このフラグメント内のどのサンプルがレンダリングされるプリミティブでカバーされているかを示すビットマスクが含まれています。 WebGPU § 23.3.11 サンプルマスキングを参照してください。 |
| フラグメント | 出力 | u32 | 現在のフラグメントのサンプルカバレッジマスク制御。
この変数に書き込まれた最後の値がシェーダー出力マスクになります。
書き込まれた値のゼロビットにより、カラーアタッチメントの対応するサンプルが破棄されます。 WebGPU § 23.3.11 サンプルマスキングを参照してください。 |
ほとんどのコンピュータ言語と同様に、WGSLにはフロー制御ステートメントがあります。
for (var i = 0; i < 10; i++) { ... }
if (i < 5) {
...
} else if (i > 7) {
..
} else {
...
}
var j = 0;
while (j < 5) {
...
j++;
}
var k = 0;
loop {
k++;
if (k >= 5) {
break;
}
}
var k = 0;
loop {
k++;
if (k >= 5) {
break;
}
}
var k = 0;
loop {
k++;
break if (k >= 5);
}
for (var i = 0; i < 10; ++i) {
if (i % 2 == 1) {
continue;
}
...
}
for (var i = 0; i < 10; ++i) {
if (i % 2 == 1) {
continue;
}
...
continuing {
// continueはここに行きます
...
}
}
if (v < 0.5) {
discard;
}
discardはシェーダーを終了します。フラグメントシェーダーでのみ使用できます。
var a : i32;
let x : i32 = generateValue();
switch x {
case 0: { // コロンはオプションです
a = 1;
}
default { // デフォルトは最後に表示する必要はありません
a = 2;
}
case 1, 2, { // 複数のセレクター値を使用できます
a = 3;
}
case 3, { // 末尾のカンマはオプションです
a = 4;
}
case 4 {
a = 5;
}
}
switchはu32またはi32でのみ機能し、ケースは定数でなければなりません。
| 名前 | 演算子 | 結合性 | バインディング |
|---|---|---|---|
| 括弧付き | (...) |
||
| プライマリ | a(), a[], a.b |
左から右 | |
| 単項 | -a, !a, ~a, *a, &a |
右から左 | 上記すべて |
| 乗法 | a * b, a / b, a % b |
左から右 | 上記すべて |
| 加法 | a + b, a - b |
左から右 | 上記すべて |
| シフト | a << b, a >> b |
括弧が必要 | 単項 |
| 関係 | a < b, a > b, a <= b, a >= b, a == b, a != b |
括弧が必要 | 上記すべて |
| バイナリAND | a & b |
左から右 | 単項 |
| バイナリXOR | a ^ b |
左から右 | 単項 |
| バイナリOR | a | b |
左から右 | 単項 |
| 短絡AND | a && b |
左から右 | 関係 |
| 短絡OR | a || b |
左から右 | 関係 |
WGSL関数リファレンスを参照してください。
if、while、switch、break-if式には括弧は必要ありません。if a < 5 {
doTheThing();
}
多くの言語には、三項演算子condition ? trueExpression : falseExpressionがあります。WGSLにはありません。WGSLにはselectがあります。
let a = select(falseExpression, trueExpression, condition);
++と--は式ではなく、ステートメントです。多くの言語には、前置インクリメントと後置インクリメント演算子があります。
// JavaScript let a = 5; let b = a++; // b = 5, a = 6 (後置インクリメント) let c = ++a; // c = 7, a = 7 (前置インクリメント)
WGSLにはどちらもありません。インクリメントおよびデクリメントステートメントのみがあります。
// WGSL var a = 5; a++; // は現在6です *++a; // エラー:前置インクリメントのようなものはありません *let b = a++; // エラー:a++は式ではなく、ステートメントです
+=、-=は式ではなく、代入ステートメントです。// JavaScript let a = 5; a += 2; // a = 7 let b = a += 2; // a = 9, b = 9
// WGSL let a = 5; a += 2; // aは7です *let b = a += 2; // エラー:a += 2は式ではありません
一部の言語ではそうですが、WGSLではそうではありません。
var color = vec4f(0.25, 0.5, 0.75, 1); *color.rgb = color.bgr; // エラー color = vec4(color.bgr, color.a); // OK
注:この機能を追加する提案があります。
_への偽の代入_は、何かを使用済みのように見せるが、実際には使用しないように代入できる特別な変数です。
@group(0) @binding(0) var<uniforms> uni1: vec4f;
@group(0) @binding(0) var<uniforms> uni2: mat4x4f;
@vertex fn vs1(): @builtin(position) vec4f {
return vec4f(0);
}
@vertex fn vs2(): @builtin(position) vec4f {
_ = uni1;
_ = uni2;
return vec4f(0);
}
上記では、uni1もuni2もvs1によってアクセスされないため、パイプラインでvs1を使用する場合、必須のバインディングとして表示されません。vs2はuni1とuni2の両方を参照するため、パイプラインでvs2を使用する場合、両方とも必須のバインディングとして表示されます。
Copyright © 2023 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
JavaScriptの変数は、undefined、null、boolean、number、string、reference-to-objectの基本型を保持します。プログラミングに慣れていない人にとって、const o = {name: 'foo'}; o.name = 'bar';が機能するのは、oがconstとして宣言されているため、混乱する可能性があります。重要なのは、oが定数であるということです。これは、オブジェクトへの定数参照です。oが参照するオブジェクトを変更することはできません。オブジェクト自体を変更することはできます。 ↩︎