ずっと昔にWebGLを触ってみたことがあったのだけど初期化が面倒で放置になってしまった。 近頃(というか今年の前半に)WebGPUというものが使えるようになったということを聞いたので、再挑戦を込めてようやく触ってみた。
デモ
WebGPUのざっくり理解
WebGPUを始めるにあたって、ググって出てきた初めての WebGPU アプリを参考にしてみた。 内容はライフゲームを作るチュートリアルで、世代更新の処理をコンピュートシェーダーを使ってGPUで並列に計算できるのが利点となっている。 また頂点・フラグメントシェーダーもモデルを1体描画するだけじゃなくインスタンシングで複数行えたり、 描画パスを複数設定して1回のコマンドバッファ起動で複数の処理や描画を行わせることができる (自分が知らないだけでWebGLでもできたのかもしれない)。
初期化
WebGLでもそうだったが、WebGPUもより一層初期化が面倒で多数の要素が必要なのでさらに複雑度が増している:
- アダプターとデバイスをリクエストする
- キャンバスを設定する
- 頂点バッファを作成
- 頂点バッファのレイアウトを定義
- シェーダーモジュールを作成(コンピュート・頂点・フラグメントシェーダー)
- パイプライン作成: ←シェーダーモジュールを指定
- パイプラインレイアウト
- ユニフォーム用のバッファを作成
- バインドグループ作成
- バインドグループレイアウト
すでに心折れかかってる…こう実行時に使うためのインスタンスを初期化で事前にあれこれ準備しておく必要があるのって管理が煩雑でわけわからなくなるんだよね…。
実行時の処理
実行も手順は多段に渡る:
- コマンドエンコーダーを作成
- コンピュートパスまたはレンダーパスを開始
- パイプラインを指定
- (レンダーパスのみ)パスに頂点バッファを設定
- ユニフォームの値を設定
- バインドグループを設定
- (レンダーパス)描画コマンド、(コンピュートパス)ディスパッチワークグループ
- コンピュートパスまたはレンダーパスを開始
- エンコーダーを
finish
して、デバイスキューにコマンドバッファを送る
「初めての…」のライフゲームでは計算を進める際にピンポンバッファパターンを用いるが、ストレージだけじゃなくバインドグループも2つ用意してフレームごとに切り替えて使用する必要がある。
シェーダー
シェーダーの文法はWGSLという、WebGLでのものなどとも違う、新たな書式になっている。 シェーダーではいくつかの種類のデータを扱うことができる:
- ユニフォーム:少量のデータ(行列、マテリアル、など)
- ストレージ:より大型のデータ(ライフゲームでのセル情報を入れる二次元配列など)
- 自分のID(インデックス):コンピュートシェーダーで並列処理させる際に利用できる
- テクスチャ:フラグメントシェーダーでテクスチャサンプラーを使って内容を参照できる
改変してみる
じゃあここからなにを作ろうかと思い、アイディアに欠けるが以前もやったことがある反応拡散系をWebGPU上で動かしてみることにした。 以前のプログラムではすべての計算をJavaScriptで動かしていたが、今回はWebGPUのシェーダーで並列化できるのが利点。
実際のところ大枠はライフゲームと同じでセルごとの計算が違うだけなので、最速でやるならコンピュートシェーダーの変更だけで事足りる。 それだけではなんなので、少しいじってみた:
- ストレージからテクスチャに変換
- 「初めての…」ではコンピュートシェーダーでストレージを更新し、描画時にフラグメントシェーダーからも参照してセル描画していたが、copyBufferToTextureでテクスチャに変換してUVによるテクスチャサンプルにしてみた
- ただなぜかサンプラーでフィルターを使うとエラーになってしまう、
nearest
しか使えず補間されないので結果は変わらず - テクスチャ化によって描画側のバインドグループはダブルバッファにする必要はなくなる
- マウスでセルの状態をいじれるようにする
- インタラクティブに変更するには、ストレージの内容をCPU側に読み出す必要がある
- 読み出し用のストレージとしてdevice.createBuffer({usage: GPUBufferUsage.MAP_READ})を指定して作成
- buffer.mapAsync(GPUMapMode.READ)、buffer.getMappedRangeで読み出す
- 編集した内容をdevice.queue.writeBufferで書き戻す
- マウスイベント時には処理ができないのと
async
での非同期も必要なので、変数に保持しておいてdraw
時に処理する
- 3D表示
- テクスチャ化したことにより、3Dモデルに貼り付けることもできる
- JS側での視点などの行列計算はglMatrixを使うものかと思ったが、WebGPU Samplesのcamerasでwgpu-matrixというのを使っていたのでそれに習ってみた
- perspectiveやlookAtなどが同じように使える
感想
- コンピュートシェーダーで並列計算できるのがよい
- シェーダー言語の文法をなぜわざわざ微妙に変えるのか…
fn
とかf32
とかswitch
のcase
はFallthroughしない- いわゆる線形補間は
lerp
じゃなくて mix
- 反応拡散系のパラメータを適当にあれこれ探してみた
- まだシマウマや牛の柄はどうやったら生成できるのかわからない…
- ヒョウみたいに崩れた丸(Cの形)みたいにするには物質が2つではうまくいかないような気がする
- WebGPUは現状パソコンのGoogle Chrome系でしか使えないようで、スマホでも動かない…残念
- 状態はわからないけどSafariが期待できないので、一般的に使用できるようになるにはまだ相当遠いことだろう。
参考
- Your first WebGPU app 日本語にするとちょっとおかしい箇所があるので、英語で見るのがいいかも
- glitchのプレビューから動作確認できる
- What’s next?
- Review the WebGPU Samples
- Compute Boids: お互いが影響を及ぼしあうのは、全部をいったん動かした後に調停者が相互作用を処理するというのではなく、Boidごとに総当たりしている
- Review the WebGPU Samples
- Further reading
- WebGPU — All of the cores, none of the canvas
- コンピュートシェーダーに注力
- ボール同士の衝突は、こちらも上記と同様に個々で衝突判定・処理している
- お互いの以前の状態を参照しているので1対1の衝突ならOK、という判断か(3つ以上だとおかしくなるはず)
- Raw WebGPU
- WebGPU Fundamentals
- WebGPU Best Practices
- WebGPU — All of the cores, none of the canvas
- Reference docs
反応拡散系関連
- 知識の宝庫!目がテン!ライブラリー 蛇柄
- 吾輩はキリンである.模様はひび割れている キリンはチューリングパターンよりもひび割れの方が似てる?