processingで生成した絵をテクスチャとしてシェーダで処理する手法

# 成果物

![9be4b737fe9a39bae3d71ba470c68bf7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80370/97b28797-b160-1fda-e55c-cd67cf94f00b.gif)

これはProcessingで文字を描き、シェーダでノイズエフェクトをかけたものです。

# やりたいこと

- Processing で生成した絵を動的にシェーダで処理したい
    - 例えば、ポストエフェクトとしてよくあるようなモノクロ処理、ノイズ加工処理をシェーダで実装したい
- また、この処理は Processing 内で完結し、別のソフトウェアを経由させためんどい手法はとらない
- Processingで動的に作成した絵をjpgのような静的なデータとして出力しない
    - 静的な画像データとしてテクスチャを生成したくないので

# どうでもいい私的な文脈

- わたしはpixel shader にハマりだした人間
- pixel shader を動かす場として Processing を使いだした
- Processing歴は浅く、シェーダでやれることはシェーダでやりたい欲求がある
- Processing と Shader を組み合わせてみた系の記事が増えてほしい

# 手法

1. PGraphics インスタンスで任意の絵を作成
1. シェーダを Processing に読み込み、PGraphics をテクスチャとして流し込む
1. シェーダ内で取り込んだテクスチャを参照、処理をする

シェーダにjpgを流し込むような要領で、PGraphicsのインスタンスを渡せばOKです。
Processing では シェーダは `PShader` インスタンスとして扱います。このインスタンスには `set()` メソッドが用意されており、任意の数値やテクスチャを設定することが可能。
テクスチャの場合、リファレンスを読むと `PImage` インスタンスならなんでも利用できそう。
`PGraphics` はどうやら `Pimage` を継承しているらしいので、Processing で作成した絵ならなんでも glsl で処理できそうという発想です。  

参考: https://processing.org/reference/PShader_set_.html


## 1. PGraphics インスタンスで任意の絵を作成

```java
PGraphics makePg() {
  // インスタンス作成
  PGraphics pg = createGraphics(width, height);
  int fontSize = 48;
  pg.beginDraw();
  // 背景色
  pg.background(0);
  // 文字色
  pg.fill(255);
  // フォント設定
  font = createFont("YuMin-Medium", fontSize);
  pg.textFont(font);
  // テキストをcenter配置
  pg.text(
    "選ばれざる国民",
    width*0.5-fontSize*7*0.5,
    height*0.5+fontSize*0.5
   );
  return pg;
}
```

上は任意のテキストをセンターに描画した `PGraphics` インスタンスを返す関数です。

これをシェーダを経由せず以下のコードで書き出した様子はこれ

```java
void setup () {
  size(512, 512, P2D);
  PGraphics pg = makePg(); // 関数呼び出し
  image(pg, 0, 0); // 書き出す
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80370/85a9bd22-ef02-629b-cff3-d2fd15e79575.png)

私は選ばれざる国民に囚われているのでテキストを描画をしましたが何でもいいです。
フォントの扱いはちょっとめんどいです。

## 2. シェーダを Processingに読み込む

```java
PShader ps; // shader を保持する変数 アニメーションさせるときに必要
float time; // 経過時間を保持する変数

void setup () {
  // renderer は 2PD を選択すること
  size(512, 512, P2D);
  // 上記の PGraphics インスタンスを作成
  PGraphics pg = makePg();
  // 経過時間を初期化
  time = 0.;
  // Shader プログラムを読み込む
  ps = loadShader("shader.frag");
  // 解像度を渡す
  ps.set("iResolution", float(width), float(height));
  // 経過時間を shader にわたす
  ps.set("iTime", time);
  // PGraphics インスタンスをテクスチャとして shader にわたす
  ps.set("iTex", pg);
}

void draw() {
    // 経過時間をフレーム毎に記録
    time += 0.01;
    // 経過時間をシェーダに渡す
    ps.set("iTime", time);
    // 描画関数を呼び出す
    render();
}

// 描画処理を関数として切り出す
void render() {
  // シェーダを使うことを宣言
  shader(ps);
  // 短形を書く。
  // ピクセルシェーダが適用された短形が画面いっぱいに描画される
  rect(0, 0, width, height);
  // これ以上シェーダを適用しないと宣言
  resetShader();
}
```

### シェーダの保存場所

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80370/446d289d-9b53-8063-3324-cec5e592145e.png)

特にパスを指定しなければProcessingは `data` ディレクトリのファイルを参照します。
`loadShader("shader.frag")` でシェーダを読み込むなら、スクショのように `data/shader.frag` として保存しておく

### シェーダを適用した短形を描画する

`shader()` という関数を呼び出すと、それ以降に描画する図形などはすべてシェーダが適用された状態になります。

https://processing.org/reference/PShader.html  
このリファレンスに詳しいです。

## 3. シェーダ内で取り込んだテクスチャを参照、処理をする

```glsl
uniform vec2 iResolution;
uniform float iTime;
uniform sampler2D iTex; // processing で渡したテクスチャのデータはここから取得できる

float rand(vec2 n) { 
	return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(vec2 p){
	vec2 ip = floor(p);
	vec2 u = fract(p);
	u = u*u*(3.0-2.0*u);
	
	float res = mix(
		mix(rand(ip),rand(ip+vec2(1.0,0.0)),u.x),
		mix(rand(ip+vec2(0.0,1.0)),rand(ip+vec2(1.0,1.0)),u.x),u.y);
	return res*res;
}

vec2 makeNoise(vec2 uv, float range) {
    vec2 noiseUv = gl_FragCoord.xy/iResolution.xy;
    noiseUv.y += iTime;
    float n = noise(vec2(noiseUv.x, noiseUv.y*16.));
    uv.x += mix(range, -range, n);
    return uv;
}

void main() {
    vec2 uv = gl_FragCoord.xy/iResolution.xy;
    uv.y = 1.-uv.y; // 上下反転
    vec3 col = vec3(0.);

    // テクスチャのピクセルをノイズをかけたuv座標で取得している
    // rgb チャンネルごとに異なるノイズをかけるとイケてる感じになる
    float r = texture2D(iTex, makeNoise(uv, .001)).r;
    float g = texture2D(iTex, makeNoise(uv, .02)).g;
    float b = texture2D(iTex, makeNoise(uv, .04)).b;

    gl_FragColor = vec4(vec3(r,g,b), 1.);
}
```

### texture2D 関数を呼び出してテクスチャのデータを参照

第一引数にprocessing で渡したテクスチャを入力し、第2引数でuv座標を渡します  
このマッピング処理、私は完全に理解していないのですが以下のような要領で渡しています

- `gl_FragCoord.xy/iResolution.xy` で 0-1 の値を作成
- `uv.y = 1.-uv.y` で上下反転させる
- 以上で得たuv座標をノイズで加工。これでグリッチをかけたような表現になります。

参考: https://thebookofshaders.com/glossary/?search=texture2D
参考: https://wgld.org/d/webgl/w026.html マッピングについて参考にしました


## ソースコード

https://github.com/aodnawg/process_by_glsl

## まとめ

私は絶対に東京事変の復活ライブを見に行く