こんにちは。この間スピルバーグ監督の新作「レディ・プレイヤー1」を観に久しぶりに映画館へ行ってきたムラタです。
この映画は仮想現実(VR)のゲームを舞台にした近未来の世界を描いたものでしたが、近未来ということもあって「マトリックス」のような首にプラグぶっさすみたいな方法じゃなく、VRヘッドセットと360°動けまわれる床というシンプルなデバイスだったのがリアルでいかにも将来実現しそう感があってワクワクしましたね。
というか実際に自分の足で動けまわれるVRやVR空間内の触感を再現するデバイスはもうあるようです。
こういったデバイスがもっと進化していって安価な値段で供給されるようになると映画のような世界もそう遠くはないんでしょうね。
そんなこんなで今回は何度目かのVRについてのお話です。
以前「Google VR Viewで360℃コンテンツをサイトに埋め込む」という記事を書きましたが、前回の「Google VR View」はページ内の1コンテンツとして360°写真を埋め込むといった形でしたが、今回使用する「A-Frame」というWebVR用のフレームワークでは1ページ丸々360°空間として処理するため、完全にWebVR特化型のコンテンツとなります。
A-Frameとは
「A-Frame」とはweb上で3Dを描画するための標準仕様「webGL」や、そのwebGLを簡単に扱えるようにするjavascriptライブラリ「three.js」を用いてweb上にVRに特化したコンテンツを作成することができるフレームワークになります。
また構築にあたりjavascriptをゴリゴリに書くのではなく、htmlタグとしてシーンやカメラ、オブジェクトなどの設定ができるため、かなりとっつきやすいです。
例えば
1 2 3 |
<a-scene> <a-sky src="image.jpg" rotation="0 170 0"></a-sky> </a-scene> |
こういった具合です。htmlをコーディングしているような感覚で記述できます。
対応ブラウザ・デバイス
WebVRの仕様を実装しているブラウザ(Firefox、chrome、eddge等)であれば閲覧すること自体には問題ありませんが、基本的にVRゴーグル・ヘッドセットでの閲覧が前提になります。
今現在対応している有名所のヘッドセットは以下の通りです。
詳しくはA-Frameのサイトの「VR Headsets & WebVR Browsers」のページを確認してください。日本語はないですが・・・
実際に使ってみる
aframe.jsの取得
それでは早速使ってみましょう。
まずA-Frameのサイトから必要なjsファイルをDLしてきます。
サイト右側の「GET STARTED」から概要ページにアクセスします。
左メニューから「Installation」ページへ飛び、「Production Version x.x.x」のボタンより「aframe.min.js」をダウンロードします。
もしくはCDNから読み込む場合は
1 |
<script src="https://aframe.io/releases/x.x.x/aframe.min.js"></script> |
とします。
背景の設定
ではまず背景となる360°写真を設定してみましょう。
先ほどのaframe.jsを読み込んだらbody直下に「<a-scene>」というタグを記述します。
基本的にこのa-sceneタグの中に記述を行なっていくことになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="https://aframe.io/releases/0.7.1/aframe.js"></script> <style> *{ padding: 0; margin: 0; } html,body{ width: 100%; height: 100%; background: #fff; } </style> </head> <body> <a-scene> </a-scene> </body> </html> |
これだけでシーン(3D空間)が形成されましたが、何も設定していないのでページを確認してもただ透明な空間ができただけです。
ではまず背景を設定してみましょう。
先ほどの<a-scene>の中に今度は「<a-sky>」と記述して、s-skyの属性としてcolorを指定します。
1 2 3 |
<a-scene> <a-sky color="#24caff"></a-sky> </a-scene> |
すると空間全部が青色に変わったと思います。
a-skyは丸い球体の内側に色や画像をテクスチャとして張り付けるイメージです。
物がない単色の空間だと上下左右もまったく分からないので、次は物体を配置してみましょう。
物体の配置
まずは四角い箱、ボックスを配置してみましょう。
ボックスを配置するには「<a-box>」とします。
先ほどと同じくa-sceneの中に記述します。skyと同じくこちらも色や配置場所などを指定することができます。
まずボックスに色を指定してみます。
1 2 3 4 |
<a-scene> <a-sky color="#24caff"></a-sky> <a-box color="#ccc"></a-box> </a-scene> |
これで灰色の箱が生成されましたが、位置情報を指定していないのでカメラ(自分の立ち位置)の真下に配置されてしまいます。
次に位置を指定してみましょう。位置は「position」で左からX座標、Y座標、Z座標を半角スペース区切りで指定します。
1 2 3 4 |
<a-scene> <a-sky color="#24caff"></a-sky> <a-box position="-1 0.5 -3" color="#ccc"></a-box> </a-scene> |
これで初期位置の前方に箱が見えるようになったはずです。
boxに指定できる属性は他にも沢山ありますが、今回は幅と高さ奥行き、それに伴って見やすい表示位置に変更を加えてみましょう。
幅は「width」、高さは「height」、奥行きは「depth」で指定します。
1 2 3 4 |
<a-scene> <a-sky color="#24caff"></a-sky> <a-box width="1" height="2" depth="50" position="-1 1.5 -27" color="#ccc"></a-box> </a-scene> |
するとこうなります。
めっちゃ奥にギュンって延びましたね。
これだけだと素っ気ないのでコルクっぽい質感のテクスチャを貼ってみましょう。
テクスチャの指定は「src」で直接画像を指定する方法と「<a-aseets>」というタグで要素として登録しておいて、それをIDでテクスチャとして指定する方法があるようです。
恐らくa-assetsとして管理する方法の方が後々分かりやすいのかなあと思います。
その辺はお好みで。
1 2 3 4 5 6 7 |
<a-scene> <a-sky color="#24caff"></a-sky> <a-assets> <img id="texture" src="texture.jpg"> </a-assets> <a-box width="1" height="2" depth="50" position="-1 1.5 -27" src="#texture" repeat="1 1" rotation="0 -2 0" color="#ccc"></a-box> </a-scene> |
a-assetsでテクスチャ画像「texture.jpg」を登録して、IDを指定します。
a-boxのsrc属性に登録したIDを指定することでその画像が張り付けられるということですね。
奥行きの面のテクスチャが延びてしまってますね・・・
指定できる属性のどれかで解消できると思いますが、自分も解決方法が分からなかったのでここでは割愛させてもらいます。
とりあえずここまでのデモを用意したので対応PCブラウザやスマートフォン等で確認してみてください。(スマートフォンで見ることをお勧めします)
ちなみにGoogle Cardboardなどのスマホ用VRデバイスをお持ちの方は画面右下のアイコンを選択した状態で見るとより立体感を得られるようですよ。お試しあれ。
360°写真の配置
今度は現在青くしている背景にTheteなどのカメラで撮影した360°写真を配置してみましょう。
やり方はとても簡単で先ほどのa-skyの属性にsrcを指定して画像ファイルへのパスを通すだけです。
もしくはboxのテクスチャと同じでassetsで登録したIDを指定する方法でもOKです。
boxは邪魔なので今回は削除しておきます。
1 2 3 4 5 6 |
<a-scene> <a-assets> <img src="image.jpg" id="sky"> </a-assets> <a-sky src="#sky"></a-sky> </a-scene> |
360°写真をただ見せたいだけならこれだけで十分ですね、非常に簡単で手軽です。
VR空間内にテキストを配置する
次に空間内にテキストを配置してみましょう。
ただA-Frameはデフォルトではテキストを表示させるような仕組みは存在していません。(自分が見つけ切れていないだけかもしれませんが)
なので有志の方々が作成されたプラグインを使わせてもらって配置することになります。
テキスト表示プラグインは色々あるようですが、日本語(マルチバイト)に対応していないものが多いらしく、今回は「a-frame-html-shader」を使います。
A-Frame HTML Shader
その名の通り2次元であるhtmlとcssで構築されたDOM要素をA-Frame内に貼りつけることができるという代物です。なので正確にはテキストを配置するというよりhtml要素をA-Frame内に配置するということですね。
何はともあれgithubのページがあるのでそこからダウンロードしてきましょう。
解凍したら「/dist/aframe-html-shader.min.js」を読み込ませて、まず適当なhtmlを書いてみましょう。
1 2 3 4 5 6 7 8 |
<p>html要素をA-Frame内に配置する</p> <a-scene> <a-assets> <img src="image.jpg" id="sky"> </a-assets> <a-sky src="#sky" rotation="0 170 0"></a-sky> </a-scene> |
これだけだと単純にVR空間の表示領域の上に普通に2次元として表示されるだけです。
必要な手順として、まずhtml要素をVR空間の表示領域の裏に隠すということと、A-Frame内にhtmlを格納するための入れ物を用意してあげるという2つになります。
htmlを裏に隠す
これは単純にcssで隠してやればいいだけです。
先ほどの<p>を<div>で括って、その<div>に対して下記のようなスタイルを当てます。
1 2 3 |
<div style="width: 100%; height: 100%; position: fixed; left: 0; top: 0; z-index: -1; overflow: hidden"> <p>html要素をA-Frame内に配置する</p> </div> |
これでdivで括ったhtml要素は表に出てこなくなりました。
※「display: none」で非表示にはしないでください。あくまで裏に隠すだけです。
A-Frame内に入れ物を用意する
次にA-Frame内に先ほど隠したhtml要素を格納するための入れ物を用意します。
<a-scene>内に<a-entity>を追加して下記のような属性を指定します。
1 2 3 4 5 6 7 |
<a-scene> <a-assets> <img src="image.jpg" id="sky"> </a-assets> <a-sky src="#sky" rotation="0 170 0"></a-sky> <a-entity geometry="primitive: plane; width: 1" position="-1 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity> </a-scene> |
geometryはオブジェクトの形状を定義することができます。箱状なのか球状なのか板状なのか、それらの幅や高さ奥行きなどもここで定義します。
今回は板状(plane)で幅は1としました。
positionは前項でも触れた通りオブジェクトの位置を指定します。
materialは形状の外見に関する指定ができます。色やテクスチャの指定などですね。今回は「aframe-html-shader」用に「shader: html」と「target: #element」を指定しています。
IDが「element」のhtml要素をテクスチャとして貼りつけるということですかね。
なので先ほどの<p>タグにIDとして「element」を指定しておきます。
1 2 3 |
<div style="width: 100%; height: 100%; position: fixed; left: 0; top: 0; z-index: -1; overflow: hidden"> <p id="element">html要素をA-Frame内に配置する</p> </div> |
さて、これでどうなったでしょうか。
かすれたような何かが板に張り付いているのが分かります。
これは幅や高さの小さいhtml要素を1枚の画像として考えると、何となくイメージがつくかと思いますが、幅や高さが小さい画像を板の1面に合うように引き伸ばして貼りつけているのでこのように何がなんだか分からないような状態になっています。
じゃあどうすればいいかというと、html要素、正確には張り付けている<p>の幅や高さをもっと大きくしてあげればいいのです。
というわけで以下のようなスタイルを指定してみます。
1 |
<p id="element" style="font-size:15em;">html要素をA-Frame内に配置する</p> |
通常のhtmlであればデカすぎるほどのフォントサイズですが、今回はこれくらいでちょうどいいくらいです。
今度はちゃんと見れるようになりましたね。
あとは余白や背景色・背景画像なんかをcssで色々調整してあげましょう。
この辺はオブジェクト自体のサイズによってもだいぶ調整が異なるので地道な調整作業が必要になります。
配置した要素にリンクを設定する
今度は配置したhtml要素に対してリンクを設定してみましょう。
「さっきのhtml要素に<a>を入れりゃいいだけじゃね?」とか思った方もいるかかもしれませんが、ハズレです。
そもそも先ほども書きましたが、出力されたhtml要素を画像としてオブジェクトに張り付けているだけなのでhtml側にリンクを設定したところで何の意味もありません。
それにPCやスマホでVRデバイス無しで見てる場合はオブジェクトをクリックやタップすることでリンクさせることができると安易に想像できますが、GoogleCardboardのようなデバイスを使った場合、手で画面を直接触ることができないですよね。
そういった場合のために「注視点カーソル」と呼ばれる、ユーザーの視点と連動して動くカーソルを用意する必要があります。
そのカーソルがオブジェクトに一定時間留まる(注視する)ことでリンクするという仕組みになります。
注視点カーソルを追加する
早速注視点カーソルを追加してみましょう。
注視点カーソルは「<a-cursol>」で追加できますが、これは「<a-camera>」の子要素として配置する必要があります。
1 2 3 4 5 6 7 8 9 10 |
<a-scene> <a-assets> <img src="image.jpg" id="sky"> </a-assets> <a-camera> <a-cursor></a-cursor> </a-camera> <a-sky src="#sky" rotation="0 170 0"></a-sky> <a-entity geometry="primitive: plane; width: 2.5" position="-1 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity> </a-scene> |
するとこうなります。
小さくて分かりづらいですが、サークル上のアイコンが追加されているのが分かります。この状態でカメラをぐりぐりと動かしてみると、このサークルは常に画面の中央にいると思います。これが注視点サークルです。
オブジェクトホバー時にアニメーションさせる
ユーザビリティを高めるためにこのカーソルがオブジェクトにホバーした際にちょっとしたアニメーションをさせるように手を加えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<a-scene> <a-assets> <img src="image.jpg" id="sky"> </a-assets> <a-camera> <a-cursor color="black" scale="2 2"> <a-animation begin="mouseenter" easing="ease-out" attribute="scale" fill="forwards" from="2 2 2" to="1 1 1" dur="300"> </a-animation> <a-animation begin="mouseenter" attribute="color" fill="forwards" from="black" to="red" dur="0"> </a-animation> <a-animation begin="mouseleave" easing="ease-in" attribute="scale" fill="forwards" from="1 1 1" to="2 2 2" dur="300"> </a-animation> <a-animation begin="mouseleave" attribute="color" fill="forwards" from="red" to="black" dur="0"> </a-animation> </a-cursor> </a-camera> <a-sky src="#sky" rotation="0 170 0"></a-sky> <a-entity geometry="primitive: plane; width: 2.5" position="-2 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity> </a-scene> |
アニメーションさせるにあたって、cursorに「color」と「scale」属性でサイズと色を指定しています。
アニメーションの指定はアニメーションさせたい要素の子要素に<a-animation>として登録していきます。
animationの各属性については下記の通りです。
beginはアニメーションを開始させるイベント名を指定します。
easingはアニメーションスピードの緩急を指定できます。
attributeは変化させる要素を指定します。
fillはアニメーション前後の状態を指定します。(違うかも)
fromはアニメーションの初めの値を指定します。
toはアニメーションの終わりの値を指定します。
durはアニメーションの時間を指定します。
今回はこれらを使って4つのアニメーションを指定しています。
1つ目は要素にサークルが乗った時にサークルの大きさを縮小するアニメーション。
2つ目は同じタイミングでサークルの色を黒から赤に変化させるアニメーション。
3つ目は要素からサークルが外れた時にサークルの大きさを拡大するアニメーション。
4つ目は同じタイミングで色を赤から黒に戻すアニメーション。
最後に肝心のリンクの設定をしていきます。
オブジェクトにリンク先を指定する
まずhtmlを貼りつけたオブジェクトに対して「data-link」として値にリンク先を指定します。
またクラス名「link-object」も付与しておきます。
1 |
<a-entity class="link-object" data-link="https://magnets.jp" geometry="primitive: plane; width: 2.5" position="-2 1.5 -2" material="shader: html; target: #element" rotation="0 -2 0"></a-entity> |
次にjavascriptで以下のように記述します。
クリック(タップ)時の処理をjavascriptで指定する
1 2 3 4 5 6 7 8 9 |
<script> var linkElements = document.getElementsByClassName("link-object"); for (var i = 0; i < linkElements.length; i++) { linkElements[i].addEventListener('click', function() { var link = this.getAttribute('data-link'); location.href = link; }, false); } </script> |
これで「link-object」クラスをもつすべてのオブジェクトに対して、クリック(タップ)した際に「data-link」に指定したリンク先へリンクするようになります。
注視時にもクリック判定をするように指定する
前述したようにVRデバイスを使用した際に画面をタップしなくても特定のオブジェクトを一定時間注視するだけでリンクするように設定します。
これに関しては特に難しいことはありません。
<a-cursor>に対して「fuse=”true”」を指定してやるだけです。
1 |
<a-cursor fuse="true" color="black" scale="2 2"> |
これで注視点カーソルがオブジェクトに対して一定時間注視した場合、クリックイベントを発火するようになりますので、先ほど記述したjavascriptも有効になるという訳です。
また発火するまでの時間を「fuse-timeput」属性を指定することで変更することもできます。デフォルトでは1500です。
1 |
<a-cursor fuse="true" fuse-timeout="3000" color="black" scale="2 2"> |
終わりに
自分もまだまだA-Frameの触りの部分しか触れていないですが、もっと突き詰めたら色々面白いことができそうな予感がします。
それではまた別のVRの記事でお会いしましょう。