p5.jsでbaseMaterialShaderとテクスチャ配列を使ってアニメーションを作る

# はじめに
　テクスチャ配列を使うと複数の画像をまとめて扱うことができます。

## コード全文
　[2D_ARRAY p5](https://openprocessing.org/sketch/2609045)
```js
/*
  参考：p5で2D_ARRAYを使おう！
  https://openprocessing.org/sketch/2529348
  ICSの記事：
  https://ics.media/web3d-maniacs/webgl2_texture2darray/
  工夫：
  https://openprocessing.org/sketch/2607583
*/

let fishImages;

function preload(){
  // 240x160の画像が縦に120個並んでいる。
  // すなわち240x19200サイズ。
  fishImages = loadImage("https://inaridarkfox4231.github.io/resources/fishPaths.png");
}

function setup() {
  createCanvas(400, 400, WEBGL);

  // shaderを作る
  const sh = baseMaterialShader().modify({
    'fragmentDeclarations':`
      precision mediump sampler2DArray;
      uniform sampler2DArray uFish;
      uniform float uFrame;
    `,
    'vec4 getFinalColor':`(vec4 color){
      color = texture(uFish, vec3(vTexCoord, uFrame));
      return color;
    }`
  });

  shader(sh); // これでprogramができる
  const gl = this._renderer.GL;
  const pg = sh._glProgram;
  gl.useProgram(pg); // これでprogramが走る
  const loc = gl.getUniformLocation(pg, "uFish");
  // 3番に入れる。
  gl.uniform1i(loc, 3);

  const tex = gl.createTexture();
  // 3番に入れる
  gl.activeTexture(gl.TEXTURE3);
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex);

  const src = fishImages.canvas;
  gl.texImage3D(
    gl.TEXTURE_2D_ARRAY, 0, gl.RGBA, src.width, src.height/120, 120,
    0, gl.RGBA, gl.UNSIGNED_BYTE, src
  );
  gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

  const loopFunction = () => {
    background(0,0,128);
    shader(sh);
    noStroke();
    sh.setUniform("uFrame", frameCount%120);
    plane(480, 320);
  }

  draw = loopFunction;
}
```
実行結果（実際は動いてる）
![wrf2f2f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437349/df18171b-1a6f-4d25-a6bf-4c9fd69cd7fb.png)

## 作り方
　元の画像は240x160のキャンバスに魚の画像を真っ白で描画してあります。背景は透明です。それが、縦に120個並んでいます。つまりロードしているfishImagesという画像は240x19200サイズのp5.Imageエレメントになります。その中のcanvas要素にキャンバスが入っています。
　今回はbaseMaterialShaderを使います。このshaderは今回、この配列の描画に特化させます。vTexCoordをダイレクトに使って描画します。描画するのは同じアスペクト比の平面です。480x320サイズです。
```js
  const loopFunction = () => {
    background(0,0,128);
    shader(sh);
    noStroke();
    sh.setUniform("uFrame", frameCount%120);
    plane(480, 320);
  }

  draw = loopFunction;
```

## 2D_ARRAY
　ICSさんの記事に使い方が書いてあります。

https://ics.media/web3d-maniacs/webgl2_texture2darray/

2D_ARRAYの使い方は以前書いたコードを参考にしています。

https://openprocessing.org/sketch/2529348

準備してるのはここで、割と短いです。

```js
  shader(sh); // これでprogramができる
  const gl = this._renderer.GL;
  const pg = sh._glProgram;
  gl.useProgram(pg); // これでprogramが走る
  const loc = gl.getUniformLocation(pg, "uFish");
  // 3番に入れる。
  gl.uniform1i(loc, 3);

  const tex = gl.createTexture();
  // 3番に入れる
  gl.activeTexture(gl.TEXTURE3);
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex);

  const src = fishImages.canvas;
  gl.texImage3D(
    gl.TEXTURE_2D_ARRAY, 0, gl.RGBA, src.width, src.height/120, 120,
    0, gl.RGBA, gl.UNSIGNED_BYTE, src
  );
  gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
```
　まずshader()でプログラムを作ります。次にuseProgramでプログラムを走らせます。uFishに3を割り当てます。テクスチャスロットは0,1,2が予約済なので3じゃないと使えないです。同じスロットから複数のテクスチャを取り出せないからです。
　次にテクスチャを2D_ARRAYで作ります。ここに割り当てるにはp5.Image要素のcanvasプロパティを使います。これはHTMLCanvasElementなのでソースに使えます。texImage3Dの使い方。まずターゲット。0,1,5の原則で1番と5番は0, 2,6,7の制約でフォーマットをここに書き、3,4に横幅と縦幅、最後にソース...がtexImage2Dの使い方でしたが、texImage3Dなので3,4のあとに枚数が追加されます。今の場合は縦に連なった画像の枚数です。つまり120ですね。それでこのように書いてます。
　フィルターを設定します。ラップと違って省略できず、これがないと描画を実行できません。

## glsl
　baseMaterialShaderの中身：
```js
  const sh = baseMaterialShader().modify({
    'fragmentDeclarations':`
      precision mediump sampler2DArray;
      uniform sampler2DArray uFish;
      uniform float uFrame;
    `,
    'vec4 getFinalColor':`(vec4 color){
      color = texture(uFish, vec3(vTexCoord, uFrame));
      return color;
    }`
  });
```
　2D_ARRAYなのでprecisionが必要です。フェッチはvec2ではなくvec3で実行します。三番目は画像の番号になります。つまり縦が160ピクセルごとに自動的に切り分けられます。フェッチには整数が使われますが、それは三番目の値をroundしたものになります。つまり0.49だと0番で、0.51だと1番が使われます。普通に整数の小数で指定すればいいですね。それをユニフォームでやっています。
　以上になります。

# おわりに
　あと使ってないのは3Dだけですね。用意の仕方は同じらしいのですがこっちはどうも補間をするみたいで...詳しくないのでそのうち調べようと思います。
　ここまでお読みいただいてありがとうございました。

## 補足；第三引数の仕様

// 引数の仕様
// このコードでは0,1,2,,,,.119までしか使ってないが、119.5以降はすべて119扱い。
// 逆に0.49以下もすべて（負の数も含めて）0扱い。つまりclampで、これは補正できない。
// ここが3Dとの決定的な違い。3DならNEARESTとREPEATでループできる。
// たとえば120だったら(0.5+n)/120でフェッチすれば0,1,2,...,119,120で元に戻る。
// まあ必要に応じて選ぶといい。

　そういうわけで、たとえば130とか140はすべて119扱いになります。-50は0扱いです。いわゆるclamp_to_edgeで、これはラップパラメータでいじることができません。3Dならフェッチの仕方を工夫することでループさせることができますが若干面倒です。必要に応じて、好きな方を選べばいいと思います。ループトランジションなどは3Dの方が楽かもしれないですね。