THETA S で全天球映像を配信するまで(3) 映像を貼り付けて表示してみました


こんにちは、インフォコム技術企画室のがねこです。

来る2016年2月16日、17日に行われる、WebRTC Conference Japan 2016RICOH THETA S を使ったリアルタイム全天球配信のデモを展示する予定です。前回はTHETA S から取得できるライブ映像をWebGLの球面上に貼り付ける準備をしたので、今回は実際に表示してみます。

video要素に表示した映像をWebGLで作ったモデルの表面に貼り付けることができます。録画済の動画ファイルでも、Webカメラの映像でも、WebRTCで通信している離れた場所の映像でも、やり方は同じです。おおざっぱに言うと次の図のような流れになります。

webgl_render_video.png

映像の描画

全天球の描画の基本的な処理は、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に公開しています。ご興味のある方は、ぜひお試しください。

参考

他にも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()の利用を継続しています。