THETA S で全天球映像を配信するまで(3) 映像を貼り付けて表示してみました
- WebRTC
- 2016/01/26
こんにちは、インフォコム技術企画室のがねこです。
来る2016年2月16日、17日に行われる、WebRTC Conference Japan 2016でRICOH THETA S を使ったリアルタイム全天球配信のデモを展示する予定です。前回はTHETA S から取得できるライブ映像をWebGLの球面上に貼り付ける準備をしたので、今回は実際に表示してみます。
video要素に表示した映像をWebGLで作ったモデルの表面に貼り付けることができます。録画済の動画ファイルでも、Webカメラの映像でも、WebRTCで通信している離れた場所の映像でも、やり方は同じです。おおざっぱに言うと次の図のような流れになります。

映像の描画
全天球の描画の基本的な処理は、Three.jsの公式サンプルを参考にしました。関連する部分を抜き出すと、次の通りです。
// 動画をテクスチャに変換するための準備
var videElement = document.getElementById('video_element'); // 動画を表示するvideo要素
var videoImage = document.getElementById('canvas_element'); // 画像に変換するために利用するcanvas要素
var videoImageContext = videoImage .getContext('2d');
var videoTexture = new THREE.Texture( videoImage );
// WebGL表示のための準備
var renderer = new THREE.WebGLRenderer({ alpha: true });
var camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 100);
var light = new THREE.AmbientLight(0xffffff);
light.position.set(0, 0, 0).normalize();
scene = new THREE.Scene();
scene.add(light);
// ... 省略 ...
// 描画開始
animate();
function animate() {
requestAnimationFrame( animate );
update();
}
function update() {
// video to image
videoImageContext.drawImage( videElement, 0, 0, videoImage.width, videoImage.height );
if ( videoTexture ) {
videoTexture.needsUpdate = true;
}
renderer.render( scene, camera );
}
※完全なソースは、Three.jsのサンプルや、Githubをご覧ください
マウス操作とタッチ操作
Three.jsの公式サンプルには、マウスのドラッグで視点を変えたり、ホイールでズーム(画角)を制御する部分も含まれています。
同様にスマートフォンやタブレットのタッチ操作に対応するコードは、次のように追加しました。
container = document.getElementById( 'container' );
container.addEventListener( 'touchstart', onDocumentTouchStart, false );
container.addEventListener( 'touchend', onDocumentTouchEnd, false );
container.addEventListener( 'touchcancel', onDocumentTouchEnd, false );
container.addEventListener( 'touchmove', onDocumentTouchMove, false );
var isPinching = false;
var pinchStartDistance = 0;
var FOV_MIN = 20;
var FOV_MAX = 140;
function onDocumentTouchStart( event ) {
event.preventDefault();
isUserInteracting = true;
var touches = event.touches;
var l = touches.length;
if (l == 1) {
var touch = touches[0];
onPointerDownPointerX = touch.clientX;
onPointerDownPointerY = touch.clientY;
onPointerDownLon = lon;
onPointerDownLat = lat;
}
else if (l == 2) {
isPinching = true;
// distance
var touch1 = touches[0];
var touch2 = touches[1];
var dx = touch1.clientX - touch2.clientX;
var dy = touch1.clientY - touch2.clientY;
pinchStartDistance = Math.sqrt(dx*dx + dy*dy);
}
}
function onDocumentTouchEnd( event ) {
isUserInteracting = false;
if (isPinching) {
isPinching = false;
}
}
function onDocumentTouchMove( event ) {
if ( isUserInteracting === true ) {
var touches = event.touches;
var l = touches.length;
if (l == 1) {
var touch = touches[0];
lon = ( onPointerDownPointerX - touch.clientX ) * 0.1 + onPointerDownLon;
lat = ( touch.clientY - onPointerDownPointerY ) * 0.1 + onPointerDownLat;
}
else if (l == 2) {
// distance
var touch1 = touches[0];
var touch2 = touches[1];
var dx = touch1.clientX - touch2.clientX;
var dy = touch1.clientY - touch2.clientY;
var distance = Math.sqrt(dx*dx + dy*dy);
camera.fov -= (distance - pinchStartDistance)*0.02;
if (camera.fov > FOV_MAX) {
camera.fov = FOV_MAX;
}
if (camera.fov < FOV_MIN) {
camera.fov = FOV_MIN;
}
camera.updateProjectionMatrix();
}
}
}
これでPCのブラウザ(Chrome, Firefox)だけでなく、Androidのブラウザ(Chrome, Firefox)でも自由に360度映像を見ることができます。
ライブラリの公開
今回調べた内容を私たちのデモから切り離し、自由にお使いいただける形でgithubに公開しています。ご興味のある方は、ぜひお試しください。
- GitHub
THETA_GL - GitHub pages THETA_GL
sample page
参考
他にもTHETA Sの映像をWebGLで表示する方法が公開されています。一部ではとてもホットな話題になっています。
UVマッピング方式
THETA S のUSBライブストリーミングをブラウザで球面マップする
THETAのDualfisheye動画をThree.jsで表示してみた
シェーダー方式
https://github.com/gtk2k/gtk2k.github.io/tree/master/aframe_theta_s_live_preview
補足
twitterのコメントで、テクスチャーはCanvasを経由せずに、直接video要素から生成できると教えていただきました。次のようにコードが処理をシンプルにすることができました。
// 動画をテクスチャに変換するための準備
var videElement = document.getElementById('video_element'); // 動画を表示するvideo要素
var videoTexture = new THREE.Texture( videElement );
function update() {
// video to image
// videoImageContext.drawImage( videElement, 0, 0, videoImage.width, videoImage.height ); <-- これは不要
if ( videoTexture ) {
videoTexture.needsUpdate = true;
}
renderer.render( scene, camera );
}
今回の用途ではCanvasの2d context で左右反転させているため、Canvasと
drawImage()の利用を継続しています。