Google Data Arts チームから、WebVR がもたらす可能性を探求するために協力してくれようと Moniker と私に話しかけたときは、とてもワクワクしました。私は何年にもわたって彼らのチームが達成する仕事を観察してきましたが、彼らのプロジェクトはいつも私の心に響きます。このコラボレーションにより Dance Tonite が誕生しました。これは LCD サウンドシステムとそのファンによる、変化し続ける VR ダンス体験です。その方法を 紹介します。
概要
Google は、WebVR を使用して一連のプロトタイプを開発することから始めました。WebVR は、ブラウザでウェブサイトにアクセスして VR の世界を楽しめるオープン スタンダードです。目標は、お使いのデバイスに関係なく、誰もが簡単に VR 体験を楽しめるようにすることです。
Google はこの点を念頭に置いています。Google の Daydream View、Cardboard、Samsung の Gear VR といったスマートフォンで動作する VR ヘッドセットから、仮想環境内の物理的な動きを反映する HTC VIVE や Oculus Rift などのルームスケール システムまで、私たちが思いついたあらゆる VR が動作するはずです。おそらく最も重要なのは、VR デバイスを持っていない人でも使える何かを作ることがウェブの思想だったということです。
1. DIY モーション キャプチャ
私たちはユーザーをクリエイティブに関わりたいと考え、VR で参加や自己表現ができる可能性について調査しました。私たちは VR でいかにきめ細やかに動き回ったり、再現性の高さに驚いたりしました。こうして 1 つのアイデアが生まれました。ユーザーに何かを見たり作ったりしてもらう代わりに、 その動きを記録してみませんか?
私たちは、VR ゴーグルとコントローラの位置を踊っているときの様子を記録するプロトタイプを作成しました。記録されている位置を抽象的な図形に置き換えて、 その結果に驚きました。結果はとても人間らしく、 個性が引き出されていました。WebVR を使えば自宅で安価にモーション キャプチャを行えることはすぐにわかりました。
WebVR では、デベロッパーは VRPose オブジェクトを介してユーザーの頭の位置と向きにアクセスできます。この値は、コードが新しいフレームを正しい視点からレンダリングできるように、VR ハードウェアによってフレームごとに更新されます。WebVR による GamePad API では、GamepadPose オブジェクトを介してユーザー コントローラの位置/向きにアクセスすることもできます。これらの位置と向きの値はすべてフレームごとに格納されるため、ユーザーの動きの「記録」が作成されます。
2. ミニマリズムと衣装
現在のルームスケール VR 機器では、頭と両手の 3 つの身体の点を追跡できます。Dance Tonite では この 3 ポイントの宇宙空間での移動においてそのために、デザインの美しさを可能な限り最小限にして、動きに集中できるようにしました。人々の頭脳を活かすというアイデアが気に入りました。
スウェーデンの心理学者 Gunnar Johansson 氏の仕事を紹介するこの動画は、可能な限りストリングを取り除くことを検討する際に参照した例の 1 つです。フローティングしている白いドットが、動いているときに被写体として即座に認識できることを表しています。
マルガレーテ ヘイスティングスが 1970 年に演奏した、オスカー シュレンマーのトライアディック バレエを 1970 年に再上演した映像は、色付きの部屋と幾何学的な衣装に着想を得たものです。
Schlemmer が抽象的な幾何学模様を着用したのは、ダンサーの動きを人形やマリオネットの動きに制限するためでしたが、Dance Tonite はそれとは逆の目標でした。
最終的に、形状の選択は、回転によって伝送する情報の量に基づいて決定しました。球体は、どのように回転させても同じように見えますが、円錐は実際には見ている方向を指しており、前面と背面では見え方が異なります。
3. 移動用ループペダル
録画された大勢の人々が一緒に踊ったり動いたりする様子を見せたいと考えていました。VR デバイスは十分に集まらないため、その場で対応することは現実的ではありません。それでも、集団が動きを通じて反応し合うことが目的です。1964 年に制作された動画「Canon」で Norman McClaren が再帰的に演奏した動画を 心に留めました。
McClaren のパフォーマンスでは、ループごとに相互に作用し始める一連の高度なコレオグラフィが特徴です。音楽のループペダルのように、ミュージシャンがさまざまな生演奏を重ねてジャムで演奏するようなものです。そこで、ユーザーが自由にゆったりしたバージョンを演奏できる環境を作りたいと考えました。
4. コネクティング ルーム
他の音楽と同様に、LCD サウンドシステムのトラックは、正確なタイミングを計って作成されます。私たちのプロジェクトで取り上げたトラック Tonite は、長さがちょうど 8 秒の measure です。私たちはトラックを 8 秒間ループするごとに、ユーザーにパフォーマンスを行ってもらいたいと考えていました。これらの小道のリズムは変化しませんが、音楽コンテンツは変化します。曲が進むにつれて、さまざまな楽器やボーカルの瞬間があり、パフォーマーはさまざまな方法で反応できます。これらの測定はそれぞれ部屋として表現され、その中で人々がそれぞれに適したパフォーマンスを発揮できます。
パフォーマンスの最適化: フレームをドロップしない
単一のコードベース上で動作するマルチプラットフォームの VR 体験を、デバイスやプラットフォームごとに最適なパフォーマンスで実現することは、簡単なことではありません。
VR ではフレームレートが動きに追い付いていないことが、最も不愉快な体験です。顔を向けても、目で見る映像と内耳が感じる動きが一致しないと、お腹がすっきりします。このため、大きなフレームレートの遅延を防ぐ必要がありました。以下に実装した最適化を示します。
1. インスタンス化されたバッファ ジオメトリ
プロジェクト全体で少数の 3D オブジェクトのみを使用するため、インスタンス化されたバッファ ジオメトリを使用することで、パフォーマンスを大幅に向上させることができました。基本的に、オブジェクトを GPU に 1 回アップロードし、1 回の描画呼び出しでそのオブジェクトの「インスタンス」を必要なだけ描画できます。Dance Tonite には 3 種類のオブジェクト(コーン、円柱、穴のある部屋)しかありませんが、数百個のオブジェクトのコピーが存在する可能性があります。インスタンスのバッファ ジオメトリは ThreeJS の一部ですが、THREE.InstanceMesh
を実装する Dusan Bosnjak 氏の試験運用版および進行中のフォークを使用したため、インスタンス化されたバッファ ジオメトリの操作がはるかに簡単になりました。
2. ガベージ コレクタの回避
他の多くのスクリプト言語と同様に、JavaScript は、割り当てられたオブジェクトのうち、もう使用されていないオブジェクトを見つけることで、自動的にメモリを解放します。このプロセスはガベージ コレクションと呼ばれます。
デベロッパーがそのタイミングを制御することはできません。ガベージ コレクタがいつか来てガベージ コレクションを空にし始めると、処理に時間がかかるためフレーム落ちになる可能性があります。
この問題を解決するには、オブジェクトをリサイクルして、ゴミをできるだけ少なくします。計算のたびに新しいベクター オブジェクトを作成するのではなく、スクラッチ オブジェクトを再利用用としてマークしました。Google はこれらの参照をスコープ外に移動することで保持しているため、削除の対象にはなりませんでした。
たとえば、ユーザーの頭と手の位置行列を、各フレームを格納する位置/回転値の配列に変換するコードは次のようになります。SERIALIZE_POSITION
、SERIALIZE_ROTATION
、SERIALIZE_SCALE
を再利用することで、関数が呼び出されるたびに新しいオブジェクトを作成した場合に発生するメモリ割り当てとガベージ コレクションを回避できます。
const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
return SERIALIZE_POSITION.toArray()
.concat(SERIALIZE_ROTATION.toArray())
.map(compressNumber);
};
3. モーションのシリアル化とプログレッシブ再生
VR でユーザーの動きをキャプチャするには、ヘッドセットとコントローラの位置と回転をシリアル化し、そのデータをサーバーにアップロードする必要がありました。私たちはまず、各フレームの完全な変換行列の取得から始めました。この方法はうまく機能しましたが、それぞれ 90 フレーム/秒で 16 の数値に 3 つの位置を掛けた値であるため、ファイルが非常に大きくなり、データのアップロードとダウンロード中に長時間待機することになります。変換行列から位置データと回転データのみを抽出することで、これらの値を 16 から 7 に減らすことができました。
ウェブサイトにアクセスしたユーザーは、リンク先の内容が正確にわからないことが多いため、ビジュアル コンテンツをすばやく表示する必要があります。そうしないと、ユーザーは数秒で離れてしまいます。
そのため、できる限り早くプロジェクトを再生できるようにしたいと考えました。当初は、移動データの読み込み形式として JSON を使用していました。問題は、JSON ファイルを解析する前に完全な JSON ファイルを読み込む必要があることです。あまり進歩的ではありません。
Dance Tonite のようなプロジェクトを可能な限り高いフレームレートで表示し続けるために、ブラウザで JavaScript による計算にかかる時間をフレームごとにわずかに抑えます。時間がかかりすぎると、アニメーションが途切れ始めます。当初は、このような巨大な JSON ファイルがブラウザでデコードされるため、途切れが発生しました。
NDJSON(改行区切り JSON)に基づいた、便利なストリーミング データ形式を見つけました。ここでのコツは、一連の有効な JSON 文字列を 1 行に 1 つずつ含むファイルを作成することです。これにより、読み込み中にファイルを解析でき、完全に読み込まれる前にパフォーマンスを表示できます。
Google の録画の一つを次に示します。
{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...
NDJSON を使用すると、パフォーマンスの個々のフレームのデータ表現を文字列として保持できます。必要な時間に達するまで待ってから位置データにデコードすることで、必要な処理を時間の経過とともに分散できます。
4. 運動の補間
30 ~ 60 件のパフォーマンスを同時に表示したいと考えていたため、データ転送速度をさらに下げる必要がありました。Data Arts チームは、Virtual Arts Sessions プロジェクトにおいて、Tilt Brush を使用して VR で絵を描くアーティストの録画を再生し、同じ問題に取り組みました。この問題は、低いフレームレートでユーザーデータの中間バージョンを作成し、再生中にフレーム間を補間することで解決されました。驚いたことに、15 FPS で実行された補間録画と、元の 90 FPS の録画の違いはほとんどわかりませんでした。
たとえば、?dataRate=
クエリ文字列を使用して、Dance Tonite にさまざまなレートでデータを再生させることができます。これを使用して、記録された動きを 90 フレーム/秒、45 フレーム/秒、または 15 フレーム/秒で比較できます。
位置については、キーフレーム間の時間間隔(比率)に基づいて、前のキーフレームと次のキーフレームの間に線形補間を行います。
const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
x1 + (x2 - x1) * ratio,
y1 + (y2 - y1) * ratio,
z1 + (z2 - z1) * ratio
);
向きについては、キーフレーム間で球面線形補間(slerp)を行います。向きは四元数として保存されます。
const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
getQuaternion(next, performanceIndex, limbIndex),
ratio
);
5. 音楽に動きを同期しています
記録されたアニメーションのどのフレームを再生するかを知るには、音楽の現在の時刻をミリ秒単位で知る必要があります。HTML Audio 要素は、サウンドを順次読み込んで再生するには最適ですが、それが提供する時間プロパティはブラウザのフレーム ループと同期して変化しないことがわかりました。常に少しずれます。ミリ秒単位でも早すぎる場合や、遅すぎる場合もあります。
これは美しいダンスの録画の途切れにつながり、何としても避けたいと考えています。これを改善するため、JavaScript で独自のタイマーを実装しました。これにより、フレーム間の変更時間が、最後のフレームから経過した時間と厳密に同じであることを確認できます。タイマーと音楽の同期が 10 ミリ秒以上遅れると、再度同期します。
6. カーリングと霧
ストーリーには必ず優れた結末が必要です。Google は、最終的に作成したユーザーに対し、意外な結末を示す必要がありました。最後の部屋を出ると 円錐と円柱が連なる静かな景色が見えます「これが終わりなの?」と疑問に思うかもしれません。さらにフィールドに進むと、突然音楽の音色が変化し、コーンとシリンダーのさまざまなグループがダンサーに変化します。大きなパーティーの最中だということに気がつきます。音楽が突然止まると、すべてが地に落ちます。
視聴者にとっては素晴らしい機能のように感じられましたが、解決すべきパフォーマンス上の障害がありました。ルームスケールの VR デバイスとハイエンドのゲーミング機器は、新しいエンディングで必要となる 40 もの珍しいパフォーマンスの中で、完璧なパフォーマンスを発揮しました。しかし、特定のモバイル デバイスのフレームレートは半分になりました。
これに対処するために、Google は「Fog」を導入しました。一定の距離が経過すると、すべてがゆっくりと黒くなります。目に見えない計算や描画を行う必要がないため、表示されていない部屋のパフォーマンスを選別することで、CPU と GPU の両方の作業を節約できます。適切な距離を決めるには、
物を投げかけても対応できるデバイスもあれば、より厳しく扱われているデバイスもあります。スライディング スケールを導入することにしました。1 秒あたりのフレーム数を継続的に測定することで、それに応じてフォグの距離を調整できます。フレームレートがスムーズに実行されている限り、フォグを押し出すことでより多くのレンダリング作業を行います。フレームレートが十分にスムーズでない場合は、フォグを近づけることで、暗い中でのレンダリング パフォーマンスをスキップできます。
// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
frames++;
const time = (performance || Date).now();
if (prevTime == null) prevTime = time;
if (time > prevTime + interval) {
fps = Math.round((frames * 1000) / (time - prevTime));
frames = 0;
prevTime = time;
const lastCullDistance = settings.cullDistance;
// if the fps is lower than 52 reduce the cull distance
if (fps <= 52) {
settings.cullDistance = Math.max(
settings.minCullDistance,
settings.cullDistance - settings.roomDepth
);
}
// if the FPS is higher than 56, increase the cull distance
else if (fps > 56) {
settings.cullDistance = Math.min(
settings.maxCullDistance,
settings.cullDistance + settings.roomDepth
);
}
}
// gradually increase the cull distance to the new setting
cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;
// mask the edge of the cull distance with fog
viewer.fog.near = cullDistance - settings.roomDepth;
viewer.fog.far = cullDistance;
}
欲しいものがきっと見つかる: ウェブ用の VR を構築
マルチプラットフォームの非対称エクスペリエンスを設計、開発するには、デバイスに応じて、各ユーザーのニーズを考慮する必要があります。設計上の決定を行うたびに その影響を確認する必要がありましたどうすれば VR で見ているものが VR なしと同じくらいワクワクすること、またその逆であることはどのように実現するのでしょうか。
1. 黄色のオーブ
したがって、ルームスケールの VR ユーザーはパフォーマンスを実際に行うことになりますが、モバイル VR デバイス(Cardboard、Daydream View、Samsung Gear など)のユーザーはプロジェクトを体験するのでしょうか。このため、環境に新しい要素として黄色の軌道を導入しました。
VR でプロジェクトを見るときは、黄色い球体の視点から見ています。部屋から部屋へ浮かぶと、ダンサーがあなたの存在に反応します。ジェスチャーを交えて体の周りで踊り、後ろでおもしろい動きをして、 ぶつからないのですぐに飛び出します。黄色のオーブが常に注目されています。
これは、パフォーマンスの録画中に、黄色のオーブが音楽に合わせて部屋の中央を通過してループするためです。オーブの位置から、実行者に自分の時間やループの残り時間を知ることができます。パフォーマンス向上に 自然に焦点を絞ることができます
2. 別の視点
特に VR が YouTube の最大の視聴者になる可能性が高いため、VR のないユーザーを除外したくありませんでした。Google は、疑似 VR 体験を作るのではなく、画面ベースのデバイスに独自のエクスペリエンスを実現したいと考えました。アイソメトリックな視点から パフォーマンスを上から見せることを思いつきましたその視点はコンピュータ ゲームには豊かな歴史があります。1982 年のスペース シューティング ゲーム「Zaxxon」で初めて使用されました。VR ユーザーは VR の領域外にいますが、アイソメトリックの観点から見たことで、アクションを神々のような視点で捉えることができます。モデルを少しスケールアップし ドールハウスのような見た目をしています
3. シャドウ: 完成まで見せる
アイソメトリックの観点から、奥行きがわかりにくいユーザーがいることが判明しました。そのため、Zaxxon は飛行物体の下に動的な影を投影した歴史上初のコンピュータ ゲームでもあります。
実は、3D で影を作る作業は難しいものなのです。特にスマートフォンなどの細いデバイスでは その傾向が強くなります当初は候補から外す難しい決断をする必要がありましたが、Three.js の作成者と経験豊富なデモハッカーの Mr doob にアドバイスを求めたところ、彼は「偽物」という新しいアイデアを思いつきました。
フローティング オブジェクトごとにライトを隠す方法を計算してさまざまな形状の影を落とすのではなく、それぞれのオブジェクトの下に同じ円形のぼかしテクスチャ画像を描画します。そもそもビジュアルは現実を模倣しているわけではないので、わずかな調整でかなり簡単に画像を変えられることがわかりました。オブジェクトが地面に近づくと、テクスチャが暗くなり、小さくなります。上に移動するとテクスチャはより透明で大きくなります。
作成には、ソフト白から黒へのグラデーション(アルファ透明度なし)でこのテクスチャを使用しました。マテリアルは透明に設定し、減算ブレンディングを使用します。これにより、両者が重なったときにきれいに調和できます。
function createShadow() {
const texture = new THREE.TextureLoader().load(shadowTextureUrl);
const material = new THREE.MeshLambertMaterial({
map: texture,
transparent: true,
side: THREE.BackSide,
depthWrite: false,
blending: THREE.SubtractiveBlending,
});
const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
const plane = new THREE.Mesh(geometry, material);
return plane;
}
4. その場所
VR を使用していなくても、パフォーマーの頭部をクリックすることで、ダンサーの視点から映像を視聴できます。ここからは 細かいことが分かりますダンスをうまく進めるために、すぐにお互いに目を向けます。オーブが部屋に入ると 緊張して方向を見ているのが見えます視聴者としてこうした動きに影響を与えることはできませんが、驚くほど没入感を表現できます。ここでも、マウスで操作できる無意識の VR バージョンをユーザーに提示するよりも、この方法を優先して表示しました。
5. 録音の共有
パフォーマーが 20 層に及ぶリアクションを複雑な振り付けで録音した動画を制作すれば、自分がどれだけ誇らしい気持ちになるかがわかります。友だちに見せたいと思うユーザーも多いでしょうしかし静止画だけでは 伝わりませんむしろ、ユーザーが自分のパフォーマンスの動画を共有できるようにしたいと考えていました。GIF にしないのはなぜでしょうか?アニメーションはフラット シェードで、このフォーマットの限られたカラーパレットに最適です。
そこで、ブラウザ内でアニメーション GIF をエンコードできる JavaScript ライブラリである GIF.js を採用しました。バックグラウンドで個別のプロセスとして実行できるウェブワーカーにフレームのエンコードをオフロードします。これにより、連携して動作する複数のプロセッサを利用できます。
残念ながら、アニメーションに必要なフレーム数のために、エンコード プロセスがまだ遅すぎます。GIF では、限定されたカラーパレットを使用して小さなファイルを作成できます。その結果、各ピクセルに最も近い色を見つけることにほとんどの時間が費やされていることがわかりました。小さなショートカットでハックすることで、このプロセスを 10 倍最適化できました。ピクセルの色が最後のピクセルと同じ場合は、以前と同じパレットの色を使用します。
これで、エンコードは高速になりましたが、生成された GIF ファイルのサイズは大きすぎます。GIF 形式では、破棄方法を定義することで、各フレームを最後のフレームの上にどのように表示するかを指定できます。ファイルのサイズを小さくするには、フレームごとに各ピクセルを更新するのではなく、変更されたピクセルのみを更新します。エンコード プロセスを再度遅くしましたが、ファイルサイズは大幅に減少しました。
6. 堅実: Google Cloud と Firebase
「ユーザー作成コンテンツ」サイトのバックエンドは複雑で脆弱であることが多く、Google Cloud と Firebase のおかげで、シンプルかつ堅牢なシステムを作り出しました。パフォーマーが新しいダンスをシステムにアップロードすると、Firebase Authentication によって匿名で認証されます。ユーザーには、Cloud Storage for Firebase を使用して、録画内容を一時的なスペースにアップロードする権限が付与されます。アップロードが完了すると、クライアント マシンは Firebase トークンを使用して Cloud Functions for Firebase HTTP トリガーを呼び出します。これによりサーバー プロセスがトリガーされ、送信内容の検証、データベース レコードの作成、Google Cloud Storage の公開ディレクトリに移動します。
公開コンテンツはすべて、Cloud Storage バケット内の一連のフラット ファイルに保存されます。つまり、世界中のデータにすばやくアクセスでき、高いトラフィックの負荷がデータの可用性に影響を与えることを心配する必要はありません。
Firebase Realtime Database と Cloud Functions のエンドポイントを使用して、シンプルなモデレーション/キュレーション ツールを構築しました。このツールにより、VR で新しく投稿された動画を確認し、どのデバイスからでも新しいプレイリストを公開できます。
7. Service Worker
Service Worker は、ウェブサイト アセットのキャッシュ管理に役立つ最近のイノベーションです。今回の場合、Service Worker はリピーターに対してコンテンツを高速で読み込み、さらにはサイトをオフラインで動作させることもできます。サイト訪問者の多くがさまざまな品質のモバイル接続を利用するため、これらは重要な機能です。
面倒な作業のほとんどを自動的に処理する便利な webpack プラグインのおかげで、Service Worker をプロジェクトに簡単に追加できます。以下の構成では、すべての静的ファイルを自動的にキャッシュに保存する Service Worker が生成されます。プレイリストは常に更新されるため、利用可能な最新のプレイリスト ファイルがネットワークから pull されます。記録用の JSON ファイルはすべて、変更されることがないため、可能であればキャッシュから取得する必要があります。
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
new SWPrecacheWebpackPlugin({
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js',
minify: true,
navigateFallback: 'index.html',
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
runtimeCaching: [{
urlPattern: /playlist\.json$/,
handler: 'networkFirst',
}, {
urlPattern: /\/recordings\//,
handler: 'cacheFirst',
options: {
cache: {
maxEntries: 120,
name: 'recordings',
},
},
}],
})
);
現在のところ、このプラグインは音楽ファイルのようなプログレッシブに読み込まれるメディア アセットを扱いません。そのため、このような回避策として、これらのファイルの Cloud Storage Cache-Control
ヘッダーを public, max-age=31536000
に設定し、ブラウザがファイルを最大 1 年間キャッシュに保存できるようにしました。
まとめ
パフォーマーがこの体験を盛り上げ、動きを活かしたクリエイティブな表現のツールとして活用してくださることを楽しみにしています。Google は、すべてのコードのオープンソースをリリースしました。公開については、https://github.com/puckey/dance-tonite をご覧ください。 VR、特に WebVR が始まったばかりの段階で、Google は、この新しいメディアがどのようなクリエイティブで予想外の方向にも転換するのかを楽しみにしています。ダンスしよう。