THREE.jsでGPGPUにより頂点を操作しパーティクルをアニメーションする

:::note info
これは [MIERUNE AdventCalendar 2022](https://qiita.com/advent-calendar/2022/mierune)　17日目の記事です。
昨日は @xinmiao1995 さんによる [PLATEAUの3D都市モデルでMIERUNEマステープの作り方](https://qiita.com/xinmiao1995/items/6cb6aad8b397526c0b17) でした。
:::

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">wind map with Mapbox GL JS<br>data: <a href="https://t.co/J8SBE59H6Z">https://t.co/J8SBE59H6Z</a><a href="https://twitter.com/hashtag/Mapbox?src=hash&amp;ref_src=twsrc%5Etfw">#Mapbox</a> <a href="https://t.co/J3NGHNSJBM">pic.twitter.com/J3NGHNSJBM</a></p>&mdash; Kanahiro (@kanahiro_iguchi) <a href="https://twitter.com/kanahiro_iguchi/status/1602674061725007877?ref_src=twsrc%5Etfw">December 13, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

> 雑に投下してややバズった上記の実装についての記事です

## はじめに

冒頭のイメージ画像のように、「パーティクル」を動かしてアニメーション表示する実装というのは、色々と先行事例があります。

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つの手法があります。

1. 頂点テクスチャ
2. Transform Feedback

前者は、頂点情報を「画像」として扱うことで、シェーダーで頂点情報を参照・更新する手法です（特に、参照のことを頂点テクスチャフェッチと言います）。後者はより直接的にGPUが頂点情報にアクセスする仕組みです。

:::note info
なぜ利用方法がめんどうな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

したがって、**頂点テクスチャ**を用いて実装することとしました。

:::note info
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というヘルパークラスを用いるのが簡単でした。技術的な詳細は下記によくまとまっています。なので本記事ではそのあたりの説明は割愛します。

https://nogson2.hatenablog.com/entry/2018/04/07/130727

### できたもの

![Screen Shot 2022-12-10 at 17.52.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/74a63255-3fb7-0680-3647-0f94d296bf47.png)

https://github.com/Kanahiro/three-gpu-particle

```typescript
// こんな感じに使います
// velocityTextureは、THREE.jsのTexture、風のベクトル画像を読み込んだもの
const gpuParticle = new GpuParticle(threeObject.renderer, velocityTexture, {
    width: 1024,
    height: 1024,
});
gpuParticle.getTexture(); // オフスクリーンレンダリングされるテクスチャを取得できる
gpuParticle.render(); // 頂点を1フレーム分動かす
```

[ここ](https://kanahiro.github.io/three-gpu-particle/)や[こちら](https://kanahiro.github.io/three-gpu-particle/maplibre.html)で動いているものを確認できます。

冒頭のスクリーンショットはMapbox GL JSにパーティクルのCanvasを重ねた参考実装です。

## おわりに

地図上にパーティクルを表示する際、[気の利いた実装](https://weatherlayers.github.io/deck.gl-particle/map.html)だとズームに追随してキャンバスをリサイズしたりするものですが、上記のコードには未実装です。今後やるかもしれないしやらないかもしれない。
ベクトル画像をどう作るかがさらに重要だったりもしますが、この記事では述べません。

:::note info
明日は@satoshi7190さんによる Maplibreのマーカーについて です！
:::