p5.js のフィルター用シェーダー「createFilterShader()」で Webカメラ画像とキャンバス上の色情報をそれぞれ取得して混ぜる

## はじめに
p5.js の createFilterShader() を使ったカスタムシェーダーの話です。
以下のように、いろいろ試しては記事を書いているシリーズの続きです。

●「シェーダー user:youtoy」の検索結果 - Qiita
　https://qiita.com/search?q=%E3%82%B7%E3%82%A7%E3%83%BC%E3%83%80%E3%83%BC%20user%3Ayoutoy&sort=created

## 今回の内容
今回は、「createFilterShader() で Webカメラの画像情報とキャンバス上の色情報をそれぞれ取得して、それぞれ取得したものをシェーダーの処理で混ぜ合わせる」ということを試してみます。

### プログラムの内容
今回用に作ったプログラム全体は以下のとおりです。

```javascript
let capture;
let s;

function setup() {
  let fragSrc = `precision highp float;

  varying vec2 vTexCoord;
  uniform sampler2D tex0;

  uniform sampler2D tex1;

  void main() {
    vec4 color = texture2D(tex0, vTexCoord);
    vec4 color2 = texture2D(tex1, vTexCoord);

    if(vTexCoord.x < 0.6) {
      gl_FragColor = color2;
    } else {
      gl_FragColor = color + color2;
    }
  }`;

  createCanvas(640, 480, WEBGL);

  noStroke();
  capture = createCapture(VIDEO);

  capture.hide();

  s = createFilterShader(fragSrc);
}

function draw() {
  const b = frameCount % 120;
  background(b);
  s.setUniform("tex1", capture);

  filter(s);
}
```

今回のプログラムで、Webカメラの画像を取得する部分と、それをフィルター用の処理を行っているフラグメントシェーダーに受け渡す部分を残したものが以下となります。

```javascript
function setup() {
  。。。

  createCanvas(640, 480, WEBGL);

  noStroke();
  capture = createCapture(VIDEO);

  capture.hide();

  。。。
}

function draw() {
  。。。
  s.setUniform("tex1", capture);

   。。。
}
```

このようにしてフラグメントシェーダーに渡した Webカメラ画像の情報は、フラグメントシェーダー内で利用しています。

その Webカメラ画像の情報を、ピクセルの処理に使っているのが以下の部分です。

```javascript
  void main() {
    。。。

    if(vTexCoord.x < 0.6) {
      gl_FragColor = color2;
    } else {
      gl_FragColor = color + color2;
    }
  }`;
```

この処理に関し、描画領域の左側から 60％の部分では、Webカメラ画像をそのまま描画する形です。
また、描画領域の右側のほう 40％の部分では、Webカメラ画像に、キャンバス上で描画した色の情報を単純に加算しています。

キャンバス上の描画処理は、以下のように最大 255 となるグレースケールの中の、0 から 119 の値のどれかで塗りつぶす（※ 塗りつぶしに使う色の値は、時間変化している）という内容です。

```javascript
function draw() {
  const b = frameCount % 120;
  background(b);
  。。。
}
```

そのため、最終的に得られる描画結果としては、描画領域の左側から 60％の部分では Webカメラ画像がそのまま描画され、残りの部分では Webカメラ画像がそのままの色だったり明るくなったり時間で変化する、という見た目のものになります。

### プログラムの実行結果
上記のプログラムを実行した結果は、以下のとおりです。
Webカメラから得られた画像の一部のみが明るくなっていて、その明るくなっている部分は時間変化するというのが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/2b85cdb7-2c22-0521-a78e-4e7a5204b797.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/8f175d0d-a25b-d50a-6012-b561d84beab3.png)

お試し用にシンプルに作った内容なので、得られる描画は「カメラ映像と色が時間変化する矩形とが混ぜ合わされた描画が実現できるかどうか」というとてもシンプルなものです。

今後は、これを応用して、カメラ画像に対してキャンバス上に描画された内容に依存した何らかのフィルター処理を、フラグメントシェーダーで実装するということをやってみると面白そうかと思いました。

## WebGL 2.0バージョン
上記のプログラムは、WebGL 1.0 に対応したものでしたが、最近、WebGL 2.0 を用いるというのもやっているため、WebGL 2.0 の書き方のものも掲載してみます。

```javascript
let capture;
let s;

function setup() {
  let fragSrc = `#version 300 es
  precision mediump float;
  
  in vec2 vTexCoord;
  uniform sampler2D tex0;

  uniform sampler2D tex1;

  out vec4 outColor;

  void main() {
    vec4 color = texture(tex0, vTexCoord);
    vec4 color2 = texture(tex1, vTexCoord);

    if(vTexCoord.x < 0.6) {
      outColor = color2;
    } else {
      outColor = color + color2;
    }
  }`;

  createCanvas(640, 480, WEBGL);

  noStroke();
  capture = createCapture(VIDEO);

  capture.hide();

  s = createFilterShader(fragSrc);
}

function draw() {
  const b = frameCount % 120;
  background(b);
  s.setUniform("tex1", capture);

  filter(s);
}
```

書き方が違う行を取り出したものは、以下の通りです。

```JavaScript
。。。
  let fragSrc = `#version 300 es
  precision mediump float;
  
  in vec2 vTexCoord;
  。。。

  out vec4 outColor;

  void main() {
    vec4 color = texture(tex0, vTexCoord);
    vec4 color2 = texture(tex1, vTexCoord);

    。。。
      outColor = color2;
    。。。
      outColor = color + color2;
    。。。
  }`;

。。。
```

## 【追記】 この仕組みを応用した試作
シェーダーに Webカメラの画像と別で描画した画像の 2つを読みこませるのに、上記の仕組みを使って、読みこんだ画像を Webカメラ画像に合成するのではなく、画像処理に使うというものを試作しました。

具体的には、ノイズ画像を自前で生成してシェーダーに読みこみ、そのノイズ画像の濃淡をシェーダー上の処理で利用して、ディゾルブシェーダーを試作してみました。

<iframe width="560" height="315" src="https://www.youtube.com/embed/d70PWYnOejo?si=JTzNIi_h4cJrd7s7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>