ギンの備忘録

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

p5.jsでのシェーダー(GLSL)入門(2)図形にシェーダーを適用

前回はp5.jsからシェーダーを呼び出して、画面全体にGLSLで描画を行いました。

gin-graphic.hatenablog.com

今回はシェーダーの適応範囲を変え、p5.jsで描画した図形を対象にしていきます。

シェーダーを図形に適用

図形を一色で塗りつぶす

まずはp5.js側で円、四角形、三角形を描きます。 それらの図形に対して、フラグメントシェーダーから青色で塗りつぶしを行ってみましょう。

p5.jsでellipe、rect、triangle、planeの4種類の図形を描いて試してみます。

let vs = `
   precision highp float;

   attribute vec3 aPosition;

   uniform mat4 uProjectionMatrix;
   uniform mat4 uModelViewMatrix;

   void main() {
      vec4 positionVec4 = vec4(aPosition, 1.0);
      gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
   }
`;

let fs = `
   precision highp float;

   void main() {
      vec3 color = vec3(0.0, 0.0, 1.0) ;
      gl_FragColor = vec4(color, 1.0);
    }
`;

let theShader;

function setup() {
   createCanvas(500, 500, WEBGL);
   theShader = createShader(vs, fs);

   rectMode(CENTER);
   noStroke();

   const wh = 75 ;

   background("#B0D0B0");

   shader(theShader);

   rect(100, -150, wh, wh);
   ellipse(100, -50, wh, wh);

   push();
   translate(100, 50);
   triangle(0, -wh/2, wh/2, wh/2, -wh/2, wh/2);
   translate(0, 100);
   plane(wh, wh);
   pop();

   resetShader();

   rect(-100, -150, wh, wh);
   ellipse(-100, -50, wh, wh);

   push();
   translate(-100, 50);
   triangle(0, -wh/2, wh/2, wh/2, -wh/2, wh/2);
   translate(0, 100);
   plane(wh, wh);
   pop();
}

f:id:gin_graphic:20201203204622p:plain:w250

GLSLの記述は前回青色でキャンバスを塗りつぶしたときとほとんど変わっていません。 ただし、最後の記述がgl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;となっています。 これは座標変換を行っています。 図形をシェーダーで扱うときはこう書くんだと思っても良いかもしれません。

少し解説すると、

  • モデルビュー行列uModelViewMatrixは、オブジェクトカメラを原点とした座標系に変換するための行列

  • プロジェクション行列uProjectionMatrixは、カメラから捉えた絵をディスプレイへ投影計算するための行列

という意味合いだと思います。 つまり、頂点座標をカメラで映し、ディスプレイへ表示するための座標変換をしているようです。

p5.jsの記述ではshader(theShader);からresetShader();で囲っている記述に対してシェーダーが適用されているのがわかると思います。

このようにして、p5.jsで記述した図形に対してシェーダーを適用し、GLSLで描画をすることが可能となります。

図形内にGLSLで描画

図形の塗りつぶしができるようになったので、図形内をGLSLで描画していきます。

前回の発光表現で使ったフラグメントシェーダーを図形内に描画してみます。

let vs = `
   precision highp float;

   attribute vec3 aPosition;
   attribute vec2 aTexCoord;
   varying vec2 vTexCoord;

   uniform mat4 uProjectionMatrix;
   uniform mat4 uModelViewMatrix;

   void main() {
      vec4 positionVec4 = vec4(aPosition, 1.0);

      vTexCoord = aTexCoord;

      gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
   }
`;

let fs = `
   precision highp float;

   varying vec2 vTexCoord;

   void main() {
      vec2 b = vTexCoord * 2.0 - 1.0;
      vec3 col = vec3(pow(1.0 - abs(b.y), 8.0) * 2.0);
      col *= vec3(0.2, 0.5, 0.9) ;

      gl_FragColor = vec4(col, 1.0);
    }
`;

let theShader;

function setup() {
   createCanvas(500, 500, WEBGL);
   theShader = createShader(vs, fs);

   rectMode(CENTER);
   noStroke();

   const wh = 75 ;

   background("#B0D0B0");

   shader(theShader);

   rect(100, -150, wh, wh);
   ellipse(100, -50, wh, wh);

   push();
   translate(100, 50);
   triangle(0, -wh/2, wh/2, wh/2, -wh/2, wh/2);
   translate(0, 100);
   plane(wh, wh);
   pop();

   resetShader();

   rect(-100, -150, wh, wh);
   ellipse(-100, -50, wh, wh);

   push();
   translate(-100, 50);
   triangle(0, -wh/2, wh/2, wh/2, -wh/2, wh/2);
   translate(0, 100);
   plane(wh, wh);
   pop();
}

f:id:gin_graphic:20201203185902p:plain:w250

フラグメントシェーダーでvTexCoordというvarying変数を使っています。 varying変数というのは頂点シェーダーとフラグメントシェーダーで値をやり取りできる変数型です。 頂点でaTexCoordという変数をフラグメントシェーダーに渡して使用します。

TexCoordというのはテクスチャ座標のことです。これによりp5.jsの図形に対するテクスチャ座標を使って、図形にGLSLで描画ができます。この値は0.0〜1.0となっています。

p5.jsの図形へGLSLによる描画ができました。 これでp5.jsとGLSLを組み合わせた表現ができるようになります。

頂点シェーダーから図形の頂点を変化させることもできますが、それは今後の記事に書いていきます。

p5.jsで図形を回転

p5.jsとGLSLを組み合わせた描画がどうなるか、もう少しだけみていきます。

p5.jsで図形を描画するときに、rotate()させてみます。

let vs = `
   precision highp float;

   attribute vec3 aPosition;
   attribute vec2 aTexCoord;
   varying vec2 vTexCoord;

   uniform mat4 uProjectionMatrix;
   uniform mat4 uModelViewMatrix;

   void main() {
      vec4 positionVec4 = vec4(aPosition, 1.0);

      vTexCoord = aTexCoord;

      gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
   }
`;

let fs = `
   precision highp float;

   varying vec2 vTexCoord;

   void main() {
      vec2 b = vTexCoord * 2.0 - 1.0;
      vec3 col = vec3(pow(1.0 - abs(b.y), 8.0) * 2.0);
      col *= vec3(0.2, 0.5, 0.9) ;

      gl_FragColor = vec4(col, 1.0);
    }
`;

let theShader;

function setup() {
   createCanvas(500, 500, WEBGL);
   theShader = createShader(vs, fs);

   rectMode(CENTER);
   noStroke();

   const wh = 75 ;

   background("#B0D0B0");

   shader(theShader);

   push();
   translate(100, -150);
   rotate(radians(30));
   rect(0, 0, wh, wh);
   pop();

   push();
   translate(100, -50);
   rotate(radians(60));
   ellipse(0, 0, wh, wh);
   pop();

   push();
   translate(100, 50);
   rotate(radians(270));
   triangle(0, -wh/2, wh/2, wh/2, -wh/2, wh/2);
   pop();

   push();
   translate(100, 150);
   rotate(radians(-30));
   plane(wh, wh);
   pop();

   resetShader();

   push();
   translate(-100, -150);
   rotate(radians(30));
   rect(0, 0, wh, wh);
   pop();

   push();
   translate(-100, -50);
   rotate(radians(60));
   ellipse(0, 0, wh, wh);
   pop();

   push();
   translate(-100, 50);
   rotate(radians(270));
   triangle(0, -wh/2, wh/2, wh/2, -wh/2, wh/2);
   pop();

   push();
   translate(-100, 150);
   rotate(radians(-30));
   plane(wh, wh);
   pop();
}

f:id:gin_graphic:20201203185909p:plain:w250

p5.jsでの図形の回転に合わせて、GLSLでの描画も回転しています。 これは座標変換を通じて、対応が取れているということですね。

この辺りを意識せずに、p5.jsとGLSLを組み合わせることができるので、意外とハードルは低いような気がします。

まとめ

今回はp5.jsで生成した図形にシェーダーを適用してみました。 GLSLから図形の塗りつぶしを行い、p5.jsとGLSLを組み合わせた描画ができるようになりました。

これによって下の作例の様にp5.jsとGLSLの組み合わせを使った作品が作れます。 作品に互いの良いところを取り入れていきたいですね。

let vs = `
   precision highp float;

   attribute vec3 aPosition;
   attribute vec2 aTexCoord;
   varying vec2 vTexCoord;

   uniform mat4 uProjectionMatrix;
   uniform mat4 uModelViewMatrix;

   void main() {
      vec4 positionVec4 = vec4(aPosition, 1.0);

      vTexCoord = aTexCoord;

      gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
   }
`;

let fs = `
   precision highp float;

   varying vec2 vTexCoord;

   void main() {
      vec2 b = vTexCoord * 2.0 - 1.0;
      vec3 col = vec3(pow(1.0 - abs(b.y), 8.0) * 2.0);
      col *= vec3(0.2, 0.5, 0.9) ;

      gl_FragColor = vec4(col, 1.0);
    }
`;

let theShader;

function setup() {
   createCanvas(500, 500, WEBGL);

   theShader = createShader(vs, fs);

   rectMode(CENTER);
   noStroke();

   const wh = 50 ;
   const N = 16 ;

   background("#B0D0B0");

   for(let i=0;i<N;i=i+1) {
      let r = radians(360/N*i)
      let x = width/2.5 * cos(r);
      let y = height/2.5 * sin(r);

      shader(theShader);
      push();
      translate(x, y);
      rotate(r);
      rect(0, 0, wh, wh);
      pop();
      resetShader();
   }
}

f:id:gin_graphic:20201203185913p:plain:w250

参考文献

logicalbeat.jp

marina.sys.wakayama-u.ac.jp

riptutorial.com

webglfundamentals.org

p5.jsでのシェーダー(GLSL)入門

p5.jsでのシェーダー(GLSL)入門(1)描画してみる - ギンGraphic

p5.jsでのシェーダー(GLSL)入門(2)図形にシェーダーを適用 - ギンGraphic

p5.jsでのシェーダー(GLSL)入門(3)シェーダーの描画を動かす - ギンGraphic

p5.jsでのシェーダー(GLSL)入門(4)図形を変形する - ギンGraphic