ギンの備忘録

デジタルアートやプログラミングやテクノロジー関連のこと。

【p5.js】シェーダー(GLSL)でポストエフェクト

p5.jsで作った描画に対して、色や形を変えることができたら、表現の方法が物凄く広がるはずです。 p5.jsで描いたものをテクスチャとしてシェーダー(GLSL)で扱うことで、p5.jsで作ったものにポストエフェクトをすることができます。

作例

まず、作例をみるのがわかりやすいと思います。

下の動画で、右がp5.jsで描画したもの。左がその描画にシェーダー(GLSL)を適用したものです。

www.youtube.com

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);
}

参考文献

wgld.org

ics.media

p5js.org

https://tech.pfq.jp/lab/1345/tech.pfq.jp


f:id:gin_graphic:20210210220221p:plain:w250