three.js概論#3

2024/6/9

アニメーション

three.jsにおけるアニメーションはストップモーションアニメーションのように、一定の間隔でシーンの状態を変更し、レンダリングすることで実現される。

PCモニターなどのディスプレイは、一般的に1秒間に60回画面を更新することができる。この更新の間隔をフレームレートと呼び、60fpsの場合、1フレームあたり約16.67msで画面を更新することになる。

しかし、ディスプレイによっては60fps以上のフレームレートのものもあり、逆に60fps未満のものもある。

そのため、フレームレートに関係なくアニメーションを実装するためには、requestAnimationFrameを使用することが推奨されている。

requestAnimationFrame

requestAnimationFrameは、引数に指定した関数を次のフレームで実行する。

そして、渡された関数内で再びrequestAnimationFrameを呼び出すことで、関数が各フレームで実行され続ける。

requestAnimationFrameの使用例
const tick = () => {
console.log("tick");
window.requestAnimationFrame(tick);
};
tick();

コンソールにtickと表示され続け、実行されるフレームレートが高いほど、より高い頻度でtickが表示される。

つまり、フレームレートによってアニメーションの速度が変わってしまう。

フレームレートに依存しないアニメーション

アニメーションをフレームレートに依存しないようにするためには、前回のフレームからの経過時間を取得し、それをアニメーションの進行度として扱うことが必要となる。

フレームレートに依存しないアニメーションの実装例
let time = Date.now();
const tick = () => {
// Time
const currentTime = Date.now();
// deltaTime: 前回のフレームからの経過時間
const deltaTime = currentTime - time;
time = currentTime;
// Update objects
mesh.rotation.y += 0.01 * deltaTime;
// ...
};
tick();

Clock

three.jsでは、Clockクラスを使用することで、簡単に経過時間を取得することができる。

Clockの使用例
const clock = new THREE.Clock();
const tick = () => {
// アニメーション開始時からの経過時間
const elapsedTime = clock.getElapsedTime();
// Update objects
camera.position.x = Math.cos(elapsedTime);
camera.position.y = Math.sin(elapsedTime);
camera.lookAt(0, 0, 0);
// ...
};
tick();

カメラ

three.jsには様々なタイプのカメラがある。

Camera

Camera

Cameraクラスはいわゆる抽象クラスで、ほかのカメラに関するクラスはこのクラスを継承している。

StereoCamera

StereoCamera

StereoCameraは、私たちが「視差効果」と呼ぶ、脳を奥行きがあるかのように錯覚させる効果を生み出すために、目を模した2つのカメラを通してシーンをレンダリングするために使用される。結果を見るには、VRヘッドセットや赤青メガネなどの適切な機器が必要。

CubeCamera

CubeCamera

CubeCameraは、各方向(前方、後方、左方、右方、上方、下方)を向いたレンダーを取得し、周囲のレンダーを作成するために使用。反射用の環境マップやシャドウマップの作成にも使える。

PerspectiveCamera

PerspectiveCamera

PerspectiveCameraは、遠近法を使った現実のカメラをシミュレートしたもの。

PerspectiveCameraクラスのインスタンス化

const camera = new THREE.PerspectiveCamera(
75, // fov: 視野角
window.innerWidth / window.innerHeight, // aspect: アスペクト比
1, // near: どれだけ近くのものをレンダリングするか
100, // far: どれだけ遠くのものをレンダリングするか
);

OrthographicCamera

OrthographicCamera

OrthographicCameraは、遠近感のないシーンの平行投影レンダリングを作成するために使用。カメラからの距離に関係なく、画面上の要素は同じサイズになる。

視野角の代わりに、カメラが各方向(左、右、上、下)にどこまで見ることができるかを指定する。そして、PerspectiveCameraで行ったように、nearとfarの値を指定。

// 比率を保つためにアスペクト比を使用する
const aspectRatio = sizes.width / sizes.height;
const camera = new THREE.OrthographicCamera(
-1 * aspectRatio, // left
1 * aspectRatio, // right
1, // top
-1, // bottom
0.1, // near
100, //
);

マウス位置をもとにカメラをコントロール

// Cursor
const cursor = {
x: 0,
y: 0,
};
window.addEventListener("pointermove", (event) => {
// カーソル位置の値が-0.5から+0.5になるように調整
cursor.x = event.clientX / sizes.width - 0.5;
// three.jsのy軸と合わせるために値を反転
cursor.y = -(event.clientY / sizes.height - 0.5);
});
// ...
const tick = () => {
// ...
// y軸をもとに最大1回転するように調整
camera.position.x = Math.sin(cursorX * Math.PI * 2) * 2;
camera.position.z = Math.cos(cursorX * Math.PI * 2) * 2;
camera.position.y = cursorY * 3;
camera.lookAt(mesh.position);
// ...
};

ビルトインコントロール

公式ドキュメントで見れば分かるが、three.jsにはあらかじめ用意されたコントロールがいくつかある。

OribitControls

OribitControls

一番使用する機会が多いであろうコントロール。画面ドラッグやスクロールを使用してカメラをコントロールできるようにする。

OrbitControlsクラスは、標準のTHREE変数からではなく、新たにインポートする必要がある。

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const controls = new OrbitControls(camera, renderer.domElement);

Target

デフォルトではカメラはシーンの中心を見ているが、Targetプロパティで変更することができる。

このプロパティはVector3でx,y,zプロパティを変更できる。

// targetをy軸方向に移動
controls.target.y = 2;
// 自分自身を更新するためにupdate()メソッドを呼び出す
controls.update();

Dumping

ダンピングは、加速度と摩擦の計算を追加することで、アニメーションを滑らかにする。

正しく動作させるには、各フレームでcontrols.update()を呼び出して更新する必要がある。

const controls = new OrbitControls(camera, renderer.domElement);
// Dumpingを有効化
controls.enableDamping = true;
// ...
const tick = () => {
// ...
// Update controls
controls.update();
// ...
};

フルスクリーンとリサイズ処理

ビューポートに合わせる

画面をビューポートいっぱいにするために、rendrer.setSize()に渡す高さと幅に、window.inndrWidthwindow.innerHeightを使用する。

const sizes = {
width: window.innerWidth,
height: window.innerHeight,
};
const renderer = new THREE.WebGLRenderer();
renderer.setSize(sizes.width, sizes.height);

また、デフォルトのスタイルを修正する必要がある。

style.css
* {
margin: 0;
padding: 0;
}
canvas {
position: fixed;
top: 0;
left: 0;
/* ドラッグ&ドロップしたときに青い輪郭が表示されないように以下のプロパティを設定 */
outline: none;
}
/* タッチスクリーンでもスクロールをなくしたい場合は、htmlとbodyの両方にoverflow: hiddenを追加 */
html,
body {
overflow: hidden;
}

リサイズ処理

イベントハンドラを設定してリサイズ処理を行う。

window.addEventListener("resize", () => {
// ウィンドウ幅と高さを更新
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
// カメラのアスペクト比を更新
camera.aspect = sizes.width / sizes.height;
// カメラプロパティを更新したら、以下のメソッドを使用してprojection matrixを更新する必要がある。
camera.updateProjectionMatrix();
// rendererを更新
renderer.setSize(sizes.width, sizes.height);
});

デバイスピクセル比の処理

デバイスピクセル比とは

デバイスピクセル比(DPR)とは、論理的な1ピクセル単位に対して、画面上の何個の物理的なピクセルで表示されるかを示す比率。

例えば、DPRが2の場合、1つの論理的なピクセルは物理的に2x2ピクセル(4ピクセル)で表示される。これにより、より高精細な表示が可能となり、画像やテキストがより滑らかに見える。

代表的な例として、AppleのRetinaディスプレイがあり、DPRは2以上で従来のディスプレイよりも高精度な表示が可能となる。

DPRが2であれば、レンダリングするピクセル数は4倍になり、DPRが3であれば、ピクセル数は9倍になる。

そして、DPRが最も高いのは通常、スマートフォンといったモバイル機器であり、フレームレートの低下を招くことになる。

デバイスピクセル比の処理

画面のピクセル比率を取得するには、window.devicePixelRatioを使用し、レンダラーのピクセル比率を更新するには、renderer.setPixelRatio()を呼び出す。

デバイスピクセル比の処理の実装例
// Math.min()によってピクセル比が2より上にならないように設定
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

フルスクリーン処理

ダブルクリックでフルスクリーンにする実装例
window.addEventListener("dblclick", () => {
// ブラウザがフルスクリーンに対応していない場合はwebkitを使用
const fullscreenElement =
document.fullscreenElement || document.webkitFullscreenElement;
// フルスクリーンかどうか判定
if (!fullscreenElement) {
if (canvas.requestFullscreen) {
canvas.requestFullscreen();
} else if (canvas.webkitRequestFullscreen) {
canvas.webkitRequestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
});
back