マイクロソフトのHoloLensなんかをみていると、これからはやはりVRとかARみたいな技術がどんどん社会を良く変えていくのだろうと、ワクワクさせられますよね。
…とはいえ、こういった技術はネイティブが主流という感じがします。リアルタイム性の高さ、ハードウェアの性能が求められるゆえに、Webだとどうしてもそういうのは難しいと考えられがちでしょう。私もそう考えていた時代がありました。
ところが、ある日のこと…
林田「なぁ、かわちゃん。NRIでハッカソンするから来てやー」
川田「は?嫌やし、なんでそんなん行かなあかんねん」
林田「そんなん言うなよー」
川田「おれ、ハッカソン苦手やねん。一人で淡々とコード書いてる方が、凄く集中できて楽しいしな」
林田「たのむってやー、モバイルWebでVRするねん!」
川田「わかった、いますぐ行く!」
学生時代の知人の誘いで、なんとなくモバイルWebでVRしに行くことになりました。GoogleもHTML5でVRのサンプルを作っていたし、できなくもないだろうと余裕をぶっかましていたのですが、現地に着くや否や、その企画のヤバさに頭を抱えることになるのです。
リアルタイムな動画をつかってVR!?
NRIハッカソン。このイベント、いわゆる大きめの企業なんかにはよくある「基盤本部」みたいな場所が主催してまして、2日がかりで何かモノを作って、その面白さで競い合うというもの。ん?これってアイデアソンじゃね?という感じもしたのですが、技術的に面白そうなガジェット類がゴロゴロ転がっていたので、これはきっとハッカソンなんだと思います。
にしても、FirefoxOSとかUnityやらOculus Riftやら、よくもまぁここまで集めたものだなぁと。こんなの、社外に転がっている私みたいなエンジニアなんかに、公開してしまっていいものなのかと。
会場内には50〜60名、ギークな人々ばかりかと思いきや、デザイナーや企画屋っぽい人なんかもいたりします。これから2日がかりで何かを発明するわけですが、まだ始まったばかりなのに、既に多くの参加者が徹夜を覚悟しているという状況です。私もちょっと遊びに行くぐらいのつもりで来たのに、いつの間にか、徹夜な空気になっていました。
林田「で、作ろうと思っているのが、ある人の目を、別の人の目に転送する、という装置なんやんかー。」
川田「え、ちょっと何言っているのかわからないんですけど。」
林田「このウェアラブルデバイスから動画を撮影した映像を、遠隔地からリアルタイムでVRできるようにしようと思っててな」
川田「ちょっとまって、・・・それ、ふとんを干す時に使うはさみにしか見えんのやけど?」
林田「そう思うでしょ?けどこれ、200円で作れる、手軽なウェアラブルデバイスやねんて!!100円均一でウェアラブルが作れるとか、マジでアツくない!?」
川田「…え、何言っとん?」
林田「これでな、俺の眼から見えるもんをWebでシェアするんや!!世界が変わるで!」
川田「やべーな、その発言!お前、どう見てもGooglerにしか見えんわ。布団バサミさえ頭に挟んでなければな。」
友人が考える、VRの仕組みはこんな感じ
確かに、これが200円でできたら面白いかも。
1. リアルタイム動画再生をWebで扱う : WebRTC
実は最初、200円の装置なんて案は無くて、某S電気メーカーのカメラを使って動画をリアルタイムにustreamへ送信する案が出ていました。しかしこれが、試してみるとこれがまったくもってダメダメ。いや、ダメだろうなぁとなんとか思いつつも、万が一にも奇跡が起きるかもしれないと期待していたのですが。
林田「これはダメや。リアルタイムって感じの臨場感が無い。もっと他の方法を探さないかんわ」
もはや終わりか、そんな空気が漂う中、思わずポロッと言ってしまったわけです。
川田「・・・これ、WebRTC使うところやで。」
林田「それや!!!!」
こうして、布団バサミとのコラボレーションが決定され、原価わずか200円のVR装置を作るという企画へと変わっていったのです。
いや、仮にWebRTCでやったとしても、モバイルでやるなんてそんなのほぼほぼ無理だろうと思ったわけです。けど、もしかしたら、何か奇跡が起きるかもしれないし、新しいアイデアが生み出されるかもしれない。試してみる価値はあるはず。一か八か、成功するかもしれない。そんなノリでWebRTCに挑んでみたわけですよ。
で、WebRTCを使うには、通信するポイント間で電話番号情報に相当するものを管理し運用しなくてはいけません。これを無料で実現する方法として、NTTコムのSkyWayが挙げられます。チーム一同、藁にもすがる思いでこのサービスに登録したのですが、登録早々、サービスのニュースの欄に書かれた一文に気がついてしまいました。
嫌な予感が…!?
Can I useを調べると、WebRTCのモバイル対応はAndroidなら大丈夫という感じに見えます。電話なんだからRTC対応ははやい!なんてことはやはり無いようです。iOSは下手したら、ずっと対応しないんじゃないだろうか。
そして、実際にAndroid版Chromeで公開されているサンプルを動かそうと試みるも、案の定、動かない。Androidはまだまだ落ち着きのないOSだし、これはもう仕方がないのでしょうね。
Mozilla Japanもこのハッカソンにハード提供していたようで、大量のFirefoxOS端末がころがっていました。うん、Mozilla、あなたならきっとRTCやってくれるはず!マニュフェストファイルがどうのこうの言われて、あんまりWebっぽくない作業が多いことに「おや?」と思いつつもセッティング。
標準ブラウザではやはり動かず。これは無理なのか…。
そんな中、たまたま検証したとあるブラウザがすごい活躍をみせることになりました。
そうです、Mozilla側からも見捨てられがちなあのブラウザ「Android版Firefox」。永遠のベータと噂されたあのブラウザが、ここにきて「モバイル×WebRTC」という、市場で唯一を手にしたのです。下手したらMozilla側からも厄介者されているんじゃないかと思えるほど扱いが雑なブラウザなのに、WebRTCは超安定して動かすという。ということで、プラットフォームはAndroid+Firefoxという、誰もが耳を疑う組み合わせをデモ環境に選びました。
今回、動画は一方通行ですが、音声は双方向でリアルタイム通信するという若干変わった仕様です。サンプルにあったコードを色々改造。また、Callee IDも別の画面から入ってきたタイミングでURL上に貼り付けてしまう形式(location.hashで拾う)にして、昨年12月に某動画サイトが公開したモバイル向け動画サービスみたいな感じの仕様にしてみました。
VRと聞くと、右目と左目で若干異なる動画を再生することで、3Dの臨場感が得られるあの仕組みを想像するかと思います。HTMLの場合、ブロック要素を左右半分に割り当てて、CSSで「overflow:hidden」すればできるわけです。DirectXなんかを使っていると、Viewポートの管理なんかをしなくちゃいけなかったりでとてもとても面倒なのですが、それが簡単にできてしまうのがWebのいいところ。
▼マウスをフレーム内で動かしてみてください
ただ、結構な試行錯誤を繰り返しましたが、「動画の再生」となると、パフォーマンスの関係上無理でした。WebRTCはvideoタグを通じて動画再生をするわけですが、今のハードだと2本同時に再生というのはまだまだリソース不足みたいです。6〜8年前、Youtubeやニコ動を見ていた時代あの頃ぐらいにはハードの性能は追いついていると思い込んでいたのですが、まだそこにも至っていない感じなんですね。
2. モーションデバイスの入力をWebで扱う : Motion Device API
さて、先ほどのふとんバサミみたいなウェアラブルデバイスからWebRTCを通じて送られてきた動画。受信はできたので、これをどのようにしてVRっぽくしていくのか?
「ハコスコ」という装置を通してみることにしました。
先程も言いましたが、WebRTCは動画1つの送信が限界なので、1つの動画だけでなんとか3D空間を見渡すような体験を与えなくてはいけません。それを助けたのがこの「ハコスコ」というツールです。先ほどのWebRTCで送られてきた動画をブラウザ上に映した状態で、ハコスコにセットして使います。WebRTCで送られきた動画が3Dっぽく見えます。
また、送られてきた動画の中を、見渡すようなことをしたい。例えば、ハコスコを覗いている人が右を向けば右の景色が、左を向けば左の景色が見えるようにしたいところ。そこでどうしたのかというと、動画を再生するvideoタグを3〜4倍に拡大し、モーションセンサーの動きに合わせてvideoタグの位置が変わるようにすることで、WebRTCで送られてきた動画の中を見渡すような体験を得られるようにしてみました。
その際、どっちを向いているのかという情報はdevicemotionを使いました。左右、上下、重力の検出による天地安定化と、3次元軸でvideoタグをグリグリと動かすのですが、思ったよりパフォーマンスが高くて驚きです。どんなアルゴリズムを使ったのか具体的に言うと…
window.addEventListener("devicemotion",function(event) { // 横方向の移動 x += event.rotationRate.alpha/15; // 単位(%) // 縦方向の移動 y -= event.rotationRate.beta/8; // 単位(%) // 首を傾ける rotate = event.accelerationIncludingGravity.y*9.0; // 単位(deg) … },false);
ただ、モーション系は生データをそのまま使うと手ブレみたいなのがすごいため、実際にCSSに値を設定するときは、ちょっとした積分の値をクッション代わりに使ってやります。毎フレームごとに、以下のコードを実行することでぬるぬると動いてくれます。この式、3Dゲームなんかで、視点移動をふわっとさせる時なんかにも使われています。
setInterval(function(){ marginLeft = ((x-marginLeft)/5.0)+x; // videoタグのmargin-left marginTop = ((y-marginTop)/5.0)+y; // videoタグのmargin-top transformRotate = ((rotate-transformRotate)/5.0)+transformRotate; // transform : rotate() … },1000/60);
この手の値補正は組み込みなんかに多くて、例えば固定小数点環境下なんかだと値の補正にシフト演算子を使うことを意識して桁のデザインをするのですが、今回はいかんせんJavaScript。あまり計算機のことは考えないで素直に作ってみました。
上記の例、setIntervalを使ってみましたが、これについては使い方次第かと。animationFrameは1000/60秒で応答するし、今回使っているdevicemotionは1000/15秒で応答するものが多いみたいです。ただ、どちらもWebの仕様というわけではなく、デバイスに強く依存するので、用途を考えて使うべきだと考えています。
このあたり、若干、組み込み系や物理の話が多めなので、詳しくはまた今度にでも。
3. VR表現を実装する : CSS Transform
a. html/bodyにoverflow:hiddenを適用する方法
モバイルブラウザ上で、擬似的にフルスクリーンのようなことをする上で最初にぶつかるのは、多くの場合「スクロール」かと思います。モバイルのCSSは結構癖が強いので、ある意味Web標準にはない裏ワザ的手段が求められます。モバイルブラウザでは、CSSでhtml/bodyに対するoverflow:hiddenが有効にならないため、ここでハマったことがある人は多いのではないでしょうか。
解決方法としては、bodyタグ直下にdivでもなんでも良いのでブロック要素を配置し、以下のようなCSSをセット。その配下に要素を配置すると、スクロールバーは基本的には出てこなくなります。
.fullscreenView { position : fixed; width : 100%; height :100%; left : 0%; top : 0%; overflow:hidden; }
余談ですが、モバイルブラウザでもデスクトップみたいな「background-attachment:fixed」みたいな背景配置を実現したいという要望も、この技を応用して解決することができます。
b. Transformにより動画をVRっぽくする
モーションセンサーからのフィードバックは、videoタグのCSSを操作することでVRっぽい感じに加工することにしました。本来ならCanvasなんかに動画の内容を打ち出して加工みたいなことをやるべきなのでしょうが、今回2日でやらなくてはいけないためじかんがありません。そうなると、Microsoft Direct2Dみたいな2Dを超簡単に扱える手段が欲しくなるはずです。
そこで今回使ったのが、CSSのtransform。例えば、スプライトに対する操作をC++(DirectX9.0c)なんかで書いているとこうなりますが…
sprite.begin() sprite.tranformRotateZ(Angle) sprite.Draw() sprite.End()
この、tranformRotateZに相当するものを、CSSのtransformだと…
transform : rotateZ(Angle);
と書けます。凄くシンプルだし、なんとなくGPUにも優しい感じがしますね。
さて、これを先ほどのモーションデバイスの入力を、JavaScriptを介してtransformに設定するなら、以下のような感じになります。
video.style.marginLeft = marginLeft; video.style.marginTop = marginTop; video.style.transform = "rotate("+transformRotate+"deg)"; video.style.WebkitTransform = "rotate("+transformRotate+"deg)";
transformはGPUを扱うプログラマーには似たような仕様のAPIに触れることが多く馴染み深いと思います。それどころか、若干レイヤーが高いためより扱いやすいです。今回のrotateなんかをみていても、360度を一周とするdeg(中学高校の数学で扱う角度の単位)を扱えるものですから、DirectXみたいにわざわざPIとか2とかの数字を使って変換するような必要もなく、とてもとても直感的です。DOMだと、子要素もまとめてスプライトみたいに扱えるのですから、10年前のプログラマーからみたら、まるで魔法のように見えているのではないでしょうか。
ただ、この機能はまだまだ問題があり悩みが多いんです。例えば、transformをアニメーションさせるためにtransition-durationを活用することが多いのですが、Firefoxの場合この機能を使うと、他のアニメーションとの競合時にパフォーマンスが恐ろしいほどに劣化します。IEも現時点での最新版だとバグがあって、今回のように「position:fixed」が指定されたブロック要素内でのtransformはまともに動きません。実は偶然にも1週間前、私はこのバグに気がついてMS Connectに報告していたんです。
こんな感じで、WebRTCだけに限らず、VRに必要なCSS機能もまだ不安定という状況でして、WebのVRはまだまだこれからだ!といった感想を感じた次第でございます。
完成、そして結果は?
サイトデザインでもしっかりと世界観を伝える感じに。そして完成へ。
たくさんの方に、遊んで頂けました。
結果として、優勝することはできませんでしたが、テクニカル賞、Mono賞、Yahoo賞など、色んな賞に選ばれて、懐が潤いました。
特に嬉しかったのがYahoo!賞です。Yahoo!から賞を頂けるなんて、なんて光栄なことだろう。「http://yahoo.co.jp」のWebパフォーマンスはいい感じでして、個人的にも教材代わりとして使わせてもらっているだけに、恐れ多すぎるといった次第です。余談ですが、少し前にYahooが公開していた、阪神淡路大震災記念のWebページ。あれもFlashに見えて、実はCSS Transformが使われているのですよ。
http://hanshinawaji.yahoo.co.jp/
今回、チームのメンバは私以外NRI社員しかいなかったのですが、隣に大学時代に物理学をやっていて物理数学の言葉をわかっている人がいたり、それで不足する物理のアルゴリズムを結構しっかりと作っていたり、またまた、がっつりとハードを作れる人がいたりで、驚くほど人に恵まれました。エンジニアと一緒にモノ作っているなぁという感じがして、超ハッピーな気分になれました。多分みんな、仕事では全く違うことをやっているのだろうけど、それにしてもここまでしっかりとした基礎があるというのが驚きです。ただ、若干エンジニアが多すぎて、マーケターや営業的な視点が不足していたのが今回の敗因かなぁとも思っています。
というわけで、みなさん、お疲れ様でした!布団バサミに世界を変えるのはまだまだ難しそうだろうけど、数年もしたら技術がまた追いつくだろうし、気が向いたらそのうちまた、今回のようにWebRTCとmotiondeviceとCSS Transformだけで、VRやってみようかと思います。