【JavaScript】three.jsで360°画像を表示する

360°画像は、従来の静止画とは異なり、全方向を一度に表示することで、ユーザーに没入感のある体験を提供します。観光地のバーチャルツアーや不動産物件の内覧、イベント会場のシミュレーションなど、さまざまな分野で活用されています。特にWebブラウザ上で手軽に表示できることから、スマートフォンやPC上で広く利用されるようになりました。
three.jsは、WebGLをラップして扱いやすくするJavaScriptライブラリで、簡単なコードで3Dシーンやアニメーションを実現できる点が大きな魅力です。本記事では、このthree.jsを使って360°画像を球体に貼り付け、ユーザーがドラッグで画像内を自由に見渡せるシンプルな実装例を紹介します。


<style>
    .body {
        margin: 0;
        padding: 0;
        overflow: hidden;
    }
    canvas { display: block; }
</style>
<div id="body"></div>
<label for="backgroundUpload">背景360°画像選択:</label>
<input type="file" id="backgroundUpload" accept="image/*"><br>
<script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.141.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.141.0/examples/jsm/"
      }
    }
</script>
<script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

   const sceneWidth = 640;
   const sceneHeight = 400;

    // シーン、カメラ、レンダラーの作成
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, sceneWidth / sceneHeight, 0.1, 1000);
    // カメラをシーンの中心に配置
    camera.position.set(0, 0, 0.1);

    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(sceneWidth , sceneHeight);
    const body = document.getElementById("body");
    body.appendChild(renderer.domElement);

    // OrbitControls の追加
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    
    // 360度背景画像の読み込み
    const textureLoader = new THREE.TextureLoader();
    changeBackground('/img/27692043_l.jpg');

    function changeBackground(url) {
        if (url) {
            textureLoader.load(url, (texture) => {
                texture.mapping = THREE.EquirectangularReflectionMapping;
                scene.background = texture;
                scene.environment = texture;
            }, undefined, (error) => {
                console.error("背景画像の読み込みに失敗しました", error);
            });
        }
    }

    const backgroundUploadInput = document.getElementById("backgroundUpload");
    backgroundUploadInput.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (!file) return;
        // FileReader を使って画像ファイルを Data URL に変換
        const reader = new FileReader();
        reader.onload = function(e) {
        const dataURL = e.target.result;
        changeBackground(dataURL)
        };
        reader.readAsDataURL(file);
    });

    // アニメーションループ
    function animate() {
      requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    }
    animate();
</script>

three.jsの基本と導入方法

three.jsは、シーン(Scene)、カメラ(Camera)、レンダラー(Renderer)の3つの基本要素を軸にして動作します。これらの要素が連携して、3Dコンテンツをブラウザ上に描画する仕組みとなります。以下にそれぞれの役割を説明します。

  • Scene(シーン)
    3D空間全体を表現するコンテナです。すべてのオブジェクト、ライト、カメラなどはこのシーンに追加されます。
  • Camera(カメラ)
    シーン内のオブジェクトをどの視点から見るかを決定する役割を持ちます。一般的には遠近感を表現するためのPerspectiveCameraが利用されます。
  • Renderer(レンダラー)
    シーンとカメラの情報をもとに、実際の描画処理(WebGL)を行います。レンダラーはHTMLのCanvas要素に結果を描画します。

three.jsの導入方法

three.jsのライブラリは、公式サイトやCDNから入手できます。今回のサンプルでは、以下のようにCDNから読み込む方法を採用します。

<script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.141.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.141.0/examples/jsm/"
      }
    }
</script>
<script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

CDN経由で読み込むことで、ローカルにファイルをダウンロードする手間がなく、すぐに実装を開始できます。また、three.jsの公式ドキュメントやサンプルコードも豊富に存在するため、今後の学習にも大変役立ちます。

three.jsシーンの初期化と基本構築

three.jsで3Dシーンを構築する基本は、シーン、カメラ、レンダラーの設定です。以下のコードは、基本的なサンプルです。

// シーンの作成
const scene = new THREE.Scene();

// カメラの作成(視野角 75度、アスペクト比はウィンドウサイズに合わせる)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 0.1); // カメラを球体の内側に配置するため、中心から少し離す

// レンダラーの作成とDOMへの追加
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);

// ウィンドウサイズ変更に対応するためのリサイズ処理
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

各パーツの役割

  • Scene
    作成したsceneオブジェクトは、すべての3Dオブジェクトを格納するコンテナとなります。
  • Camera
    PerspectiveCameraを用いて、遠近感のある視点を設定しています。360°画像を内側から見るため、カメラは球体の中心近くに配置されることが一般的です。
  • Renderer
    WebGLRendererがシーン全体をレンダリングし、作成したCanvasをHTML内のコンテナに追加しています。リサイズイベントに対応することで、画面サイズが変更されても正しく描画されるようにしています。

360°画像の読み込みと球体へのマッピング

360°画像を表示するための代表的な手法は、画像を内側にテクスチャとして貼り付けた球体(スフィア)を作成し、その内部にカメラを配置する方法です。これにより、ユーザーは球体の内側から360°画像を眺めることができます。

球体ジオメトリの作成

球体ジオメトリは、three.jsのTHREE.SphereGeometryクラスを使用して作成します。通常の球体は外側にテクスチャが貼られるため、内側にテクスチャを映すために面の向きを反転させる必要があります。これは、マテリアルのsideプロパティをTHREE.BackSideに設定することで実現できます。

以下は、そのためのコード例です。

// 球体ジオメトリの作成(半径50、縦横の分割数を大きめに設定)
const geometry = new THREE.SphereGeometry(50, 60, 40);

// テクスチャの読み込み
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/your/360image.jpg', () => {
  renderer.render(scene, camera);
});

// マテリアルの作成(内側にテクスチャを表示するためにBackSideを指定)
const material = new THREE.MeshBasicMaterial({
  map: texture,
  side: THREE.BackSide
});

// 球体メッシュの作成とシーンへの追加
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

ポイントと注意点

  1. 画像の解像度
    360°画像は広範囲をカバーするため、なるべく高解像度な画像を用いると細部までくっきり表示されます。ただし、画像サイズが大きいと読み込み時間やパフォーマンスに影響するため、適度なバランスが必要です。
  2. 反転表示
    マテリアルのside: THREE.BackSideを設定することで、球体の内側にテクスチャが貼られます。逆に、THREE.FrontSide(デフォルト)だと外側にテクスチャが映るため、内側からは何も見えません。
  3. ジオメトリの分割数
    球体の分割数(セグメント数)を高く設定すると、画像の曲面がより滑らかになりますが、頂点数が増加するためレンダリング負荷も高まります。用途や端末環境に合わせた調整が求められます。

ユーザーインタラクションの実装:OrbitControlsの利用

360°画像をただ表示するだけでなく、ユーザーがドラッグやタッチで自由に視点を変更できるようにするためには、three.jsに同梱されているOrbitControlsを活用します。OrbitControlsは、シーン内のカメラ操作を直感的に実現できる便利なツールです。

OrbitControlsの設定とコード例

まず、HTMLにOrbitControlsのスクリプトを読み込んでおく必要があります。前述のHTMLサンプルでも読み込んでいますが、以下にOrbitControlsを利用したコード例を示します。

// OrbitControlsの初期化
const controls = new THREE.OrbitControls(camera, renderer.domElement);

// 操作の制限(例えば、ズームを禁止する場合)
controls.enableZoom = false;

// マウスやタッチでの回転操作の感度を調整
controls.rotateSpeed = 0.5;

// アニメーションループ内で、コントロールの更新を行う
function animate() {
  requestAnimationFrame(animate);
  controls.update(); // カメラの更新処理
  renderer.render(scene, camera);
}
animate();

設定の詳細

  • enableZoom
    ユーザーがズーム操作を行えないようにするための設定です。360°パノラマの場合、基本的にズームは不要なことが多いため、falseに設定するケースが一般的です。
  • rotateSpeed
    回転速度を調整できます。環境やユーザーの操作感に応じて適切な値に設定してください。
  • updateメソッド
    毎フレームのレンダリングループ内でcontrols.update()を呼び出すことで、ユーザー操作によるカメラの変化が反映されます。

OrbitControlsを利用することで、直感的な操作感が得られるだけでなく、スマートフォンやタブレットといったタッチデバイスにも柔軟に対応可能となります。

コードの詳細解説と各パーツの役割

ここまでで紹介してきたコードは、全体として360°画像表示のための最小限の実装となっていますが、各パーツの役割や動作原理をさらに詳しく解説します。

1. シーンの作成

const scene = new THREE.Scene();
  • シーンの役割
    シーンは、すべてのオブジェクト、ライト、カメラなどが配置される仮想空間です。シーンに追加されたオブジェクトは、レンダラーによってカメラの視点から描画されます。three.jsでは、このシーンオブジェクトが3D描画の中心となります。

2. カメラの設定

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 0.1);
  • PerspectiveCamera
    遠近法をシミュレートするカメラです。視野角(FOV)、アスペクト比、近クリップ面、遠クリップ面を設定することで、どの範囲のオブジェクトが描画されるかを決定します。
  • カメラの配置
    360°画像の場合、カメラは球体の中心付近に配置される必要があります。球体の内側にテクスチャを貼るため、カメラは少しだけ球面の内側にずらす(位置を微調整する)ことが推奨されます。

3. レンダラーとキャンバスの設定

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
  • WebGLRenderer
    three.jsのレンダラーは、WebGLを用いて高速かつ効率的に3Dシーンを描画します。レンダラーは内部でCanvas要素を生成し、HTMLドキュメント内に追加されます。
  • ウィンドウサイズへの対応
    ブラウザのウィンドウサイズに合わせてレンダラーのサイズを変更するため、setSizeメソッドを利用しています。これにより、画面サイズが変わってもシーンが正しく表示されるようになります。

4. 球体ジオメトリとテクスチャのマッピング

const geometry = new THREE.SphereGeometry(50, 60, 40);
const texture = textureLoader.load('path/to/your/360image.jpg');
const material = new THREE.MeshBasicMaterial({
  map: texture,
  side: THREE.BackSide
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
  • SphereGeometry
    半径50、水平分割60、垂直分割40とすることで、十分に滑らかな球体を作成します。分割数は、パフォーマンスと描画品質のバランスを考慮して設定してください。
  • TextureLoader
    three.jsが提供するTextureLoaderを利用して、360°画像を非同期に読み込みます。画像が読み込まれると、レンダリングが開始される仕組みです。
  • MeshBasicMaterial
    ライティングの影響を受けないマテリアルです。360°画像の表示では、照明効果を考慮しないため、シンプルなMeshBasicMaterialで十分です。また、side: THREE.BackSideにより、内側にテクスチャが適用されるようになります。

5. アニメーションループとレンダリング

function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}
animate();
  • requestAnimationFrame
    ブラウザに最適なタイミングでレンダリングを更新するためのメソッドです。これにより、スムーズなアニメーションが実現されます。
  • レンダリングループ
    毎フレームごとにcontrols.update()でカメラの状態を更新し、renderer.render(scene, camera)でシーン全体を描画します。これが、ユーザーの操作やシーン内オブジェクトの変化をリアルタイムに反映させる仕組みです。

6. OrbitControlsの活用

OrbitControlsを用いることで、ユーザーはマウスやタッチ操作で直感的にカメラの視点を変更できます。特に360°画像の場合、ユーザーが自由に見渡せる体験は非常に重要です。操作性の向上のために、各種パラメータの微調整を行い、最適なユーザーエクスペリエンスを実現しましょう。

具体的な実装例と全体コード

ここまでの各パートを統合した、実際に動作する全体コード例を以下に示します。なお、360°画像は適宜用意した画像ファイルに差し替えてください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>three.jsで360°画像を表示する (Module版)</title>
  <style>
    body, html {
      margin: 0;
      padding: 0;
      overflow: hidden;
      width: 100%;
      height: 100%;
      background-color: #000;
    }
    #container {
      width: 100%;
      height: 100%;
    }
  </style>
  <!-- importmapでthree.js関連のモジュールのパスを指定 -->
  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.141.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.141.0/examples/jsm/"
      }
    }
  </script>
</head>
<body>
  <div id="container"></div>
  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

    // シーン、カメラ、レンダラーの初期化
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.set(0, 0, 0.1);
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.getElementById('container').appendChild(renderer.domElement);

    // ウィンドウリサイズ時の処理
    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });

    // 球体ジオメトリとテクスチャの設定
    const geometry = new THREE.SphereGeometry(50, 60, 40);
    const textureLoader = new THREE.TextureLoader();
    // ※360°画像のパスを自身の画像パスに変更してください
    const texture = textureLoader.load('/img/27692043_l.jpg', () => {
      renderer.render(scene, camera);
    });
    const material = new THREE.MeshBasicMaterial({
      map: texture,
      side: THREE.BackSide
    });
    const sphere = new THREE.Mesh(geometry, material);
    scene.add(sphere);

    // OrbitControlsの設定
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableZoom = false;
    controls.rotateSpeed = 0.5;

    // アニメーションループ
    function animate() {
      requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>
</html>

マウス移動による自動回転の実装

ユーザーが操作しなくても、シーンがゆっくりと自動回転することで、よりダイナミックな雰囲気を演出できます。例えば、以下のコードをアニメーションループに追加することで、カメラの位置や球体自体を回転させることが可能です。

function animate() {
  requestAnimationFrame(animate);
  // 自動回転の例:球体をゆっくり回転させる
  sphere.rotation.y += 0.001;
  controls.update();
  renderer.render(scene, camera);
}

応用編:エフェクト追加とパフォーマンス最適化

ここまでで基本的な360°画像表示の実装が完了しましたが、さらに発展させるための応用テクニックやパフォーマンス最適化のポイントについても解説します。

1. VR対応への展開

three.jsはWebVR/WebXRにも対応しており、360°画像をVRヘッドセットで体験できるようにすることも可能です。

  • WebXRの導入
    最新のブラウザではWebXR APIが利用可能です。VRボタンを設置して、ユーザーがVRモードに切り替えられるようにすると、より没入感のある体験が提供できます。
  • 実装例
    VR対応用のサンプルコードや公式ドキュメントを参考に、VRボタンの設置、モード切替、レンダリングループの調整などを行いましょう。

2. エフェクトの追加

360°画像表示にさらなるエフェクトを加えることで、より魅力的な体験を提供できます。以下はその一例です。

  • ポストプロセス処理
    three.jsには、ポストプロセシングエフェクトを追加できる仕組みがあり、色調補正やぼかし、グレア効果など、画像にフィルターをかけることが可能です。
  • ホットスポットの追加
    画像内に特定の位置を示すホットスポットを配置し、クリックやホバーで情報を表示するなどのインタラクションを追加することもできます。これにより、観光地のバーチャルツアーやインタラクティブな解説コンテンツとして応用が広がります。

3. パフォーマンス最適化

360°画像表示は、画像の解像度やジオメトリの分割数、使用するエフェクトにより、レンダリング負荷が高くなる可能性があります。以下のポイントに注意してパフォーマンスの最適化を行いましょう。

  • テクスチャの最適化
    高解像度画像は美しいですが、ファイルサイズが大きくなるため、圧縮や最適なフォーマット(JPEG、WebPなど)の利用を検討してください。
  • ジオメトリの分割数の調整
    描画品質とパフォーマンスのバランスを見極め、必要以上に細かい分割を避けることで、レンダリング負荷を低減できます。
  • 不要なエフェクトの削減
    特にモバイル環境では、ポストプロセスエフェクトや複雑なシェーダーはパフォーマンスに影響するため、必要な部分だけを適用するように工夫しましょう。
  • フレームレートの監視
    デバッグツールやthree.jsの統計情報(Stats.jsなど)を活用して、フレームレートやレンダリング負荷を定期的に確認し、必要に応じた調整を行います。

4. デバッグとトラブルシューティング

開発中に直面するであろう問題点とその対処法についても理解しておくことが重要です。

  • 画像が表示されない場合
    • 画像パスが正しいか確認する
    • CORSの設定に注意する(ローカル環境での読み込み時は特に問題となる場合がある)
  • 操作がスムーズでない場合
    • OrbitControlsの設定値(rotateSpeedやenableDampingなど)を調整する
    • レンダリングループが適切に動作しているか確認する
  • パフォーマンス低下時
    • テクスチャの解像度を下げてテストする
    • 不要なオブジェクトやエフェクトがないか確認する
    • ブラウザの開発者ツールでレンダリング負荷やメモリ使用量を監視する

最後に

three.jsを使った360°画像表示は、比較的シンプルなコードで実現できる一方、奥深い技術や応用の幅が非常に広い分野です。初心者の方でも基本的な仕組みを理解することで、インタラクティブな3Dコンテンツの作成に一歩踏み出すことができるでしょう。また、中級者の方は、ここで紹介した基本技術を基盤として、さらに高度な機能や独自のインタラクションを追加するなど、プロジェクトの幅を広げることができます。
本記事で解説したコードや手法は、あくまで一例に過ぎません。実際の開発環境や目的に合わせて、細部を調整・拡張することで、より最適なソリューションを構築してください。学習過程での試行錯誤や、コミュニティとの情報交換も大変有益です。three.jsの公式フォーラムやGitHubリポジトリ、各種ブログ記事などを参考にしながら、自分だけの360°体験を作り上げていってください。
最後に、この記事が皆さんの開発の一助となれば幸いです。常に最新の情報や技術動向をチェックしながら、楽しくクリエイティブな3Dコンテンツ作りに挑戦していきましょう。

PR広告



楽天ブックスでthree.jsの本を探す