[2015.06.03 追記]
WebVRのドラフトはGithub上にあるようです。
先日、FirefoxのNightlyビルドでWebVR APIが実装された、というニュースがありました。
Oculusを広めたい自分としては色々と興味がそそられるAPIです。
ちなみに、 Firefox Nightlyビルドで試したところ、Oculusを接続してからFirefoxを起動しないと認識されないようです。 なので、認識されない場合はFirefox自体を再起動することで認識されました。
※ ChromiumのWebVRを実装したビルドもあります。
今回作ったもの
せっかくなので、過去に配布されていたジプシー・デンジャーのモデルデータを使ってみました。
動画はこちら↓
https://www.youtube.com/watch?v=sZvQ_RslvuE&feature=youtu.be
上記デモはGithubに公開しています。(実際の動作デモはこちら)
※ ちなみに海の表現はThree.jsのexampleから拝借しました。VRRendererをそのまま利用すると海の反射がおかしなことになったのでちょっとだけ手を入れています。
Firefox NightlyへのWebVRはAdd-onとして提供されている
最初、VR Deviceを取得しようとしてそのAPI(navigator.getVRDevice
)がなくて焦りました。
が、少し調べてみると、どうやらAdd-onという形で提供されているようです。
(WEB VR LANDS IN FIREFOX NIGHTLYを参考にしました。Add-onはこちらです。(直接DLされます))
さらにAdd-onを追加しただけではダメで、Add-on追加後、Fileメニューから「New Non-e10s Window」を選択して専用のウィンドウを立ち上げる必要があります。
Getting Started
New Non-e10s Windowを立ち上げたらいよいよWebVRが利用できるようになります。
こちらの「Getting started with the WebVR API」に、ごく簡単なデモとそのコードが紹介されていました。
デモではThree.jsを組み合わせて実装されています。
Three.jsについての説明は今回は割愛します。デモで使われているWebVR APIとその表示方法に絞って書こうと思います。
VR Deviceの取得
OculusなどのVR Deviceを取得するには以下のように取得するメソッドを実行します。
if (navigator.getVRDevices) {
navigator.getVRDevices().then(vrDeviceCallback);
}
else {
console.log('WebVR is not supported!');
}
参考にした記事はmozGetVRDevices
もチェックして実行していましたが、現在のNightlyビルドではmoz
のプレフィクスはなくなっていました。
見てもらうと分かりますが、さりげなくPromise
が使われています。
デバイスの取得が非同期で行われるためでしょう。
そしてそのコールバックでデバイス取得の処理を行います。
function vrDeviceCallback(vrdevs) {
for (var i = 0; i < vrdevs.length; ++i) {
if (vrdevs[i] instanceof HMDVRDevice) {
vrHMD = vrdevs[i];
break;
}
}
for (var i = 0; i < vrdevs.length; ++i) {
if (vrdevs[i] instanceof PositionSensorVRDevice &&
vrdevs[i].hardwareUnitId == vrHMD.hardwareUnitId) {
vrHMDSensor = vrdevs[i];
break;
}
}
initScene();
initRenderer();
render();
}
最初のfor
ループがHMDの本体を取得するループです。
次のfor
ループは、HMDのセンサーを取得しています。
本体と同じhardwreUnitId
を持つものを取得しているのが分かると思います。
シーンのセットアップ
デバイスが取得できたら、そのままシーンのセットアップを行います。
といっても、ここは普通にThree.jsでのシーンのセットアップとまったく同じです。
function initScene() {
camera = new THREE.PerspectiveCamera(60, 1280 / 800, 0.001, 10);
camera.position.z = 2;
scene = new THREE.Scene();
var geometry = new THREE.IcosahedronGeometry(1, 1);
var material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
レンダラーのセットアップ
続いてレンダラーのセットアップです。
こちらも基本的にはThree.jsのレンダラーのセットアップそのままです。
が、一点だけ異なるのはVR用のレンダラーを用意することです。
function initRenderer() {
renderCanvas = document.getElementById("render-canvas");
renderer = new THREE.WebGLRenderer({
canvas: renderCanvas,
});
renderer.setClearColor(0x555555);
renderer.setSize(1280, 800, false);
vrrenderer = new THREE.VRRenderer(renderer, vrHMD);
}
レンダリング
セットアップが終わったらレンダリングを行います。
レンダリングはvrrenderer
が担当します。
レンダリング時にカメラの位置と回転を、VR Deviceからの値でupdateすれば、OculusのVR体験が実現できる、というわけです。
function render() {
requestAnimationFrame(render);
mesh.rotation.y += 0.01;
var state = vrHMDSensor.getState();
camera.quaternion.set(state.orientation.x,
state.orientation.y,
state.orientation.z,
state.orientation.w);
vrrenderer.render(scene, camera);
}
歪み補正
ちなみに、Oculusはレンズを使って歪ませて視野を確保していますが、そのために、画面に出力される内容を、Oculusのレンズに合わせて歪み補正をする必要があります。
その補正を担当してくれているのがTHREE.VRRenderer
というわけです。
補正の詳しい計算については、上記のVRRendererの実装を見てもらうと分かります。
というか、ぶっちゃけなにをしているかよく分かりませんw
(ただ、通常のprojection matrixに似た計算を行っているので、Oculusの歪みを補正するSkew的な処理を入れてるのかも?)
HMDから得られるFOVから、色々計算していい感じに補正しているようです。
VRRenderer
デモで使われているThree.jsのプラグインという形で提供されているTHREE.VRRenderer
ですが、コード自体はそれほど多くありません。
大きくやっていることを箇条書すると、
初期化時
- HMDの目線位置を取得
- HMDの左右のカメラの
FieldOfView
を取得
update時
- 渡されたカメラを2つに複製
- 複製したカメラをそれぞれ左半分、右半分のレンダリングに使う
- 複製したカメラの
projectionMatrix
を、歪み補正をしたものに差し替え - カメラの位置を目線位置から算出して再計算
という流れでレンダリングを行っているようです。
これを実行することで、Oculusのコンテンツでよく見る、ふたつに画面分割されたものが表示されます。
HMDのトラッキング
最後に、update時にはHMDの状態をトラッキングし、それをカメラに反映させます。
具体的には以下のコードです。
var state = vrHMDSensor.getState();
camera.quaternion.set(state.orientation.x,
state.orientation.y,
state.orientation.z,
state.orientation.w);
最初に取得したVR Sensorから現在の状態を取得し、カメラに適用します。
フルスクリーンモード
上記までで、OculusからのデータをThree.jsの世界に反映することができました。
最後に実装するのはフルスクリーンモードです。特に、モニタとリンクさせている場合はフルスクリーンにしないと正常に見えません。
window.addEventListener("keypress", function(e) {
if (e.charCode == 'f'.charCodeAt(0)) {
if (renderCanvas.mozRequestFullScreen) {
renderCanvas.mozRequestFullScreen({
vrDisplay: vrHMD
});
} else if (renderCanvas.webkitRequestFullscreen) {
renderCanvas.webkitRequestFullscreen({
vrDisplay: vrHMD,
});
}
}
}, false);
デモではF
キーを押すことでフルスクリーンになるように実装されています。
ここで行っていることは、フルスクリーン用のAPIに対して、{ vrDisplay: vrHMD }
として、vrDisplay
に、デバイス取得で取得したHMDのインスタンスを渡すことです。
これで完成です。