Processing Community Day 2021
12/11(Saturday)
名前 : 独楽回しeddy
twitter : https://twitter.com/EKey2210
週に1つくらいの間隔でProcessing、p5.js、glslなどを使ってお絵描きして動画投稿してます。
※ 右下のカーソルで右を押すと次の章、下を押すとその章の解説へ行きます。
createGraphics(width, height, [renderer]);
let grachics;
function setup(){
// 一度だけ呼ばれる
createCanvas(600, 600); // 600×600 の画面を作る
background(color("#255699")); // 背景を塗りつぶす(色は任意)
graphics = createGraphics(450, 300); // 450×300 の新たな描画領域を作る
}
function draw(){
// 何度も呼ばれる(1秒につき30 ~ 60回)
background(37, 86, 153);
graphics.background(255); // 新たな白で描画領域は塗りつぶす
// createGraphicsで作った描画領域は画像として扱えるので画像の描画方法と同じ
imageMode(CENTER); // 画像の中心を配置基準とする
image(graphics, width/2, height/2); // 画面の中心(横幅の半分、縦幅の半分)に配置する
}
let grachics;
function setup(){
// 一度だけ呼ばれる
createCanvas(600, 600); // 600×600 の画面を作る
background(color("#255699")); // 背景を塗りつぶす(色は任意)
graphics = createGraphics(450, 300); // 450×300 の新たな描画領域を作る
graphics.background(255); // 白で描画領域は塗りつぶす
graphics.ellipseMode(CENTER); // 円を描画する際はその中心を配置基準とする
graphics.rectMode(CENTER); // 四角を描画する際はその中心を配置基準とする
graphics.noStroke(); // 線は書かない
graphics.push(); // 描画する上での設定を保存
graphics.translate(graphics.width/2, graphics.height/2); // 描画領域の中心を原点とする
graphics.fill(color("#50d0d0")); // 水色
graphics.ellipse(-120, -80, 75); // 円を描く
graphics.fill(color("#be1e3e")); // 赤色
graphics.rect(120, -80, 75, 75); // 四角を描く
graphics.fill(color("#7967c3")); // 紫色
graphics.rect(-120, 80, 75, 75); // 四角を描く
graphics.fill(color("#ffc639")); // 黄色
graphics.ellipse(120, 80, 75); // 円を描く
graphics.pop(); // 描画する上での設定を元に戻す
}
function draw(){
// 何度も呼ばれる(1秒につき30 ~ 60回)
background(37, 86, 153);
// createGraphicsで作った描画領域は画像として扱えるので画像の描画方法と同じ
imageMode(CENTER); // 画像の中心を配置基準とする
image(graphics, width/2, height/2); // 画面の中心(横幅の半分、縦幅の半分)に配置する
}
変更箇所
let gWidth = 450; // メッシュの横の長さ
let gHeight = 300; // メッシュの縦の長さ
let len = 10; // 小さな四角形の1辺の長さ
let col = gWidth/len; // 全体の横の長さをlenで割って、行の点の数を求める
let row = gHeight/len; // 全体の縦の長さをlenで割って、列の点の数を求める
function setup() {
createCanvas(600, 600);
background(color("#255699")); // 背景塗り潰し
}
function draw() {
background(color("#255699")); // 背景塗り潰し
translate((width-gWidth)/2, (height-gHeight)/2); // 平面を中央に置くために必要
noFill(); // 塗り潰ししない
stroke(255); // 白い線
for (let y = 0; y < row - 1; y++) { // 1周毎に次の列の頂点まで見るので列数-1まで
beginShape(TRIANGLE_STRIP); // 順に連結した三角形 を生成する
for (let x = 0; x < col; x++) { // 1行の頂点全てで見ていく
vertex(x * len, y * len); // 頂点 x, y
vertex(x * len, (y + 1) * len); // 頂点 x, y+1
}
endShape(); // 1列分が終わったら一旦区切る
}
}
次のページで補足
補足
beginShape(TRIANGLE_STRIP)
は連結した三角形を作るための宣言function setup(){
createCanvas(600, 600, WEBGL); // texture()を使うにはWEBGLモードでなければならない
...
textureMode(NORMAL); // 画像を貼る際に画像上の位置指定を0~1で行うようにする
}
function draw(){
...
texture(graphics); // 画像を貼り付けるための宣言
beginShape(); // 頂点を結んで図形の形成することの宣言
vertex(0, 0, 0, 0); // 四角形の頂点の座標、uv座標
vertex(0, 150, 0, 1); // 四角形の頂点の座標、uv座標
vertex(150, 150, 1, 1); // 四角形の頂点の座標、uv座標
vertex(150, 0, 1, 0); // 四角形の頂点の座標、uv座標
endShape(); // 頂点を結んで図形の形成することを終了する宣言
}
createCanvasでWEBGLモードを指定しなければtexture()を使えないので指定します。
vertex()は位置座標の後に画像の座標を指定でき、図の赤い線で示している部分を対応させてます。
let gWidth = 450; // メッシュの横の長さ
let gHeight = 300; // メッシュの縦の長さ
let len = 10; // 小さな四角形の1辺の長さ
let col = gWidth/len; // 全体の横の長さをlenで割って、行の点の数を求める
let row = gHeight/len; // 全体の縦の長さをlenで割って、列の点の数を求める
let graphics;
function setup() {
createCanvas(600, 600, WEBGL); // texture()を使うにはWEBGLモードでなければならない
background(color("#255699")); // 背景塗り潰し
graphics = createGraphics(gWidth, gHeight); // 平面と同じサイズで作成
graphics.background(255); // 新たな白で描画領域は塗りつぶす
graphics.ellipseMode(CENTER); // 円を描画する際はその中心を配置基準とする
graphics.rectMode(CENTER); // 四角を描画する際はその中心を配置基準とする
graphics.noStroke(); // 線は書かない
graphics.push(); // 描画する上での設定を保存
graphics.translate(graphics.width/2, graphics.height/2); // 描画領域の中心を原点とする
graphics.fill(color("#50d0d0")); // 水色
graphics.ellipse(-120, -80, 75); // 円を描く
graphics.fill(color("#be1e3e")); // 赤色
graphics.rect(120, -80, 75, 75); // 四角を描く
graphics.fill(color("#7967c3")); // 紫色
graphics.rect(-120, 80, 75, 75); // 四角を描く
graphics.fill(color("#ffc639")); // 黄色
graphics.ellipse(120, 80, 75); // 円を描く
graphics.pop(); // 描画する上での設定を元に戻す
textureMode(NORMAL); // 画像を貼る際に画像上の位置指定を0~1で行うようにする
}
function draw() {
background(color("#255699")); // 背景塗り潰し
translate(-gWidth/2, -gHeight/2); // 平面を中央に置くために必要
noFill(); // 塗り潰ししない
stroke(255); // 白い線
texture(graphics); // 画像を貼り付ける宣言
for (let y = 0; y < row - 1; y++) { // 1周毎に次の列の頂点まで見るので列数-1まで
beginShape(TRIANGLE_STRIP); // 順に連結した三角形 を生成する
for (let x = 0; x < col; x++) { // 1行の頂点全てで見ていく
// uv座標(0~1)を求める
let u = map(x, 0, col-1, 0, 1);
let v1 = map(y, 0, row-1, 0, 1);
let v2 = map(y+1, 0, row-1, 0, 1);
vertex(x * len, y * len, u, v1); // 位置座標、uv座標
vertex(x * len, (y + 1) * len, u, v2); // 位置座標、uv座標
}
endShape(); // 1列分が終わったら一旦区切る
}
}
createCanvasでWEBGLモードを指定しなければtexture()を使えないので指定します。
貼り付ける画像を生成
描画モードをWEBGLにすることで画面中央が原点になるので位置調整
uv座標を計算し、平面を作るvertexの位置座標の後に設定
let gWidth = 450; // メッシュの横の長さ
let gHeight = 300; // メッシュの縦の長さ
let len = 10; // 小さな四角形の1辺の長さ
let col = gWidth/len; // 全体の横の長さをlenで割って、行の点の数を求める
let row = gHeight/len; // 全体の縦の長さをlenで割って、列の点の数を求める
let graphics;
function setup() {
createCanvas(600, 600, WEBGL); // texture()を使うにはWEBGLモードでなければならない
background(color("#255699")); // 背景塗り潰し
graphics = createGraphics(gWidth, gHeight); // 平面と同じサイズで作成
graphics.background(255); // 新たな白で描画領域は塗りつぶす
graphics.ellipseMode(CENTER); // 円を描画する際はその中心を配置基準とする
graphics.rectMode(CENTER); // 四角を描画する際はその中心を配置基準とする
graphics.noStroke(); // 線は書かない
graphics.push(); // 描画する上での設定を保存
graphics.translate(graphics.width/2, graphics.height/2); // 描画領域の中心を原点とする
graphics.fill(color("#50d0d0")); // 水色
graphics.ellipse(-120, -80, 75); // 円を描く
graphics.fill(color("#be1e3e")); // 赤色
graphics.rect(120, -80, 75, 75); // 四角を描く
graphics.fill(color("#7967c3")); // 紫色
graphics.rect(-120, 80, 75, 75); // 四角を描く
graphics.fill(color("#ffc639")); // 黄色
graphics.ellipse(120, 80, 75); // 円を描く
graphics.pop(); // 描画する上での設定を元に戻す
textureMode(NORMAL); // 画像を貼る際に画像上の位置指定を0~1で行うようにする
}
function draw() {
background(color("#255699")); // 背景塗り潰し
translate(-gWidth/2, -gHeight/2); // 平面を中央に置くために必要
noFill(); // 塗り潰ししない
stroke(255); // 白い線
texture(graphics); // 画像を貼り付ける宣言
for (let y = 0; y < row - 1; y++) { // 1周毎に次の列の頂点まで見るので列数-1まで
beginShape(TRIANGLE_STRIP); // 順に連結した三角形 を生成する
for (let x = 0; x < col; x++) { // 1行の頂点全てで見ていく
// uv座標(0~1)を求める
let u = map(x, 0, col-1, 0, 1);
let v1 = map(y, 0, row-1, 0, 1);
let v2 = map(y+1, 0, row-1, 0, 1);
vertex(x * len, y * len + sin(frameCount*0.2+u*TWO_PI*2) * len, u, v1); // y座標にsin関数分の値を足す(uの値で特徴づけ)
vertex(x * len, (y + 1) * len + sin(frameCount*0.2+u*TWO_PI*2) * len, u, v2); // y座標にsin関数分の値を足す(uの値で特徴づけ)
}
endShape(); // 1列分が終わったら一旦区切る
}
}
y座標にsin関数で計算された値を足して動きをつける。
// バーテックスシェーダーのコードをvsに文字列として入れる。
let vs = `
precision mediump float; // どれくらいの精度で計算するかを定義
attribute vec3 aPosition; // 頂点の位置
attribute vec2 aTexCoord; // 画像のuv座標
uniform mat4 uProjectionMatrix; // プロジェクション変換行列(カメラに映る範囲の決定に使う)
uniform mat4 uModelViewMatrix; // ビュー変換行列(カメラの視点の決定に使う)
uniform float time; // 時間(p5.jsのスケッチ側から送ってもらう)
varying vec2 vTexCoord; // 画像のuv座標(フラグメントシェーダーに送る)
void main() {
vec3 uPosition = aPosition + vec3(0.0, sin(time + aPosition.x * 0.02)*20.0, 0.0); // 元々の位置座標に対して頂点のx座標で重み付けをしてsin関数の動きをつける
vec4 positionVec4 = vec4(uPosition, 1.0); // 位置座標をvec4型にして格納
gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4; // 座標変換を実行(プロジェクション変換行列×ビュー変換行列×頂点座標)
vTexCoord = aTexCoord; // フラグメントシェーダー側に送るためのuv座標受け渡し
}
`;
// フラグメントシェーダーのコードをfsに文字列として入れる。
let fs = `
#ifdef GL_ES
precision mediump float; // どれくらいの精度で計算するかを定義
#endif
varying vec2 vTexCoord; // バーテックスシェーダー側から送られた画像のuv座標
uniform sampler2D tex; // 送られた画像の情報
uniform float time; // 時間(p5.jsのスケッチ側から送ってもらう)
void main() {
// 送られた画像の色情報をそのまま返す
vec2 uv = vTexCoord;
vec4 texture = texture2D(tex, uv);
gl_FragColor = texture;
}
`;
let gWidth = 450; // メッシュの横の長さ
let gHeight = 300; // メッシュの縦の長さ
let len = 10; // 小さな四角形の1辺の長さ
let col = gWidth/len; // 全体の横の長さをlenで割って、行の点の数を求める
let row = gHeight/len; // 全体の縦の長さをlenで割って、列の点の数を求める
let graphics;
let useShader; // シェーダー
function setup() {
createCanvas(600, 600, WEBGL); // texture()を使うにはWEBGLモードでなければならない
background(color("#255699")); // 背景塗り潰し
useShader = createShader(vs, fs); // シェーダーを作る
graphics = createGraphics(gWidth, gHeight); // 平面と同じサイズで作成
graphics.background(255); // 新たな白で描画領域は塗りつぶす
graphics.ellipseMode(CENTER); // 円を描画する際はその中心を配置基準とする
graphics.rectMode(CENTER); // 四角を描画する際はその中心を配置基準とする
graphics.noStroke(); // 線は書かない
graphics.push(); // 描画する上での設定を保存
graphics.translate(graphics.width/2, graphics.height/2); // 描画領域の中心を原点とする
graphics.fill(color("#50d0d0")); // 水色
graphics.ellipse(-120, -80, 75); // 円を描く
graphics.fill(color("#be1e3e")); // 赤色
graphics.rect(120, -80, 75, 75); // 四角を描く
graphics.fill(color("#7967c3")); // 紫色
graphics.rect(-120, 80, 75, 75); // 四角を描く
graphics.fill(color("#ffc639")); // 黄色
graphics.ellipse(120, 80, 75); // 円を描く
graphics.pop(); // 描画する上での設定を元に戻す
textureMode(NORMAL); // 画像を貼る際に画像上の位置指定を0~1で行うようにする
}
function draw() {
background(color("#255699")); // 背景塗り潰し
shader(useShader); // シェーダーの実行を宣言する
useShader.setUniform("time", frameCount*0.1); // 総フレーム数 × 0.01を時間としてシェーダー側に送る
useShader.setUniform("tex", graphics); // 作った画像(graphics)をシェーダー側に送る
noStroke(); // メッシュの線を書かないようにする
push();
translate(-gWidth/2, -gHeight/2); // 平面を中央に置くために必要
//texture(graphics); // この宣言も不要になる
for (let y = 0; y < row - 1; y++) { // 1周毎に次の列の頂点まで見るので列数-1まで
beginShape(TRIANGLE_STRIP); // 順に連結した三角形 を生成する
for (let x = 0; x < col; x++) { // 1行の頂点全てで見ていく
// uv座標(0~1)を求める
let u = map(x, 0, col-1, 0, 1);
let v1 = map(y, 0, row-1, 0, 1);
let v2 = map(y+1, 0, row-1, 0, 1);
vertex(x * len, y * len, u, v1); // シェーダー側で頂点を動かすのでここで動かす処理は不要になる
vertex(x * len, (y + 1) * len, u, v2); // シェーダー側で頂点を動かすのでここで動かす処理は不要になる
}
endShape(); // 1列分が終わったら一旦区切る
}
pop();
}
バーテックスシェーダーのコードを文字列として定義
フラグメントシェーダーのコードを文字列として定義
定義した文字列を引数としてcreateShaderを実行し、シェーダーを実行できる状態にする。
シェーダーを実行を宣言し、適用する。
シェーダーにスケッチ側から情報を送る場合はsetUniformを使う。
シェーダー側の座標変換の前にsin関数の動きを付ける。
シェーダー側で頂点座標を動かすのでここで動かすようにする必要がなくなる。
描画した結果を画像として扱って動く平面に貼り付ける方法を説明しました。これが出来ると色々面白いことに応用できないかなと思います。
かなり簡潔にはなりましたがシェーダーを使って頂点座標を動かす方法についても解説しました。バーテックスシェーダーは自分個人としてはとっかかりに苦労したので、これで少しでも始める上での敷居が下げられる助けになればと思います。
ここまで見てくださりありがとうございました。