本篇將介紹如果使用Three.js進行動態畫面的渲染。此外,將會介紹一個Three.js作者寫的另外一個庫stat.js,用來觀測每秒幀數(FPS)。
1.實現動畫效果
1.1 動畫原理
對於Three.js程序而言,動畫的實現是通過在每秒中多次重繪畫面實現的。
為了衡量畫面切換速度,引入了每秒幀數FPS(Frames Per Second)的概念,是指每秒畫面重繪的次數。FPS越大,則動畫效果越平滑,當FPS小於20時,一般就能明顯感受到畫面的卡滯現象。
那么FPS是不是越大越好呢?其實也未必。當FPS足夠大(比如達到60),再增加幀數人眼也不會感受到明顯的變化,反而相應地就要消耗更多資源(比如電影的膠片就需要更長了,或是電腦刷新畫面需要消耗計算資源等等)。因此,選擇一個適中的FPS即可。
對於Three.js動畫而言,一般FPS在30到60之間都是可取的。
1.2 setInterval方法
如果要設置特定的FPS(雖然嚴格來說,即使使用這種方法,JavaScript也不能保證幀數精確性),可以使用JavaScript DOM定義的方法:
setInterval(func, msec)
其中,func是每過msec毫秒執行的函數,如果將func定義為重繪畫面的函數,就能實現動畫效果。setInterval函數返回一個id,如果需要停止重繪,需要使用clearInterval方法,並傳入該id,具體的做法為:
首先,在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入門指南》
