three.jsを使って360度動画をブラウザ上で再生してみる

今日は、three.jsを使って360度動画をブラウザ上で再生してみます。

さらにマウスを使ってカメラの方向を変えてみます。

今回使う360動画はmp4形式ですが、OggとWebM形式でもブラウザで再生できます。

完成イメージはこんな感じになります。

GitHubで完成したコードはこちらになります。

動画に使っている動画をそのまま再生すると下記の画像のように伸びたような状態になっています。

three.jsを使ってこれを球体に変換してあげます。

その球体の中央にカメラを置いて360度に見えるようにすることが今日の目的です。

画像では水平に画像が並んでいますが、実際には球体です。

今回はthree.jsのサンプルコードを参考にしています。

今日の環境

  • npmがインストールされている
  • JavaScriptの基礎が理解できている
  • 前回の記事からthree.jsの基礎を理解できている

コードのクローン

ではgitを使って完成したコードを自分の環境にクローン(コピー)しましょう。Gitの使い方はこの動画でも説明しています。

git clone https://github.com/TraitOtaku/three.js-360video.git
cd three.js-360video
npm i

npm run dev

#結果
  VITE v4.1.1  ready in 412 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

ブラウザでhttp://localhost:5173/にアクセスしても、このままだと何も表示されません。

まずは、自分の360度動画を用意してthree.js-360video直下にvideoフォルダを作成して、その中身sample_video.mp4の名称で動画を入れると動画が再生されます。

もし、ファイルパスや動画名を変えたい場合はindex.htmlの下記の行を変えましょう。

  <source src="video/sample_video.mp4" />

これで動画が再生できるようになりました。

コードの中身を理解

では、コードの中身を説明していきます。main.jsを参照してください。

まずはinit()関数です。

function init() {
  const container = document.getElementById("container");

  camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    1,
    1100
  );

  scene = new THREE.Scene();

  const geometry = new THREE.SphereGeometry(500, 60, 40);
  // invert the geometry on the x-axis so that all of the faces point inward
  geometry.scale(-1, 1, 1);

  const video = document.getElementById("video");
  video.play();

  const texture = new THREE.VideoTexture(video);
  const material = new THREE.MeshBasicMaterial({ map: texture });

  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  container.appendChild(renderer.domElement);

  document.addEventListener("pointerdown", onPointerDown);
  document.addEventListener("pointermove", onPointerMove);
  document.addEventListener("pointerup", onPointerUp);

  window.addEventListener("resize", onWindowResize);
}

前回の記事でも説明したようにthree.jsに必要な要素を作成しinit(イニシャライズ)させます。

必要な要素はカメラ、シーン、物体:geometry(今回は球体 = SphereGeometry)です。

ここで、球体のgeometryにたいして、videoテクスチャを使用して、360度動画を貼り付けています。

rendererはレンダラーといい、シーンをHTMLにレンダーする役目をします。

ここではHTMLの幅いっぱいにレンダーするように指示しています。

最後に下記のイベントリスナーを加えています。

  document.addEventListener("pointerdown", onPointerDown);
  document.addEventListener("pointermove", onPointerMove);
  document.addEventListener("pointerup", onPointerUp);

  window.addEventListener("resize", onWindowResize);

ではこれらを見ていきます。

onWindowResize関数

onWindowResize()関数では、ブラウザの幅が変わるたびに360動画のサイズを変更するように指示する関数です。

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

ポインター操作

マウスを使ってカメラの方向を変えます。いわば視線を変える関数になります。

function onPointerDown(event) {
  isUserInteracting = true;

  onPointerDownPointerX = event.clientX;
  onPointerDownPointerY = event.clientY;

  onPointerDownLon = lon;
  onPointerDownLat = lat;
}

function onPointerMove(event) {
  if (isUserInteracting === true) {
    lon = (onPointerDownPointerX - event.clientX) * 0.1 + onPointerDownLon;
    lat = (onPointerDownPointerY - event.clientY) * 0.1 + onPointerDownLat;
  }
}

function onPointerUp() {
  isUserInteracting = false;
}

動画の再生

動画の再生は2つの関数からできています。

animateでrequestAnimationFrameとupdateを繰り返しています。

requestAnimationFrameはJavScriptのデフォルトで使えるメソッド(Windowsオブジェクト)になります。これでブラウザが動画をレンダーする際に、update()を繰り返すようにしています。

update関数で使う用語を説明します。

lon:Longitude(経度)

lat:Latitude(緯度)

phi:poles(極・極座標)

theta:equator angle(赤道角)

function animate() {
  requestAnimationFrame(animate);
  update();
}

function update() {
  lat = Math.max(-85, Math.min(85, lat));
  phi = THREE.MathUtils.degToRad(90 - lat);
  theta = THREE.MathUtils.degToRad(lon);

  camera.position.x = distance * Math.sin(phi) * Math.cos(theta);
  camera.position.y = distance * Math.cos(phi);
  camera.position.z = distance * Math.sin(phi) * Math.sin(theta);

  camera.lookAt(0, 0, 0);

  renderer.render(scene, camera);
}

これで、マウスを使って目線を変えるときの位置情報を更新するようにしました。