ひとことで
GPU の何千もの演算器を、画面を描くためではなく、一般の計算のために使うシェーダーです。粒子 1 個ごとの位置の更新、流体の格子 1 マスごとの計算のように、「同じ処理を大量のデータに独立して行う」仕事は、GPU で一斉に計算すると CPU よりけた違いに速くなります。計算の結果は GPU のメモリに残るので、そのまま描画に使えるのも利点です。
ゲームでの使いどころ
- 大量の粒子:数十万〜数百万の火花や煙の粒子の位置・速度の更新(Unreal Engine の Niagara、Unity の VFX Graph)。
- 流体・布・髪:安定流体の格子の計算、布や髪の頂点の拘束。
- 描画の準備:見える物体を選ぶ GPU カリング、インダイレクト描画の引数づくり、光源を画面の区画に割り振るタイル化。
- 画像処理:ブルーム、被写界深度、ヒストグラムや自動露出の計算。
しくみ
コンピュートシェーダーは、決まった数のスレッドをグループに分けて起動します(例:64 スレッドのグループを 4096 個起動し、26 万個の粒子に 1 スレッドずつ割り当てる)。
- 各スレッドは自分の番号(グローバル ID)を持ち、その番号のデータを読んで計算し、バッファやテクスチャに書き込みます。
- 同じグループのスレッドは、高速な共有メモリを使って値をやり取りできます(プレフィックスサムやぼかしなど)。
- 別のスレッドが書いた値を読むときは、同期(バリア)が必要です。グループをまたぐ同期は、ディスパッチ(起動)を分けて行います。
GPU は、同じ命令を多数のデータに同時に適用する作りなので、スレッドごとに違う分岐をたくさんすると遅くなります。また、メモリを隣り合うスレッドが隣り合う場所から読む(まとめて読める)ように並べると速くなります(SoA)。
WebGL の場合:WebGL にはコンピュートシェーダーがないので、データをテクスチャに入れ、全画面の四角形を描いて、画素ごとのシェーダーで計算して別のテクスチャに書く方法(GPGPU)で代用します。読み元と書き先を毎回入れ替えるので、ピンポンバッファを使います。WebGPU にはコンピュートシェーダーがあります。
実装のポイント
- グループの大きさ:64 や 256 のように、GPU の実行単位(32 や 64 スレッド)の倍数にします。
- データを GPU に置いたままにする:計算結果を毎フレーム CPU へ読み戻すと、転送と待ちで利点が消えます。描画も GPU 上のデータから直接行います。
- 競合を避ける:複数のスレッドが同じ場所に書くときは、アトミック操作を使うか、書く場所がぶつからないように設計します。
- 最初は CPU 版と比べる:GPU の計算は間違いを見つけにくいので、小さなデータで CPU 版と結果が一致するか確かめます。
つまずきやすい点
- 少ないデータでは遅い:起動や転送の手間があるので、数百個程度の処理なら CPU の方が速いことがあります。
- 読み戻しで止まる:GPU の結果を CPU で使おうとすると、GPU の処理が終わるまで待つことになります。どうしても必要なら、数フレーム遅れて読む作りにします。
- 精度と再現性:浮動小数点の足し算の順番がスレッドの実行順で変わるので、合計などは実行ごとにわずかに違う値になることがあります。
デモで試すこと
- 「GPU(並列)」では、65,536 個の粒子が流れの場に乗って渦を巻きます。左下の「更新にかかった時間(CPU 側)」はほとんど 0 です。CPU は計算を命じるだけだからです。
- 「CPU(1 個ずつ)」に切り替えると、同じ計算を JavaScript で 1 個ずつ行い、時間が大きく増えます。
- 「粒子の数」を 262,144 にすると、CPU では 1 フレームの予算(16.7 ms)の大半をこの計算だけで使います。GPU では、ほとんど変わりません。
- 視点を回しながら、粒子の色(速い粒子ほど黄色)で流れの速さを見てください。