p5.js WebGL入門 5 カメラ

本稿は「18.5: Camera and Perspective – WebGL and p5.js Tutorial」YouTube動画を元にしていますが、扱われているp5.jsのバージョンが前のものなので、新しい機能を加えて書き換えています。

3DCGのカメラ

カメラは3D空間を眺めるときののぞき窓のようなもので、これまで見てきたどのプログラムでも、p5.js WEBGLモードで最初から設定されているデフォルトのカメラを通して、キャンバスに描画されています。

下図の左はBlenderの画面で、ここではカメラとサルのジオメトリを客観的にとらえています。左上の図ではカメラはサルから見て左斜め上の方向にあるので、このカメラから見ると、サルは下図の右上のように見えます。

サルに変化は加えず、カメラをサルの左の方に回り込ませ、カメラから見ると、サルは右下のように見えます。

カメラには、カメラを置く位置や眺める方向、どの向きをカメラの上とするか、装着するレンズの特性など、実際のカメラと同じような設定項目があります。カメラを操作できるようになると、デフォルトの固定カメラにはなかった、表現の広がりが生まれます。

p5.Camera

このクラスは、p5.jsのWEBGLモードで使用するカメラを表し、3Dシーンのレンダリングに必要なカメラの位置、向いている方向、投影情報を持つ。

新しいp5.CameraオブジェクトはcreateCamera()関数で作成し、以下のメソッドで制御できる。この方法で作成したカメラは、これらの属性がさまざまなメソッドで変更されるまで、シーンのデフォルト位置とデフォルトの投資投影を使用する。カメラは複数作成することができ、その場合には、setCamera()関数を使って、現在のカメラを設定することができる。

ノート:以下のメソッドは2つの座標システムで動作する。ワールド座標システム(ワールド座標系)は、XYZ軸に沿った原点との関係性から位置を表し、もう1つのカメラのローカル座標システム(ローカル座標系)は、カメラの視点からの位置を表す(カメラから見た左右、上下、手前奥)。たとえばmove()メソッドはカメラ自身の軸に沿ってカメラを移動させるが、setPosition()メソッドはカメラ位置をワールド空間内で設定する。

カメラの位置

まずは慣れるために、カメラの位置を変更してみましょう。

let myCamera;
let sliderX, sliderY, sliderZ;
let camX = 0,
    camY = 0,
    camZ = 0;
let infoP;

function setup() {
    createCanvas(400, 300, WEBGL);
    angleMode(DEGREES);
    noStroke();
    debugMode();

    myCamera = createCamera();


    sliderX = createXYZslider(-200, 200, camX, {
        x: 10,
        y: 310
    });
    sliderX.input(() => {
        camX = sliderX.value();
    });


    sliderY = createXYZslider(-200, 200, camY, {
        x: 10,
        y: 330
    });
    sliderY.input(() => {
        camY = sliderY.value();
    });

    sliderZ = createXYZslider(-200, 200, camZ, {
        x: 10,
        y: 350
    });
    sliderZ.input(() => {
        camZ = sliderZ.value();
    });

    infoP = createP();
    infoP.position(10, 370);
}

function createXYZslider(min, max, cam, pos) {
    const slider = createSlider(min, max, cam, 1);
    slider.position(pos.x, pos.y);
    slider.style('width', '400px');
    slider.input(function() {
        cam = slider.value();
    });
    return slider;
}

function draw() {
    background(100);
    lights();
    // カメラ位置を設定
    myCamera.camera(camX, camY, camZ + (height / 2.0) / tan(180 * 30.0 / 180.0), 0, 0, 0, 0, 1, 0);

    infoP.html('(' + camX + ', ' + +camY + ', ' + +camZ + ')');

    sphere(40);
    translate(-100, 0, 0);
    box(30, 80, 50);
    translate(200, 0, -250);
    cone();
}

上のスライダを動かすとカメラのx位置を、真ん中のスライダではカメラのy位置を、下のスライダではカメラのz位置を変えることができます。またそのときのカメラの位置は一番下にxyz座標値で表示されます。

たとえば、ジオメトリから見て左に回り込んで、上に移動し、さらに近づくと、カメラからは下図のように見えます。ここで感覚として覚えておくべきなのは、スライダ操作で動いて見えるのは、ジオメトリが移動しているのではなく、カメラが移動しているからだということです。

デフォルトでない、自分のカメラはreateCamera()で作成します。するとそれ以降、キャンバスにレンダリングされるのは、そのカメラから見た3D世界になります。

myCamera = createCamera();

カメラの位置はcamera()メソッドで設定できます。上記コードでは、camX, camY, camZという3つの変数で位置を決めています。

myCamera.camera(camX, camY, camZ + (height / 2.0) / tan(180 * 30.0 / 180.0), 0, 0, 0, 0, 1, 0);

カメラ位置はデフォルトで、(0,0,(height / 2.0) / tan(PI * 30.0 / 180.0))に設定されます。(height / 2.0) / tan(PI * 30.0 / 180.0)は、今の場合おおよそ260なので、カメラは最初、原点から手前に260近づいた位置に置かれることになります。

camera()メソッドに渡している4つめから6つめの引数0,0,0はカメラがどこを見ているかです。0,0,0は原点なので、ここでは3D空間の原点をずっと見ていることになります。

その後の3つの引数0, 1, 0は、カメラのどの向きを上にするかです。最も分かりやすいのが、ここで使用している、y方向を上にする0,1,0です。

createCamera()

新しいp5.Cameraオブジェクトを作成し、そのカメラを使うようレンダラに伝える。p5.Cameraオブジェクトを返す。
シンタックス
createCamera()
戻り p5.Camera: 新たに作成されたカメラオブジェクト

camera()

カメラの位置と向きを設定する。これはcamera()関数を呼び出すのと同等。

*注意:camera()には、グローバル関数と、同名のp5.Camera.camera()メソッドがあります。グローバル関数はデフォルトのカメラ操作に用い、p5.Camera.camera()はcreateCamera()で作成したp5.Cameraに用います。グローバル関数とp5.Cameraメソッドが混在しているように見えるので、注意が要ります。

camera()(グローバル関数)

3Dスケッチのカメラ位置を設定する。この関数のパラメータは、カメラの位置、スケッチのセンター(カメラが指している場所)、上方向(カメラの向き)を定義する。

この関数はカメラの移動をシミュレーションし、さまざまな角度からオブジェクトが見えるようにする。ただし、移動しているのはオブジェクト自体ではなく、カメラだということは覚えておく必要がある。たとえば、centerX値が正のとき、カメラはスケッチの右側に回転しているので、オブジェクトは左に移動しているように見える。

カメラ位置の確認にはこのサンプルが役立つ。

引数なしで呼び出されると、この関数は、camera(0, 0, (height/2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, 1, 0)と同等のデフォルトカメラを作成する。

シンタックス
new camera([x], [y], [z], [centerX], [centerY], [centerZ], [upX], [upY], [upZ])
パラメータ
x 数値: x軸上のカメラ位置(オプション)
y 数値: y軸上のカメラ位置(オプション)
z 数値: z軸上のカメラ位置(オプション)
centerX 数値: スケッチのセンターを表すx座標(オプション)
centerY 数値: スケッチのセンターを表すy座標(オプション)
centerZ 数値: スケッチのセンターを表すz座標(オプション)
upX 数値: カメラからの’上’方向のx成分(オプション)
upY 数値: カメラからの’上’方向のy成分(オプション)
upZ 数値: カメラからの’上’方向のz成分(オプション)

パンとティルト

カメラは左右に振ること(パン)ができ、上下に振ること(ティルト)ができます。パンはp5.Cameraのpan()で、ティルトはp5.Cameraのtilt()メソッドで行います。

let myCamera;

let isPanLeft = false,
    isPanRight = false,
    isTiltUp = false,
    isTiltDown = false;

function setup() {
    createCanvas(400, 300, WEBGL);
    angleMode(DEGREES);
    noStroke();
    debugMode();

    myCamera = createCamera();

    const panLeftButton = setButton('PanLeft', {
        x: 10,
        y: 310
    });
    panLeftButton.mousePressed(() => {
        isPanLeft = true;
    });
    panLeftButton.mouseReleased(() => {
        isPanLeft = false;
    });

    const panRightButton = setButton('PanRight', {
        x: 110,
        y: 310
    });
    panRightButton.mousePressed(() => {
        isPanRight = true;
    });
    panRightButton.mouseReleased(() => {
        isPanRight = false;
    });

    const tiltUpButton = setButton('TiltUp', {
        x: 210,
        y: 310
    });
    tiltUpButton.mousePressed(() => {
        isTiltUp = true;
    });
    tiltUpButton.mouseReleased(() => {
        isTiltUp = false;
    });

    const tiltDownButton = setButton('TiltDown', {
        x: 310,
        y: 310
    });
    tiltDownButton.mousePressed(() => {
        isTiltDown = true;
    });
    tiltDownButton.mouseReleased(() => {
        isTiltDown = false;
    });

}

function setButton(label, pos) {
    const button = createButton(label);
    button.size(80, 30);
    button.position(pos.x, pos.y);
    return button;
}

function draw() {
    background(100);
    lights();

    if (isPanLeft) {
        myCamera.pan(-1);
    }
    if (isPanRight) {
        myCamera.pan(1);
    }
    if (isTiltUp) {
        myCamera.tilt(1);
    }
    if (isTiltDown) {
        myCamera.tilt(-1);
    }

    sphere(40);
    translate(-100, 0, 0);
    box(30, 80, 50);
    translate(200, 0, -250);
    cone();
}

各ボタンを押し下げつづけると、カメラがそれぞれのボタン名の動作を実行します。

pan()

パンは、カメラビュー(カメラからの眺め、レンダリング結果)を左右に回転させる。
シンタックス
pan(angle)
パラメータ
angle 数値: 現在のangleMode単位でカメラを回転させる量。0より大きい場合、反時計回り(左)に回転する

tilt()

ティルトは、カメラビュー(カメラからの眺め、レンダリング結果)を上下に回転させる。
シンタックス
tilt(angle)
パラメータ
angle 現在のangleMode単位でカメラを回転させる量。0より大きい場合、反時計回り(上)に回転する

周回カメラ

カメラのlookAt()メソッドを使うと、3D世界のある位置をカメラに見させつづけることができます。またカメラのsetPosition()メソッドを使うと、カメラの位置が設定できます(lookAt()もsetPosition()も、前出のcamera()メソッドを扱いやすくした簡易版と言えます)。

カメラの位置を連続して変化させつつ、対象をカメラでとらえ続けると、いわゆるロックオンができます。ロックオンは、戦闘機ゲームなどで敵機を視界にとらえたときに有効にすると目標がずっと追尾できる機能です。

また動かない対象を見つづけ、その周りを旋回する周回カメラも作成できます。なお以降のコードでは、カメラが移動、回転していることが分かりやすいように、Blenderなどの3DCGツールで作成した3Dモデルをp5.jsに読み込んで使っています。モデルの読み込みについては次の「p5.js WebGL入門 6 3Dモデル」で見ていきます。

let myCamera, monkey;
let tex;
let angle = 0;
let isOrbitCamera = false;
let px = 0,
    pz = 0;

function preload() {
    // サルのモデルとテクスチャ画像をロード
    monkey = loadModel('assets/monkey.obj');
    tex = loadImage('assets/grass.png');
}

function setup() {
    createCanvas(400, 300, WEBGL);
    angleMode(DEGREES);
    noStroke();
    debugMode();
    // カメラを作成
    myCamera = createCamera();

    // [Monky]ボタンの押し下げでカメラをサルに向ける => サルのあごあたりがセンターに来る
    const lookMonkeyButton = setButton('Monkey', {
        x: 10,
        y: 310
    });
    lookMonkeyButton.mousePressed(() => {
        isOrbitCamera = false;
        myCamera.lookAt(-100, -25, 0);
    });
    // [Monky]ボタンの押し下げでカメラを白い球体に向ける => 球体がセンターに来る
    lookMonkeyButton.mouseReleased(() => {
        myCamera.lookAt(0, 0, 0);
    });

    // [Orbit]ボタンの押し下げで、周回カメラをオンにする
    const orbitAroundMonkeyButton = setButton('Orbit', {
        x: 110,
        y: 310
    });
    orbitAroundMonkeyButton.mousePressed(() => {
        isOrbitCamera = true;
    });

}

function setButton(label, pos) {
    const button = createButton(label);
    button.size(80, 30);
    button.position(pos.x, pos.y);
    return button;
}

function draw() {
    background(100);
    // 周回カメラ
    if (isOrbitCamera) {
        // カメラをサルに向けつづける(ロックオン)
        myCamera.lookAt(-100, -25, 0);
        // 円運動するためのサインとコサイン値
        const sinValue = sin(radians(angle));
        const cosValue = cos(radians(angle));
        // x値
        const x = -100 + cosValue * 150;
        // z値
        const z = 0 + sinValue * 150;
        // カメラ位置を設定
        myCamera.setPosition(x, -150, z);
    }

    // ライティング
    ambientLight(200);
    pointLight(255, 255, 255, -100, -100, 0);
    // 3D世界のセンターに球体を配置
    sphere(5);
    // 草原の地面
    push()
        // 平面はデフォルトで直立するので寝かす
    rotateX(90);
    // 草のテクスチャ
    texture(tex);
    // 大きな平面
    plane(1000, 1000);
    pop();

    // サル
    push();
    // センターからずらすため左に移動
    // サル全体を少し上に移動(あごが草に隠れるので)
    translate(-100, -25, 0);
    // サルのモデルは小さいので適当なサイズに拡大
    scale(40);
    // 少し傾けてあごを上げる
    rotateX(30);
    // サルのモデルはそのままでは逆さなので上下を逆にする
    rotateZ(180);
    // 明るい茶色のテクスチャ
    ambientMaterial(153, 51, 0);
    // サルのモデルを描画
    model(monkey);
    pop();

    angle += 40;
}

最初画面センターには白い小さな球体があります。[Monkey]ボタンを押し下げると、サルが画面中央に来、放すと球体がセンターに来ます。また[Orbit]ボタンをクリックすると、サルと地面が回っているように見えます。これは、サルの周りをカメラが旋回しているためです。

周回カメラは次のようなものです。次のビデオは、Blenderでカメラをサルにロックオンさせ、サル周りを回らせたときの様子です。

[Monky]ボタンの押し下げで、myCamera.lookAt(-100, -25, 0)が実行されます。(-100, -25, 0)は、draw()内で実行しているサルの移動先(translate(-100, -25, 0))です。これによりカメラは点(-100, -25, 0)を見、サルがキャンバスのセンターに来ます。

[Monky]ボタンを放すと、myCamera.lookAt(0, 0, 0)が実行されるので、カメラは3D世界の原点の方を向きます。これにより、(0,0,0)にある白い球体がキャンバスのセンターに描画されます。

[Orbit]ボタンをクリックすると、変数isOrbitCameraがtrueになり、draw()内のmyCamera.lookAt(-100, -25, 0)や、myCamera.setPosition(x, -150, z)が実行されます。すると、毎フレーム、カメラはサルの方を見て、自分の位置を(x, -150, z)に設定するので、画面には、サルを中心にサルと地面が回転する結果が描画されます。

次のコードは、円状に動かしたいとき非常に役立つ円運動のコードです。sin()とcos()で円周上の座標が得られるので、ここではそれをxとzに割り当てています。これにより、(-100,0)を中心とする半径150の円周上の座標が得られます。円運動については「7_6:円運動 p5.js JavaScript」で述べています。

const sinValue = sin(radians(angle));
const cosValue = cos(radians(angle));
const x = -100 + cosValue * 150;
const z = 0 + sinValue * 150;

lookAt()とsetPosition()は、前出のcamera()を、特定機能で分けたメソッドだと考えられます。周回カメラに使っているmyCamera.lookAt(-100, -25, 0)とmyCamera.setPosition(x, -150, z)は、次のcamera()と同じです。

myCamera.camera(x, -150, z, -100, -25, 0, 0, 1, 0);
lookAt()

ワールド空間での位置を見るように、カメラの向きを変更する。
シンタックス
lookAt(x, y, z)
パラメータ
x 数値: ワールド空間での点のx位置
y 数値: ワールド空間での点のy位置
z 数値: ワールド空間での点のz位置

setPosition()

現在のカメラの向きを維持しつつ、ワールド空間でのカメラ位置を設定する
シンタックス
setPosition(x, y, z)
パラメータ
x 数値: ワールド空間での点のx位置
y 数値: ワールド空間での点のy位置
z 数値: ワールド空間での点のz位置

追跡カメラ

カメラの移動はまた、move()メソッドでも行えます。move()はsetPosition()と似ていますが、位置ではなく移動量を取ります。また、setPosition()はワールド空間の座標を指定するのに対し、move()はそのときのカメラの状態を基準にした左右、上下、手前/奥方向における移動量を指定します。

次のコードでは、回転するサルが向こう向きに移動します。カメラはサルをロックオンしており、キーボードの上下左右矢印キーで移動できます。さながら、突然現れた謎のサルを哨戒する偵察機から見た眺めのようです、

let myCamera, monkey;
let tex;
let angle = 0;
const speed = 1;
// 画面変化のオンオフを切り替える変数
let isRunning = false
    // カメラの移動量
let xSpeed = 0,
    zSpeed = 0;

function preload() {
    // サルのモデルとテクスチャ画像をロード
    monkey = loadModel('assets/monkey.obj');
    tex = loadImage('assets/opengl.png');
}

function setup() {
    createCanvas(400, 300, WEBGL);
    angleMode(DEGREES);
    noStroke();
    debugMode();
    // カメラを作成
    myCamera = createCamera();
    // カメラ位置を少し高く、サルより手前に設定
    myCamera.setPosition(0, -50, -350);
    // カメラはサルの初期位置を見る
    myCamera.lookAt(0, -50, 0);
}

function draw() {
    // 背景はうすい水色
    background(153, 204, 255);
    // 画面変化がオンなら
    if (isRunning) {
        // 適当な確率でカメラを揺らす
        const rnd = random();
        if (rnd < 0.03) {
            // myCamera.lookAt(rnd * 2, rnd - 25, angle + rnd);
        }
        else {
            // カメラはサルを見つづける
            myCamera.lookAt(0, -50, angle);
        }
        // カメラの移動
        myCamera.move(xSpeed, 0, zSpeed);
    }
    // ライティング
    ambientLight(100);
    directionalLight(255, 255, 255, 0, 1, 1);
    // 地面
    push();
    // テクスチャの端まで移動
    translate(0, 0, 1200)
        // 平面はデフォルトで直立するので寝かす
    rotateX(90);
    rotateZ(90);
    texture(tex);
    // かなり大きな平面
    plane(3000, 1000);
    pop();

    // サル
    push();
    // サルの移動
    translate(0, -60, angle);
    // サルのモデルは小さいので適当なサイズに拡大
    scale(40);
    // サルのモデルはそのままでは逆さなので上下を逆にする
    rotateZ(180);
    rotateY(angle * 6);
    // 明るい茶色のテクスチャ
    ambientMaterial(153, 51, 0);
    // サルのモデルを描画
    model(monkey);
    pop();

    if (isRunning) {
        angle += speed;
    }
}

// ページのクリックで画面変化のオン/オフを切り替える
function mousePressed() {
    isRunning = !isRunning;
}

// 上下左右キーでカメラを移動
function keyPressed() {
    if (keyCode === UP_ARROW) {
        zSpeed = -2
    }
    if (keyCode === DOWN_ARROW) {
        zSpeed = 2
    }
    if (keyCode === LEFT_ARROW) {
        xSpeed = -2
    }
    if (keyCode === RIGHT_ARROW) {
        xSpeed = 2
    }
    return false;
}

function keyReleased() {
    xSpeed = 0;
    zSpeed = 0;
}

ページのクリックでカメラ移動のオン/オフを切り替えることができます。

move()

カメラを、現在のカメラの向きを維持しつつ、ローカル軸に沿って移動する。
シンタックス
move(x, y, z)
パラ
x 数値: カメラの左右軸(ローカルのx軸)に沿って移動する量
y 数値: カメラの上下軸(ローカルのy軸)に沿って移動する量
z 数値: カメラの前後軸(ローカルのz軸)に沿って移動する量

透視投影と平行投影

p5.js WEBGLの世界はx,y,zの座標を持つ3Dの世界ですが、これをコンピュータのモニタに映すときにはモニタの2D平面にマッピングすることになります。これを投影(プロジェクション)と言い、透視投影(パースペクティブ プロジェクション、perspective projection)と平行投影(オーソグラフィック プロジェクション, orthographic projection)はその計算方法です。

下図はBlenderのカメラ設定を使った透視投影と平行投影の比較です。透視投影では、肉眼で見たときと同じように、遠くの物は小さく、近くの物が大きく描かれます。平行投影ではそうでなく、同じ大きさの物体は、遠くにある物も近くにある物も同じ大きさで描かれます。

例をもう1つ。下図は、3Dゲーム開発でよく使用されるUnityで作成された3Dシーンを、透視投影と平行投影で描いた図です。左はリアルな城に見えますし、右はゲームに出て来る城のように見えます。

透視投影

透視投影は下図左に示す要素を使って計算されます。近い平面と遠い平面にはさまれたピラミッド状の形状は視錐台(しすいだい、frustum)と呼ばれ、ここに含まれるジオメトリが描画されます。

p5.jeで透視投影に使用されるp5.Camera.perspective()メソッドはパラメータとして、視野角、アスペクト比、近い平面までの距離、遠い平面までの距離を取り、デフォルトで次の引数が与えられます。

perspective(PI/3.0, width/height, eyeZ/10.0, eyeZ*10.0)
* eyeZは(height / 2.0) / tan(degrees(PI) * 60.0 / 360.0) = 約260

キャンバスが400 x 300の場合で計算すると、
PI/3.0 = 180度/3 = 60度
width/height = 400/300 = 1.33333…
eyeZ/10.0 = 26
eyeZ*10.0 = 2600 なので、60度の視野角で見たときの、カメラから26から2600の間にあるジオメトリが、400 x 300のキャンバスに投影されることになります。

平行投影

平行投影は下図に示す要素を使って計算されます。

p5.jsで平行投影に使用されるp5.Camera.ortho()メソッドには視錐台の値を与えます。デフォルトでは次の引数が渡されます。
ortho(-width/2, width/2, -height/2, height/2,0,widthとheightの大きい方)
キャンバスが400 x 300の場合、下図の視錐台が作成されることになります。

次のコードは、ビルに見立てた複数の大きな直方体の近くを赤い車に見立てた小さな直方体が移動する同じ3Dシーンを、透視投影のカメラと平行投影のカメラを切り替えて比較する例です。また透視投影カメラを使って赤い車の後を追うカメラと、高所から赤い車を追うカメラも加えています。

let perspectiveCamera, orthoCamera;
let dollyCamera, highCamera;
let currentCamera;
let carX = -50;
let radio;

function setup() {
    createCanvas(400, 300, WEBGL);
    angleMode(DEGREES);
    noStroke();
    debugMode();

    // 透視投影カメラ
    perspectiveCamera = createCamera();
    perspectiveCamera.setPosition(-50, -50, 250);
    perspectiveCamera.lookAt(0, 0, 0);

    // 平行投影カメラ
    orthoCamera = createCamera();
    orthoCamera.ortho();
    orthoCamera.setPosition(-50, -50, 250);
    orthoCamera.lookAt(0, 0, 0);

    // ドリーカメラ(台車に乗って撮影)
    dollyCamera = createCamera();
    dollyCamera.setPosition(carX - 200, 35, 50);
    dollyCamera.lookAt(carX, 35, 50);

    // ハイカメラ(高所から撮影)
    highCamera = createCamera();
    highCamera.setPosition(0, -400, 150);
    highCamera.lookAt(carX, 35, 50);

    // 最初は透視投影カメラ
    currentCamera = perspectiveCamera;
    setCamera(currentCamera);

    // ラジオボタン
    radio = createRadio();
    radio.option('perspective');
    radio.option('ortho');
    radio.option('dolly');
    radio.option('high');
    radio.style('width', '300px');
    radio.value('perspective');
    radio.position(10, 280);
    // カメラを変える
    radio.changed(() => {
        const val = radio.value();
        if (val === 'ortho') {
            currentCamera = orthoCamera;
            setCamera(orthoCamera);
        }
        else if (val === 'perspective') {
            currentCamera = perspectiveCamera;
            setCamera(perspectiveCamera);
        }
        else if (val === 'dolly') {
            currentCamera = dollyCamera;
            setCamera(dollyCamera);
        }
        else if (val === 'high') {
            currentCamera = highCamera;
            setCamera(highCamera);
        }
    })
}

function draw() {
    background(200);
    // 現在のカメラがドリーカメラなら
    if (currentCamera === dollyCamera) {
        // 赤い車の後を追う
        dollyCamera.setPosition(carX - 200, -20, 50);
        dollyCamera.lookAt(carX, 35, 50);
        // ハイカメラなら
    }
    else if (currentCamera === highCamera) {
        // 高所から赤い車を追う
        highCamera.lookAt(carX, 35, 50)
    }

    // ライティング
    ambientLight(100);
    directionalLight(255, 255, 255, 0, 1, -1);
    ambientMaterial(255);

    // ビルに模した直方体
    translate(100, -40, 0)
    box(50, 80, 30);
    translate(-70, 0, 0)
    box(50, 80, 30);
    translate(-70, 0, 0)
    box(50, 80, 30);
    translate(-70, 0, 0)
    box(50, 80, 30);

    // 車に模した赤い直方体
    translate(carX, 35, 50);
    ambientMaterial(255, 0, 0);
    box(20, 10, 10);
    // ページがクリックされるまで動かない
    if (isStart) {
        carX += 1;
    }
    if (carX > 400) {
        carX = -50;
    }
}

// ページのクリックでスタート
let isStart = false;

function mousePressed() {
    isStart = true;
}

ページのクリックで赤い車がスタートします。ラジオボタンのクリックでカメラを切り替えます。

カメラの作成にはcreateCamera()関数を使用します。createCamera()関数はデフォルトで透視投影カメラを作成するので、透視投影カメラを使用する場合には、createCamera()関数が返すカメラをそのまま使用できます。平行投影カメラを使用する場合には、createCamera()関数が返したp5.Cameraオブジェクトからortho()メソッドを呼び出します。カメラを作成したら、setPosition()で位置を決め、lookAt()で見る対象を定めます。

// 透視投影カメラ
perspectiveCamera = createCamera();
perspectiveCamera.setPosition(-50, -50, 250);
perspectiveCamera.lookAt(0, 0, 0);

// 平行投影カメラ
orthoCamera = createCamera();
orthoCamera.ortho();
orthoCamera.setPosition(-50, -50, 250);
orthoCamera.lookAt(0, 0, 0);

複数作成したカメラは、グローバル関数のsetCamera()で使用するカメラに切り替えることができます。

setCamera(highCamera);
perspective()

p5.Cameraオブジェクトの透視投影を設定し、グローバル関数のperspective()シンタックスにしたがって、その投影用パラメータを設定する。

perspective()グローバル関数

3Dスケッチのカメラの透視投影を設定する。この投影は、カメラに近い物体は実際のサイズに見え、カメラから遠い物体は小さく見えるという遠近法の奥行きを表す。この関数のパラメータは、垂直方向の視野角とアスペクト比(通常は幅/高さ)、ニアとファーのクリッピング平面を使って、視錐台(その中にある物体をカメラが見ることのできる角錐台)を定義する。

引数なしで呼び出されると、perspective(PI/3.0, width/height, eyeZ/10.0, eyeZ*10.0)がデフォルトで渡される。eyeZは((height/2.0) / tan(PI*60.0/360.0))。

シンタックス
perspective([fovy], [aspect], [near], [far])
パラメータ
fovy 数値: 視野の下から上へのカメラ視錐台の視野角、angleMode単位(オプション)
aspect 数値: カメラ視錐台のアスペクト比(オプション)
near 数値: カメラ視錐台のニア平面の長さ(オプション)
far 数値: カメラ視錐台のファー平面の長さ(オプション)

setCamera()

rendererGLの現在のカメラをp5.Cameraオブジェクトに設定する。複数のカメラの切り替えが可能になる。
シンタックス
setCamera(cam)
パラメータ
cam p5.Camera: p5.Cameraオブジェクト

ortho()

p5.Cameraオブジェクトの平行投影を設定し、グローバル関数のortho()シンタックスにしたがって、その投影用パラメータを設定する。

ortho()グローバル関数

3Dスケッチのカメラの平行投影を設定し、その中にあるオブジェクトが見える、ボックス形状の視錐台を定義する。

この投影では、同じサイズのオブジェクトは、カメラからの遠近に関係なく、すべて同じサイズに見える。この関数のパラメータは、leftとrightが最小と最大のx値、topとbottomが最小と最大のy値、nearとfarが最小と最大のz値を指定する。パラメータが与えられない場合には、デフォルトで、ortho(-width/2, width/2, -height/2, height/2)が使用される。
シンタックス
ortho([left], [right], [bottom], [top], [near], [far])
パラメータ
left 数値: カメラ視錐台の左平面(オプション)
right 数値: カメラ視錐台の右平面(オプション)
bottom 数値: カメラ視錐台のボトム平面(オプション)
top 数値: カメラ視錐台のトップ平面(オプション)
near 数値: カメラ視錐台のニア平面(オプション)
far 数値: カメラ視錐台のファー平面(オプション)

コメントを残す

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

CAPTCHA