Three.jsを使い360度画像をドラッグとジャイロセンサーで視点操作する。

Tatsuya Asami
20 min readDec 3, 2018

--

【所要時間】

約4時間(11月30日、12月1日)

【概要】

【要約・学んだこと】

かなり紆余曲折しながら作ったが、とりあえず触ってみる場合は

→ドキュメントのGetting Startedを一通り読む(そんなに多くない)。
→ブラウザで確認しがら調整する。
OrbitControlsを読み、調整の仕方を学ぶ。

の流れがいいと思った。

まずどのライブラリを使えばいいかを探すのに時間がかかり、ドキュメントのどこを読めば必要な情報が載っているのかわからなかった(ドキュメントをすべて読む時間と根気がなかった)。Three.jsは360度画像のためのライブラリというよりは、3Dオブジェクトの作図などに使うオブジェクトのようである。

本質的な部分が理解できていないと思われるが、とりあえず思い通りに表示できているので、調べたことをまとめる。

そこで、下記のページとサンプルのコードを参考にし、公式ドキュメントからそれぞれのコードを理解するようにした。

お手軽360°パノラマ制作入門! JSでパノラマビューワーを自作しよう — ICS MEDIA

画像はhttps://pixabay.com/ja/photos/360%C2%B0/ からフリー素材をダウンロードした(画像ではなく動画だったり、ダウンロードするまでの登録が大変なサイトが多かったが、こちらは手軽にダウンロードできるのでよかった。)。

元画像はこうなっている。

PC上ではこのように表示されドラッグで視点変更ができ、

パソコン上から見た画面

スマホではこのようにに表示され、ジャイロセンサーで視点変更ができる。

公式サイトからthree.jsをダウンロードし、必要なライブラリをプロダクトにインポートする。それぞれの役割は

  • three.min.js
    こちらが本体のようなもの。three.jsとの違いは今回調べなかったが、おそらくこちらが基本機能のみが使えるものだと思われる。
  • OrbitControls.js
    こちらはドラッグで視点操作をするライブラリ
  • DeviceOrientedControls.js
    こちらはジャイロセンサー(スマホの傾き)で視点操作をするライブラリ

最初にハマりそうな注意点

  • 公式ドキュメントにも書いてあるが、ローカルホストを立ち上げるか、GoogleChromeの設定を変更しないと、ローカル環境で画像が表示されないので注意。Chromeの設定を変えて表示する方法は下記のチュートリアルの最後に書いてある。

最初に作ったプロダクトは、360度画像だけでなく通常のjpgファイルも表示するもので、通常のjpgファイルは表示されていたのに360度画像が表示されていなかったので、すぐに気が付いたが、それがなければハマっていた可能性があった。

今回はなんとなくPHPサーバーを立ち上げてやってみた。

  • これは普通のことだが、ローカル環境と本番環境で画像のurlを変える必要がある場合は、そこにも注意。
  • 調整する必要が出てくる可能性の高いコードの説明
js/main.js(() => {
const clickAdv = document.getElementById('clickAdv');
const message = document.getElementById('message');
const contentsHeight = document.getElementById('adv-image').clientHeight;
const contentsWidth = document.getElementById('adv-image').clientWidth;
let element, scene, camera, renderer, controls;const init = () => {scene = new THREE.Scene();camera = new THREE.PerspectiveCamera(60, 1.80, 1, 1800);
camera.position.set(0, 0, 0);
scene.add(camera);
// 初期の向き、見え方はここで調整
// SphereGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float);
const geometry = new THREE.SphereGeometry(5, 60, 40, -1.58);

geometry.scale(-1, 1, 1);
const texture = new THREE.TextureLoader().load(
// ローカル、デプロイでURLを変更
// "/images/winter.jpg"
"/three360/images/winter.jpg"
);
const material = new THREE.MeshBasicMaterial({ map: texture});sphere = new THREE.Mesh(geometry, material);scene.add(sphere);renderer = new THREE.WebGLRenderer();
// 表示サイズの調整
renderer.setSize(contentsWidth, contentsHeight);
element = renderer.domElement;
document.getElementById("adv-image").appendChild(element);
renderer.render(scene, camera);
// デバイスの判別
let isAndroid = false;
let isIOS = false;
if (navigator.userAgent.indexOf("Android") != -1) {
isAndroid = true;
} else if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
isIOS = true;
}
if (isAndroid || isIOS) {
// android, iosではジャイロセンサーで視点変更する。
window.addEventListener("deviceorientation", setOrientationControls, true);
message.textContent="スマートフォンを傾けて視点操作"
} else {
// その他デバイスではマウスドラッグで視点変更する。
setOrbitControls();
message.textContent="マウスでドラッグして視点操作"
}
render();
}
// ジャイロセンサーで視点変更する
const setOrientationControls = (e) => {
// android, ios以外では無効にする
if (!e.alpha) {
return;
}
controls = new THREE.DeviceOrientationControls(camera, true);
controls.connect();
controls.update();
window.removeEventListener("deviceorientation", setOrientationControls, true);
}
const setOrbitControls = () => {
// android, ios以外のデバイスでマウスドラッグで視点操作する
const htmlelm = document.getElementById("adv-image")
controls = new THREE.OrbitControls(camera, htmlelm);
controls.target.set(
camera.position.x + 0.15,
camera.position.y,
camera.position.z
);
controls.enableDamping = true;// 視点変更(+にすると逆回転になる)
controls.rotateSpeed = -0.07;
// ズーム機能
controls.enableZoom = false;
// 表示する垂直アングル最大値の調整
controls.maxPolarAngle = 2.60;
controls.minPolarAngle = 0.50 ;
}
const render = () => {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
}
window.addEventListener("load", init, false);})();

簡単な使い方として、上記のコードをコピペして、コメントアウトしてる部分を調整すればいいと思う。

  • 表示サイズの調整

今回はクライアントサイドのHTMLのadv-image id要素を取得し、それを縦横サイズとした。contentsWidthとcontentsHeightをピクセル値に変えても問題ない。

const contentsHeight = document.getElementById('adv-image').clientHeight;
const contentsWidth = document.getElementById('adv-image').clientWidth;
renderer = new THREE.WebGLRenderer();
// 表示サイズの調整
renderer.setSize(contentsWidth, contentsHeight);
  • 360度画像の見え方の調整
// 初期の向き、見え方はここで調整
// SphereGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float);
const geometry = new THREE.SphereGeometry(5, 60, 40, -1.58);
geometry.scale(-1, 1, 1);

公式ドキュメントに詳しく書いてある。下記のように調整できる。

SphereGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float)radius — sphere radius. Default is 1.
widthSegments — number of horizontal segments. Minimum value is 3, and the default is 8.
heightSegments — number of vertical segments. Minimum value is 2, and the default is 6.
phiStart — specify horizontal starting angle. Default is 0.
phiLength — specify horizontal sweep angle size. Default is Math.PI * 2.
thetaStart — specify vertical starting angle. Default is 0.
thetaLength — specify vertical sweep angle size. Default is Math.PI.
The geometry is created by sweeping and calculating vertexes around the Y axis (horizontal sweep) and the Z axis (vertical sweep). Thus, incomplete spheres (akin to ‘sphere slices’) can be created through the use of different values of phiStart, phiLength, thetaStart and thetaLength, in order to define the points in which we start (or end) calculating those vertices.
  • ドラッグした際の動きの速さ、垂直アングルの範囲を調整
// 視点変更(+にすると逆回転になる)
controls.rotateSpeed = -0.07;
// ズーム機能
controls.enableZoom = false;
// 表示する垂直アングル最大値の調整
controls.maxPolarAngle = 2.60;
controls.minPolarAngle = 0.50 ;
}

こちらも公式ドキュメントに書いてある。

.rotateSpeed : Float
Speed of rotation. Default is 1.
.maxPolarAngle : FloatHow far you can orbit vertically, upper limit. Range is 0 to Math.PI radians, and default is Math.PI.

数値をマイナスにすることで、ドラッグした時の動きを逆にできる。数値を変えると、スクロールスピードを変えることができる。

今回は最初に作ったプロダクトで垂直のスクロール範囲方向を80度前後にしたかったので、設定した。水平方向のスクロール範囲も調整可能。
何も設定しなければ360度回すことができる。

  • その他コードの説明

プログラミングというよりも図形描写の点で理解が不十分な点が多いが、一度セットしてから書き換えることはなかった。出来上がりを見ながら数値の調整をしたのはこれ以前に書いた部分となる。

const init = () => {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, 1.80, 1, 1800);
camera.position.set(0, 0, 0);
scene.add(camera);

シーンの作成。公式ドキュメントに説明がある。

何かを表示するためにscene, camera, rendererの3つが必要で、ここではsceneとcameraをセットしている。

今回は公式ドキュメントのサンプルと同様、cameraにPerspectiveCamera(遠近カメラ?)をセットしてみたところうまくいったのでそのまま使った。それぞれの引数を変更することでField of View、視野角、遠近感などを調整できる。数値を変えてみて、実際にどう見えるかを確認して調整した。

const material = new THREE.MeshBasicMaterial({ map: texture});sphere = new THREE.Mesh(geometry, material);scene.add(sphere);renderer = new THREE.WebGLRenderer();
// 表示サイズの調整
renderer.setSize(contentsWidth, contentsHeight);
element = renderer.domElement;
document.getElementById(“adv-image”).appendChild(element);
renderer.render(scene, camera);

materialについてはこちらのドキュメントに説明があるが、MeshBasicMaterialは光の影響を受けないマテリアルで、シンプルなもののようだ。

rendererにはWebGLRendererを使った。WebGLRendererのドキュメントに説明がある。WebGLとはWeb Graphics Libraryの略。

どちらも意味がわからない部分が多かったが、グラフィック系のことが書いてあると思われる。

  • デバイスの判別
// デバイスの判別
let isAndroid = false;
let isIOS = false;
if (navigator.userAgent.indexOf("Android") != -1) {
isAndroid = true;
} else if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
isIOS = true;
}
if (isAndroid || isIOS) {
// android, iosではジャイロセンサーで視点変更する。
window.addEventListener("deviceorientation", setOrientationControls, true);
message.textContent="スマートフォンを傾けて視点操作"
} else {
// その他デバイスではマウスドラッグで視点変更する。
setOrbitControls();
message.textContent="マウスでドラッグして視点操作"
}
render();
}

私のデバイスではうまくいったが、文字列を検出することによるブラウザ識別は推奨されないようだ。https://developer.mozilla.org/ja/docs/Web/API/NavigatorID/userAgent

  • ジャイロセンサーの視点変更
// ジャイロセンサーで視点変更する
const setOrientationControls = (e) => {
// android, ios以外では無効にする
if (!e.alpha) {
return;
}
controls = new THREE.DeviceOrientationControls(camera, true);
controls.connect();
controls.update();
window.removeEventListener(“deviceorientation”, setOrientationControls, true);
}

DeviceOrientationControlsに関しては公式ドキュメントもどこにあるかはわからなかったが、特に変更せずにうまくいったので、定型文に近いと考えた。

  • ドラッグによる視点変更
const setOrbitControls = () => {
// android, ios以外のデバイスでマウスドラッグで視点操作する
const htmlelm = document.getElementById("adv-image")
controls = new THREE.OrbitControls(camera, htmlelm);
controls.target.set(
camera.position.x + 0.15,
camera.position.y,
camera.position.z
);

controls.enableDamping = true;
// 視点変更(+にすると逆回転になる)
controls.rotateSpeed = -0.07;
// ズーム機能
controls.enableZoom = false;
// 表示する垂直アングル最大値の調整
controls.maxPolarAngle = 2.60;
controls.minPolarAngle = 0.50 ;
}

ドキュメントに詳しく書いてある。先に解説したようにズーム機能の有無、回転方向、アングルの調整などが可能。こちらに関してはグラフィックがわからなくてもわかる内容が多かった。

  • シーンのレンダリング
const render = () => {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
}

こちらもドキュメントの通り。controls.update()は、カメラをマニュアルでチェンジした場合には必ず必要。

【わからなかったこと】

  • ジャイロセンサーの加速度調整のやり方(調整できない仕様なのかどうかもわからなかった)
  • ドラッグでスクロールした後に手を離すと、クリック動作として捕らえられてしまう。
    最初のプロダクトでは、画像にリンクが貼ってあったので、スクロール後にリンクが立ち上がってしまうのを防げなかった。
    Three.jsの問題というよりは、HTMLやJavaScriptの部分かもしれない。

【感想】

ドキュメントに色々書いてあるが、グラフィックに関する知識がないため、理解できない部分は多かった。また、色々できるライブラリのようだが、全てのドキュメントを読むにはなかなかの量があり厳しい。

触りながらやってみたらうまくいったという状態なので、次回使う際に参考にしようと思った。

--

--

Tatsuya Asami
Tatsuya Asami

Written by Tatsuya Asami

Front end engineer. React, TypeScript, Three.js

No responses yet