【完走賞ゲット-22】p5.js での 3D表現（クリエイティブコーディングの教科書の例）に手を加える（z軸方向の位置に合わせて色を変える）

## はじめに
こちらは、[完走賞ゲットのため小ネタを毎日投稿しようとチャレンジする Advent Calendar 2022](https://qiita.com/advent-calendar/2022/youtoy) の 22日目の記事です。

[クリエイティブコーディングの教科書](https://zenn.dev/baroqueengine/books/a19140f2d9fc1a) の中にある「3Dの表現」をもとに、少し手を加えたものを作ってみたという話です。

●3Dの表現｜クリエイティブコーディングの教科書

https://zenn.dev/baroqueengine/books/a19140f2d9fc1a/viewer/3126b6

## 元の内容
まず元にした内容は、こちらです。
以下はアニメーションGIF にした際に、元のものよりフレームレートが落ちたりしていますが、どんな 3D表現を行っているかという内容は伝わるかと思います。
![参照元のもの](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/54edadbe-e858-c4bd-00cd-0763317321bb.gif)
※ [ソースコードは、元のページのもの](https://zenn.dev/baroqueengine/books/a19140f2d9fc1a/viewer/3126b6#z%E8%BB%B8%E3%81%AB%E3%82%88%E3%82%8B%E3%82%BD%E3%83%BC%E3%83%88)をご覧ください

### 元の内容の補足
この描画について、少し補足します。
p5.js は WEBGLモードを使って 3D表現を扱う方法もありますが、上記は 2D描画用のモードを使っています。

2D の描画で 3D的な見え方を作っていく過程も、その[元ページ](https://zenn.dev/baroqueengine/books/a19140f2d9fc1a/viewer/3126b6)には何ステップか掲載されています（※ 具体的には以下の内容）。

- 3D表現を作っていくステップ
   - z軸による拡大縮小
   - 消失点
   - 消失点のアニメーション
   - 透視投影
   - 透視投影のアニメーション
   - z軸によるソート

上でアニメーションGIF をのせたものは、上記の各ステップの実装がなされた最終版となっています。

## 変更を加えてみる部分
それでは、元の内容に手を加えたアニメーションを作っていきます。
具体的には、「色」を変化させる処理を加えてみます。

### 色を変更する（シンプルな実装）
ここ最近、p5.js を使った色関連の処理で、[chroma.js](https://gka.github.io/chroma.js/) を使ったお試しをいくつか試してみました。

- [【JavaScript 2022】chroma.js を使った複数色のカラースケールでカラーパレットを生成して p5.js で描画（chroma.bezier() や chroma.scale() の scale.correctLightness() などを利用） - Qiita](https://qiita.com/youtoy/items/6b1f11238a76e327d078)
- [【完走賞ゲット-21】p5.js で描画する複数の色でのグラデーション：chroma.js を使った方法と drawingContext.createLinearGradient を使った方法の比較 - Qiita](https://qiita.com/youtoy/items/741282d588c81c45ee8b)

この中で、カラースケールを作り、そこからカラーパレット・グラデーションを作るということをやっていました。その 2つのうち、グラデーションを作った仕組みを応用すると、この 3D表現に z軸方向の位置に合わせて色を変えるということが簡単にできそうでした。

そんな思いつきで実装をしてみようと思ったものです。
まずは、「黒 ⇒ 青 ⇒ 白」と変化するカラースケールを作り、そのスケールと　z軸方向の位置を紐付けてみます。

```javascript
let camera, targets;

// 追加1
const colorList = ["white", "blue", "black"];
colorScale = chroma.scale(colorList).out("hex");

function setup() {
  createCanvas(windowWidth, windowHeight);
  rectMode(CENTER);

  camera = -1;

  targets = [];
  for (let i = 0; i < 30; i++) {
    const x = random(-width / 2, width / 2);
    const y = random(-height / 2, height / 2);
    const w = 100;
    const h = 80;
    const z = random(10, 100);
    targets.push({ x, y, w, h, z });
  }
}

function draw() {
  clear();
  background(0);
  translate(width / 2, height / 2);

  targets.sort((a, b) => b.z - a.z);
  targets.forEach((t) => {
    t.z -= 0.1;
    if (t.z < 0) {
      t.z = 100;
    }
    const s = norm(0, camera, t.z);

    // 追加２
    const ratio = map(t.z, 0, 100, 0, 1);
    fill(colorScale(ratio));

    rect(t.x * s, t.y * s, t.w * s, t.h * s);
  });
}
```

これを実行した結果は、以下のようになりました。
※ 動いている途中のキャプチャ画像
![1つ目のお試し](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/e602f91b-8932-2840-02c0-00df8f76f9e7.png)
色は変わっているようなのですが、z軸方向の奥側で主な色変化が起こってしまい、手前側にきたときには変化があまり見られません。

手前に動いてくるにつれて色が変わる、というのをはっきり示すには、ちょっと工夫が必要なようです。

### 色を変更するポイントを変える
何か良い方法がないか、[chroma.js のドキュメント](https://gka.github.io/chroma.js/)を見てみます。

そうすると、色の変化点を変えることができる「[scale.domain()](https://gka.github.io/chroma.js/#scale-domain)」というものがありました。
![scale.domain()](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/bf381d51-839b-fe6a-cb81-68bedc7b2e4e.png)

これを使って、青と白の変化点をもっと近くなるように設定してみます。
`domain([0, 0.15, 1])` という部分を追加しました。

```javascript
let camera, targets;

// 追加1 の部分に少し手を加える
const colorList = ["white", "blue", "black"];
colorScale = chroma.scale(colorList).domain([0, 0.15, 1]).out("hex");

function setup() {
  createCanvas(windowWidth, windowHeight);
  rectMode(CENTER);

  camera = -1;

  targets = [];
  for (let i = 0; i < 30; i++) {
    const x = random(-width / 2, width / 2);
    const y = random(-height / 2, height / 2);
    const w = 100;
    const h = 80;
    const z = random(10, 100);
    targets.push({ x, y, w, h, z });
  }
}

function draw() {
  clear();
  background(0);
  translate(width / 2, height / 2);

  targets.sort((a, b) => b.z - a.z);
  targets.forEach((t) => {
    t.z -= 0.1;
    if (t.z < 0) {
      t.z = 100;
    }
    const s = norm(0, camera, t.z);

    // 追加２
    const ratio = map(t.z, 0, 100, 0, 1);
    // console.log(ratio)
    fill(colorScale(ratio));

    rect(t.x * s, t.y * s, t.w * s, t.h * s);
  });
}
```

その変更を加えた結果は、以下のとおりです。

https://twitter.com/youtoy/status/1605909117041840129

奧から手前に移動してくる過程で、色変化がはっきり分かるようになりました。

#### 色を変化させるポイントを動かす前後の比較
なお、お試し 1つ目と 2つ目のカラースケールを、それぞれグラデーションの塗りで表示させると以下のようになります。
![色を変化させるポイントを動かす前後の比較](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/2bbc7ba4-81b8-7d0c-fa79-9a80c6a45d33.png)
2つ目のほうは、白っぽい領域がかなり少なくなっているのが分かると思います。

## 余談
ちなみに、この「クリエイティブコーディングの教科書」は、他にもいろいろコンテンツがあり、p5.js 関連のコンテンツでオススメしたいものの 1つです。

https://zenn.dev/baroqueengine/books/a19140f2d9fc1a