p5.js WebGL入門 8 3D カスタムシェイプ

本稿は「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)を使用した場合、

結果は下図のようになります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA