Three.js基础探寻十——动画

  本篇将介绍若是使用Three.js进行动态画面的渲染。此外,将会介绍一个Three.js做者写的另一个库stat.js,用来观测每秒帧数(FPS)。 javascript

1.实现动画效果html

  1.1 动画原理java

  对于Three.js程序而言,动画的实现是经过在每秒中屡次重绘画面实现的。 git

  为了衡量画面切换速度,引入了每秒帧数FPS(Frames Per Second)的概念,是指每秒画面重绘的次数。FPS越大,则动画效果越平滑,当FPS小于20时,通常就能明显感觉到画面的卡滞现象。 github

  那么FPS是否是越大越好呢?其实也未必。当FPS足够大(好比达到60),再增长帧数人眼也不会感觉到明显的变化,反而相应地就要消耗更多资源(好比电影的胶片就须要更长了,或是电脑刷新画面须要消耗计算资源等等)。所以,选择一个适中的FPS便可。 web

  对于Three.js动画而言,通常FPS在30到60之间都是可取的。 canvas

  1.2 setInterval方法浏览器

  若是要设置特定的FPS(虽然严格来讲,即便使用这种方法,JavaScript也不能保证帧数精确性),可使用JavaScript DOM定义的方法: app

setInterval(func, msec)

  其中,func是每过msec毫秒执行的函数,若是将func定义为重绘画面的函数,就能实现动画效果。setInterval函数返回一个id,若是须要中止重绘,须要使用clearInterval方法,并传入该id,具体的作法为:dom

  首先,在init函数中定义每20毫秒执行draw函数的setInterval,返回值记录在全局变量id中:

id = setInterval(draw, 20);

  在draw函数中,咱们首先设定在每帧中的变化,这里咱们让场景中的长方体绕y轴转动。而后,执行渲染:

function draw() {

  mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);

  renderer.render(scene, camera);

}

  这样,每20毫秒就会调用一次draw函数,改变长方体的旋转值,而后进行重绘。最终获得的效果就是FPS为50的旋转长方体。

  咱们在HTML中添加一个按钮,按下后中止动画:

<button id="stopBtn" onclick="stop()">Stop</button>

  对应的stop函数为:

function stop() {

    if (id !== null) {

        clearInterval(id);

        id = null;

    }

}        

  源码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>3.js测试10.1</title>
    </head>
    <body onload="init()">
        <canvas id="mainCanvas" width="400px" height="300px" ></canvas>
        <button id="stopBtn" onclick="stop()">Stop</button>
    </body>
    <script type="text/javascript" src="js/three.min.js"></script>
    <script type="text/javascript">
        var scene = null;
        var camera = null;
        var renderer = null;
        
        var mesh = null;
        var id = null;
        
        function init() {
            renderer = new THREE.WebGLRenderer({
                canvas: document.getElementById('mainCanvas')
            });
            renderer.setClearColor(0x000000);
            scene = new THREE.Scene();
            
            camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);
            camera.position.set(5, 5, 20);
            camera.lookAt(new THREE.Vector3(0, 0, 0));
            scene.add(camera);
            
            mesh = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3), 
                new THREE.MeshLambertMaterial({
                    color: 0xffff00
            }));
            scene.add(mesh);
            
            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(20, 10, 5);
            scene.add(light);
            
            id = setInterval(draw, 20);
        }
        
        function draw() {
            mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
            renderer.render(scene, camera);
        }
        
        function stop() {
            if (id !== null) {
                clearInterval(id);
                id = null;
            }
        }
    </script>
</html>

  你会看到一个傻缺在一直转。

  1.3 requestAnimationFrame方法

  若是不在乎多久重绘一次,可使用requestAnimationFrame方法。它告诉浏览器在合适的时候调用指定函数,一般可能达到60FPS。

  requestAnimationFrame一样有对应的cancelAnimationFrame取消动画:

function stop() {

    if (id !== null) {

        cancelAnimationFrame(id);

        id = null;

    }

}

  和setInterval不一样的是,因为requestAnimationFrame只请求一帧画面,所以,除了在init函数中须要调用,在被其调用的函数中须要再次调用requestAnimationFrame:

function draw() {

    mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);

    renderer.render(scene, camera);

    id = requestAnimationFrame(draw);

}

  由于requestAnimationFrame较为“年轻”,于是一些老的浏览器使用的是试验期的名字:mozRequestAnimationFrame、webkitRequestAnimationFrame、msRequestAnimationFrame,为了支持这些浏览器,咱们最好在调用以前,先判断是否认义了requestAnimationFrame以及上述函数:

var requestAnimationFrame = window.requestAnimationFrame

|| window.mozRequestAnimationFrame

|| window.webkitRequestAnimationFrame

|| window.msRequestAnimationFrame;

window.requestAnimationFrame = requestAnimationFrame;

  源码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>3.js测试10.2</title>
    </head>
    <body onload="init()">
        <canvas id="mainCanvas" width="400px" height="300px" ></canvas>
        <button id="stopBtn" onclick="stop()">Stop</button>
    </body>
    <script type="text/javascript" src="js/three.min.js"></script>
    <script type="text/javascript">
        var requestAnimationFrame = window.requestAnimationFrame 
                || window.mozRequestAnimationFrame
                || window.webkitRequestAnimationFrame
                || window.msRequestAnimationFrame;
        window.requestAnimationFrame = requestAnimationFrame;
            
        var scene = null;
        var camera = null;
        var renderer = null;
        
        var mesh = null;
        var id = null;
        
        function init() {
            renderer = new THREE.WebGLRenderer({
                canvas: document.getElementById('mainCanvas')
            });
            renderer.setClearColor(0x000000);
            scene = new THREE.Scene();
            
            camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);
            camera.position.set(5, 5, 20);
            camera.lookAt(new THREE.Vector3(0, 0, 0));
            scene.add(camera);
            
            mesh = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3), 
                new THREE.MeshLambertMaterial({
                    color: 0xffff00
            }));
            scene.add(mesh);
            
            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(20, 10, 5);
            scene.add(light);
            
            id = requestAnimationFrame(draw);
        }
        
        function draw() {
            mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
            renderer.render(scene, camera);
            id = requestAnimationFrame(draw);
        }
        
        function stop() {
            if (id !== null) {
                cancelAnimationFrame(id);
                id = null;
            }
        }
    </script>
</html>

  和上面的差很少,点stop会中止。

  1.4 两种方法的比较

  setInterval方法与requestAnimationFrame方法的区别较为微妙。一方面,最明显的差异表如今setInterval能够手动设定FPS,而requestAnimationFrame则会自动设定FPS;但另外一方面,即便是setInterval也不能保证按照给定的FPS执行,在浏览器处理繁忙时,极可能低于设定值。当浏览器达不到设定的调用周期时,requestAnimationFrame采用跳过某些帧的方式来表现动画,虽然会有卡滞的效果可是总体速度不会拖慢,而setInterval会所以使整个程序放慢运行,可是每一帧都会绘制出来;总而言之,requestAnimationFrame适用于对于时间较为敏感的环境(可是动画逻辑更加复杂),而setInterval则可在保证程序的运算不至于致使延迟的状况下提供更加简洁的逻辑(无需自行处理时间)。

 

2.使用stat.js记录FPS

  stat.js是Three.js的做者Mr. Doob的另外一个有用的JavaScript库。不少状况下,咱们但愿知道实时的FPS信息,从而更好地监测动画效果。这时候,stat.js就能提供一个很好的帮助,它占据屏幕中的一小块位置(如左上角),效果为:

,单击后显示每帧渲染时间:

  首先,咱们须要下载stat.js文件,能够在https://github.com/mrdoob/stats.js/blob/master/build/stats.min.js找到。下载后,将其放在项目文件夹下,而后在HTML中引用:

<script type="text/javascript" src="js/stat.js"></script>

  在页面初始化的时候,对其初始化并将其添加至屏幕一角。这里,咱们以左上角为例:

var stat = null;

function init() {

    stat = new Stats();

    stat.domElement.style.position = 'absolute';

    stat.domElement.style.right = '0px';

    stat.domElement.style.top = '0px';

    document.body.appendChild(stat.domElement);

    // Three.js init ...

}

  而后,在上一节介绍的动画重绘函数draw中调用stat.begin();与stat.end();分别表示一帧的开始与结束:

function draw() {

    stat.begin();

    mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);

    renderer.render(scene, camera);

    stat.end();

}

  最终就能获得FPS效果了。

  源码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>3.js测试10.4</title>
    </head>
    <body onload="init()">
        <canvas id="mainCanvas" width="400px" height="300px" ></canvas>
        <button id="stopBtn" onclick="stop()">Stop</button>
    </body>
    <script type="text/javascript" src="js/three.js"></script>
    <script type="text/javascript" src="js/stats.min.js"></script>
    <script type="text/javascript">
        var requestAnimationFrame = window.requestAnimationFrame 
                || window.mozRequestAnimationFrame
                || window.webkitRequestAnimationFrame
                || window.msRequestAnimationFrame;
        window.requestAnimationFrame = requestAnimationFrame;
            
        var scene = null;
        var camera = null;
        var renderer = null;
        
        var mesh = null;
        var id = null;
        
        var stat = null;
        
        function init() {
            stat = new Stats();
            stat.domElement.style.position = 'absolute';
            stat.domElement.style.right = '0px';
            stat.domElement.style.top = '0px';
            document.body.appendChild(stat.domElement);
            
            renderer = new THREE.WebGLRenderer({
                canvas: document.getElementById('mainCanvas')
            });
            renderer.setClearColor(0x000000);
            scene = new THREE.Scene();
            
            camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);
            camera.position.set(5, 5, 20);
            camera.lookAt(new THREE.Vector3(0, 0, 0));
            scene.add(camera);
            
            mesh = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3), 
                new THREE.MeshLambertMaterial({
                    color: 0xffff00
            }));
            scene.add(mesh);
            
            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(20, 10, 5);
            scene.add(light);
            
            id = requestAnimationFrame(draw);
        }
        
        function draw() {
            stat.begin();
            
            mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
            renderer.render(scene, camera);
            id = requestAnimationFrame(draw);
            
            stat.end();
        }
        
        function stop() {
            if (id !== null) {
                cancelAnimationFrame(id);
                id = null;
            }
        }
    </script>
</html>

 

 

整理自张雯莉《Three.js入门指南》

相关文章
相关标签/搜索