これは MIERUNE AdventCalendar 2022 17日目の記事です。
昨日は @xinmiao1995 さんによる PLATEAUの3D都市モデルでMIERUNEマステープの作り方 でした。
wind map with Mapbox GL JS
— Kanahiro (@kanahiro_iguchi) December 13, 2022
data: https://t.co/J8SBE59H6Z#Mapbox pic.twitter.com/J3NGHNSJBM
雑に投下してややバズった上記の実装についての記事です
はじめに
冒頭のイメージ画像のように、「パーティクル」を動かしてアニメーション表示する実装というのは、色々と先行事例があります。
https://github.com/Esri/wind-js
https://github.com/cambecc/earth
https://github.com/astrosat/windgl
https://github.com/mapbox/webgl-wind
https://github.com/sakitam-fdd/wind-layer
https://github.com/weatherlayers/deck.gl-particle
私が地図界隈の人間なので地図ベースの実装に寄っていますが、ベースとなっている技術は地図というよりはWebGLだとかGLSLだとか、いわゆるシェーダーです。大量の頂点を操作・描画するため、CPUではなくGPUによる並列処理が有効なわけです。
ただし、上記の先行事例は実験的であったり、使いやすくライブラリ化されていなかったりしました(単に私が求めるものがなかったとも言える)。なので今回は勉強を兼ね、THREE.jsをもちいて上記の実装を再現してみました。
GPGPUとは
Wikipediaによると
General-purpose computing on graphics processing units; GPUによる汎用計算
https://ja.wikipedia.org/wiki/GPGPU
GPUというと、3D的なアレをいい感じに高速に表示するためのもの(機械?仕組み?)を第一にイメージしますが、GPUが得意とする大量の並行処理を、一般的な計算に応用する、というのがGPGPUです。近年では機械学習ではGPUの利用が一般的になっていますね。
今回は大量の頂点の位置を、GPGPUにより操作することを試みます。上記の先行事例の全ては、GPGPUにより頂点を操作しています(全部の実装を見ていないですがそのはず)。
GPGPUで頂点を操作する方法
調べた限りは大きく2つの手法があります。
- 頂点テクスチャ
- Transform Feedback
前者は、頂点情報を「画像」として扱うことで、シェーダーで頂点情報を参照・更新する手法です(特に、参照のことを頂点テクスチャフェッチと言います)。後者はより直接的にGPUが頂点情報にアクセスする仕組みです。
なぜ利用方法がめんどうなGPUで頂点を操作したいのかというと、CPUよりはるかに多くの頂点をパフォーマンスを落とさずに操作できるからです。下記の記事を読めば、理解しやすいでしょう。
https://wgld.org/d/webgl/w082.html
https://wgld.org/d/webgl/w083.html
どちらを利用するか
両者ともGPUで計算しているため、パフォーマンスの違いは大きくないはずです。
後者(Transform Feedback)はWebGL2で実装された機能です。WebGL2は、対応が遅れていたSafariが対応したことで基本的に全てのブラウザで利用できるようになった、と考えています。一方、前者は浮動小数点テクスチャというWebGL1の拡張機能を利用していますが、こちらも現代の全てのブラウザで使えるものと考えられます。なので互換性も判断基準にはなりません(WebGL1で完結する分、1が優勢ではある)。
THREE.jsで実装したい
WebGLを、ブラウザからアクセスできるAPIからそのまま利用すると、結構つらいです(APIがかなり低レベル)。THREE.jsはそのへんの面倒臭さを全て隠蔽してくれるので、是非ともTHREE.jsで実装したいところです。
ここで、THREE.jsはTransform Feedbackに対応していないという問題があります。
2018年からイシューはOpenしているが…
https://github.com/mrdoob/three.js/issues/14416
したがって、頂点テクスチャを用いて実装することとしました。
https://qiita.com/Kanahiro/items/4c58a5584fb93f570b8a
luma.glを用いてTransformFeedbackを使った記事はこちら
THREE.jsで頂点テクスチャを利用する
THREE.jsにはDataTextureというクラスがあり、これにより浮動小数点テクスチャを利用できます。
浮動小数点テクスチャ
浮動小数点テクスチャとはなんなのかというと、一般のテクスチャ(=画像)はRGBA値がそれぞれ8bitの整数値をとる一方(0-255)、RGBA値に実数値を保存できるのが浮動小数点テクスチャです。3D空間内の頂点は当然実数値となるため、その頂点位置をそのまま保存できる浮動小数点テクスチャは非常に有用です。
ただし必ずしも浮動小数点テクスチャでなければ頂点情報を保存できないかというとそうではなく、一般のテクスチャで実装した例もありました。32-bitのRGBA値と実数値をやりとりするエンコーディングを定義することで実現しています。
https://medium.com/mapbox/how-i-built-a-wind-map-with-webgl-b63022b5537f
GPUComputationRendererでGPGPU
THREE.jsでGPGPUをする場合は、GPUComputationRendererというヘルパークラスを用いるのが簡単でした。技術的な詳細は下記によくまとまっています。なので本記事ではそのあたりの説明は割愛します。
できたもの
// こんな感じに使います
// velocityTextureは、THREE.jsのTexture、風のベクトル画像を読み込んだもの
const gpuParticle = new GpuParticle(threeObject.renderer, velocityTexture, {
width: 1024,
height: 1024,
});
gpuParticle.getTexture(); // オフスクリーンレンダリングされるテクスチャを取得できる
gpuParticle.render(); // 頂点を1フレーム分動かす
冒頭のスクリーンショットはMapbox GL JSにパーティクルのCanvasを重ねた参考実装です。
おわりに
地図上にパーティクルを表示する際、気の利いた実装だとズームに追随してキャンバスをリサイズしたりするものですが、上記のコードには未実装です。今後やるかもしれないしやらないかもしれない。
ベクトル画像をどう作るかがさらに重要だったりもしますが、この記事では述べません。
明日は@satoshi7190さんによる Maplibreのマーカーについて です!