ギンの備忘録

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

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

前回までフラグメントシェーダーを中心に、p5.js で書いた図形の描画を変化させてきました。

gin-graphic.hatenablog.com

今回は頂点シェーダー(バーテックスシェーダー)を利用して、図形の位置や形を変えてみます。

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() {
      vec3 pos = aPosition;
      pos.y += 1.0;
      vec4 positionVec4 = vec4(pos, 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);
   rect(0, 0, wh, wh);
   pop();

   push();
   translate(100, 150);
   ellipse(0, 0, wh, wh);
   pop();

   resetShader();

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

   push();
   translate(-100, 150);
   ellipse(0, 0, wh, wh);
   pop();
}

f:id:gin_graphic:20201210210114p:plain:w250

シェーダー適用した図形がy方向に移動していると思います。

頂点シェーダーのpos.y += 1.0;により、頂点が移動しています。 y方向に1.0、つまり画面下方向に図形の一つ分移動させています。

図形の拡大

続けて、図形を拡大させてみましょう。

let vs = `
   precision highp float;

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

   uniform mat4 uProjectionMatrix;
   uniform mat4 uModelViewMatrix;

   void main() {
      vec3 pos = aPosition;
      pos.y *= 2.0;
      pos.y -= 0.5;
      vec4 positionVec4 = vec4(pos, 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);
   rect(0, 0, wh, wh);
   pop();

   push();
   translate(100, 150);
   ellipse(0, 0, wh, wh);
   pop();

   resetShader();

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

   push();
   translate(-100, 150);
   ellipse(0, 0, wh, wh);
   pop();
}

f:id:gin_graphic:20201210210119p:plain:w250

頂点シェーダーでpos.y *= 2.0;として、y方向に2倍拡大した図形を描画できているはずです。

`pos.y -= 0.5;としているのは、元々の図形の中心が(x, y)=(0.5, 0.5)なので、拡大後の中心をそこに合わせるためです。

図形の回転

ここまでで移動、拡大ができるようになりました。 少しだけ難易度を上げて、図形を回転させてみましょう。

let vs = `
   precision highp float;

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

   uniform mat4 uProjectionMatrix;
   uniform mat4 uModelViewMatrix;

   mat2 rotate2d(float _angle){
      return mat2( cos(_angle), sin(_angle),
                  -sin(_angle), cos(_angle) );
   }

   uniform float u_angle;

   void main() {
      vec3 pos = aPosition;
      pos.xy -= 0.5 ;
      pos.xy = rotate2d(u_angle) * pos.xy;
      pos.xy += 0.5;
      vec4 positionVec4 = vec4(pos, 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);

   theShader.setUniform('u_angle', radians(30));
   push();
   translate(100, -150);
   rect(0, 0, wh, wh);
   pop();

   theShader.setUniform('u_angle', radians(60));
   push();
   translate(100, 150);
   ellipse(0, 0, wh, wh);
   pop();

   resetShader();

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

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

f:id:gin_graphic:20201211213228p:plain:w250

p5.jsから回転させる角度をu_angleで指定して、図形を回転させています。

図形の回転は

pos.xy = rotate2d(u_angle) * pos.xy;

として、行列演算をして座標変換を行っています。

回転行列は次のように定義されます。

   mat2 rotate2d(float _angle){
      return mat2( cos(_angle), sin(_angle),
                  -sin(_angle), cos(_angle) );
   }

これらで図形の回転を実現しています。

前後で0.5を足したり、引いたりする意味は、

中心を(0.5, 0.5)から(0, 0)に移動させて、回転処理。 その後(0.5, 0.5)に戻しています。

      pos.xy -= 0.5 ;
      pos.xy = rotate2d(u_angle) * pos.xy;
      pos.xy += 0.5;

演算の中の詳しい説明が気になる方は調べてみてください。

図形の変形

最後に図形の変形を行います。

let vs = `
   precision highp float;

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

   uniform mat4 uProjectionMatrix;
   uniform mat4 uModelViewMatrix;

   void main() {
      vec3 pos = aPosition;
      pos.y += (pos.x < 0.5) ? 0.5 : -0.5 ;
      vec4 positionVec4 = vec4(pos, 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);
   rect(0, 0, wh, wh);
   pop();

   push();
   translate(100, 150);
   ellipse(0, 0, wh, wh);
   pop();

   resetShader();

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

   push();
   translate(-100, 150);
   ellipse(0, 0, wh, wh);
   pop();
}

f:id:gin_graphic:20201211213234p:plain:w250

条件によって、頂点の演算を変えてています。 今回はx座標が0.5より小さいかどうかで、移動方向を変えました。

      pos.y += (pos.x < 0.5) ? 0.5 : -0.5 ;

x座標の右半分と左半分で画面の上下に移動する方向を変えてみました。 変形後は斜めにずらしたような形になっています。

まとめ

今回はp5.jsの図形をGLSLの頂点シェーダーで移動、拡大、回転、変形させてみました。 これで、頂点を扱うことができるようになり、表現できる図形の形が増えたはずです。 応用すれば、おもしろいものが作れるのではないでしょうか。

参考文献

www.nogson.blog

qiita.com

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

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

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

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

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

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

前回までは静止した描画を行ってきました。

gin-graphic.hatenablog.com

今回はp5.jsから変数を渡すことで、動的にGLSLでの描画を変化させ、動画を作りたいと思います。

シェーダー描画を動かす

シェーダーの描画を動かすために、p5.js から変数を渡すします。 p5.js でのframeCountに相当するフレームが進むにつれて変化する変数をGLSLに渡せば、時間変化する描画をすることごできます。

GLSLでy方向に上下に動く描画を試してみます。

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;

   uniform float u_time;
   const float pi = 3.1415926535;

   void main() {
      vec2 b = vTexCoord * 2.0 - 1.0;
      b.y += sin(pi/10.0 * u_time );
      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();
}

function draw(){
   const wh = 75 ;
   let t = frameCount / 10;

   background("#B0D0B0");

   shader(theShader);

   theShader.setUniform('u_time', t);
   push();
   translate(100, -150);
   rotate(radians(30));
   rect(0, 0, wh, wh);
   pop();

   theShader.setUniform('u_time', t/2);
   push();
   translate(100, -50);
   rotate(radians(60));
   ellipse(0, 0, wh, wh);
   pop();

   theShader.setUniform('u_time', t/3);
   push();
   translate(100, 50);
   rotate(radians(270));
   triangle(0, -wh/2, wh/2, wh/2, -wh/2, wh/2);
   pop();

   theShader.setUniform('u_time', t/4);
   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:20201208210945p:plain:w250

www.youtube.com

p5.js からGLSLへ変数を渡すにはtheShader.setUniform();という関数を使います。 GLSLでuniform型で宣言した変数は、p5.js でtheShader.setUniform();を使って値を代入できます。

コードではtheShader.setUniform('u_time', t);として、frameCountから作ったtを代入しています。

また、theShader.setUniform();で変数の値を何度か変えることもできます。 図形毎に渡す値をtt/2t/3t/4と変えて、4つの図形の描画速度を異なるものにしています。

まとめ

p5.js からGLSLへ変数を渡すことで、シェーダー描画を動かすことごできました。 これにより、シェーダーを使った動画の作成ができるようになりました。 p5.jsとGLSLでの作品づくりの表現が一つ増えました。

参考文献

wgld.org

webglfundamentals.org

thebookofshaders.com

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

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

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

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

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

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

数学から創るジェネラティブアート - Processingで学ぶかたちのデザインを購入

「数学から創るジェネラティブアート - Processingで学ぶかたちのデザイン」という参考書を購入しました。

数学と芸術のつながりに焦点を当てた本のようで、面白そう。 自分のやる気を出す目的もあり、勢いで購入しました。 この本からは数学的なアイディアで作品をつくる考え方とかを学べそう。 パラパラ眺めても、綺麗な作例ばかりなので少しワクワクします。

時間を作って読み進めて、ジェネラティブアートを少しでも深く理解できるよう頑張ろう。

f:id:gin_graphic:20201203202333j:plain