Three.js -5

立体視用画像出力

BotsNewというVR装置用を想定し、画面の左右に右目・左目用の画を出すよう改修する。
(通常の立体視と同じ仕組みで、右側に右目用、左側に左目用の画像を出す)
OpenGLでは

  • 片側のViewportの設定
  • 片側のオブジェクト描画
  • もう片側のViewportの設定
  • もう片側のオブジェクト描画

という流れで実現できるが、Three.jsのサンプルを見るとこれらの処理の間にScissorの設定を入れる必要があるようだ。
実際、Scissor設定を省くと指定されたViewportに切り替わるが、他方のViewportにオブジェクトが描画されない(おそらく一方を描画するタイミングで消されている)挙動を示した。
参考:http://threejs.org/examples/#webgl_multiple_views

indexl.html

<!DOCTYPE html>
<html lang="ja">
    <head>
        <title>Three.js My Tutorial 5</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,user-scalable=0" />

        <meta http-equiv="Pragma" content="no-cache" />
        <meta http-equiv="Cache-Control" content="no-cache" />
        <meta http-equiv="Expires" content="0" />

        <script charset="utf-8" src="lib_ext/three.min.js"></script>
        <script charset="utf-8" src="lib_int/tutorial5.js"></script>
    </head>

    <body>
    </body>
</html>

tutorial5.js

var Program = function() {

    var vectorZero = new THREE.Vector3(0, 0, 0);
    var vectorUnitX = new THREE.Vector3(1, 0, 0);
    var vectorUnitY = new THREE.Vector3(0, 1, 0);
    var vectorUnitZ = new THREE.Vector3(0, 0, 1);

    var vectorForward = vectorUnitZ;
    var vectorLeft = vectorUnitX;
    var vectorUp = vectorUnitY;

    var renderer = null;
    var scene = null;
    var camera = null;
    //var radianCameraMovementUnit = Math.PI / 36;
    var lengthCameraMovementUnit = 0.2;

    var fpsUpdate = 30;
    var lockRender = null;

    var queueAction = new Array();
    var CMD_TRANSLATE_CAMERA_LEFT_DELTA = 0x1001;
    var CMD_TRANSLATE_CAMERA_RIGHT_DELTA = 0x1002;
    var CMD_TRANSLATE_CAMERA_FORWARD_DELTA = 0x1004;
    var CMD_TRANSLATE_CAMERA_BACKWARD_DELTA = 0x1008;
    


    var getTime = function() {
        return new Date().getTime();
    };

    

    var sleep = function(msWait) {
        var timeStart = getTime();
        for(var timeNow = getTime(); (timeNow - timeStart) < msWait; timeNow = getTime()) { }
        return;
    };



    var THREE_createAxesMesh = function() {
        var axes = new THREE.Object3D();

        var axisXBody = new THREE.Mesh(
            new THREE.CylinderGeometry(0.01, 0.05, 1, 4),
            new THREE.MeshBasicMaterial({color: 0xff0000})
        );
        axisXBody.translateX(0.5);
        axisXBody.quaternion.setFromAxisAngle(vectorUnitZ, -Math.PI / 2);
        axes.add(axisXBody);

        var axisXHead = new THREE.Mesh(
            new THREE.CylinderGeometry(0.01, 0.05, 0.125, 4),
            new THREE.MeshBasicMaterial({color: 0xff0000})
        );
        axisXHead.translateX(0.9375);
        axisXHead.quaternion.setFromAxisAngle(vectorUnitZ, -Math.PI / 2);
        axes.add(axisXHead);

        var axisYBody = new THREE.Mesh(
            new THREE.CylinderGeometry(0.01, 0.05, 1, 4),
            new THREE.MeshBasicMaterial({color: 0x00ff00})
        );
        axisYBody.translateY(0.5);
        axes.add(axisYBody);

        var axisYHead = new THREE.Mesh(
            new THREE.CylinderGeometry(0.01, 0.05, 0.125, 4),
            new THREE.MeshBasicMaterial({color: 0x00ff00})
        );
        axisYHead.translateY(0.9375);
        axes.add(axisYHead);

        var axisZBody = new THREE.Mesh(
            new THREE.CylinderGeometry(0.01, 0.05, 1, 4),
            new THREE.MeshBasicMaterial({color: 0x0000ff})
        );
        axisZBody.translateZ(0.5);
        axisZBody.quaternion.setFromAxisAngle(vectorUnitX, Math.PI / 2);
        axes.add(axisZBody);

        var axisZHead = new THREE.Mesh(
            new THREE.CylinderGeometry(0.01, 0.05, 0.125, 4),
            new THREE.MeshBasicMaterial({color: 0x0000ff})
        );
        axisZHead.translateZ(0.9375);
        axisZHead.quaternion.setFromAxisAngle(vectorUnitX, Math.PI / 2);
        axes.add(axisZHead);

        return axes;
    };



    var THREE_createEyeCamera = function(fov, aspect, near, far, distance) {
        var cameras = new THREE.Camera();
        cameras.distance = distance; // add new variable ".distance"
        cameras.target = new THREE.Vector3(); // add new variable ".target"
        
        var cameraLeft = new THREE.PerspectiveCamera(fov, aspect, near, far);
        cameraLeft.visible = false;
        cameraLeft.translateX(-0.5 * distance);
        cameras.add(cameraLeft);
        cameras.left = cameraLeft; // add new variable ".left"

        var cameraRight = new THREE.PerspectiveCamera(fov, aspect, near, far);
        cameraRight.visible = false;
        cameraRight.translateX(0.5 * distance);
        cameras.add(cameraRight);
        cameras.right = cameraRight; // add new variable ".right"

        cameras.updateProjectionMatrix = function() { // add new function ".updateProjectionMatrix"
            this.left.aspect = this.aspect;
            this.left.updateProjectionMatrix();
            this.right.aspect = this.aspect;
            this.right.updateProjectionMatrix();
            return;
        };
        
        return cameras;
    };



    this.onLoaded = function() {

        // 各種設定値
        var pixWidthScreen = window.innerWidth;
        var pixHeightScreen = window.innerHeight;

        // レンダラーの初期化
        // = WebGLの有効化+HTMLへ要素の追加
        renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true});
        renderer.setSize(pixWidthScreen, pixHeightScreen);
        renderer.enableScissorTest(true);
        renderer.setPixelRatio(window.devicePixelRatio);
        document.body.style.marginTop = "0px";
        document.body.style.marginLeft = "0px";
        document.body.appendChild(renderer.domElement);

        // シーンの初期化
        scene = new THREE.Scene();
        scene.position = vectorZero;

        // カメラの初期化
        camera = new THREE_createEyeCamera(130, pixWidthScreen / pixHeightScreen * 0.5, 0.25, 128, 0.08);
        camera.position.set(0, 1, 1.5);
        camera.target = vectorZero;
        camera.lookAt(vectorZero);
        scene.add(camera);

        // シーンオブジェクト1 光源の初期化
        var light = new THREE.PointLight(0xffffff, 1, 16); // 白色光の点光源
        light.position.set(1, 1, 1);
        scene.add(light);

        // シーンオブジェクト2 座標軸の初期化
        var axes = THREE_createAxesMesh();
        scene.add(axes);

        var axes_5_5 = THREE_createAxesMesh();
        axes_5_5.position.set(5, 0, 5);
        scene.add(axes_5_5);

        var axes_5_0 = THREE_createAxesMesh();
        axes_5_0.position.set(5, 0, 0);
        scene.add(axes_5_0);

        var axes_0_5 = THREE_createAxesMesh();
        axes_0_5.position.set(0, 0, 5);
        scene.add(axes_0_5);

        // シーンオブジェクト3 箱の初期化
        var sphere = new THREE.Mesh(
            new THREE.BoxGeometry(0.5, 0.5, 0.5, 4, 4, 4),
            new THREE.MeshPhongMaterial({color: 0x00ffff})
        )
        sphere.position.set(0, 0, 0);
        scene.add(sphere);

        // 非同期描画処理
        var queueRender = null;
        queueRender = window.requestAnimationFrame(function _render() {
            var pixWidthScreen = window.innerWidth;
            var pixHeightScreen = window.innerHeight;

            if(lockRender == null) {
                // LEFT
                renderer.setViewport(0, 0, pixWidthScreen / 2, pixHeightScreen);
                renderer.setScissor(0, 0, pixWidthScreen /2, pixHeightScreen);
                renderer.render(scene, camera.left);
            }

            if(lockRender == null){
                // RIGHT
                renderer.setViewport(pixWidthScreen / 2, 0, pixWidthScreen / 2, pixHeightScreen);
                renderer.setScissor(pixWidthScreen / 2, 0, pixWidthScreen / 2, pixHeightScreen);
                renderer.render(scene, camera.right);
            }

            queueRender = window.requestAnimationFrame(_render);
        });

        // 非同期更新処理
        var queueUpdate = null;
        var msUpdateWaitIdeal = 1000 / fpsUpdate;
        var timeUpdateLast = getTime();
        queueUpdate = window.setTimeout(function _update() {
            var timeUpdateBefore = getTime();
            var msUpdateWaitOver = timeUpdateBefore - timeUpdateLast;

            // イベントによる更新命令の処理
            while(queueAction.length > 0) {
                var command = queueAction.shift();
                switch(command) {
                case CMD_TRANSLATE_CAMERA_LEFT_DELTA:
                    camera.translateX(-lengthCameraMovementUnit);
                    break;
                case CMD_TRANSLATE_CAMERA_RIGHT_DELTA:
                    camera.translateX(+lengthCameraMovementUnit);
                    break;
                case CMD_TRANSLATE_CAMERA_FORWARD_DELTA:
                    camera.translateZ(-lengthCameraMovementUnit);
                    break;
                case CMD_TRANSLATE_CAMERA_BACKWARD_DELTA:
                    camera.translateZ(+lengthCameraMovementUnit);
                    break;
                }
            }

            // オブジェクトの更新
            var qCurrent = sphere.quaternion.clone();
            sphere.quaternion.setFromAxisAngle(vectorUp, Math.PI / 30);
            sphere.quaternion.multiply(qCurrent);
            qCurrent = sphere.quaternion.clone();
            sphere.quaternion.setFromAxisAngle(vectorLeft, Math.PI / 60 * (0.5 -  Math.random()));
            sphere.quaternion.multiply(qCurrent);

            var timeUpdateAfter = getTime();
            var msUpdate = timeUpdateAfter - timeUpdateBefore;
            var msUpdateWait = msUpdateWaitIdeal - msUpdate - msUpdateWaitOver;

            queueUpdate = window.setTimeout(_update, msUpdateWait);
            timeUpdateLast = getTime();
        }, msUpdateWaitIdeal);

        return;
    };



    var queueResize = null;
    var msResizeWait = 100;

    this.onResized = function() {

        window.clearTimeout(queueResize);
        queueResize = window.setTimeout(function() {
            var pixWidthScreen = window.innerWidth;
            var pixHeightScreen = window.innerHeight;

            if(renderer != null) {
                lockRender = true;
                renderer.setSize(pixWidthScreen, pixHeightScreen);
                lockRender = null;
            }

            if(cameraLeft != null) {
                cameraLeft.aspect = pixWidthScreen / pixHeightScreen / 2;
                cameraLeft.updateProjectionMatrix();
                //cameraLeft.setViewOffset(pixWidthScreen, pixHeightScreen, 0, 0, pixWidthScreen / 2, pixHeightScreen);
            }

            if(cameraRight != null) {
                cameraRight.aspect = pixWidthScreen / pixHeightScreen / 2;
                cameraRight.updateProjectionMatrix();
                //cameraRight.setViewOffset(pixWidthScreen, pixHeightScreen, pixWidthScreen / 2, 0, pixWidthScreen, pixHeightScreen);
            }

            if(camera != null) {
                camera.aspect = pixWidthScreen / pixHeightScreen / 2;
                camera.updateProjectionMatrix();
            }

            return;
        }, msResizeWait);

        return;
    };



    this.onKeyDown = function(key) {
        switch(key.keyCode) {
        case 37: // LEFT
            queueAction.push(CMD_TRANSLATE_CAMERA_LEFT_DELTA);
            break;
        
        case 38: // UP
            queueAction.push(CMD_TRANSLATE_CAMERA_FORWARD_DELTA);
            break;

        case 39: // RIGHT
            queueAction.push(CMD_TRANSLATE_CAMERA_RIGHT_DELTA);
            break;
        
        case 40: // DOWN
            queueAction.push(CMD_TRANSLATE_CAMERA_BACKWARD_DELTA);
            break;

        default:
            console.log(key);
            break;
        }
        return;
    };
};



var program = new Program();

window.addEventListener("load", program.onLoaded, false);
window.addEventListener("resize", program.onResized, false);
window.addEventListener("keydown", program.onKeyDown, false);

メモ

スマートフォンのブラウザで表示させBotsNewにスマートフォンをセットして覗くと立体的に見える。
また、PCブラウザに限るが、キーボードの矢印キーでカメラを平行移動させることができる。

次回は、一旦BotsNewから離れて、外部のモデルエディタで作成したモデルデータを表示させてみたい。
tutorial5

1件のコメント

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です