WebGPU
はじめに
編集WebGPUの概要と重要性
編集WebGPUは、ウェブ上での高性能な3Dグラフィックスやデータ並列処理を可能にする新しいWeb API標準です。WebGPUはGPU(Graphics Processing Unit)をネイティブに活用することで、従来のWebGL APIよりも高い描画性能と柔軟性を発揮します。
WebGPUの重要性は、次の点にあります。
- ウェブアプリケーションの3Dグラフィックス性能の向上
- WebGPUはGPUの機能を最大限に活用できるため、リッチで高性能な3Dグラフィックスエクスペリエンスをウェブアプリケーションで実現できます。ゲーム、3Dモデリング、仮想現実(VR)、拡張現実(AR)などの分野で活躍が期待されています。
- 汎用コンピューティングの実現
- WebGPUはグラフィックス処理だけでなく、GPUを使った汎用並列コンピューティングにも対応しています。画像処理、物理シミュレーション、機械学習などの並列処理ワークロードを高速化できます。
- 低レベルなハードウェアコントロール
- WebGPUはGPUを低レベルで直接制御できるため、より細かい最適化が可能です。メモリ管理やレンダリングパイプラインの構築など、GPU資源の効率的な活用が期待できます。
- ウェブプラットフォームにおけるGPUコンピューティングの標準化
- WebGPUはGPUコンピューティングをウェブの世界に統合する標準規格です。ベンダー非依存で、広範なハードウェアとソフトウェアスタックに対応しています。
WebGPUは、ウェブ上でグラフィックスやコンピューティングの新しい可能性を切り拓く重要な技術となり、より高度なインタラクティブなウェブアプリケーションの開発を促進するでしょう。
WebGPUとWebGL、その他のグラフィックスAPIとの違い
編集WebGPUとWebGL
編集WebGPUとWebGLはどちらもウェブ上で3Dグラフィックスを実現するAPIですが、大きな違いがあります。
- 低レベルAPIとハイレベルAPI
- WebGPUは低レベルなGPUコントロールを提供する低レベルAPIで、一方でWebGLはOpenGL ESに準拠したハイレベルなAPIです。WebGPUではリソース管理やパイプラインの構築など、より詳細な制御が可能です。
- newer APIとLegacy API
- WebGPUは最新のハードウェア機能とGPUアーキテクチャを最大限に活用できる新しいAPIです。一方、WebGLは古いOpenGLアーキテクチャに基づいており、新しいGPU機能を十分に活用できません。
- シェーダー言語
- WebGPUは最新のGPUシェーディング言語であるWGSL(WebGPU Shading Language)、GLSL(OpenGL Shading Language)の使用をサポートしています。WebGLはOpenGL ES Shader Languageのみをサポートしています。
- コンピューティングシェーダー
- WebGPUはグラフィックス以外の一般的なGPUコンピューティングをサポートしています。WebGLにはこの機能がありません。
- デバッグとプロファイリング
- WebGPUには優れたデバッグとプロファイリングツールが用意されています。WebGLのツールは限られています。
その他のグラフィックスAPI
編集- Direct3D
- Direct3DはMicrosoftがWindowsで提供するネイティブグラフィックスAPI群です。WebGPUはDirect3Dを部分的にモデル化しています。
- Metal
- MetalはAppleがmacOS、iOS、iPadOS向けに提供するGPUアクセスAPIです。WebGPUはMetalをモデル化した部分もあります。
- Vulkan
- VulkanはクロスプラットフォームのローレベルグラフィックスAPIで、モバイル機器からPCゲームまで様々な用途で使用されています。WebGPUはVulkanの設計の一部を取り入れています。
WebGPUは、これらの最新のネイティブグラフィックスAPIの長所を取り入れ、ウェブ向けに最適化された新しいグラフィックスAPIとなっています。
WebGPUの利点と適用例
編集WebGPUには以下のような主な利点があります。
- 高パフォーマンス
- WebGPUはGPUを低レベルで直接制御できるため、最新のGPU機能を最大限に活用し、高いグラフィックスパフォーマンスを発揮できます。特にリアルタイムの3Dグラフィックスアプリケーションにおいて、WebGLよりも優れたフレームレートが期待できます。
- 柔軟性
- WebGPUはレンダリングパイプラインやリソース管理をプログラマブルに構築できます。この柔軟性は、カスタムレンダリング手法の実装やGPUコンピューティングなど、様々な用途に適用できます。
- ポータビリティ
- WebGPUはベンダー非依存のオープン標準で、幅広いハードウェアアーキテクチャとOSに対応しています。これにより、クロスプラットフォームのアプリケーション開発が容易になります。
- デバッグとプロファイリングツール
- WebGPUには優れたデバッグとプロファイリングツールが用意されており、パフォーマンス最適化や問題のデバッグが効率的に行えます。
将来的な発展性
編集WebGPUは最新のGPUアーキテクチャと機能をターゲットにしており、GPU技術の進歩に合わせて発展していく設計となっています。
こうした利点から、WebGPUは以下のような分野でその活用が期待されています。
- 3Dグラフィックスゲーム
- リッチで高パフォーマンスな3Dゲームが可能になります。
- 3Dモデリングツール
- 精密な3Dモデリングが可能になり、設計、建築、医療などの分野で活躍できます。
- 仮想現実(VR)、拡張現実(AR)アプリ
- リアルタイムのグラフィックス性能が重要なVR/ARアプリに適しています。
- 科学技術計算、機械学習
- WebGPUのGPUコンピューティング機能を使った高速な並列処理ができます。
- ビデオ編集、画像処理
- 映像や画像のリアルタイム処理が高速に行えます。
WebGPUはウェブ上でグラフィックスとコンピューティングの新しい可能性を切り拓く重要な技術であり、様々な分野で幅広く活用が見込まれています。
WebGPUの基礎
編集GPUとは
編集GPU(Graphics Processing Unit)は、コンピューターのグラフィックス処理を専門的に担当する演算プロセッサです。CPUとは異なり、GPUは大量の並列処理に特化した設計となっており、単一の複雑な処理ではなく、多数の軽量な演算を同時に行うことが得意です。
GPUの主な役割は以下の通りです。
- 3Dグラフィックス処理
- GPUは3Dグラフィックスデータの処理に非常に適しています。頂点変換、テクスチャマッピング、ピクセルシェーディングなどの複雑な計算を、GPUの多数のコアで並列に行うことで高速化できます。ゲームやCAD、VR/ARなどのリアルタイムレンダリングはGPUに大きく依存しています。
- 汎用並列計算
- GPUは単にグラフィックス処理だけでなく、汎用の並列計算にも使用できます。大量のデータに対して同じ計算を並列で実行するGPUの性質は、機械学習、科学技術計算、ビデオ編集など様々な分野で活用されています。この分野をGPGPU(General-Purpose computing on GPU)と呼びます。
- メディア処理
- 動画や画像のデコーディング、エンコーディング、フィルタリングなどの処理においても、GPUを利用することで大幅な高速化が図れます。特に4K/8K映像など高解像度のメディアストリームの処理にGPUは不可欠です。
GPUは特化した並列アーキテクチャにより、CPUよりも高い数理演算性能を発揮します。一方で、CPUは一般的な汎用計算に適しているため、GPUとCPUは補完的な役割を果たしながら連携します。ハイパフォーマンスコンピューティングでは、CPUとGPUを組み合わせることで、計算性能を最大化できます。
WebGPUのアーキテクチャ概要
編集WebGPUは、GPUを制御するための低レベルなAPIで、その設計にはいくつかの主要なコンポーネントがあります。
- GPUインスタンス
- GPUインスタンスは、WebGPUの最上位のオブジェクトで、GPUへのアクセスを提供します。アプリケーションはGPUインスタンスを作成することから始まります。
- GPUアダプター
- GPUアダプターは、利用可能なGPU(デバイス)を表すオブジェクトです。アプリケーションは、アダプターを列挙し、目的に合ったデバイスを選択します。
- GPUデバイス
- GPUデバイスは、実際にGPUリソースを制御するためのオブジェクトです。デバイスを通じてバッファ、テクスチャ、レンダリングパイプラインなどを作成し、コマンドを発行します。
- コマンドキュー
- コマンドキューは、GPUへ送信されるコマンドをスケジューリングするための待ち行列です。アプリケーションは、コマンドエンコーダーを使ってコマンドを記録し、キューに送信します。
- コマンドエンコーダー
- コマンドエンコーダーは、GPUへ送信するコマンドを記録するためのオブジェクトです。レンダリングやコンピューティングのコマンドをエンコーダーに記録し、コマンドバッファとしてキューに送信します。
- シェーダーモジュール
- シェーダープログラム(頂点シェーダー、フラグメントシェーダーなど)はシェーダーモジュールとしてGPUにアップロードされます。これらはレンダリングパイプラインに組み込まれ実行されます。
- バッファ、テクスチャ
- GPUがアクセスするデータはバッファやテクスチャとして表現され、GPUメモリにアップロードされます。頂点データ、ユニフォームデータ、テクスチャデータなどがこれらのリソースに対応します。
- レンダリングパイプライン
- レンダリングパイプラインは、特定のレンダリング処理の流れを定義するオブジェクトです。シェーダープログラム、プリミティブ種別、レンダリングターゲットなどを組み合わせて構築します。
- バインドグループ
- バインドグループは、シェーダーが参照する様々なリソース(バッファ、テクスチャ、サンプラーなど)をまとめて管理するオブジェクトです。
このように、WebGPUではGPUの各機能をオブジェクト指向のAPIで抽象化しています。これらのオブジェクトを適切に作成、設定、組み合わせることで、目的の並列処理やグラフィックスレンダリングを実現できます。
WebGPUのレンダリングパイプライン
編集WebGPUでは、3Dグラフィックスレンダリングの処理フローは「レンダリングパイプライン」として定義されます。レンダリングパイプラインは、データの入力から最終的な出力画像を生成するまでの一連の段階を表しています。
WebGPUのレンダリングパイプラインは、以下の主要なステージから構成されています。
- 入力アセンブラーステージ
- 頂点データがGPUメモリからフェッチされ、プリミティブ(三角形、線分など)を構成する頂点の集まりに変換されます。
- 頂点シェーダーステージ
- 各頂点に対して頂点シェーダーが実行され、頂点の座標変換、頂点属性の計算などが行われます。
- プリミティブ操作ステージ
- 頂点からプリミティブが組み立てられ、プリミティブの裏面カリングやフラットシェーディングなどの操作が行われます。
- ラスタライザーステージ
- プリミティブがピクセル/フラグメントに分割され、ピクセルカバレッジの計算などが実行されます。
- フラグメントシェーダーステージ
- 各フラグメント(ピクセル)に対してフラグメントシェーダーが実行され、色や深度などの値が計算されます。
- テストとブレンドステージ
- 深度テスト、ステンシルテスト、アルファブレンディングなどが実行され、最終的な色値が決定されます。
- フレームバッファ出力ステージ
- 決定された色値がフレームバッファに書き込まれ、画面に出力されます。
レンダリングパイプラインには、オプションで以下のステージを組み込むこともできます。
- ジオメトリシェーダーステージ
- テッセレーションステージ
- コンピューティングシェーダーステージ
これらは、頂点シェーダーとフラグメントシェーダー以外の追加のシェーダーを挟み込むことで、よりリッチなレンダリング効果を実現できます。
WebGPUではこのレンダリングパイプラインを直接プログラムできるため、従来のグラフィックスAPIよりも柔軟で、パフォーマンスの高いレンダリングが可能となります。シェーダーやステートの組み合わせを自由に設定できるのが大きな特徴です。
WebGPUのデータ構造(バッファ、テクスチャ、サンプラーなど)
編集WebGPUではGPUがアクセスするデータを、主に以下の3つのリソースとして表現します。
- GPUバッファ
- バッファはGPUメモリ上の線形の配列データを表します。頂点データ、インデックスデータ、ユニフォームデータ(シェーダーへの入力値)などがバッファに格納されます。バッファは読み取り専用でもよいし、GPUから書き込み可能なストリームバッファとしても使えます。
- GPUテクスチャ
- テクスチャは2D、3D、キューブマップなどの画像データをGPUメモリ上にレイアウトしたリソースです。テクスチャにはカラーデータ、深度データ、ステンシルデータなどを格納でき、テクスチャマッピングやレンダーターゲットとして利用できます。
- GPUサンプラー
- サンプラーはテクスチャのサンプリング方法を定義するオブジェクトです。フィルタリング、境界の取り扱い、アドレス変換などのサンプリングパラメータを指定できます。
これらのリソースは、バッファやテクスチャデータをGPUメモリにアップロードし、GPUデバイスを通じて作成されます。作成後は、シェーダーやレンダーパイプラインからバインドされて利用されます。
また、WebGPUには以下の補助的なリソースも存在します。
- バインドグループ
- バインドグループはシェーダーにバインドするリソース(バッファ、テクスチャ、サンプラー)をグループ化するオブジェクトです。
- バインドグループレイアウト
- バインドグループレイアウトはバインドグループの構造を定義するものです。レンダーパイプラインの作成時にバインドされます。
- フェンス
- フェンスはGPUとCPUの同期をとるためのシグナルオブジェクトです。GPUの特定の処理が完了するのを待機できます。
これらのリソースを適切に設定、管理することで、GPU上でデータにアクセスしたり、データフローを構築したりできます。特にバッファ、テクスチャ、サンプラーの効率的な活用は、WebGPUを使ったグラフィックスレンダリングやGPUコンピューティングの性能に大きな影響を与えます。
WebGPUの設定と初期化
編集WebGPUの有効化と機能確認
編集WebGPUを使用する前に、まずブラウザがWebGPUをサポートしているかを確認する必要があります。現時点では、WebGPUはまだ最終的な勧告候補段階にあり、すべてのブラウザで完全にサポートされているわけではありません。
- WebGPUの有効化
- WebGPUを有効化するには、ナビゲーターオブジェクトの
gpu
プロパティをチェックします。このプロパティは、WebGPUがサポートされている場合にのみ存在します。 if (!navigator.gpu) { throw Error("WebGPU APIをサポートしていない"); }
- GPUインスタンスの取得
- WebGPUが有効な場合、
navigator.gpu.requestAdapter()
メソッドを呼び出して、GPUアダプターを取得する必要があります。アダプターは利用可能なGPUデバイスを表すオブジェクトです。 navigator.gpu.requestAdapter().then( (adapter) => { // 利用可能なGPUアダプターが見つかった } ).catch( (error) => { // エラー処理 } );
- WebGPU機能の確認
- 特定のWebGPU機能がサポートされているかどうかは、アダプターの
features
プロパティを確認することで判断できます。features
はGPUFeatureName
型の値の集合で、サポートされる機能フラグが含まれています。 const features = adapter.features; if (features.has('depth-clip-control')) { // デプスクリッピングコントロールがサポートされている } if (features.has('texture-compression-bc')) { // BC形式のテクスチャ圧縮がサポートされている }
- また、アダプターの
limits
プロパティを確認することで、GPUのリソースの上限値(最大テクスチャサイズ、最大サンプラー数など)を取得できます。これらの情報に基づいて、GPUの能力に応じたコンテンツの最適化や制御が可能になります。
このように、WebGPUの実装では機能のサポート状況を確認する必要があります。WebGPUを安全に利用するには、機能の有無やリソース制限を把握し、適切なフォールバックやコンテンツの調整を行う必要があります。
GPUインスタンスの作成
編集WebGPUでGPUを制御するための最初のステップは、GPUインスタンスを作成することです。GPUインスタンスは、GPUへのアクセスを提供する最上位のオブジェクトです。
- GPUインスタンスの作成
- GPUインスタンスは
navigator.gpu
オブジェクトから作成できます。 const gpuInstance = navigator.gpu;
- GPUインスタンスを取得するだけで、まだGPUへのアクセスは許可されていません。次のステップとして、GPUアダプターを列挙し、利用可能なGPUデバイスを選択する必要があります。
- GPUアダプターの列挙
navigator.gpu.requestAdapter()
メソッドを呼び出すと、利用可能なGPUアダプターがPromise
で返されます。アダプターは物理的なGPUデバイスを表すオブジェクトです。const adapter = await navigator.gpu.requestAdapter();
- 複数のGPUが存在する場合、
requestAdapter()
は最も優先されるデフォルトのアダプターを返します。特定のアダプターを選択したい場合は、adapter.requestAdapterInfo()
を使って、すべてのアダプター情報を列挙できます。 const adapterInfo = await adapter.requestAdapterInfo(); // adapterInfoからデバイス情報を確認し、適切なアダプターを選択する
- アダプターの機能確認
- アダプターの
features
プロパティを調べることで、サポートされているWebGPU機能を確認できます。機能によってはアプリケーションの動作に影響があるため、適切な処理が必要です。 const features = adapter.features; if (features.has('texture-compression-bc')) { // BC形式のテクスチャ圧縮がサポートされている } else { // サポートされていない場合の処理 }
- GPUインスタンスとアダプターを取得した後は、次のステップとしてアダプターからGPUデバイスを作成します。デバイスを経由してリソースの作成やコマンドの発行が行われます。
GPUインスタンスの作成は、WebGPUでGPUを使う上での出発点となる重要なステップです。適切なアダプターを選択し、サポートされている機能を把握することが、安定したグラフィックス処理の実現に欠かせません。
アダプタの選択
編集WebGPUで利用するGPUデバイスを決定する際、アダプタの選択は重要なステップです。アダプタは物理的なGPUハードウェアを表すオブジェクトで、複数のGPUが存在する環境ではアダプタを適切に選択する必要があります。
- 利用可能なアダプタの一覧取得
- 最初のステップとして、
adapter.requestAdapterInfo()
メソッドを呼び出すことで、利用可能なすべてのアダプタの情報を取得できます。 const adapters = await adapter.requestAdapterInfo();
adapters
はGPUAdapterInfo
オブジェクトの配列で、各アダプタの情報が含まれています。- アダプタの機能とリソースの確認
- 各アダプタについて、サポートされているWebGPU機能や利用可能なリソース量を確認することが重要です。これらの情報に基づいて、適切なアダプタを選択する必要があります。
for (const adapterInfo of adapters) { const adapter = adapterInfo.adapter; // アダプタがサポートするWebGPU機能の確認 const features = adapter.features; if (!features.has('texture-compression-bc')) { // BC形式のテクスチャ圧縮がサポートされていない場合はこのアダプタを除外 continue; } // アダプタのリソース制限の確認 const limits = adapter.limits; if (limits.maxTextureSize < 4096) { // 最大テクスチャサイズが4096未満の場合はこのアダプタを除外 continue; } // 適切なアダプタが見つかった場合、これを選択 selectedAdapter = adapter; break; }
アダプタ選択の要因
編集アダプタの選択基準は、アプリケーションの要件によって異なります。一般的には以下の点を考慮します。
- サポートされているWebGPU機能
- リソース制限(最大テクスチャサイズ、最大バッファサイズなど)
- パフォーマンス(ベンダー、世代、アーキテクチャ)
- 電力効率(モバイルデバイス向けの要件)
- 統合型GPUかディスクリートGPUか
これらの要件を満たす最適なアダプタを選ぶことが大切です。
アダプタを選択した後は、そのアダプタからGPUデバイスを作成します。デバイスは実際のリソース管理やコマンド発行を行うオブジェクトなので、アプリケーションコードの中核となります。適切なアダプタを選ぶことが、パフォーマンスや機能面で大きな影響を及ぼします。
デバイスの作成
編集GPUデバイスは、実際にGPUリソースを制御し、コマンドを発行するための中心的なオブジェクトです。デバイスを作成するには、前のステップで選択したGPUアダプターを使用します。
- デバイスの作成
- デバイスは
GPUAdapter
オブジェクトのrequestDevice()
メソッドを呼び出すことで作成できます。 const deviceDescriptor = { requiredFeatures: [...] // 必須のGPU機能 }; const device = await adapter.requestDevice(deviceDescriptor);
requestDevice()
にはGPUDeviceDescriptor
オブジェクトを渡す必要があります。このオブジェクトには、デバイスに要求される機能を指定できます。- *
requiredFeatures
プロパティには、必須のGPU機能の配列を指定します。 - *
nonGuaranteedFeatures
には、任意で有効にしたい機能を指定できます。 - *
defaultQueue
には、デフォルトのコマンドキューを指定できます。 - デバイスは作成時に指定された機能セットに基づいて初期化されます。指定された機能がサポートされていない場合、
requestDevice()
は失敗します。 - デバイスの機能とリソース制限の確認
- 作成したデバイスでサポートされている機能は、
device.features
プロパティで確認できます。また、device.limits
プロパティには、デバイスのリソース制限が含まれています。 const features = device.features; const limits = device.limits; if (features.has('texture-compression-bc')) { // BC形式のテクスチャ圧縮がサポートされている } if (limits.maxTextureSize >= 4096) { // 最大テクスチャサイズが4096以上 }
- サポートされる機能とリソース制限に基づいて、アプリケーションのレンダリングパイプラインやリソース割り当てを適切に設定する必要があります。
デバイスが作成されると、次はデバイスを使ってコマンドキューやコマンドエンコーダーを作成します。コマンドキューはGPUへ送信されるコマンドのスケジューリングを行い、コマンドエンコーダーはコマンドの記録を担当します。これらを使って、実際のレンダリングやコンピューティングのコマンドをGPUに送ることになります。
GPUデバイスはWebGPUアプリケーションの中核を成すオブジェクトです。適切なデバイスを作成し、そのサポート状況を把握することが、安定したGPU処理の実現に不可欠です。
キュー、コマンドエンコーダーの作成
編集GPUデバイスが作成されると、次はそのデバイスを使ってコマンドキューとコマンドエンコーダーを作成します。これらは、実際のレンダリングやコンピューティングのコマンドをGPUに送信するための重要なオブジェクトです。
- コマンドキューの作成
- コマンドキューは、GPUへ送信されるコマンドをスケジューリングするための待ち行列です。デバイスの
createQueue()
メソッドを呼び出すことで作成できます。 const queue = device.createQueue();
- デフォルトでは、単一のキューが作成されます。必要に応じて、複数のキューを作成し、プライオリティの異なるコマンドを別々のキューに分けて送信することもできます。
- コマンドエンコーダーの作成
- コマンドエンコーダーは、GPUへ送信するコマンド列を記録するためのオブジェクトです。デバイスの
createCommandEncoder()
メソッドを使って作成します。 const commandEncoder = device.createCommandEncoder();
- コマンドエンコーダーには、レンダリングコマンドやコンピューティングコマンドなど、様々なコマンドを記録できます。記録されたコマンド列は、最終的にコマンドバッファとしてコマンドキューに送信されます。
- レンダリングコマンドの記録
- レンダリングコマンドは、
commandEncoder.beginRenderPass()
メソッドを使って記録を開始します。 const textureView = context.currentTexture.createView(); const renderPassDescriptor = { colorAttachments: [{ view: textureView, loadValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, }] }; const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor); renderPass.setPipeline(pipelineState); renderPass.setBindGroup(...); renderPass.draw(...); renderPass.endPass();
beginRenderPass()
にレンダーターゲットの設定を渡し、その後にパイプラインの設定、リソースのバインド、描画コマンドなどを記録します。最後にendPass()
で記録を終了します。- コンピューティングコマンドの記録
- コンピューティングコマンドは、
commandEncoder.beginComputePass()
で記録を開始します。 const computePass = commandEncoder.beginComputePass(); computePass.setPipeline(computePipeline); computePass.setBindGroup(...); computePass.dispatch(...); computePass.endPass();
- ここでは、コンピューティングパイプラインの設定、リソースのバインド、ディスパッチコマンドを記録します。
- コマンドバッファの作成とキューへの送信
- コマンドの記録が完了したら、
commandEncoder.finish()
メソッドでコマンドバッファを作成し、それをコマンドキューに送信します。 const commandBuffer = commandEncoder.finish(); queue.submit([commandBuffer]);
- コマンドキューへ送信されたコマンドは、GPUによって実行されます。GPUの処理が完了するまで、アプリケーションはフェンスを使って同期を取ることができます。
このように、WebGPUではコマンドキューとコマンドエンコーダーを使うことで、GPUに対して様々なコマンドを発行できます。適切なコマンドの記録と送信を行うことで、高度なレンダリングやコンピューティング処理を実現できます。
レンダリングパイプラインの構築
編集シェーダーモジュールの作成
編集WebGPUではシェーダープログラムをシェーダーモジュールとして扱います。シェーダーモジュールは、GPUデバイスにアップロードされ、レンダリングパイプラインに組み込まれて実行されます。
シェーダーコードの準備
編集シェーダープログラムは、WGSL(WebGPU Shading Language)、GLSL(OpenGL Shading Language)のいずれかの言語で記述します。以下は簡単な頂点シェーダーとフラグメントシェーダーの例です(GLSLの場合)。
- 頂点シェーダー(vertex.glsl)
#version 450 layout(location=0) in vec3 position; layout(location=1) in vec3 normal; layout(location=0) out vec3 vNormal; void main() { gl_Position = vec4(position, 1.0); vNormal = normal; }
- フラグメントシェーダー(fragment.glsl)
#version 450 layout(location=0) in vec3 vNormal; layout(location=0) out vec4 outColor; void main() { outColor = vec4(vNormal * 0.5 + 0.5, 1.0); }
シェーダーコードは文字列としてJavaScriptに読み込まれます。
- シェーダーモジュールの作成
シェーダーコードを読み込んだ後、GPUデバイスのcreateShaderModule()
メソッドを使ってシェーダーモジュールを作成します。
const vertexShaderGLSL = await loadShader('vertex.glsl'); const vertexShaderModule = device.createShaderModule({ code: vertexShaderGLSL, sourceMap: undefined, }); const fragmentShaderGLSL = await loadShader('fragment.glsl'); const fragmentShaderModule = device.createShaderModule({ code: fragmentShaderGLSL, });
createShaderModule()
には、シェーダーコードとソースマップを渡します。シェーダーコードは文字列かUint32Array
で指定します。ソースマップはデバッグ用に指定できます。
- シェーダーモジュールの検証
作成したシェーダーモジュールに問題がないかは、createShaderModule()
から返されるGPUShaderModule
オブジェクトのcompilationInfo
プロパティで確認できます。
if (vertexShaderModule.compilationInfo.messages.length > 0) { console.error('Vertex shader compilation messages:', vertexShaderModule.compilationInfo.messages); }
messages
プロパティには、シェーダーのコンパイルエラーや警告メッセージが含まれています。
シェーダーモジュールの作成は、レンダリングパイプラインを構築する上で重要なステップです。作成したシェーダーモジュールは、次のステップでレンダリングパイプラインレイアウトとパイプラインの定義に使用されます。
レンダリングパイプラインレイアウトの定義
編集WebGPUではレンダリングパイプラインを構築する際に、まずパイプラインレイアウトを定義する必要があります。パイプラインレイアウトとは、そのパイプラインが使用するリソース(バッファ、テクスチャ、サンプラーなど)のバインド情報を記述するものです。
- バインドグループレイアウトの作成
- パイプラインレイアウトを定義するための最初のステップは、バインドグループレイアウトを作成することです。バインドグループレイアウトは、シェーダープログラム内で参照されるリソースのバインディング情報を指定します。
const bindGroupLayout = device.createBindGroupLayout({ entries: [ { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } }, { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "2d" } } ] });
entries
プロパティには、バインディングスロット番号、シェーダーステージの種類、リソースの種類(バッファ、テクスチャ、サンプラー)を指定します。この例ではバッファとテクスチャがバインドされています。- パイプラインレイアウトの作成
- バインドグループレイアウトが作成できたら、次はパイプラインレイアウトを作成します。パイプラインレイアウトはデバイスの
createPipelineLayout()
メソッドで作成できます。 const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
bindGroupLayouts
には、パイプラインで使用するバインドグループレイアウトの配列を指定します。
パイプラインレイアウトの役割
編集パイプラインレイアウトは、レンダリングパイプラインとシェーダープログラムの間のインターフェイスを定義します。シェーダーがアクセスするリソースのバインド情報を事前に指定しておくことで、GPUがリソースへのアクセスを適切に行えるようになります。
また、パイプラインレイアウトを明示的に定義することにより、WebGPUはレンダリングパイプラインの内部表現を最適化できます。これにより、パフォーマンスの向上が期待できます。
パイプラインレイアウトの定義は、レンダリングパイプラインを構築する上で重要なステップです。正しくレイアウトを定義することで、シェーダーとリソースのバインディングが適切に行われ、効率的なレンダリングが可能になります。
頂点バッファの設定
編集WebGPUでは、頂点データをGPUバッファに格納し、レンダリングパイプラインから参照する必要があります。頂点バッファを適切に設定するには、以下の手順を踏みます。
- 頂点データの準備
- 最初に、頂点データを適切な形式で用意する必要があります。頂点データは頂点属性に分けられ、各頂点属性にはデータ型、コンポーネント数、正規化の有無などの情報が付与されます。
- 以下は、位置、法線、テクスチャ座標の3つの頂点属性を持つ頂点データの例です。
const vertexData = new Float32Array([ // 位置 法線 UV 0.0, 0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, // 頂点0 -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // 頂点1 0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0 // 頂点2 ]);
- GPUバッファの作成
- 次に、頂点データを格納するGPUバッファを作成します。GPUバッファは
device.createBuffer()
メソッドで作成できます。 const vertexBuffer = device.createBuffer({ size: vertexData.byteLength, usage: GPUBufferUsage.VERTEX, mappedAtCreation: true });
usage
プロパティで、このバッファが頂点データ用であることを指定しています。mappedAtCreation
をtrue
にすると、バッファへの書き込みが可能になります。- バッファへのデータ書き込み
- 作成したバッファにデータを書き込みます。
mappedAtCreation
がtrue
の場合、createBuffer()
からGPUBuffer
オブジェクトのmapAsync()
メソッドを呼び出し、プロミスが解決したらgetMappedRange()
でバッファのメモリ領域を取得できます。 const arrayBuffer = await vertexBuffer.mapAsync(GPUMapMode.WRITE); const bufferView = new Float32Array(arrayBuffer); bufferView.set(vertexData); vertexBuffer.unmap();
- データの書き込みが終了したら、
unmap()
メソッドを呼び出してメモリマッピングを解除する必要があります。 - 頂点レイアウトの定義
- 最後に、頂点属性のレイアウトを定義します。これは、レンダリングパイプラインを作成する際に必要になります。
const vertexBuffers = [ { arrayStride: 8 * Float32Array.BYTES_PER_ELEMENT, attributes: [ { shaderLocation: 0, offset: 0, format: "float32x3" }, { shaderLocation: 1, offset: 3 * Float32Array.BYTES_PER_ELEMENT, format: "float32x3" }, { shaderLocation: 2, offset: 6 * Float32Array.BYTES_PER_ELEMENT, format: "float32x2" } ] } ];
- この例では、
arrayStride
で単一頂点のバイトサイズを指定し、attributes
配列で頂点シェーダーのロケーション番号、オフセット、データ型を指定しています。
頂点バッファを適切に設定することで、レンダリングパイプラインから頂点データにアクセスできるようになります。頂点データのフォーマットを正しく定義し、GPUバッファに正しく書き込むことが重要です。
レンダリングパイプラインの作成
編集WebGPUでは、3Dグラフィックスのレンダリング処理の流れをレンダリングパイプラインとして定義します。レンダリングパイプラインを作成するには、これまでに作成したシェーダーモジュール、パイプラインレイアウト、頂点バッファの設定を組み合わせる必要があります。
- レンダリングパイプライン構造の定義
- レンダリングパイプラインの構造は、
GPURenderPipelineDescriptor
オブジェクトで定義します。 const pipelineDescriptor = { layout: pipelineLayout, vertex: { module: vertexShaderModule, entryPoint: "main", buffers: vertexBuffers }, fragment: { module: fragmentShaderModule, entryPoint: "main", targets: [ { format: "bgra8unorm" } ] }, primitive: { topology: "triangle-list" } };
layout
- 前に作成したパイプラインレイアウトを指定します。
vertex
- 頂点シェーダーのシェーダーモジュール、エントリポイント関数名、頂点バッファの設定を指定します。
fragment
- フラグメントシェーダーのシェーダーモジュール、エントリポイント、レンダリングターゲットのフォーマットを指定します。
primitive
- ジオメトリの種類(三角形リスト、ストリップなど)を指定します。
- さらに、必要に応じて以下の設定も追加できます。
depthStencil
- 深度ステンシルバッファの設定
multisample
- マルチサンプリング設定
rasterization
- ラスタライザーのステート
- パイプラインの作成
- パイプラインの構造が定義できたら、
device.createRenderPipeline()
メソッドを使ってレンダリングパイプラインを作成します。 device.createRenderPipeline(pipelineDescriptor).then( (pipeline) => { // レンダリングパイプラインが正常に作成された renderPipeline = pipeline; } ).catch( (error) => { // エラー処理 } );
- 正常にパイプラインが作成されると、
GPURenderPipeline
オブジェクトが返されます。作成に失敗した場合は、適切なエラー処理を行う必要があります。 - レンダリングパイプラインの使用
- 作成したレンダリングパイプラインは、コマンドエンコーダーのレンダーパスで使用します。
const commandEncoder = device.createCommandEncoder(); const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor); renderPass.setPipeline(renderPipeline); renderPass.setVertexBuffer(0, vertexBuffer); renderPass.draw(vertexCount); renderPass.endPass();
renderPass.setPipeline()
でパイプラインを設定し、setVertexBuffer()
で頂点バッファをバインドした後、draw()
メソッドで描画コマンドを実行します。
レンダリングパイプラインは、シェーダープログラム、頂点データ、レンダリングターゲットの設定などを統合する重要なオブジェクトです。適切にパイプラインを作成し、描画コマンドでパイプラインを設定することで、期待どおりの3Dグラフィックスレンダリングが実現できます。
レンダリングとリソース管理
編集レンダリングターゲットの設定
編集WebGPUでレンダリングを実行する際には、レンダリングターゲットを適切に設定する必要があります。レンダリングターゲットとは、レンダリング結果を出力する宛先のことで、通常はフレームバッファやテクスチャになります。
フレームバッファをレンダリングターゲットとする場合
編集Canvasレンダリングコンテキストから、フレームバッファ用のテクスチャビューを取得します。
const context = canvas.getContext('gpupresent'); const currentTexture = context.getCurrentTexture(); const renderTargetView = currentTexture.createView();
テクスチャをレンダリングターゲットとする場合
編集あらかじめテクスチャを作成し、そのテクスチャビューをレンダリングターゲットとして使用します。
const descriptor = { size: {width: 1024, height: 1024}, format: 'bgra8unorm', usage: GPUTextureUsage.RENDER_ATTACHMENT }; const texture = device.createTexture(descriptor); const renderTargetView = texture.createView();
レンダーパスの設定
編集実際のレンダリングターゲットの設定は、コマンドエンコーダーのbeginRenderPass()
メソッドで行います。ここでGPURenderPassDescriptor
を指定し、色付けバッファ(カラーアタッチメント)、深度ステンシルバッファを設定します。
const colorAttachment = { view: renderTargetView, loadValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, storeOp: 'store' }; const renderPassDescriptor = { colorAttachments: [colorAttachment] }; const commandEncoder = device.createCommandEncoder(); const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
colorAttachments
配列には、レンダリングターゲットとなるカラーアタッチメントを設定します。view
プロパティにレンダリングターゲットビューを指定し、loadValue
でクリア値、storeOp
で書き込み操作を指定します。
深度ステンシルバッファが必要な場合は、depthStencilAttachment
プロパティを追加します。
レンダリングコマンドの記録
編集レンダーパスが開始されたら、renderPass
オブジェクトを使って実際のレンダリングコマンドを記録できます。
renderPass.setPipeline(renderPipeline); renderPass.setBindGroup(...); renderPass.setVertexBuffer(...); renderPass.draw(...); renderPass.endPass();
レンダリングターゲットを適切に設定し、コマンドエンコーダーでレンダリングコマンドを記録することで、期待したレンダリング結果をターゲットに出力できます。フレームバッファにレンダリングすれば画面に表示され、テクスチャにレンダリングすればポストプロセスなどの後処理が可能になります。
コマンドバッファの記録
編集WebGPUでは、GPUに送信する処理の命令列を「コマンドバッファ」として記録する必要があります。コマンドバッファには、レンダリングコマンドやコンピューティングコマンドなどを含めることができます。
コマンドエンコーダーの作成
編集コマンドバッファの記録は、コマンドエンコーダーを使って行います。コマンドエンコーダーはGPUDevice
のcreateCommandEncoder()
メソッドで作成できます。
const commandEncoder = device.createCommandEncoder();
レンダリングコマンドの記録
編集レンダリングコマンドは、commandEncoder.beginRenderPass()
メソッドでレンダーパスを開始することから始まります。
const renderPassDescriptor = { // レンダリングターゲットの設定 }; const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
次に、レンダーパスオブジェクトを使って、パイプラインの設定、リソースのバインド、描画コマンドなどを記録していきます。
renderPass.setPipeline(renderPipeline); renderPass.setBindGroup(0, bindGroup); renderPass.setVertexBuffer(0, vertexBuffer); renderPass.draw(vertexCount); renderPass.endPass();
最後にendPass()
でレンダーパスを終了します。
コンピューティングコマンドの記録
編集コンピューティングコマンドは、commandEncoder.beginComputePass()
でコンピュートパスを開始し、同様にパイプライン、リソース、ディスパッチコマンドなどを記録します。
const computePass = commandEncoder.beginComputePass(); computePass.setPipeline(computePipeline); computePass.setBindGroup(0, computeBindGroup); computePass.dispatch(workgroupCountX); computePass.endPass();
コマンドバッファの完成とコマンドキューへの送信
編集記録が完了したら、commandEncoder.finish()
メソッドでコマンドバッファを生成します。
const commandBuffer = commandEncoder.finish();
そして、このコマンドバッファをコマンドキューに送信することで、GPUが実際にコマンドを実行するようになります。
const queue = device.createQueue(); queue.submit([commandBuffer]);
コマンドキューの処理が完了するまで待機したい場合は、queue.onSubmittedWorkDone()
を使ってフェンスを作成し、非同期で完了を検知できます。
コマンドバッファの記録は、WebGPUでGPUを使った処理を行う上で中心的な役割を果たします。適切にコマンドを記録し、コマンドキューに送信することで、高度なグラフィックスレンダリングやGPUコンピューティングを実現できます。
コマンドの送信とGPUの同期
編集WebGPUでは、記録したコマンドバッファをコマンドキューに送信することで、GPUがコマンドを実行するようになります。しかし、GPUの処理は基本的に非同期で行われるため、CPU側でGPUの処理が完了するまで待機する必要がある場合があります。このためにWebGPUでは、GPUと同期を取るための機構が用意されています。
コマンドキューへのコマンド送信
編集まず、作成したコマンドバッファをコマンドキューに送信します。
const commandQueue = device.createQueue(); commandQueue.submit([commandBuffer]);
submit()
メソッドにコマンドバッファの配列を渡すことで、それらのコマンドがGPUで実行されるようになります。
GPUの処理完了の待機 - フェンス
編集コマンドが実行されている間、CPU側でGPUの処理が完了するまで待機する必要がある場合があります。例えば、フレームバッファのコンテンツを読み取る前に、レンダリングが完了していることを確認する必要があります。
このような同期を取るために、WebGPUではフェンスを使用します。フェンスは、GPUの特定の処理が完了したことを示すシグナルオブジェクトです。
const fenceValue = commandQueue.submit([commandBuffer]); // GPUの処理が完了するまでホスト側でブロックする commandQueue.onSubmittedWorkDone(fenceValue, MAX_SAFE_INTEGER, function() { // フレームバッファの読み取りなどの処理 });
submit()
の戻り値を使って、onSubmittedWorkDone()
メソッドでフェンスを作成します。コールバック関数は、指定したコマンドが完了した時に呼び出されます。
この方法では、CPUがGPUの処理を直接待機するため、パフォーマンスが低下する可能性があります。
GPUの処理完了の待機 - GPUQuerySet
編集より効率的な方法として、GPUQuerySet
を使ってGPUの処理完了を非同期に検知することができます。
const querySet = device.createQuerySet({...}); const commandBuffer = encoder.finish(); const fenceValue = commandQueue.submit([commandBuffer]); // 別のコマンドバッファでクエリを記録 encoder.resolveQuerySet(querySet, ...); commandQueue.submit([encoder.finish()]); // 非同期でクエリ結果を取得 querySet.onValue().then(value => { // GPUの処理が完了した });
GPUQuerySet
を使うと、GPUの処理完了を非同期にポーリングできるため、CPUリソースを効率的に使えます。
GPUと適切に同期を取ることで、レンダリング結果の確実な読み取りや、コマンドの正しい実行順序を保証できます。フェンスやクエリを上手に活用し、パフォーマンスへの影響を最小限に抑えることが重要です。
リソース(バッファ、テクスチャ)の作成と更新
編集WebGPUでは、GPUがアクセスするデータ(頂点データ、テクスチャ、ユニフォームデータなど)を、バッファまたはテクスチャのリソースとして表現し、GPUメモリにアップロードする必要があります。これらのリソースは、GPUデバイスを介して作成・更新できます。
GPUバッファの作成
編集バッファはGPUDevice
のcreateBuffer()
メソッドで作成できます。作成時にバッファのサイズ、使用目的、マップ方式を指定します。
const vertexData = [/* 頂点データ */]; const vertexBufferDescriptor = { size: vertexData.byteLength, usage: GPUBufferUsage.VERTEX, mappedAtCreation: true }; const vertexBuffer = device.createBuffer(vertexBufferDescriptor);
ここでは、頂点データ用のバッファを作成しています。usage
で使用目的を指定し、mappedAtCreation
をtrue
にすると、作成直後にバッファへの書き込みが可能になります。
GPUバッファへのデータ書き込み
編集作成したバッファにデータを書き込むには、GPUBuffer
のmapAsync()
メソッドを使ってメモリをマップし、getMappedRange()
でバッファ領域を取得します。
const arrayBuffer = await vertexBuffer.mapAsync(GPUMapMode.WRITE); const bufferView = new Uint8Array(arrayBuffer); bufferView.set(vertexData); vertexBuffer.unmap();
データの書き込みが完了したら、unmap()
を呼んでメモリマッピングを解除する必要があります。
GPUテクスチャの作成
編集テクスチャはGPUDevice
のcreateTexture()
メソッドで作成できます。作成時にサイズ、フォーマット、使用目的などを指定します。
const textureDescriptor = { size: { width: 1024, height: 1024 }, format: "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST }; const texture = device.createTexture(textureDescriptor);
ここでは、1024x1024のrgba8unorm
フォーマットのテクスチャを作成しています。テクスチャにはテクスチャバインディングとコピー先としての使用を許可しています。
GPUテクスチャへのデータコピー
編集テクスチャへの画像データのコピーにはGPUTexture
のcreateView()
メソッドとGPUQueue
のcopyImageBitmapToTexture()
メソッドを組み合わせて使用します。
const imageBitmap = await createImageBitmap(imageData); const textureView = texture.createView(); const bytesPerRow = imageBitmap.width * 4; const imageCopyBuffer = device.createBuffer({ size: bytesPerRow * imageBitmap.height, usage: GPUBufferUsage.COPY_DST, mappedAtCreation: true }); new Uint8Array(imageCopyBuffer.getMappedRange()).set(imageBitmap.getBytesPerPlane(0)); imageCopyBuffer.unmap(); const imageCopyBufferView = imageCopyBuffer.createBufferView(); const commandEncoder = device.createCommandEncoder(); commandEncoder.copyBufferToTexture( { buffer: imageCopyBufferView }, { texture: textureView }, { width: imageBitmap.width, height: imageBitmap.height } ); const commandBuffer = commandEncoder.finish(); queue.submit([commandBuffer]);
ここでは、ImageBitmap
から作成したバッファを介して、テクスチャへ画像データをコピーしています。
WebGPUではリソースの作成と更新は明示的に行う必要があり、データのアップロードにはいくつかのステップが必要になります。一方で、リソースの制御が細かくできるため、効率的なデータ転送やリソース管理が可能になります。
シェーディングとジオメトリ
編集WebGPUでのシェーダーの書き方
編集WebGPUでは、シェーダープログラムの記述にGLSL(OpenGL Shading Language)、WGSL(WebGPU Shading Language)のいずれかを使用できます[1]。これらのシェーディング言語は、GPUでの並列計算に特化した構文を持っています。
- GLSLによるシェーダーの例
#version 450 layout(location=0) in vec3 position; layout(location=1) in vec3 normal; layout(location=2) in vec2 uv; layout(location=0) out vec3 vNormal; layout(location=1) out vec2 vUV; layout(set=0, binding=0) uniform Uniforms { mat4 modelViewProjectionMatrix; }; void main() { gl_Position = modelViewProjectionMatrix * vec4(position, 1.0); vNormal = normal; vUV = uv; }
- この例はGLSLによる頂点シェーダーで、以下の機能を持っています。
in
で頂点属性(位置、法線、UVなど)を入力として受け取るuniform
ブロックでシェーダー外部からユニフォームデータを受け取るout
で次のシェーダーステージへデータを出力するmain
関数内で頂点変換、データの割り当てなどの処理を行う
- シェーダーのバインディングとリソースの割り当て
- シェーダーがアクセスするリソース(バッファ、テクスチャ、サンプラーなど)は、レンダーパスでバインディングする必要があります。
const bindGroup = device.createBindGroup({...}); const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor); renderPass.setPipeline(renderPipeline); renderPass.setBindGroup(0, bindGroup);
- ここでは、
createBindGroup()
でバインドグループを作成し、setBindGroup()
でレンダーパスにバインドしています。バインドグループの中身は、シェーダーでのリソース参照と対応付けられています。
シェーダーの種類
編集WebGPUでは、以下のようなシェーダーの種類をサポートしています。
- 頂点シェーダー
- 頂点の変換、頂点属性の処理を行います。
- フラグメントシェーダー
- ピクセル/フラグメントの色値を決定します。
- ジオメトリシェーダー
- プリミティブの組み立てや破壊を行えます。
- テッセレーションシェーダー
- 細分化された曲面を生成できます。
- コンピューティングシェーダー
- 汎用の並列計算を実装できます。
これらのシェーダーは、それぞれのステージ専用のエントリポイント関数を持ちます。
シェーダープログラミングは、GPUの並列アーキテクチャを最大限に活かす上で重要な役割を果たします。適切なシェーダーを実装することで、高性能なグラフィックスレンダリングやGPUコンピューティングが可能になります。
以下に、GLSLとWGSLの相違点を修正して示します。
- APIの違い:
- GLSLは、OpenGLやOpenGL ESなどのグラフィックスAPIに使用される言語であり、主にOpenGL系のアプリケーションで使用されます。
- WGSLは、WebGPU APIに特化したシェーダープログラムの言語であり、Webブラウザ上での3Dグラフィックスの描画や並列計算に使用されます。
- 言語のデザインと機能:
- GLSLは、OpenGLの歴史的な背景に基づいており、OpenGLの機能や特性に対応するために設計されています。そのため、一部の古い機能や構文が残っています。
- WGSLは、WebGPU APIの要件に合わせて設計されており、よりモダンで安全な言語です。静的型付けやメモリアクセスの安全性の向上など、Rust風の特性があります。また、JavaScriptとの相互運用性も考慮されています。
- データ型:
- GLSLも同様に静的型付け言語ですが、データ型や機能がOpenGLの要件に合わせて設計されています。例えば、GLSLには行列型が組み込まれており、ベクトルや行列演算をサポートしています。
- WGSLは、Rust風の静的型付け言語であり、整数、浮動小数点数、ベクトル、行列などの基本的なデータ型があります。また、構造体や列挙型などの複合型もサポートされています。
- 標準化とサポート:
- GLSLは、OpenGLやOpenGL ESの標準規格の一部として開発されています。そのため、特定のグラフィックスAPIによってサポートされる範囲が異なる場合があります。
- WGSLは、WebGPUの標準規格の一部として開発されており、WebGPU APIとともにW3Cによって標準化されています。そのため、将来的にはすべてのWebブラウザでWGSLをサポートすることが期待されています。
頂点シェーダー
編集頂点シェーダーは、WebGPUのレンダリングパイプラインにおいて最初に実行されるシェーダーステージです。頂点シェーダーの主な役割は、頂点データから幾何情報を処理し、各頂点の最終的な位置を決定することです。
頂点シェーダーへの入力
編集頂点シェーダーには、頂点バッファから以下のようなデータが入力されます。
- 頂点座標(位置ベクトル)
- 頂点属性(法線ベクトル、テクスチャ座標、カラーなど)
また、外部から以下のようなデータを受け取ることもできます。
- ユニフォームデータ(モデル行列、ビュー行列、プロジェクション行列など)
- テクスチャデータ
- 各種パラメータ
頂点シェーダーでの処理
編集頂点シェーダーでは、主に以下のような処理を行います。
- 頂点座標の変換(モデル変換、ビュー変換、射影変換)
- 頂点属性の計算(法線の変換、テクスチャ座標の生成など)
- ライティングの計算
- 頂点データの補間
- カリングとクリッピング
頂点シェーダーの出力は、ラスタライザーステージに渡され、プリミティブ(三角形や線分など)を構成する各頂点のデータとして使用されます。
頂点シェーダーの例(GLSL)
編集#version 450 layout(location=0) in vec3 inPosition; layout(location=1) in vec3 inNormal; layout(location=0) out vec3 outWorldPosition; layout(location=1) out vec3 outNormal; layout(set=0, binding=0) uniform Transforms { mat4 modelMatrix; mat4 viewMatrix; mat4 projectionMatrix; }; void main() { vec4 worldPosition = modelMatrix * vec4(inPosition, 1.0); gl_Position = projectionMatrix * viewMatrix * worldPosition; outWorldPosition = worldPosition.xyz; outNormal = (modelMatrix * vec4(inNormal, 0.0)).xyz; }
この例では、頂点座標とモデル行列、ビュー行列、射影行列を入力として受け取り、変換された頂点の位置と法線ベクトルを出力しています。
頂点シェーダーは、ジオメトリの変換や頂点データの処理を担うため、レンダリングの品質や効率に大きな影響を与えます。適切な頂点シェーダーを実装することが、高品質な3Dグラフィックスを実現する上で重要になります。
フラグメントシェーダー
編集フラグメントシェーダー(ピクセルシェーダー)は、WebGPUのレンダリングパイプラインにおいて、最終的な画素(フラグメント)の色や深度値を決定する役割を担います。フラグメントシェーダーは、ラスタライズされた各フラグメントに対して実行されます。
フラグメントシェーダーへの入力
編集フラグメントシェーダーには、主に以下のようなデータが入力されます。
- 頂点シェーダーから補間された値(頂点座標、法線、テクスチャ座標など)
- テクスチャデータ
- ユニフォームデータ(マテリアルパラメータ、ライト情報など)
フラグメントシェーダーは、入力されたデータに基づいて、ライティング計算、テクスチャマッピング、フォグ適用など、様々な処理を実行します。
フラグメントシェーダーの出力
編集フラグメントシェーダーの主な出力は、フラグメントの最終的な色と深度値です。さらに、次のステージに渡すデータを出力することもできます。
フラグメントシェーダーの例(GLSL)
編集#version 450 layout(location=0) in vec3 inWorldPosition; layout(location=1) in vec3 inNormal; layout(location=2) in vec2 inUV; layout(location=0) out vec4 outColor; layout(set=0, binding=0) uniform Uniforms { vec4 lightPosition; vec3 lightColor; vec3 cameraPosition; }; layout(set=0, binding=1) uniform sampler texSampler; layout(set=0, binding=2) uniform texture2D diffuseTexture; void main() { vec3 N = normalize(inNormal); vec3 L = normalize(lightPosition.xyz - inWorldPosition); vec3 V = normalize(cameraPosition - inWorldPosition); vec3 H = normalize(L + V); float diffuse = max(dot(N, L), 0.0); float specular = pow(max(dot(N, H), 0.0), 32.0); vec3 diffuseColor = diffuse * lightColor * texture(sampler2D(diffuseTexture, texSampler), inUV).rgb; vec3 specularColor = specular * lightColor; outColor = vec4(diffuseColor + specularColor, 1.0); }
この例のフラグメントシェーダーでは、法線ベクトルとテクスチャ座標を使って拡散反射とスペキュラー反射を計算し、テクスチャをサンプリングして最終的なフラグメント色を決定しています。
フラグメントシェーダーはピクセルの最終的な色を決めるため、シェーディングの品質を大きく左右します。フラグメントシェーダーの実装次第で、写実的なレンダリング、スタイリッシュなトゥーンレンダリング、ポストプロセス効果の実現など、様々な表現が可能になります。
ジオメトリシェーダー
編集ジオメトリシェーダーは、WebGPUのレンダリングパイプラインにおいてオプションのシェーダーステージです。ジオメトリシェーダーは、頂点シェーダーの出力であるプリミティブ(三角形や線分など)に対して実行され、それらの形状を変更したり、新しいプリミティブを生成・破壊したりすることができます。
ジオメトリシェーダーへの入力
編集ジオメトリシェーダーは、頂点シェーダーから出力された頂点データを受け取ります。具体的には以下のようなデータが入力されます。
- プリミティブを構成する頂点の座標
- 頂点属性(法線ベクトル、テクスチャ座標、カラーなど)
- ユニフォームデータ
ジオメトリシェーダーでの処理
編集ジオメトリシェーダーでは、以下のような処理を行うことができます。
- プリミティブの変形(頂点の移動、スケーリング、回転など)
- 頂点の生成・破棄
- プリミティブの生成・削除
- プリミティブタイプの変更(三角形→線分、など)
- 頂点属性の変更・生成
これらの処理を利用することで、たとえば細分化曲面の生成、ファー(モヘア)レンダリング、パーティクル描画、ジオメトリシェイダーなどの高度な効果を実現できます。
ジオメトリシェーダーの例(GLSL)
編集#version 450 layout(triangles) in; layout(triangle_strip, max_vertices=3) out; layout(location=0) in vec3 inPosition[]; layout(location=1) in vec3 inNormal[]; layout(location=0) out vec3 outWorldPosition; layout(location=1) out vec3 outNormal; layout(set=0, binding=0) uniform Transforms { mat4 modelMatrix; }; void main() { for (int i = 0; i < 3; i++) { gl_Position = gl_in[i].gl_Position; vec4 worldPosition = modelMatrix * vec4(inPosition[i], 1.0); outWorldPosition = worldPosition.xyz; outNormal = (modelMatrix * vec4(inNormal[i], 0.0)).xyz; EmitVertex(); } EndPrimitive(); }
この例では、入力された三角形プリミティブに対して、変換行列を適用してジオメトリを変形しています。EmitVertex()
とEndPrimitive()
を使って、変形された頂点データを次のステージに渡しています。
ジオメトリシェーダーを使うことで、よりリッチなジオメトリ処理が可能になります。ただし、ジオメトリシェーダーはレンダリングパイプラインに大きな負荷をかける可能性があるため、その使用には注意が必要です。シェーダーのパフォーマンスを考慮し、必要最小限の処理に抑えることが肝心です。
テッセレーションシェーダー
編集WebGPUにおけるテッセレーションシェーダーは、テッセレーションと呼ばれるプロセスを制御するためのシェーダープログラムです。テッセレーションとは、モデルの表面を細かく分割して、より滑らかで詳細な表現を可能にする技術です。
テッセレーションシェーダーは、通常のグラフィックスパイプラインにおいて以下の3つのステージで構成されます。
- 制御シェーダー(Control Shader):
- テッセレーションの制御を行うシェーダーステージで、入力としてパッチ(ポリゴンの集合)を受け取り、テッセレーションのレベルや分割方法を決定します。
- 評価シェーダー(Evaluation Shader):
- テッセレーションされたパッチの各頂点を評価し、新しい頂点の位置や属性を計算します。このシェーダーステージは、テッセレーションされたポリゴンの頂点をグラフィックスパイプラインに送るために使用されます。
- 出力マージャ(Outptut Merger):
- テッセレーションされたポリゴンのピクセルごとの最終的な色や深度などの属性を計算し、フレームバッファに書き込みます。
以下は、GLSLでの基本的なテッセレーションシェーダーの使用例です。これは、制御シェーダーと評価シェーダーの両方を含みます。
#version 450 // 制御シェーダー layout (vertices = 3) out; void main() { // 各頂点の位置をコントロールポイントとして設定 if (gl_InvocationID == 0) { gl_TessLevelOuter[0] = 4.0; // 外側のテッセレーションレベルを設定 gl_TessLevelOuter[1] = 4.0; gl_TessLevelOuter[2] = 4.0; gl_TessLevelInner[0] = 4.0; // 内側のテッセレーションレベルを設定 } gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; } // 評価シェーダー layout(triangles, equal_spacing, cw) in; void main() { // ベジェ曲線の評価 float u = gl_TessCoord.x; float v = gl_TessCoord.y; float w = gl_TessCoord.z; vec3 position = mix( mix(gl_in[0].gl_Position.xyz, gl_in[1].gl_Position.xyz, v), mix(gl_in[1].gl_Position.xyz, gl_in[2].gl_Position.xyz, v), u ); // 評価した位置を出力 gl_Position = vec4(position, 1.0); }
この例では、三角形の制御シェーダーが3つの頂点を受け取り、それぞれの頂点の位置を制御ポイントとして設定します。また、評価シェーダーでは、三角形を等間隔にテッセレートし、ベジェ曲線の評価を行います。最終的な位置は、テッセレーションされたポリゴンの各頂点に対して計算され、出力されます。
テッセレーションシェーダーを使用することで、モデルの詳細度を必要に応じて動的に変化させることができます。これにより、よりリアルな表面表現や細かいディテールを持つオブジェクトを描画することが可能になります。また、距離に応じてオブジェクトの詳細度を変えることで、パフォーマンスを最適化することもできます。
テクスチャとサンプリング
編集WebGPUにおいて、テクスチャはシェーダープログラムに渡される2Dまたは3Dのデータとして重要な役割を果たします。これにより、画像やパターンなどをオブジェクトに描画することができます。また、テクスチャのサンプリングは、ピクセルごとに適切な色を取得する過程であり、高品質なレンダリングの鍵となります。
テクスチャデータのアップロード
編集テクスチャをGPUに送るためには、まずデータを正しくフォーマットし、アップロードする必要があります。WebGPUではGPUTexture
オブジェクトを作成し、ピクセルデータをアップロードして、シェーダーで利用可能にします。
サンプラーオブジェクトの作成
編集テクスチャをどのようにサンプリングするかを制御するために、GPUSampler
オブジェクトを作成します。サンプラーは、拡大・縮小の際の補間方法や、テクスチャの境界をどのように処理するかなどを定義します。
テクスチャのサンプリング
編集シェーダーでテクスチャを利用する際には、テクスチャ座標を使って色を取得します。サンプリングの精度や品質は、サンプラーオブジェクトの設定やテクスチャデータの解像度に依存します。
mipmapの生成と利用
編集高解像度テクスチャのパフォーマンスを向上させるために、mipmapを生成して利用することが一般的です。WebGPUでは、自動的にmipmapを生成する機能があり、適切に設定することでテクスチャの縮小時のレンダリング品質を高めることができます。
パフォーマンスとデバッグ
編集WebGPUを利用したアプリケーションのパフォーマンスを最適化することは、スムーズで高速なグラフィック体験を提供する上で重要です。加えて、エラー処理やデバッグツールを駆使して、開発中の問題を早期に解決することが求められます。
WebGPUのパフォーマンス最適化
編集最適化のためには、GPUの負荷を軽減するために、描画のバッチ処理や適切なリソース管理を行う必要があります。また、フレームバッファの解像度やシェーダーの計算負荷も、最適化のポイントです。
GPUクエリによる計測
編集パフォーマンスのボトルネックを発見するために、GPUクエリを使用してレンダリング時間を計測します。これにより、具体的にどの処理が時間を消費しているかを特定し、改善が可能となります。
デバッグツールの活用
編集WebGPUには、ChromeのデベロッパーツールやFirefoxのWebGPUデバッガーなどのデバッグツールが存在します。これらのツールを活用して、パフォーマンスやエラーの解析を行います。
エラー処理
編集WebGPUのAPIでは、エラーが発生した際にそれをキャッチするためのエラーハンドリング機構が用意されています。適切なエラー処理を行うことで、予期しない動作を防ぎ、信頼性の高いアプリケーションを構築できます。
応用例
編集WebGPUは幅広い応用が可能であり、ゲームやシミュレーションなどの3Dグラフィックス、さらには物理ベースレンダリングまで対応できます。また、ポストプロセシングやコンピューティングシェーダーを用いた高度な効果も実現できます。
3Dグラフィックス
編集WebGPUを利用して、リアルタイムの3Dグラフィックスを描画することができます。これには、シェーダープログラミングを駆使して、光の反射や影の表現などの高度な効果を実装することが含まれます。
ポストプロセシング効果
編集ポストプロセシングを活用して、レンダリング後に画面全体に対してフィルター処理やエフェクトを適用することが可能です。ぼかしや色補正などの効果が代表的な例です。
コンピューティングシェーダー
編集WebGPUでは、グラフィックスだけでなく、計算処理にも利用できるコンピューティングシェーダーをサポートしています。これにより、大規模な物理シミュレーションやパーティクルシステムなど、計算負荷の高い処理を効率的に実行することが可能です。
物理ベースレンダリング
編集物理ベースレンダリング(PBR)は、現実世界の光の物理特性を模倣して高品質なマテリアル表現を実現します。WebGPUではPBRの実装が可能であり、リアルな質感を再現することができます。
WebGPUとその他のWeb技術の連携
編集WebGPUは他のWeb技術と組み合わせることで、さらに高機能なアプリケーションを構築できます。Canvas APIやWebGL、Web Assemblyとの連携により、従来の技術との互換性を確保しつつ、GPUの性能を最大限に活用することができます。
WebGPUとCanvasの統合
編集Canvas APIとWebGPUを統合することで、2Dおよび3Dの描画をシームレスに切り替えたり、混在させたりすることができます。これにより、インタラクティブなユーザーインターフェースや、グラフィックスを使用したWebアプリケーションの開発が容易になります。
WebGPUとWebGL、Web Assembly、GPUコンピューティングの連携
編集WebGPUはWebGLやWeb Assemblyとの連携も可能です。これにより、既存のWebGLコンテンツをWebGPUに移行したり、Web Assemblyを通じてネイティブコードをWeb上で動かすことができ、性能面での大幅な向上が見込めます。また、GPUコンピューティングとの連携によって、グラフィックス以外の計算タスクにもGPUのパワーを活用できます。
WebGPUとWebXRの連携
編集WebXRは、バーチャルリアリティ(VR)や拡張現実(AR)コンテンツをWebブラウザ上で表示するためのAPIです。WebGPUを利用して、リアルタイムでリッチな3Dコンテンツを表示することで、没入感のあるVR/AR体験を提供できます。
まとめとWebGPUの将来展望
編集WebGPUは、Web上でのグラフィックスとコンピューティングの新しい標準として、今後ますます重要性を増していく技術です。そのパフォーマンスや柔軟性により、様々な分野での応用が期待されており、今後の技術革新と共にさらなる進化が見込まれます。