本篇將介紹若是使用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入門指南》