【p5.js】シェーダー(GLSL)でポストエフェクト
p5.jsで作った描画に対して、色や形を変えることができたら、表現の方法が物凄く広がるはずです。 p5.jsで描いたものをテクスチャとしてシェーダー(GLSL)で扱うことで、p5.jsで作ったものにポストエフェクトをすることができます。
作例
まず、作例をみるのがわかりやすいと思います。
下の動画で、右がp5.jsで描画したもの。左がその描画にシェーダー(GLSL)を適用したものです。
p5.jsで作成したあとの描画に、色や形の変化を付け加えているのがわかるはずです。ここではグラデーションで色を変えるのと、三角関数で全体を波打つ様に歪ませています。
ソースコードは最後に記載しているので参考して下さい。 次の章から実現方法の解説をしていきたいと思います。
シェーダー(GLSL)でテクスチャを扱う
はじめに、シェーダー(GLSL)でテクスチャを扱う方法を説明していきます。
シェーダー(GLSL)のフラグメントシェーダーでテクスチャを用いて描画することができます。
GLSLではsampler2D
型uniform 変数で2Dテクスチャを変数として使用できます。
テクスチャのサイズは各テクスチャで異なるはずです。
テクスチャをuv座標に対応させて、色情報を取り出すGLSLの関数がtexture2D
になります。
vec4 texture2D (sampler2D, vec2)
引数はテクスチャとuv座標です。 テクスチャのピクセル情報をuv座標に対応付けた時の色が返ってきます。
このtexture2D
関数を用いることで、シェーダー(GLSL)でテクスチャを扱う描画が可能になります。
p5.jsの描画をテクスチャとして生成する
p5.jsで描画する時、キャンバスへ表示するのが一般的です。
createGraphics
で作成した変数に対して描画すると、画像(ピクセル情報)としてバッファされます。
つまり、データとして生成されますが、キャンバスへ表現されません。
あくまでもピクセル情報なので、キャンバスへの表示はimage()
関数を使う必要があります。
今回はcreateGraphics
作成した描画をシェーダー(GLSL)へ渡して、加工後に表示してみます。
シェーダー(GLSL) での扱い方
キーになるのは次の2行です。 上で説明した様に、p5.jsの描画を生成して、シェーダー(GLSL)の変数にセットします。
pg = createGraphics(w/2, w/2); ... theShader.setUniform(`u_tex`, pg);
シェーダー(GLSL)では、フラグメントシェーダーの中で、テクスチャのuv座標を生成します。 uv座標を三角関数を使うことで、テクスチャを波打つ様に歪ませる表現をしています。 変数uvのy成分によって、x成分をずらすことで、テクスチャを左右にずらして表示させることができます。
vec2 uv = vTexCoord; uv.x += 0.2 * cos(uv.y*pi*5.0 + u_time); vec4 tex = texture2D(u_tex, uv);
最後に変数colorと掛け合わせると色を変えたりしています。 今回はグラデーションとなるような色情報を作って、テクスチャに掛け合わせました。
まとめ
今回はp5.jsの描画をテクスチャとして、シェーダーで扱いました。 フラグメントシェーダーでテクスチャのuv座標を変化させたり、色情報を掛け合わせて、ポストエフェクトのような効果を実現しました。
p5.jsで作った作品をポストエフェクトすることで、色々な応用作品を作ってみるのも楽しいと思います。
ソースコード
let vs = ` precision highp float; precision highp int; attribute vec3 aPosition; attribute vec2 aTexCoord; varying vec2 vTexCoord; uniform mat4 uProjectionMatrix; uniform mat4 uModelViewMatrix; void main() { vec4 positionVec4 = vec4(aPosition, 1.0); gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4; vTexCoord = aTexCoord; } `; let fs = ` precision highp float; precision highp int; varying vec2 vTexCoord; uniform sampler2D u_tex; uniform float u_time; uniform vec3 u_color0; uniform vec3 u_color1; float pi = 3.14159265358979; void main() { vec3 color = mix(u_color0, u_color1, vTexCoord.x); vec2 uv = vTexCoord; uv.x += 0.2 * cos(uv.y*pi*5.0 + u_time); vec4 tex = texture2D(u_tex, uv); gl_FragColor = vec4(color, 1.0) * tex; } `; const w = 720; let theShader ; let pg; let color0, color1; function setup() { createCanvas(w, w, WEBGL); rectMode(CENTER); imageMode(CENTER); textureMode(NORMAL); colorMode(HSB); noStroke(); theShader = createShader(vs, fs); color0 = rand_color(); color1 = rand_color(); pg = createGraphics(w/2, w/2); pg.rectMode(CENTER); } function draw(){ set_graphics(); background(0); fill(0, 0, 0, 0); shader(theShader); theShader.setUniform(`u_tex`, pg); theShader.setUniform(`u_time`, -frameCount/35); theShader.setUniform(`u_color0`, color0); theShader.setUniform(`u_color1`, color1); rect(-w/4, 0, w/2, w/2); image(pg, w/4, 0); } function rand_color() { let rc = color(random(180,260), random(70, 100), random(90, 100)); return [red(rc)/255.0, green(rc)/255.0, blue(rc)/255.0]; } function set_graphics(){ const pgw = pg.width/2; const pgr = pg.width/4 * 0.6; let px, py; pg.background(100); pg.stroke(255); pg.fill(255); pg.rect(pgw, pgw, pgr, pgr); pg.noFill(); pg.strokeWeight(20); px = pgw + pgw*0.8*cos(frameCount/60); py = pgw + pgw*0.8*sin(frameCount/60); pg.ellipse(px, py, pgr, pgr); px = pgw + pgw*0.8*cos(frameCount/50 + PI); py = pgw + pgw*0.8*sin(frameCount/50 + PI); pg.rect(px, py, pgr, pgr); }
参考文献
https://tech.pfq.jp/lab/1345/tech.pfq.jp