--- title: WebVR APIの使い方 slug: Web/API/WebVR_API/Using_the_WebVR_API translation_of: Web/API/WebVR_API/Using_the_WebVR_API ---

{{draft("この WebVR API ドキュメントは現在v1.0の仕様への対応作業の途中です.従って、ここにある情報のいくつかは最新ではありません。この作業について質問がある場合は ~~chrisdavidmills へ連絡をとってください.")}}

WebVR API はウェブ開発者のツールキットへのすばらしい追加機能で、Oculus Rift のようなバーチャルリアリティハードウェアへのアクセスが可能となります。そして出力された動きや向きはウェブアプリの描画更新に変換されます。しかし VR アプリを開発はどのようにやればいいのでしょうか? この記事では、それに関する基礎的な解説を行います。

注記: WebVR は現在実験的な段階にあります(最新の仕様はこちらにあります); 今の段階でもっとも正常に動作するのは Firefox Nightly/Developer Edition で、一部の機能は Google Chrome でも動作します。詳細は Brandon Jonesの  Bringing VR to Chrome を参照してください。

始めるには

WebVRを始めるには,VRハードウェアのマニュアルに従ったセットアップと、WebVR environment setup に示されているコンピュータへの設定が必要になります、スムースな動作には専用GPUが推奨されます。

Firefox Nightly (または Developer Edition) のインストールと合わせて WebVR Enabler Add-on も必要となります。

いちど環境が設定できたら、テストのために私たちの MozVR projects を開いて、[Enter VR] ボタンをクリックすることを試してください。

注記: より深い情報のために,WebVR environment setup をチェックしてください。

注記: モバイルデバイスを HMD として用いるような安価な選択肢もあります。この場合,位置センサは利用できませんので、代わりに deviceorientation API を用いて擬似的な向きデータを使う必要があるかもしれません。

簡単なデモ

WebVR のデモは MozVR team repo や MDN webvr-tests repo にたくさんありますが、この記事では、主にpositionsensorvrdevice について (動作しているデモ) を例に解説します。

これは簡単な 2.5D のデモで,HTML5 Canvas にレンダリングされた Firefox ロゴが右目と左目のビューに表示されるものです.VR HMDでデモを見ているときにキャンバスをクリックすると、デモはフルスクリーンになり、Firefox ロゴに近づけるようになります。あなたが動くと頭の動きに合わせて上下左右や回転してリアルに動きます。

あなたが WebVR のコードがどう動いているかを簡単に確認できるように、デモは意図的にシンプルになるよう保持されています。API は十分シンプルであるため,単純な DOM ベースインターフェイスでも複雑な WebGL シーンでも、好きなアプリに WebVR 制御の移動を簡単に適用できます。

アプリはどう動く?

このセクションでは、アプリを動作させるために必要なコードの変更箇所を通じて、基礎的なレベルで何が必要かを知ることができます。

VRデバイスへのアクセス

最初にコンピュータに接続中のVRハードウェアへのプログラム的な参照を取得します。それには接続中の全 VR デバイスの配列へと解決できるプロミスを返す {{domxref("Navigator.getVRDevices")}} を使います。

返される可能性のあるオブジェクトが2種類あります:

 vrdevice demo で基本的なデバイス情報を表示するための非常に簡単なコードを見ることができます。

本当に欲しいものはデバイスのペアを取得するものです (将来のマルチプレイヤVRゲームでは複数のペアになるかもですが)。WebVR 仕様からもってきた(そして positionsensorvrdevice デモでも使っている)次のコードはかなりよく使うトリックです:

var gHMD, gPositionSensor;

navigator.getVRDevices().then(function(devices) {
  for (var i = 0; i < devices.length; ++i) {
    if (devices[i] instanceof HMDVRDevice) {
      gHMD = devices[i];
      break;
    }
  }

  if (gHMD) {
    for (var i = 0; i < devices.length; ++i) {
      if (devices[i] instanceof PositionSensorVRDevice && devices[i].hardwareUnitId === gHMD.hardwareUnitId) {
        gPositionSensor = devices[i];
        break;
      }
    }
  }
});

最初に見つかった {{domxref("HMDVRDevice")}} のインスタンスを取得し、それを gHMD 変数へ保存します.次に見つかった {{domxref("PositionSensorVRDevice")}} のインスタンスを取得して gPositionSensor 変数に代入していますが,それは先ほど取得した gHMD オブジェクトの {{domxref("VRDevice.hardWareUnitId")}} プロパティが一致するものだけを対象にしています。同一のハードウェアは複数のデバイスとして取得されますが、それらはハードウェアユニットIDを共有しています — これは取得した 2 つのデバイスの参照がマッチングしているかをチェックする方法です。

アプリの初期化

シーンを描画する {{htmlelement("canvas")}} 要素を次のように作成し、配置します:

var myCanvas = document.createElement('canvas');
var ctx = myCanvas.getContext('2d');
var body = document.querySelector('body');
body.appendChild(myCanvas);

次に、新しい image を作成し、アプリの main loop であるdraw()を実行する前に image が ロードされているかをチェックするために {{event("load")}} イベントを使います:

var image = new Image();
image.src = 'firefox.png';
image.onload = draw;

メインループ

draw() は次のように実装します:

function draw() {
  WIDTH = window.innerWidth;
  HEIGHT = window.innerHeight;
  lCtrOffset = WIDTH*0.25;
  rCtrOffset = WIDTH*0.25;

  myCanvas.width = WIDTH;
  myCanvas.height = HEIGHT;

  setView();
  drawImages();
  drawCrosshairs();

  requestAnimationFrame(draw);
}

window の WIDTH と HEIGHT は各フレームでリサンプリングされ,次の設定に使われます:

これによってブラウザウィンドウがリサイズされたとしても、シーンが正しくリサイズされます。

次にメインループの中で3つの関数を実行しています:

これらの詳細は、後ほど解説します。

ループの最後に requestAnimationFrame(draw)  を実行し、draw() ループが連続して呼び出されるようにします。

位置と向き情報の受取り

では  setView() 関数の詳細を見ていきましょう。コードの各部分を順に追って、そこで何をしているかを説明します:

function setView() {
  var posState = gPositionSensor.getState();

位置センサへの参照を使って {{domxref("PositionSensorVRDevice.getState")}} を呼び出します。このメソッドは、あなたが知りたい現在のHMDの状態のすべてを返します —  {{domxref("VRPositionState")}} オブジェクトへのアクセスを通じて — 位置、向き、そして速度/ 加速度や角速度 / 角加速度のようなより高度な情報を含んでいます。

  if(posState.hasPosition) {
    posPara.textContent = 'Position: x' + roundToTwo(posState.position.x) + " y"
                                + roundToTwo(posState.position.y) + " z"
                                + roundToTwo(posState.position.z);
    xPos = -posState.position.x * WIDTH * 2;
    yPos = posState.position.y * HEIGHT * 2;
    if(-posState.position.z > 0.01) {
      zPos = -posState.position.z;
    } else {
      zPos = 0.01;
    }
  }

HMDのスイッチがOFFにされたり位置センサを向いていなかったりした場合など、アプリがエラーになったり停止したりしないように、 {{domxref("VRPositionState.hasPosition")}} を使って HMD の正常な位置情報が利用可能かを確認する方法をチェックします。

そして通知を目的として、アプリのUI内のパラグラフへ現在の位置情報を出力します。読みやすくするために、カスタム関数を使って小数点以下 2 桁に丸めています。

最後に {{domxref("VRPositionState.position")}} に格納されている位置情報に関して、xPosyPoszPos 変数に代入します。zPos の値を 0.01 以上にするのに if ... else ブロックが利用されていることに気付くでしょう — このアプリは0以下になると例外を投げていました。

  if(posState.hasOrientation) {
    orientPara.textContent = 'Orientation: x' + roundToTwo(posState.orientation.x) + " y"
                                + roundToTwo(posState.orientation.y) + " z"
                                + roundToTwo(posState.orientation.z);
    xOrient = posState.orientation.x * WIDTH;
    yOrient = -posState.orientation.y * HEIGHT * 2;
    zOrient = posState.orientation.z * 180;

  }

次に同じような処理をして、HMDの向きに応じてシーンの更新処理をします — {{domxref("VRPositionState.hasOrientation")}} を使って有効な向きデータかをチェックして,向きのデータを通知用のUIに表示し、xOrientyOrientzOrient の値を {{domxref("VRPositionState.orientation")}} に格納されている値から設定します.

  timePara.textContent = 'Timestamp: ' + Math.floor(posState.timeStamp);
}

最後に {{domxref("VRPositionState.timeStamp")}} に格納されている現在のタイムスタンプを通知 UI に出力します。この値は位置データが更新済みか、どんな順序で更新が発生したかを判断するのに役立ちます。

シーンの更新

setView() で取得された xPosyPoszPosxOrientyOrientzOrient の値は、drawImages() で行われるシーン病がの更新のための変更値として使用されます。どうやっているかを見ていきますが、左目のビューの描画コードだけをウォークスルーしていきます。右目については、右にオーバーシフトしている以外はほぼ同じです:

function drawImages() {
  ctx.fillStyle = 'white';
  ctx.fillRect(0,0,WIDTH,HEIGHT);

最初に次のフレームが描画される前にシーンをクリアするため、白い {{domxref("CanvasRenderingContext2D.fillRect","fillRect()")}} を描画します。

  ctx.save();
  ctx.beginPath();
  ctx.translate(WIDTH/4,HEIGHT/2);
  ctx.rect(-(WIDTH/4),-(HEIGHT/2),WIDTH/2,HEIGHT);

次に左目のビューを別の画像として扱って右目のビューに影響を与えないコードにするので、 {{domxref("CanvasRenderingContext2D.save","save()")}} でコンテキスト状態を保存します。

そして {{domxref("CanvasRenderingContext2D.beginPath","pathを開始し")}}, {{domxref("CanvasRenderingContext2D.translate","canvasを変換します")}}、これによって原点を左目の中心(全体の1/4幅で半分の高さ)に移動させます。回転を正しく動かすためにもこれは必要です。回転はcanvasの原点が中心となります。そして左目のビュー全体を覆うように {{domxref("CanvasRenderingContext2D.rect","rect()")}} を描画します。

rect() はマイナスの 1/4 幅,マイナスの1/2 高さから描画し始めていることに注意してください。これは原点が既に移動しているためです。

  ctx.clip();

canvas を {{domxref("CanvasRenderingContext2D.clip","clip()")}} します。rect() が描画された直後にこれを呼ぶので、キャンバス上に対して行うことは rect() の内側に制限されrestore() が呼び出されるまですべてのオーバーフローは隠蔽されます(後述)。これは左ビュー全体が右ビューから独立したままであることを保証します。

  ctx.rotate(zOrient * Math.PI / 180);

頭の回転と同じようにシーンを回転させるために、zOrientの値に従った回転が画像に適用します。

  ctx.drawImage(image,-(WIDTH/4)+lCtrOffset-((image.width)/(2*(1/zPos)))+xPos-yOrient,-((image.height)/(2*(1/zPos)))+yPos+xOrient,image.width*zPos,image.height*zPos);

実際に画像を描画しましょう!  この少し厄介なコードを、ここでは引数ごとに分解してみましょう:

  ctx.strokeStyle = "black";
  ctx.stroke();

左目ビューの周囲に黒い {{domxref("CanvasRenderingContext2D.stroke","stroke()")}} を描画します。これはビューの分離をちょっとだけわかりやすくする手助けとなります。

  ctx.restore();

右目ビューの描画の実施に移行するため、キャンバスの復元を {{domxref("CanvasRenderingContext2D.restore","restore()")}} で行います。

  ...
}

注記: ここである種のチートをしていて,2D キャンバスを使って3Dシーンを擬似的に表現しています。学習目的の場合、物事を簡単にすることができます。WEBテクノロジで作成された任意のアプリで、ビューレンダリングを修正するために上述した位置と向きのデータを使うことができます。例えば 3Dpositionorientation デモでは、Three.js を使って作成されたWebGLシーンのビューを制御するために上述の方法と非常によく似たコードを使っています。

注記:  drawCrosshairs() のコード は drawImages()と比較して非常にシンプルですので、もし興味があるなら自分自身で勉強することをおすすめします!

フルスクリーン表示

VRエフェクトはアプリを フルスクリーンモード で実行すると非常に効果的です。ディスプレイのダブルクリックやボタンの押下のような、特定のイベントが発生した時に {{htmlelement("canvas")}} 要素をフルスクリーンにするための一般的な設定を説明します。

シンプルさを保つために、ここではキャンバスのクリック時に fullScreen() 関数を実行します:

myCanvas.addEventListener('click',fullScreen,false);

fullScreen() 関数は、できるだけ互換性を保つために、ブラウザによって異なるキャンバスに実装されている requestFullscreen() メソッドのバージョンをチェックして、見つかった適切な関数を呼び出します:

function fullScreen() {
  if (myCanvas.requestFullscreen) {
    myCanvas.requestFullscreen();
  } else if (myCanvas.msRequestFullscreen) {
    myCanvas.msRequestFullscreen();
  } else if (myCanvas.mozRequestFullScreen) {
    myCanvas.mozRequestFullScreen();
  } else if (myCanvas.webkitRequestFullscreen) {
    myCanvas.webkitRequestFullscreen();
  }
}

FOV とデバイスのキャリブレーション

現在のデモではあまり考えませんでしたが,商用アプリでは,ユーザが持っているVRハードウェアを正しく動作させるためにユーザキャリブレーションをする必要があるでしょう.WebVR API はそれを手助けする多くの機能があります。

HMDの位置と姿勢をリセットするために {{domxref("PositionSensorVRDevice.resetSensor")}} メソッドを利用できます。実行すると、現在のヘッドセットの位置/向きが 0 にセットされます。実行前に,ヘッドセットが検知可能な位置にあることを保証する必要があります。positionsensorvrdevice demo*** では、[Reset Sensor] ボタンでそれを実行することができます:

<button>Reset Sensor</button>
document.querySelector('button').onclick = function() {
  gPositionSensor.resetSensor();
}

他にもヘッドセットの視野角 (FOV) を、シーン内で上,右,下,左方向に見える範囲がどの程度かキャリブレーションします。それぞれの目のパラメータを別々に返す {{domxref("HMDVRDevice.getEyeParameters")}} メソッドを呼ぶと、両目それぞれの情報を個別に受け取ることができます。なお左目用パラメータで 1 回、右目用で 1 回の計 2 回の呼出しが必要です。それぞれの目用に {{domxref("VREyeParameters")}} オブジェクトを返します。

一例として、 {{domxref("VREyeParameters.currentFieldOfView")}} を用いて片目分の現在のFOV を受け取れます。これは次の 4 つのプロパティを持つ {{domxref("VRFieldOfView")}} オブジェクトを返します:

FOVは眼を頂点としたピラミッド形になっています.

あなたのアプリに適切なFOVをユーザが持っているかをチェックし,もしそうでないなら  {{domxref("HMDVRDevice.setFieldOfView")}} メソッドを使って新しいFOVを設定します.これを扱う簡単な関数は次のような感じです:

function setCustomFOV(up,right,down,left) {
  var testFOV = new VRFieldOfView(up,right,down,left);

  gHMD.setFieldOfView(testFOV,testFOV,0.01,10000.0);
}

この関数は引数として4つの角度を受け取り、VRFieldOfView() コンストラクタを用いて新しい {{domxref("VRFieldOfView")}} オブジェクトを作成します。これを setFieldOfView() の最初の2つの引数(左目と右目のFOV)として渡します。第 3、4 引数は,FOV のオブジェクト可視領域を定義する眼からの最短、最大距離を示す zNear と zFar です.