p5.js用のライブラリ「p5.warp」を使って簡単に 3D描画を歪めるエフェクトを扱う

今回扱う「p5.warp」は、リニューアルされたばかりの [p5.js のサイト](https://p5js.org/)に掲載されている[ライブラリ](https://p5js.org/libraries/)の話です。以下の記事で扱った「p5.animS」と同様に、リニューアル後のサイトで掲載されるようになりました。

●p5.js用のライブラリ「p5.animS」を使って描画の軌跡のアニメーションを手軽に実現（まずは軽いお試しから） - Qiita
　https://qiita.com/youtoy/items/6e639ca81372b54a0796

今回の「p5.warp」は、簡単に 3D描画を歪めるエフェクトを扱えるライブラリです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/bec249c6-dd9f-c71b-1ce4-caafb0756c4e.png)

## 実現できる内容
まずは、何が実現できるかを見てもらうほうが分かりやすいかと思うので、自分が試した内容の 1つを掲載してみます。

https://x.com/youtoy/status/1807761137427239205

上記で、3D空間内に描画しているオブジェクトは、単純な球です。
それを「p5.warp」を使って、ゆらゆらする見た目の、表示を歪めるエフェクトをかけています。

上記の動画では、意図的に途中で「p5.warp」によるエフェクトを外している部分を作っていますので、p5.warp によるエフェクトが適用されていない状態の描画内容も、確認できるかと思います。

### 実装内容
実装した内容は、公式サンプルに塗りつぶしの色を指定する処理を加えただけのものです。コードは、以下のようにわりとシンプルです。

```javascript
let distort;

function setup() {
  createCanvas(500, 450, WEBGL);

  distort = createWarp(({ glsl, millis, position }) => {
    const t = millis.div(1000);
    return glsl.vec3(
      t.mult(2).add(position.y().mult(4)).sin().mult(0.15),
      t.mult(0.5).add(position.z().mult(2)).sin().mult(0.15),
      t.mult(1.5).add(position.x().mult(3)).sin().mult(0.15)
    );
  });
}

function draw() {
  background(220);
  distort();
  fill(180, 180, 250);
  lights();
  sphere(150);
}
```

## ポイントになる部分
処理のポイントになる部分を見ていきます。

描画に歪みのエフェクトを加える処理のポイントとなる部分は、以下だと思われます。

- createWarp()
- millis.div()
- glsl.vec3()
- t.mult().・・・

これらの処理について、以下の公式ページで仕様を軽く確認しつつ、内容を見ていきます。

●davepagurek/p5.warp: Fast 3D domain warping in p5
　https://github.com/davepagurek/p5.warp

### createWarp() の部分から紐解いていく
今回の処理で使っている createWarp() の仕様は、以下のとおりです。
オプションとして、様々な種類のものを指定できるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/183dc7c4-2db2-34c6-ba70-5346470bc676.png)

今回の実装で用いた公式サンプルでは、その引数の中で以下が使われていました。

- glsl: AD
- millis: Param
- position: VecParam

これらについて、それぞれの内容を見ていきます。

#### millis
millis は以下のように説明されています。float で、ミリ秒を示す数値となるようです。

実装に用いたサンプルでは、div() による除算で、ミリ秒を秒に変換して使っているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/feaf0851-31eb-655c-9236-a36a7a957e1c.png)

なお、ここで除算に使われている div() という処理は、p5.js や JavaScript 標準のものではなく、以下のライブラリの処理になるようでした。

●davepagurek/glsl-autodiff: Tired of doing math to get normals in your vertex shader? Same.
　https://github.com/davepagurek/glsl-autodiff#operations

#### glsl と position
glsl と position については以下のように説明されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/495b3139-0c3b-67df-4f7f-7fdf13958418.png)

先ほどと同様、計算の部分では、以下で提供される処理が使われているようです。

●davepagurek/glsl-autodiff: Tired of doing math to get normals in your vertex shader? Same.
　https://github.com/davepagurek/glsl-autodiff#operations

具体的な処理を見ていきます。

glsl.vec3() という部分は、x・y・z のそれぞれの軸の方向の処理を表しているようです。それぞれの 3つの軸方向で、描画を歪める処理を適用する、という意味になるかと思います。

また以下が、x・y・z のそれぞれの軸方向の処理に関わる部分のようです。

```javascript
      t.mult(2).add(position.y().mult(4)).sin().mult(0.15),
      t.mult(0.5).add(position.z().mult(2)).sin().mult(0.15),
      t.mult(1.5).add(position.x().mult(3)).sin().mult(0.15)
```

時間 t と、空間座標の「position.y()、position.z()、position.x()」を使った計算が行われています。

例えば、1番上の「position.y()」を使ったものは、時間と位置座標を使って以下のようなことを行っているようです。

- t.mult(2)： 時間 t に対して2倍のスピードで進行
- .add(position.y().mult(4))： y座標に基づいて位相をシフト（その際の係数を 4 という大きさに設定）
- .sin()： -1から1の間の値で周期的な変化を適用
　- .mult(0.15)： 歪みの度合いを 0.15 に設定

とりあえず試していくという時には、詳細な仕様を追って仕組みを作っていくのは骨が折れそうです。

そのため、サンプルに手を加えるところから始めて、理解を深めていくのが個人的には良さそうかと思いました（少なくとも、自分はそういう進め方をしていくのが合ってそうに思います）。

今回の場合で言うと、時間 t の除算の部分の数字を変えてみたり、glsl.vec3() の中の数値を変更してみたりして、描画にどう影響するかを見ていくのが分かりやすそうでした。

## 余談： p5.js での歪めるようなエフェクトの描画
過去に、描画ライブラリや自分で仕組みの部分を書いたカスタムシェーダーで、p5.js による 2D描画を歪めるようなものをいくつか試作したことがありました。

### 波紋のようなエフェクト
以下は、描画ライブラリの PixiJS のフィルターを使ったものです。

https://x.com/youtoy/status/1690295402510340096

### カスタムシェーダーで描画を歪めるエフェクト
カスタムシェーダーで描画を歪めるようなエフェクトを書いたものとしては、以下があります。

https://x.com/youtoy/status/1748002507467763754

https://x.com/youtoy/status/1707058882030575734

上記の 3つの試作は全て 2Dのカメラ映像に適用していました。
それと「p5.warp」を使った場合とを比較すると、p5.warp を使ったやり方は簡単に 3Dモデルに適用できることや、その処理を簡単に書けることがメリットだと感じました。

## 「p5.warp」の仕組みの詳細な解説
p5.warp で用いられている仕組みの詳細は、説明は英語になりますが、以下に色々と書かれているようです。

画像とコードが割と含まれているので、それらをざっと見ていくだけでも、少しイメージはつかめるかもしれません（細かく読んでいくには、自分には大変そうでした）。

●Deforming 3D shapes in real time, for the algebraically challenged - Dave Pagurek
　https://www.davepagurek.com/blog/realtime-deformation/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/23ac9991-b0c4-bfd4-e46d-d231914e7bd7.png)

●Vertex Shader Domain Warping with Automatic Differentiation - Dave Pagurek
　https://www.davepagurek.com/programming/shader-domain-warping/

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/2042ed5c-113d-276b-bc57-6bd5f2e84088.png)