メインコンテンツまでスキップ

WGSL リフレクション

PlayCanvas は WGSL シェーダーのソースからリソースを直接反映(リフレクト)します。ユニフォーム、テクスチャ、ストレージバッファを @group/@binding インデックスなしで宣言すると、エンジンがこれらの宣言を解析し、バインドグループ形式を構築してバインディングを自動的に割り当てます。この簡略化された構文は、頂点・フラグメント・コンピュートシェーダーで使用されます。

以下のセクションでは、リソースの宣言と反映の方法を説明します。頂点/フラグメント固有の構文(属性、バリイング、フラグメント出力)については、WGSL 頂点・フラグメントシェーダー を参照してください。

簡略化されたシェーダーインターフェース構文

標準的なWGSL(WebGPUシェーディング言語)では、ユニフォーム、属性、バリイングを宣言する際に、各リソースに対して@group@bindingインデックスを明示的に指定する必要があります。これは冗長でエラーが発生しやすく、特に一般的なパターンでは問題となります。

使いやすさを向上させ、シェーダー開発を効率化するために、GLSLに似た簡略化された構文を採用しています。このモデルでは、@group@binding属性を手動で指定してはいけません。エンジンはシェーダー処理中にそのようなアノテーションを取り除き、事前定義されたレイアウトに基づいてバインディングを再割り当てします。

比較例

標準的なWGSL:

struct Uniforms {
uTime: f32,
};

struct FragmentInput {
@location(0) uv0: vec2f,
@builtin(position) position: vec4f
};

@group(0) @binding(0) var<uniform> ub: Uniforms;

@fragment fn fragmentMain(FragmentInput) -> @location(0) vec4f {
// 本体
}

対照的に、簡略化された構文では多くの定型コードを省略できます。

uniform uTime: f32;
varying uv0: vec2f;

@fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput {
// 本体
}

ユニフォーム

ユニフォームは、エンジンからシェーダーに数値リソースを渡すために使用されます。

ユニフォームは以下の簡略化された構文を使用して宣言されます:

uniform view_position: vec3f;
uniform tints: array<vec3f, 4>;
uniform weights: array<f32, 8>;

内部的には、ユニフォームは自動的にユニフォームバッファに配置され、シェーダーコードではuniform.プレフィックスを使用してアクセスされます:

var pos = uniform.view_position;
var color = uniform.tints[2];

// 配列で使用されるf32およびvec2<>型は、アラインメント要件のためにアラインされた構造体でラップされ、
// 値は`element`プロパティとして利用可能です。
// struct WrappedF32 { @size(16) element: f32 }
var weight = uniform.weights[3].element;

エンジンはレンダリング時に適切なユニフォーム値を自動的に設定します。

注記

現在、ユニフォームシステムはf32i32u32を含む単純な型、およびベクトルと行列(例:vec4fmat4x4f)のみをサポートしています。構造体は現時点ではサポートされていないため、すべてのユニフォーム値は基本型の個別の変数として宣言する必要があります。

テクスチャリソース

テクスチャリソースは簡略化されたWGSL構文を使用し、各リソースに対して@group@bindingインデックスを指定する必要はありません。

テクスチャのサンプリング

WGSLでは、GLSLとは異なり、テクスチャとサンプラーは別々のオブジェクトとして扱われます。

テクスチャをサンプリングする(フィルタリングされたテクセル値を取得する)場合、テクスチャオブジェクトの直後にサンプラーを提供する必要があります。

// サンプラー付き2Dテクスチャの宣言
var diffuseMap: texture_2d<f32>;
var diffuseMapSampler: sampler;

// テクスチャサンプリング
var texel = textureSample(diffuseMap, diffuseMapSampler, coords);

テクスチャのフェッチ

生のテクセルデータのみを読み取る必要がある場合(フィルタリング、ミップマッピング、アドレッシングモードなし)、textureSampleの代わりにtextureLoadを使用できます。これはノンフィルタアクセス、または単にテクセルフェッチと呼ばれます。

このような場合、サンプラーは必要ありませんし、許可されていません。例えば:

// サンプラーなしのキューブマップテクスチャ
var noSamplerMap: texture_cube<f32>;

// テクセルのフェッチ
let texel = textureLoad(noSamplerMap, coords, mipLevel);

フィルタリング不可テクスチャ

WebGPUはフィルタリング不可のfloatテクスチャをサポートしており、これは通常、フィルタリングが許可されていない深度テクスチャからのサンプリングなどの特殊な目的に使用されます。しかし、WGSLはこれらのフィルタリング不可floatテクスチャを宣言するための構文で明確なサンプルタイプを提供していません。この制限に対処し、シェーダー宣言に基づいた適切なバインドグループの自動生成を可能にするために、uff(unfilterable-float)という新しいサンプルタイプを導入しています。

uffを使用すると、シェーダーでフィルタリング不可floatテクスチャを明示的に宣言できます:

// 宣言
var colorMap: texture_2d<uff>;

// サンプリング
let data: vec4f = textureLoad(colorMap, uv, 0);

この拡張により、エンジンはテクスチャのサンプリング機能を正しく解釈し、適切にバインドできます。内部的には、エンジンは出力されるWGSLでtexture_2d<uff>texture_2d<f32>に書き換え、SAMPLETYPE_UNFILTERABLE_FLOATを持つBindTextureFormatを自動的に生成します。

uffとサンプラーのペアリング

uffテクスチャはsampler宣言とペアにすることもでき、textureSampleLeveltextureGather、またはtextureSampleのフィルタリングなしフォームを使用できます。これは、深度レンダーターゲットを生のfloatとして読み取るための標準的なパターンです(例:階層Zバッファ、深度認識ブラー、スクリーンスペースアンビエントオクルージョン)。

var srcDepth: texture_2d<uff>;
var srcDepthSampler: sampler;

// 明示的なミップレベルでのシングルタップサンプル読み取り。
let z = textureSampleLevel(srcDepth, srcDepthSampler, uv, 0.0).r;

// 2x2ギャザー(4つの隣接テクセルのrチャンネルを返します)。
let four = textureGather(0, srcDepth, srcDepthSampler, uv);
注記

uffをサンプラーとペアにする場合、バインドするpc.Texture非フィルタリングのサンプラー構成(通常はminFilter: FILTER_NEARESTmagFilter: FILTER_NEAREST、および*_NEARESTミップフィルター)でなければなりません。フィルタリングサンプラーはフィルタリング可能なfloatテクスチャに対してのみ有効であり、フィルタリングサンプラーをフィルタリング不可floatテクスチャと組み合わせると、WebGPUは描画時にバインドグループを拒否します。

深度テクスチャをバインドする方法の選択

深度テクスチャ(任意のpc.PIXELFORMAT_DEPTH*形式)は、デバイスがオプションのfloat32-filterable機能を公開していない限り、WebGPU上で単なるtexture_2d<f32>としてサンプリングすることはできません。必要な処理に基づいて正しい宣言を選択してください:

ユースケースWGSL宣言サンプラー
シャドウ比較(textureSampleCompare*texture_depth_2dsampler_comparison
フィルタリングなしの生の深度float(HZB、SSAO、深度認識ブラー)texture_2d<uff>非フィルタリングsampler、またはtextureLoad使用時はサンプラーなし
フィルタリングされた線形深度texture_2d<f32>フィルタリングsampler — デバイスのfloat32-filterable機能が必要
注記

バインドグループ形式は、シェーダーのリソース宣言から派生します。作成後にShaderインスタンスのmeshBindGroupFormatを変更しても、シェーダー処理がソースから再生成するため効果がありません。サンプルタイプを制御するには、後からバインドグループ形式を上書きするのではなく、テクスチャをuff(または他のサンプルタイプ固有のWGSLフォーム)で宣言してください。

注記

texture_externalのサポートはまだ利用できませんが、将来追加される予定です。

ストレージバッファ

ストレージバッファは、シェーダーがランダムアクセスで任意のデータを読み書きできるGPUアクセス可能なメモリリソースです。WGSLではvar<storage>を使用して宣言され、パーティクルシステム、コンピュートデータ、動的ジオメトリなどの大規模または構造化されたデータセットの処理に理想的です。ユニフォームとは異なり、ストレージバッファは読み取りと書き込みの両方のアクセスをサポートしています(適切なアクセス制御付き)。

頂点シェーダーでストレージバッファを使用する例:

struct Particle {
position: vec3f,
velocity: vec3f,
}

// 読み取り専用モードのパーティクルストレージバッファ
var<storage, read> particles: array<Particle>;

ストレージテクスチャ

ストレージテクスチャを使うと、シェーダーはサンプラーなしでテクセルを直接書き込み(および必要に応じて読み取り)できます。最も一般的にはコンピュートシェーダーの出力として使用されます。簡略化されたtexture_storage_*構文で、フォーマットとアクセスモードを指定して宣言します:

// 書き込み専用ストレージテクスチャ(コンピュート出力の一般的なケース)
var outputTexture: texture_storage_2d<rgba8unorm, write>;

// テクセルの書き込み
textureStore(outputTexture, vec2i(global_id.xy), color);

アクセスモードはwritereadread_writeのいずれかです。ストレージテクスチャの読み取り(read / read_write)には、device.supportsStorageTextureReadのサポートと、筆者が記述するrequires readonly_and_readwrite_storage_textures;ディレクティブが必要です。WGSL ケイパビリティを参照してください。

バインドするテクスチャはstorage: trueオプションで作成する必要があります(コンピュートシェーダーを参照)。