成果物
これはProcessingで文字を描き、シェーダでノイズエフェクトをかけたものです。
やりたいこと
- Processing で生成した絵を動的にシェーダで処理したい
- 例えば、ポストエフェクトとしてよくあるようなモノクロ処理、ノイズ加工処理をシェーダで実装したい
- また、この処理は Processing 内で完結し、別のソフトウェアを経由させためんどい手法はとらない
- Processingで動的に作成した絵をjpgのような静的なデータとして出力しない
- 静的な画像データとしてテクスチャを生成したくないので
どうでもいい私的な文脈
- わたしはpixel shader にハマりだした人間
- pixel shader を動かす場として Processing を使いだした
- Processing歴は浅く、シェーダでやれることはシェーダでやりたい欲求がある
- Processing と Shader を組み合わせてみた系の記事が増えてほしい
手法
- PGraphics インスタンスで任意の絵を作成
- シェーダを Processing に読み込み、PGraphics をテクスチャとして流し込む
- シェーダ内で取り込んだテクスチャを参照、処理をする
シェーダにjpgを流し込むような要領で、PGraphicsのインスタンスを渡せばOKです。
Processing では シェーダは PShader
インスタンスとして扱います。このインスタンスには set()
メソッドが用意されており、任意の数値やテクスチャを設定することが可能。
テクスチャの場合、リファレンスを読むと PImage
インスタンスならなんでも利用できそう。
PGraphics
はどうやら Pimage
を継承しているらしいので、Processing で作成した絵ならなんでも glsl で処理できそうという発想です。
参考: https://processing.org/reference/PShader_set_.html
1. PGraphics インスタンスで任意の絵を作成
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
インスタンスを返す関数です。
これをシェーダを経由せず以下のコードで書き出した様子はこれ
void setup () {
size(512, 512, P2D);
PGraphics pg = makePg(); // 関数呼び出し
image(pg, 0, 0); // 書き出す
}
私は選ばれざる国民に囚われているのでテキストを描画をしましたが何でもいいです。
フォントの扱いはちょっとめんどいです。
2. シェーダを Processingに読み込む
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();
}
シェーダの保存場所
特にパスを指定しなければProcessingは data
ディレクトリのファイルを参照します。
loadShader("shader.frag")
でシェーダを読み込むなら、スクショのように data/shader.frag
として保存しておく
シェーダを適用した短形を描画する
shader()
という関数を呼び出すと、それ以降に描画する図形などはすべてシェーダが適用された状態になります。
https://processing.org/reference/PShader.html
このリファレンスに詳しいです。
3. シェーダ内で取り込んだテクスチャを参照、処理をする
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 マッピングについて参考にしました
ソースコード
まとめ
私は絶対に東京事変の復活ライブを見に行く