Hello, afroWorld

afroscriptのafroblog

PC内臓カメラからの映像を、Three.jsで3D空間に表示する

完成イメージ

こんな感じで、PC内臓カメラからの映像が3D空間に描画され、360度グリグリと回すことができます。

f:id:afroscript:20190127190211g:plain
Demo

環境

  • ブラウザはChrome、PCはMacです。
  • Three.jsはr98です。

解説

今回は、DOM要素を3D空間に描写することの延長です。

3D空間にDOM要素を描写するには、CSS3DRendererを使うのですが、ポイントは下記の5つです。

  • シーンとレンダラーは1つずつ(sceneとcssScene、rendererとcssRenderer)用意する
  • 通常のsceneに平面オブジェクト(planeObject)を描写し、cssSceneにはDOMオブジェクト(cssObject;)を 同じサイズで 描写する
  • DOMオブジェクトでビデオタグを描写する
  • DOMオブジェクトのrotationは、平面オブジェクトのrotationと同じにする
  • レンダリング時に、どちらのレンダラーも動かす

ちなみに、前にYoutubeを同様に3D空間に表示したのですが、仕組みは同じです。iframeタグを描写するだけです。

qiita.com

コード全貌

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>3dPcCamera</title>
    <style rel="stylesheet" href="css/index.css"></style>
</head>

<body>
    <script src="js/three.min.js"></script>
    <script src="js/libs/OrbitControls.js"></script>
    <script src="js/libs/CSS3DRenderer.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

main.js

// WebRTC関連の変数
let localStream = null;

// 3D関連の変数
var container;
var camera;

var scene, renderer;
var planeObject;
var cssScene, cssRenderer;
var element;
var cssObject;
var orbitControls;
var initCameraPositinZ = 1000;
var pcWidth = 640;
var pcHeight = 480;

init();
animate();

function init() {

    // 3Dを表示するdivを追加
    container = document.createElement( 'div' );
    document.body.appendChild( container );

    // scene
    scene = new THREE.Scene();
    cssScene = new THREE.Scene();

    // camera
    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
    camera.position.z = initCameraPositinZ;

    // light
    var ambientLight = new THREE.AmbientLight( 0xcccccc, 0.4 );
    scene.add( ambientLight );
    cssScene.add( ambientLight );

    var pointLight = new THREE.PointLight( 0xffffff, 0.8 );
    camera.add( pointLight );
    scene.add( camera );

    // WebGLRenderer
    renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    container.appendChild( renderer.domElement );

    // CSS3DRenderer
    cssRenderer = new THREE.CSS3DRenderer();
    cssRenderer.setSize( window.innerWidth, window.innerHeight );
    cssRenderer.domElement.style.position = 'absolute';
    cssRenderer.domElement.style.top = 0;
    cssRenderer.domElement.style.margin = 0;
    cssRenderer.domElement.style.padding = 0;
    container.appendChild( cssRenderer.domElement );

    // Controls
    orbitControls = new THREE.OrbitControls(camera, cssRenderer.domElement);

    // planeオブジェクトの追加
    var floorGeo = new THREE.PlaneGeometry(pcWidth,pcHeight);
    var floorMesh = new THREE.MeshBasicMaterial({color:0xc0c0c0});
    // var floorMesh = new THREE.MeshBasicMaterial({wireframe:true});
    planeObject = new THREE.Mesh( floorGeo, floorMesh );
    var objePositionX = 0;
    var objePositionY = 0;
    var objePositionZ = 0;
    planeObject.position.set(objePositionX, objePositionY, objePositionZ);
    scene.add( planeObject );

    // cssObjectの追加
    element = document.createElement('video');
    element.id = "local_video";
    element.autoplay = true;
    element.muted = false;
    element.style.width = pcWidth + 'px';
    element.style.height = pcHeight + 'px';
    var cssObject = new THREE.CSS3DObject( element );
    cssObject.position.set(objePositionX, objePositionY, objePositionZ);
    cssObject.rotation = planeObject.rotation;
    cssScene.add( cssObject );

    startVideo(element);

}

function animate() {

    requestAnimationFrame( animate );
    render();

}

function render() {

    
    cssRenderer.render( cssScene, camera )
    renderer.render( scene, camera );

}

// getUserMediaでカメラ、マイクにアクセス
async function startVideo(localVideo) {
    try{
        localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: false});
        playVideo(localVideo,localStream);
    } catch(err){
        console.error('mediaDevice.getUserMedia() error:', err);
    }
}

// Videoの再生を開始する
async function playVideo(element, stream) {
    element.srcObject = stream;
    try {
        await element.play();
    } catch(erro) {
        console.log('error auto play:' + error);
    }
}