Three.js -4

変数スコープの修正と更新・描画処理の非同期化、更新間隔の自動調整

前回にwindow.setIntervalで更新と描画を同期的に行った。
しかし、突貫的に作成したためスコープが適切でなかったり
そもそも描画と更新がsetIntervalによるためCPUを使う。
(正確には、ブラウザが非表示などバックグラウンドに移ったとしてもCPUを使い続ける)。
また、setIntervalでは次の呼び出しまで一定の間隔待つため、内部の処理に時間がかかった場合に関数が呼ばれる間隔が一定ではない(と思われる、簡単に検証したときではそのような挙動だった)。

そこで、今回は

  • 描画をwindow.requestAnimationFrame、更新をwindow.setTimeoutに分離、バックグランド時にCPUパワーを抑える
  • setTimeout + スリープ間隔調整で、更新が極力一定間隔で行われるようにする

という改修を入れる。

index.html

これはほぼ何も変わりない。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <title>Three.js My Tutorial 4</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/tutorial4.js"></script>
    </head>

    <body>
    </body>
</html>

tutorial4.js

Programというオブジェクトを作り、その中で共有変数やコールバック関数を定義するよう改修。

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 fpsRender = 30;
    var fpsUpdate = 30;

    

    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;
    };



    this.onLoaded = function() {

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

        // レンダラーの初期化
        renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true});
        renderer.setSize(pixWidthScreen, pixHeightScreen);
        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.PerspectiveCamera(130, pixWidthScreen / pixHeightScreen, 0.25, 128); // 透視投影の場合
        camera.visible = false;
        camera.position.set(1, 0.5, 0.5);
        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);

        // シーンオブジェクト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() {
            renderer.render(scene, camera);
            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;

            // オブジェクトの更新
            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);
            // 検証のため、不規則なsleepを入れても一定速度で回転するかを確認する
            sleep(Math.random() * 30);

            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 = 500;

    this.onResized = function() {

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

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

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

            return;
        }, msResizeWait);

        return;
    };
};



var program = new Program();

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

メモ

書店でBotsNewという面白そうなVR装置をみつけたので、これ用に2枚の投影画像を出力する改修をしようと思う。

コメントを残す

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