Transform Feedbackの勉強の一環で、弊社ロゴをパーティクルにしてみた。

## 初めに

初めまして。ディップ株式会社に20新卒で入社したWebフロントエンドエンジニアの@tato_lolです。

さて、皆さんは弊社の公式サイトを見たことがあるでしょうか？
https://www.dip-net.co.jp/
サイトにアクセスすると、真っ先に目に入るのが、パーティクルで形成されたディップのロゴマークだと思います。

実はこのサイトは皆さんもおなじみの[WebGL総本山](https://webgl.souhonzan.org/entry/?v=1635)で紹介されたこともあるサイトで、独特の粘性のあるパーティクルを持ち、websocketを利用して利用者が動かしたパーティクルの座標が他の利用者の画面にも反映されるようになっています。

現状でも中々に魅力的な実装だと思いますが、
僕みたいな頭空っぽマンは
「**パーティクルいっぱい！！！**」とか
「**すっごい動く！！！**」とか
「**60FPS！！！**」
みたいな分かりやすくてパワーみのある指標が欲しいので（？？？）、
WebGL2から追加されたTransform Feedbackを使っていい感じに弊社のロゴを動かしてみようと思います。

## Transform Feedback とは？

僕が説明するまでもなく、諸先輩方が素晴らしい解説・サンプルを残してくださっているので、
まずはこちらをご覧になっていただくのが良いと思います。

- [wgld.org | WebGL2: Transform Feedback の基礎 |](https://wgld.org/d/webgl2/w014.html)
- [サンプルで理解するWebGL 2.0 - Transform Feedbackによるパーティクル表現 - ICS MEDIA](https://ics.media/entry/17505/)

### 自分なりの説明

私の説明は、「とりあえず記事を読むためになんとなくの概要だけ教えてくれ！」という方向けの説明にとどめます。

まず、ざっくり説明すると、
Transform Feedback =「GPUの計算結果を使い、頂点データを更新することが出来る機能」です。

これがどういうことなのかというと、今までWebGLにおけるレンダリングの流れは一方通行で
以下のような形になっていました。

```text:従来のWebGLにおけるレンダリング
1. CPUからGPUに頂点データを送信
2. 頂点シェーダ―で頂点データの計算
3. 計算結果をラスタライズ（ピクセルに起こす）
4. フラグメントシェーダでピクセル計算
```

これが、Transform Feedbackを使うことにより、
以下のような形になりました。

```text:WebGL2におけるTransformFeedbackを使ったレンダリング
1. CPUからGPUに頂点データを送信
2. 頂点シェーダ―で頂点データの計算
3. 頂点データを更新
4. 計算結果をラスタライズ（ピクセルに起こす）
5. フラグメントシェーダでピクセル計算
```

さらに、頂点データ更新以降の処理をスキップできるようになったため、
このような形にもできます。

```text:WebGL2におけるTransformFeedbackを使ったレンダリング（短縮）
1. CPUからGPUに頂点データを送信
2. 頂点シェーダ―で頂点データの計算
3. 頂点データを更新
```

### Transform Feedbackを使うメリット

従来までは、頂点データは最後まで一方通行に描画されたグラフィックスを更新する形で処理を行う必要がありました。
まず、これがラスタライズ以降の処理をスキップできるようになったことで描画を行わず計算のみを行うことができ、より効率的に処理できるようになりました。
加えて、ここでは深く説明しませんが、VBOの中身を直接GPUで変更できるようになったため、よりシンプルに頂点を扱えるようになりました。

## 本題

では、実際に作成したものがこちらになります。
DEMO：https://yoheisuda.github.io/my-transform-feedback-sample/

![my transform feedback sample - Google Chrome 2020_10_10 15_17_18 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303906/9647e41a-b7ea-281a-5b72-6f23a52f671e.png)

こちらは、h_doxasさんの[wgld.org | WebGL2: Transform Feedback で GPGPU |](https://wgld.org/d/webgl2/w015.html)を参考に作成したものです。

Transform Feedbackを使うことにより、Intel UHD Graphics 620という~~クソザコ~~GPUの私のPCでも、
60FPSを保ちつつ、160,000パーティクルを飛ばすことが出来ました。

### 実装

参考にさせていただいたサンプルとそこまで変わりません（大きな違いとしては座標のリセットを加えた部分ぐらいかと思います）が、

```javascript
var VBOArray = [
create_vbo(position),
create_vbo(velocity),
create_vbo(color),
create_vbo(resetPos)
];
```
このようにVBOを定義し、

```javascript
set_attribute(VBOArray, attLocation, attStride);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, VBOArray[0]);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, VBOArray[1]);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, VBOArray[2]);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 3, VBOArray[3]);
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);

gl.uniform1f(uniLocation[0], nowTime);
gl.uniform2fv(uniLocation[1], mousePosition);
gl.uniform1f(uniLocation[2], mouseMovePower);
gl.uniform1f(uniLocation[3], resetFlg);
gl.drawArrays(gl.POINTS, 0, imageWidth * imageHeight);

gl.disable(gl.RASTERIZER_DISCARD);
gl.endTransformFeedback();
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 3, null);
```
render関数の中で、Transform Feedback を実行し、
処理が終わった後、閉じるという形で実装しています。

より詳しい中身については、下記のサンプルを見て頂ければと思います。
https://github.com/YoheiSuda/my-transform-feedback-sample

### まとめ

Transform Feedback使うと、軽量に多くのパーティクルを動かせるので良いですね。
まだまだ自分で理解できていない部分も多いため、これからも勉強していきます。

お読みいただきありがとうございました。