本稿は「18.8: 3D Custom Shapes – WebGL and p5.js Tutorial」YouTube動画を元に再構成したものです。
p5.jsでは、カスタムシェイプ、つまり形状の自作は、beginShape()とendShape()関数の間に、vertex()関数を複数実行してシェイプの頂点を作成し、頂点をつなげることで作成できます。ただし頂点の座標を自分で求める必要があるので、3Dとなるとさらに面倒であることに違いありません。beginShape()とendShape()関数、vertex()関数については、「2_7:p5.js シェイプを手作りする」で述べています。
目次
正方形
頂点の計算が最も楽に思われるのは正方形です。
次のコードは3D世界に正方形を作成します。
beginShape();
vertex(0, 0, 0);
vertex(100, 0, 0);
vertex(100, 100, 0);
vertex(0, 100, 0);
endShape(CLOSE);
beginShape()とendShape()関数はその間に記述されたvertex()関数が作成する頂点をつないでシェイプを作成します。今の場合には、下図に示す4つの頂点を作成しているので、正方形がキャンバスの右下に描画されます。endShape()関数には定数CLOSEを渡しているので、最後の頂点(0, 100, 0)と最初の頂点(0, 0, 0)の間も線でつながれます。
三角形
三角形も簡単ですが、正三角形や二等辺三角形となると、頂点の計算は難しくなります。
特にそのシェイプの中心に変換を適用したい、たとえばシェイプのセンターで回転させた場合には、vertex()に渡すxyz値を、原点を基準に決め、位置はtranslate()で決める方法が比較的簡単です。
次のコードでは、beginShape()とendShape()、vertex()関数で正方形と正三角形を作成し、それぞれのセンターで回転させています。ライトとマテリアルも設定しています。
let angle = 0;
function setup() {
createCanvas(400, 300, WEBGL);
angleMode(DEGREES);
// 枠線を描画しない
//noStroke();
}
function draw() {
background(100);
// ライトとマテリアル
ambientLight(200, 200, 0);
directionalLight(255, 255, 255, 1, 1, 0);
ambientMaterial(200);
specularMaterial(255);
// 座標変換を個別に適用したいのでpush()とpop()で囲む
push();
translate(-100, 0, 0);
rotateY(angle);
beginShape();
// 頂点の座標は(0,0,0)を基準に決める
vertex(-50, -50, 0);
vertex(50, -50, 0);
vertex(50, 50, 0);
vertex(-50, 50, 0);
endShape(CLOSE);
pop();
push();
translate(100, 0, 0);
rotateY(angle);
beginShape();
vertex(0, -50, 0);
vertex(50, 50, 0);
vertex(-50, 50, 0);
endShape(CLOSE);
pop();
if (isAction) {
angle += 1;
}
}
let isAction = false;
function mousePressed() {
isAction = !isAction
}
画面のクリックでアクションのオン/オフが切り替えられます。
星型
星型の座標は相当面倒です。
beginShape();
vertex(-10, 10, 0);
vertex(0, 35, 0);
vertex(10, 10, 0);
vertex(35, 0, 0);
vertex(10, -8, 0);
vertex(0, -35, 0);
vertex(-10, -8, 0);
vertex(-35, 0, 0);
endShape(CLOSE);
pop();
正n角形
p5.jsの「Regular Polygon」ページにあるpolygon()関数を改変すると、正n角形のシェイプが作成できます。この関数については「2:形(Form)」で述べています。
// 半径と頂点数を受け取り、多角形を描画する
function polygon(radius, npoints) {
let angle = 360 / npoints;
beginShape();
for (let a = 0; a < 360; a += angle) {
let sx = cos(a) * radius;
let sy = sin(a) * radius;
vertex(sx, sy, 0);
}
endShape(CLOSE);
}
次のコードは、左に星型を描き、右に正n角形を描きます。
let angle = 0;
let n = 3;
function setup() {
createCanvas(400, 300, WEBGL);
angleMode(DEGREES);
const slider = createSlider(3, 20, 3, 1);
slider.position(10, 320);
slider.style('width', '300px');
slider.input(() => {
n = slider.value();
infoP.html('正' + n + '角形');
});
const infoP = createP();
infoP.position(335, 305);
infoP.html('正' + n + '角形');
}
function draw() {
background(100);
ambientLight(200);
directionalLight(255, 255, 0, 1, 0, 1);
ambientMaterial(0, 255, 0);
// 星型
push();
translate(-100, 0, 0);
rotateY(angle);
beginShape();
vertex(-10, 10, 0);
vertex(0, 35, 0);
vertex(10, 10, 0);
vertex(35, 0, 0);
vertex(10, -8, 0);
vertex(0, -35, 0);
vertex(-10, -8, 0);
vertex(-35, 0, 0);
endShape(CLOSE);
pop();
// 正n角形
push();
translate(100, 0, 0);
rotateY(angle);
polygon(40, n);
pop();
angle -= 1;
}
// 半径と頂点数を受け取り、多角形を描画する
function polygon(radius, npoints) {
let angle = 360 / npoints;
beginShape();
for (let a = 0; a < 360; a += angle) {
let sx = cos(a) * radius;
let sy = sin(a) * radius;
vertex(sx, sy, 0);
}
endShape(CLOSE);
}
正n角形の辺数はスライダの操作で変更できます。
カスタムシェイプへのテクスチャマッピング
beginShape()とendShape()、vertex()で作成したカスタムシェイプには、textureMode(NORMAL)への呼び出しとvertex()のu,vパラメータを指定することで、テクスチャを適用することができます。
textureMode(NORMAL)はsetup()内で実行します。これによりテクスチャマッピングがvertex()のu,vパラメータを使って行われる”正規化”モードに変わります。
function setup() {
createCanvas(400, 300, WEBGL);
// テクスチャマッピングを正規化モードに
textureMode(NORMAL);
}
vertex()関数には、指定した頂点が対応するUV座標を追加します。
beginShape();
vertex(0, 0, 0, 0, 0);
vertex(100, 0, 0, 1, 0);
vertex(100, 100, 0, 1, 1);
vertex(0, 100, 0, 0, 1);
endShape(CLOSE);
UV座標は、テクスチャ上の点の位置を表すための座標で、下図のように、(0,0)はテクスチャの左上を、(1,1)は右下を指します。またテクスチャのセンターを指すUV座標は(0.5,0.5)になります。vertex(0, 0, 0, 0, 0)では、頂点(0,0,0)を(0,0)、つまりテクスチャの左上に対応させ。vertex(100, 100, 0, 1, 1)では、頂点(100,100,0)を(1,1)、つまりテクスチャの右下に対応させているわけです。これによって、テクスチャとして適用されたイメージのピクセルがシェイプに転写されます。
100 x 200の長方形に、同じ縦横比率(1:2)の画像をテクスチャとして適用するには、UV座標を次のように指定します(上のコードと同じです)。
push();
texture(tex2);
translate(-50, -100, 0);
rotateY(angle);
beginShape();
vertex(0, 0, 0, 0, 0);
vertex(100, 0, 0, 1, 0);
vertex(100, 200, 0, 1, 1);
vertex(0, 200, 0, 0, 1);
endShape();
pop();
それぞれの場合の実行例です。
let angle = 0;
let tex1, tex2
function preload() {
// 100 x 100の正方形画像
tex1 = loadImage('assets/texImage100_100.jpg');
// 300 x 600の長方形画像
tex2 = loadImage('assets/texImage300_600.jpg');
}
function setup() {
createCanvas(400, 300, WEBGL);
angleMode(DEGREES);
noStroke();
// テクスチャマッピングを正規化モードに
textureMode(NORMAL);
}
function draw() {
background(100);
// ライトとマテリアル
ambientLight(255);
directionalLight(255, 255, 255, 1, 1, 0);
ambientMaterial(255)
specularMaterial(255);
// 100x100のtex1
push();
texture(tex1);
translate(-100, 0, 0)
rotateY(angle);
beginShape();
vertex(0, 0, 0, 0, 0);
vertex(100, 0, 0, 1, 0);
vertex(100, 100, 0, 1, 1);
vertex(0, 100, 0, 0, 1);
endShape(CLOSE);
pop();
// 300x600のtex2
push();
texture(tex2);
translate(50, -100, 0);
rotateY(angle);
beginShape();
vertex(0, 0, 0, 0, 0);
vertex(100, 0, 0, 1, 0);
vertex(100, 200, 0, 1, 1);
vertex(0, 200, 0, 0, 1);
endShape();
pop();
if (isAction) {
angle += 1;
}
}
let isAction = false;
function mousePressed() {
isAction = !isAction
}
画面のクリックでアクションのオン/オフが切り替えられます。
これを応用すると、1枚の画像が割れて回転するアニメーションが作成できます。
let angle = 0;
let tex;
function preload() {
// 正方形画像(350 x 350)
tex = loadImage('assets/texImage.jpg');
}
function setup() {
createCanvas(400, 300, WEBGL);
angleMode(DEGREES);
noStroke();
// テクスチャマッピングを正規化モードに
textureMode(NORMAL);
}
function draw() {
background(240);
// ライトとマテリアル
ambientLight(255);
directionalLight(255, 255, 255, 1, 1, 0);
ambientMaterial(255)
specularMaterial(255);
texture(tex);
// 正方形のtexを、100x200の平面に半分ずつ適用
push();
translate(-50, -50, 0);
rotateY(-angle);
beginShape();
vertex(-50, -50, 0, 0, 0);
vertex(50, -50, 0, 0.5, 0);
vertex(50, 150, 0, 0.5, 1);
vertex(-50, 150, 0, 0, 1);
endShape();
pop();
push();
translate(50, -50, 0);
rotateY(angle);
beginShape();
vertex(-50, -50, 0, 0.5, 0);
vertex(50, -50, 0, 1, 0);
vertex(50, 150, 0, 1, 1);
vertex(-50, 150, 0, 0.5, 1);
endShape();
pop();
if (isAction) {
angle += 1;
}
}
let isAction = false;
function mousePressed() {
isAction = !isAction
}
画面のクリックでアクションのオン/オフが切り替えられます。
三角形や多角形にテクスチャを適用すると、テクスチャは歪んで描画されます。
let angle = 0;
let tex1;
let tex2;
let n = 3;
function preload() {
tex1 = loadImage('assets/texture1.png');
tex2 = loadImage('assets/texImage350_350.jpg');
}
function setup() {
createCanvas(400, 300, WEBGL);
angleMode(DEGREES);
noStroke();
textureMode(NORMAL);
const slider = createSlider(3, 20, 3, 1);
slider.position(10, 320);
slider.style('width', '300px');
slider.input(() => {
n = slider.value();
infoP.html('正' + n + '角形');
});
const infoP = createP();
infoP.position(335, 305);
infoP.html('正' + n + '角形');
}
function draw() {
background(200);
ambientLight(200);
directionalLight(255, 255, 255, 1, 0, 1);
texture(tex1)
// 左に手作り三角形
push();
translate(-100, 0, 0);
rotateY(angle);
beginShape();
vertex(0, -50, 0, 0, 0);
vertex(50, 50, 0, 1, 1);
vertex(-50, 50, 0, 0, 1)
endShape(CLOSE);
pop();
// 右に関数で作る正n角形
push();
translate(100, 0, 0);
rotateY(angle);
polygon(90, n);
pop();
if (isAction) {
angle -= 1;
}
}
let isAction = false;
function mousePressed() {
isAction = !isAction
}
// 半径と頂点数を受け取り、多角形を描画する。
// UV座標も計算する
function polygon(radius, npoints) {
let angle = 360 / npoints;
beginShape();
for (let a = 0; a < 360; a += angle) {
let sx = cos(a) * radius;
let sy = sin(a) * radius;
let u = map(sx, 0, radius, 0.5, 1);
let v = map(sy, 0, radius, 0.5, 1);
vertex(sx, sy, 0, u, v);
}
endShape(CLOSE);
}
textureMode()
テクスチャマッピングの座標空間を設定する。デフォルトのモードは、イメージの実際の座標を参照するIMAGEモード。NORMALモードは0から1の範囲の値で正規化された空間を参照する。この関数はWEBGLモードでのみ機能する。
100 x 200ピクセルのイメージを、矩形シェイプ全体にマッピングするには、IMAGEモードでは点(0,0)、(100, 0)、(100,200)、 (0,200)を用い、NORMALモードではUV座標の(0,0) (1,0) (1,1) (0,1)を用いる。
シンタックス
textureMode(mode)
パラメータ
mode 定数: IMAGEかNORMAL
textureWrap()
グローバルなテクスチャラッピングモード(テクスチャの折り返し方)を設定する。これは、UVが0から1の範囲外になったときの振る舞いを制御する。オプションにはCLAMPとREPEAT、MIRRORの3つがある。
CLAMP(固定)は、テクスチャの端のピクセルを境界まで引き延ばす(テクスチャの端の色がシェイプの端までつづく)。REPEAT(繰り返し)は、境界に到達するまで、テクスチャを繰り返しタイリングする。MIRROR(ミラー)はREPEATに似ているが、新しいタイルごとにテクスチャを反転する。
REPEATとMIRRORはともに、テクスチャが2の累乗サイズ(128, 256, 512, 1024, …)の場合のみ使用できる。
この関数は、それ以降textureWrap()呼び出しが行われるまで、すべてのテクスチャに影響する。
引数が1つだけ渡された場合には、水平と垂直両方の軸に適用される。
シンタックス
textureWrap(wrapX, [wrapY])
パラメータ
wrapX 定数: CLAMPかREPEATまたはMIRROR
wrapY 定数: CLAMPかREPEATまたはMIRROR(オプション)
簡単な例:
次のテクスチャ画像(128 x 128)を使用した場合、
結果は下図のようになります。