three.js概論#3
2024/6/9
アニメーション
three.jsにおけるアニメーションはストップモーションアニメーションのように、一定の間隔でシーンの状態を変更し、レンダリングすることで実現される。
PCモニターなどのディスプレイは、一般的に1秒間に60回画面を更新することができる。この更新の間隔をフレームレートと呼び、60fpsの場合、1フレームあたり約16.67msで画面を更新することになる。
しかし、ディスプレイによっては60fps以上のフレームレートのものもあり、逆に60fps未満のものもある。
そのため、フレームレートに関係なくアニメーションを実装するためには、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
クラスを使用することで、簡単に経過時間を取得することができる。
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
CameraCameraクラスはいわゆる抽象クラスで、ほかのカメラに関するクラスはこのクラスを継承している。
StereoCamera
StereoCameraStereoCameraは、私たちが「視差効果」と呼ぶ、脳を奥行きがあるかのように錯覚させる効果を生み出すために、目を模した2つのカメラを通してシーンをレンダリングするために使用される。結果を見るには、VRヘッドセットや赤青メガネなどの適切な機器が必要。
CubeCamera
CubeCameraCubeCameraは、各方向(前方、後方、左方、右方、上方、下方)を向いたレンダーを取得し、周囲のレンダーを作成するために使用。反射用の環境マップやシャドウマップの作成にも使える。
PerspectiveCamera
PerspectiveCameraPerspectiveCameraは、遠近法を使った現実のカメラをシミュレートしたもの。
PerspectiveCameraクラスのインスタンス化
const camera = new THREE.PerspectiveCamera( 75, // fov: 視野角 window.innerWidth / window.innerHeight, // aspect: アスペクト比 1, // near: どれだけ近くのものをレンダリングするか 100, // far: どれだけ遠くのものをレンダリングするか);
OrthographicCamera
OrthographicCameraOrthographicCameraは、遠近感のないシーンの平行投影レンダリングを作成するために使用。カメラからの距離に関係なく、画面上の要素は同じサイズになる。
視野角の代わりに、カメラが各方向(左、右、上、下)にどこまで見ることができるかを指定する。そして、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, //);
マウス位置をもとにカメラをコントロール
// Cursorconst 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.inndrWidth
とwindow.innerHeight
を使用する。
const sizes = { width: window.innerWidth, height: window.innerHeight,};
const renderer = new THREE.WebGLRenderer();renderer.setSize(sizes.width, sizes.height);
また、デフォルトのスタイルを修正する必要がある。
* { 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(); } }});