在本章以前,全部畫面都是靜止的,本章將介紹若是使用Three.js進行動態畫面的渲染。此外,將會介紹一個Three.js做者寫的另一個庫,用來觀測每秒幀數(
FPS
)。javascript
animation
)。正如動畫片的原理同樣,動畫的本質是利用了人眼的視覺暫留特性,快速地變換畫面,從而產生物體在運動的假象。而對於Three.js程序而言,動畫的實現也是經過在每秒鐘屢次重繪
畫面實現的。FPS(Frames Per Second)
的概念,是指每秒畫面重繪的次數
。FPS越大
,則動畫效果越平滑
,當FPS小於20
時,通常就能明顯感覺到畫面的卡滯
現象。好比達到60
),再增長幀數人眼也不會感覺到明顯的變化,反而相應地就要消耗更多資源(好比電影的膠片就須要更長了,或是電腦刷新畫面須要消耗計算資源等等)。所以,選擇一個適中的FPS便可。30
到60
之間都是可取的。若是要設置特定的FPS(雖然嚴格來講,即便使用這種方法,JavaScript也不能保證幀數精確性),可使用JavaScript DOM定義的方法:html
setInterval(fn,mesc)
fn
是每過msec
毫秒執行的函數
,若是將fn
定義爲重繪畫面的函數,就能實現動畫效果。setInterval
函數返回一個變量timer
,若是須要中止重繪,須要使用clearInterval
方法,並傳入該變量timer
,具體的作法爲:init
函數中定義每20毫秒
執行draw
函數的setInterval
,返回值記錄在全局變量timer
中:timer = setInterval(draw,20);
draw
函數中,咱們首先設定在每幀中的變化(畢竟,若是每幀都是相同的,即便重繪再屢次,仍是不會有動畫的效果),這裏咱們讓場景中的長方體繞y
軸轉動。而後,執行渲染:function draw() { // 每過20ms 就會執行一次這個函數,rotation.y就會加0.01 // 轉完360度就會進行取餘,因此就會一直轉下去 mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2); renderer.render(scene, camera); }
20
毫秒就會調用一次draw
函數,改變長方體的旋轉值,而後進行重繪。最終獲得的效果就是FPS
爲50
的旋轉長方體。<button id="stopBtn" onclick="stop()">Stop</button> <button id="startBtn" onclick="start()">Start</button>
stop
和start
函數爲:function stop() { if (timer !== null) { clearInterval(timer); timer = null; } } function start() { if (timer == null) { clearInterval(timer); timer = setInterval(draw, 20); } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>動畫效果</title> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript"> var scene = null; var camera = null; var renderer = null; var mesh = null; var timer = 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); timer = setInterval(draw, 20); } function draw() { mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2); renderer.render(scene, camera); } function stop() { if (timer !== null) { clearInterval(timer); timer = null; } } function start() { if (timer == null) { clearInterval(timer); timer = setInterval(draw, 20); } } </script> </head> <body onload="init()"> <canvas id="mainCanvas" width="800px" height="600px"></canvas> <button id="stopBtn" onclick="stop()">Stop</button> <button id="startBtn" onclick="start()">Start</button> </body> </html>
大多數時候,咱們並不在乎多久重繪一次,這時候就適合用
requestAnimationFrame
方法了。它告訴瀏覽器在合適的時候調用指定函數,一般可能達到60FPS
。java
requestAnimationFrame
一樣有對應的cancelAnimationFrame
取消動畫:function stop() { if (timer !== null) { cancelAnimationFrame(timer); timer = null; } }
setInterval
不一樣的是,因爲requestAnimationFrame
只請求一幀畫面,所以,除了在init
函數中須要調用,在被其調用的函數中須要再次調用requestAnimationFrame
:function draw() { mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2); renderer.render(scene, camera); timer = requestAnimationFrame(draw); }
requestAnimationFrame
較爲「年輕」,於是一些老的瀏覽器使用的是試驗期的名字:mozRequestAnimationFrame
、webkitRequestAnimationFrame
、msRequestAnimationFrame
,爲了支持這些瀏覽器,咱們最好在調用以前,先判斷是否認義了requestAnimationFrame
以及上述函數:var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; window.requestAnimationFrame = requestAnimationFrame;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>動畫效果</title> <script type="text/javascript" src="js/three.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 timer = 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); timer = requestAnimationFrame(draw); } function stop() { if (timer !== null) { cancelAnimationFrame(timer); timer = null; } } function start() { if (timer == null) { timer = requestAnimationFrame(draw); } } </script> </head> <body onload="init()"> <canvas id="mainCanvas" width="800px" height="600px"></canvas> <button id="stopBtn" onclick="stop()">Stop</button> <button id="startBtn" onclick="start()">Start</button> </body> </html>
setInterval和requestAnimationFrame的區別:node
setInterval
方法與requestAnimationFrame
方法的區別較爲微妙。一方面,最明顯的差異表如今setInterval
能夠手動設定FPS
,而requestAnimationFrame
則會自動設定FPS
;但另外一方面,即便是setInterval
也不能保證按照給定的FPS執行,在瀏覽器處理繁忙時,極可能低於設定值。當瀏覽器達不到設定的調用週期時,requestAnimationFrame
採用跳過某些幀的方式來表現動畫,雖然會有卡滯的效果可是總體速度不會拖慢,而setInterval
會所以使整個程序放慢運行,可是每一幀都會繪製出來;requestAnimationFrame
適用於對於時間較爲敏感
的環境(可是動畫邏輯更加複雜),而setInterval
則可在保證程序的運算不至於致使延遲的狀況下提供更加簡潔的邏輯(無需自行處理時間)。
stat.js
是Three.js的做者Mr.Doob
的另外一個有用的JavaScript庫。不少狀況下,咱們但願知道實時的FPS
信息,從而更好地監測動畫效果。這時候,stat.js就能提供一個很好的幫助,它佔據屏幕中的一小塊位置(如左上角),效果爲:,單擊後顯示每幀渲染時間:。git
stat.js
文件,能夠在https://github.com/mrdoob/stats.js/blob/master/build/stats.min.js找到。下載後,將其放在項目文件夾下,而後在HTML中引用:<script type="text/javascript" src="stat.js"></script>
var stat = null; function init() { stat = new Stats(); stat.domElement.style.position = 'absolute'; stat.domElement.style.left = '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(); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>stats</title> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript" src="Stats.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.left = '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); timer = requestAnimationFrame(draw); } function draw() { stat.begin(); mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2); renderer.render(scene, camera); timer = requestAnimationFrame(draw); stat.end(); } function stop() { if (timer !== null) { cancelAnimationFrame(timer); timer = null; } } </script> </head> <body onload="init()"> <canvas id="mainCanvas" width="800px" height="600px"></canvas> <button id="stopBtn" onclick="stop()">Stop</button> </body> </html>
本節咱們將使用一個彈球的例子來完整地學習使用動畫效果。github
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; window.requestAnimationFrame = requestAnimationFrame; var stat; var renderer; var scene; var camera; var light; function init() { stat = new Stats(); stat.domElement.style.position = 'absolute'; stat.domElement.style.left= '0px'; stat.domElement.style.top = '0px'; document.body.appendChild(stat.domElement); renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('mainCanvas') }); scene = new THREE.Scene(); timer = requestAnimationFrame(draw); } function draw() { stat.begin(); renderer.render(scene, camera); timer = requestAnimationFrame(draw); stat.end(); } function stop() { if (timer !== null) { cancelAnimationFrame(timer); timer = null; } }
球體
做爲彈球模型,建立一個平面
做爲彈球反彈的平面。爲了在draw
函數中改變彈球的位置,咱們能夠聲明一個全局變量ballMesh
,以及彈球半徑ballRadius
。var ballMesh; var ballRadius = 0.5;
init
函數中添加球體
和平面
,使彈球位於平面上,平面採用棋盤格圖像做材質:// 加載貼圖 texture = THREE.ImageUtils.loadTexture('images/chess.png', {}, function() { renderer.render(scene, camera); }); texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(4, 4); // 平面模型 var plane = new THREE.Mesh(new THREE.PlaneGeometry(8, 8), new THREE.MeshLambertMaterial({ map: texture })); // 沿x軸旋轉-90度 plane.rotation.x = Math.PI / -2; scene.add(plane); // 球模型 ballMesh = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 40, 16), new THREE.MeshLambertMaterial({ color: 0xffff00 }));
位置
、速度
、加速度
三個矢量,爲了簡單起見,這裏彈球只作豎直方向上的自由落體運動,所以位置、速度、加速度只要各用一個變量表示。其中,位置就是ballMesh.position.y
,不須要額外的變量,所以咱們在全局聲明速度v
和加速度a
:var v = 0; var a = -0.01;
a = -0.01
表明每幀小球向y
方向負方向
移動0.01
個單位。maxHeight
(本身定義的一個高度)處自由下落,掉落到平面上時會反彈
,而且速度有損耗
。當速度很小的時候,彈球會在平面上做振幅微小的抖動,因此,當速度足夠小時,咱們須要讓彈球中止跳動。所以,定義一個全局變量表示是否在運動,初始值爲false
:var isMoving = false;
<button id="dropBtn" onclick="drop()">Drop</button> <script> function drop() { isMoving = true; ballMesh.position.y = maxHeight; v = 0; } </script>
function draw() { stat.begin(); if (isMoving) { ballMesh.position.y += v; // a= -0.01 v += a; // 當小球從定義的高度落到小球停在平面時的高度的時候 if (ballMesh.position.y <= ballRadius) { // 讓小球彈起來 v = -v * 0.9; } // 當小球的速度小於設定值的時候 if (Math.abs(v) < 0.001) { // 讓它停下來 isMoving = false; ballMesh.position.y = ballRadius; } } renderer.render(scene, camera); requestAnimationFrame(draw); stat.end(); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>彈彈彈</title> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript" src="Stats.js"></script> <style> * { margin: 0; padding: 0; } body { position: fixed; } button { position: fixed; left: 50%; margin-left: -50px; bottom: 20%; width: 100px; height: 30px; background-color: #d3d3d3; border: none; border-radius: 15px; outline: none; font-size: 18px; font-weight: 700; color: #333; box-shadow: -1px -1px 1px #fff, 1px 1px 1px #000; } </style> <script> var stat; var renderer; var scene; var camera; var light; var texture; var ballMesh; var ballRadius = 0.5; var isMoving = false; var maxHeight = 5; var v = 0; var a = -0.01; function init() { // 處理requireAnimationFrame兼容性 var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; window.requestAnimationFrame = requestAnimationFrame; // FPS 插件 stat = new Stats(); stat.domElement.style.position = 'absolute'; stat.domElement.style.left = '0px'; stat.domElement.style.top = '0px'; document.body.appendChild(stat.domElement); // 渲染器 renderer = new THREE.WebGLRenderer({ antialias: true }); width = window.innerWidth; height = window.innerHeight; renderer.setSize(width, height); document.body.appendChild(renderer.domElement); renderer.setClearColor(0xd3d3d3); // 場景 scene = new THREE.Scene(); // 相機 camera = new THREE.OrthographicCamera(width / -128, width / 128, height / 128, height / -128, 1, 1000); camera.position.set(10, 15, 25); camera.lookAt(new THREE.Vector3(0, 0, 0)); scene.add(camera); // 添加光照 light = new THREE.DirectionalLight(0xffffff); light.position.set(-10, 30, 25); scene.add(light); // 加載貼圖 texture = THREE.ImageUtils.loadTexture('images/chess.png', {}, function() { renderer.render(scene, camera); }); texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(4, 4); // 平面模型 var plane = new THREE.Mesh(new THREE.PlaneGeometry(8, 8), new THREE.MeshLambertMaterial({ map: texture })); // 沿x軸旋轉-90度 plane.rotation.x = Math.PI / -2; scene.add(plane); // 球模型 ballMesh = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 40, 16), new THREE.MeshLambertMaterial({ color: 0xffff00 })); // 設置球的位置 ballMesh.position.y = ballRadius; scene.add(ballMesh); // 座標軸 /* drawAxes(scene); function drawAxes(scene) { // x-axis var xGeo = new THREE.Geometry(); xGeo.vertices.push(new THREE.Vector3(0, 0, 0)); xGeo.vertices.push(new THREE.Vector3(7, 0, 0)); var xMat = new THREE.LineBasicMaterial({ color: 0xff0000 }); var xAxis = new THREE.Line(xGeo, xMat); scene.add(xAxis); // y-axis var yGeo = new THREE.Geometry(); yGeo.vertices.push(new THREE.Vector3(0, 0, 0)); yGeo.vertices.push(new THREE.Vector3(0, 7, 0)); var yMat = new THREE.LineBasicMaterial({ color: 0x00ff00 }); var yAxis = new THREE.Line(yGeo, yMat); scene.add(yAxis); // z-axis var zGeo = new THREE.Geometry(); zGeo.vertices.push(new THREE.Vector3(0, 0, 0)); zGeo.vertices.push(new THREE.Vector3(0, 0, 7)); var zMat = new THREE.LineBasicMaterial({ color: 0x00ccff }); var zAxis = new THREE.Line(zGeo, zMat); scene.add(zAxis); } */ requestAnimationFrame(draw); } // 計算球運動的速度和位置 function draw() { stat.begin(); if (isMoving) { ballMesh.position.y += v; // a= -0.01 v += a; // 當小球從定義的高度落到小球停在平面時的高度的時候 if (ballMesh.position.y <= ballRadius) { // 讓小球彈起來 v = -v * 0.9; } // 當小球的速度小於設定值的時候 if (Math.abs(v) < 0.001) { // 讓它停下來 isMoving = false; ballMesh.position.y = ballRadius; } } renderer.render(scene, camera); requestAnimationFrame(draw); stat.end(); } // 觸發函數 function drop() { isMoving = true; // 小球起落位置 ballMesh.position.y = maxHeight; // 加速度爲0 v = 0; } </script> </head> <body onload="init();"> <button id="dropBtn" onclick="drop();">Drop</button> </body> </html>
前面咱們瞭解到,使用Three.js建立常見幾何體是十分方便的,可是對於人或者動物這樣很是複雜的模型使用幾何體組合就很是麻煩了。所以,Three.js容許用戶導入由3ds Max等工具製做的三維模型,並添加到場景中。web
Three.js有一系列導入外部文件的輔助函數,是在
three.js
以外的,使用前須要額外下載,在https://github.com/mrdoob/three.js/tree/master/examples/js/loaders能夠找到,選擇對應的模型加載器,系在下來。npm
*.obj
是最經常使用的模型格式,導入*.obj
文件須要OBJLoader.js
;導入帶*.mtl
材質的*.obj
文件須要MTLLoader.js
以及OBJMTLLoader.js
。另有PLYLoader.js
、STLLoader.js
等分別對應不一樣格式的加載器,能夠根據模型格式自行選擇。目前,支持的模型格式有:canvas
*.obj
*.obj, *.mtl
*.dae
*.ctm
*.ply
*.stl
*.wrl
*.vtk
本節中,咱們將將導出的沒有材質的模型使用Three.js導入場景中。瀏覽器
<script type="text/javascript" src="OBJLoader.js"></script>
*.obj
模型,在init
函數中,建立loader
變量,用於導入模型:var loader = new THREE.OBJLoader();
loader
導入模型的時候,接受兩個參數,第一個表示模型路徑
,第二個表示完成導入後的回調函數
,通常咱們須要在這個回調函數中將導入的模型添加
到場景中。loader.load('../lib/port.obj', function(obj) { //儲存到全局變量中 mesh = obj; scene.add(obj); });
function draw() { renderer.render(scene, camera); mesh.rotation.y += 0.01; if (mesh.rotation.y > Math.PI * 2) { mesh.rotation.y -= Math.PI * 2; } }
雙面繪製
,須要這樣設置:var loader = new THREE.OBJLoader(); loader.load('port.obj', function(obj) { obj.traverse(function(child) { if (child instanceof THREE.Mesh) { child.material.side = THREE.DoubleSide; } }); mesh = obj; scene.add(obj); });
<!DOCTYPE html> <html lang="en"> <head> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript" src="OBJLoader.js"></script> <style> * { margin: 0; padding: 0; } body { position: fixed; } </style> <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(); renderer.setSize(800, 600); document.body.appendChild(renderer.domElement); renderer.setClearColor(0x000000); scene = new THREE.Scene(); camera = new THREE.OrthographicCamera(-8, 8, 6, -6, 0.1, 100); camera.position.set(15, 25, 25); camera.lookAt(new THREE.Vector3(0, 2, 0)); scene.add(camera); var loader = new THREE.OBJLoader(); loader.load('port.obj', function(obj) { obj.traverse(function(child) { if (child instanceof THREE.Mesh) { child.material.side = THREE.DoubleSide; } }); mesh = obj; scene.add(obj); }); var light = new THREE.DirectionalLight(0xffffff); light.position.set(20, 10, 5); scene.add(light); id = setInterval(draw, 20); } function draw() { renderer.render(scene, camera); mesh.rotation.y += 0.01; if (mesh.rotation.y > Math.PI * 2) { mesh.rotation.y -= Math.PI * 2; } } </script> </head> <body onload="init()"> </body> </html>
模型的材質能夠有兩種定義方式,一種是在
代碼中
導入模型後設置材質,另外一種是在建模軟件中導出材質
信息。下面,咱們將分別介紹這兩種方法。
這種方法與上一節相似,不一樣之處在於回調函數中設置模型的材質:
var loader = new THREE.OBJLoader(); loader.load('port.obj', function(obj) { obj.traverse(function(child) { if (child instanceof THREE.Mesh) { /* 修改這裏如下的代碼 */ child.material = new THREE.MeshLambertMaterial({ color: 0xffff00, side: THREE.DoubleSide }); /* 修改這裏以上的代碼 */ } }); mesh = obj; scene.add(obj); });
導出3D模型的時候,選擇導出
port.obj
模型文件以及port.mtl
材質文件。
OBJLoader.js
,而是使用MTLLoader.js
與OBJMTLLoader.js
,而且要按該順序
引用:<script type="text/javascript" src="MTLLoader.js"></script> <script type="text/javascript" src="OBJMTLLoader.js"></script>
var mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath(''); mtlLoader.load('port.mtl', function(materials) { materials.preload(); // model loader var objLoader = new THREE.OBJLoader(); objLoader.setMaterials(materials); objLoader.setPath(''); objLoader.load('port.obj', function(object) { object.position.y = -95; // if has object, add to scene if (object.children.length > 0) { scene.add(object.children[0]); } }); });
<html> <head> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript" src="MTLLoader.js"></script> <script type="text/javascript" src="OBJLoader.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(); renderer.setSize(800, 600); document.body.appendChild(renderer.domElement); renderer.setClearColor(0x000000); scene = new THREE.Scene(); camera = new THREE.OrthographicCamera(-8, 8, 6, -6, 0.1, 100); camera.position.set(15, 25, 25); camera.lookAt(new THREE.Vector3(0, 2, 0)); scene.add(camera); // material loader var mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath(''); mtlLoader.load('port.mtl', function(materials) { materials.preload(); // model loader var objLoader = new THREE.OBJLoader(); objLoader.setMaterials(materials); objLoader.setPath(''); objLoader.load('port.obj', function(object) { object.position.y = -95; // if has object, add to scene if (object.children.length > 0) { scene.add(object.children[0]); } }); mesh = materials; console.log(mesh); }); var light = new THREE.DirectionalLight(0xffffff); light.position.set(20, 10, 5); scene.add(light); id = setInterval(draw, 20); } function draw() { renderer.render(scene, camera); } </script> </head> <body onload="init()"> </body> </html>
圖像渲染的豐富效果很大程度上也要歸功於光與影的利用。真實世界中的光影效果很是複雜,可是其本質—光的傳播原理卻又是很是單一的,這即是天然界繁簡相成的又一例證。爲了使計算機模擬豐富的光照效果,人們提出了幾種不一樣的光源模型(
環境光
、平行光
、點光源
、聚光燈
等),在不一樣場合下組合利用,將能達到很好的光照效果。
環境光是指
場景總體
的光照效果,是因爲場景內若干光源的屢次反射造成的亮度一致的效果,一般用來爲整個場景指定一個基礎亮度。所以,環境光沒有明確的光源位置,在各處造成的亮度也是一致的。
var light = new THREE.AmbientLight(hex); scene.add(light);
hex
是十六進制的RGB顏色信息,如紅色表示爲0xff0000
// 建立一個綠色的正方體 var greenCube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), new THREE.MeshLambertMaterial({color: 0x00ff00})); greenCube.position.x = 3; scene.add(greenCube); // 建立一個白色的正方體 var whiteCube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), new THREE.MeshLambertMaterial({color: 0xffffff})); whiteCube.position.x = -3; scene.add(whiteCube);
new THREE.AmbientLight(0xcccccc)
等,效果爲:
點光源是不計光源大小,能夠看做一個點發出的光源。點光源照到不一樣物體表面的亮度是線性遞減的,所以,離點光源距離越
遠
的物體會顯得越暗
。
THREE.PointLight(hex, intensity, distance);
hex
是光源十六進制的顏色值;intensity
是亮度,缺省值爲1
,表示100%
亮度;distance
是光源最遠照射到的距離,缺省值爲0
。var light = new THREE.PointLight(0xffffff, 2, 100); light.position.set(0, 1.5, 2); scene.add(light);
咱們都知道,太陽光經常被看做平行光,這是由於相對地球上物體的尺度而言,太陽離咱們的距離足夠遠。對於任意平行的平面,平行光照射的亮度都是相同的,而與平面所在位置無關。
THREE.DirectionalLight(hex, intensity)
hex
是光源十六進制的顏色值;intensity
是亮度,缺省值爲1
,表示100%
亮度。位置
尤其重要。var light = new THREE.DirectionalLight(); light.position.set(2, 5, 3); scene.add(light);
(2, 5, 3)
點射出(若是是的話,就成了點光源),而是意味着,平行光將以矢量(-2, -5, -3)
的方向照射到全部平面。所以,平面亮度與平面的位置無關,而只與平面的法向量
相關。只要平面是平行的,那麼獲得的光照也必定是相同的。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script type="text/javascript" src="js/three.js"></script> <script> var stat; var renderer; var scene; var camera; function init() { // 渲染器 renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(800, 600); document.body.appendChild(renderer.domElement); renderer.setClearColor(0x000000); // 場景 scene = new THREE.Scene(); // 相機 var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100); camera.position.set(5, 15, 25); camera.lookAt(new THREE.Vector3(0, 0, 0)); scene.add(camera); // 平行光 var light = new THREE.DirectionalLight(); light.position.set(2, 5, 3); scene.add(light); // 右側正方體 var rightCube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshLambertMaterial({ color: 0x00ff00 })); rightCube.position.x = 1; rightCube.position.y = -1; scene.add(rightCube); // 左側正方體 var leftCube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshLambertMaterial({ color: 0x00ff00 })); leftCube.position.x = -1; scene.add(leftCube); // 渲染 renderer.render(scene, camera); } </script> </head> <body onload="init();"> </body> </html>
能夠看出,聚光燈是一種特殊的點光源,它可以朝着一個方向投射光線。聚光燈投射出的是相似
圓錐形
的光線,這與咱們現實中看到的聚光燈是一致的。
THREE.SpotLight(hex, intensity, distance, angle, exponent)
angle
和exponent
兩個參數。angle
是聚光燈的張角,缺省值是Math.PI / 3
,最大值是Math.PI / 2
;exponent
是光強在偏離target
的衰減指數(target須要在以後定義,缺省值爲(0, 0, 0)
),缺省值是10
。target
:light.position.set(x1, y1, z1); light.target.position.set(x2, y2, z2);
light.target.position
的方法外,若是想讓聚光燈跟着某一物體移動(就像真的聚光燈!),能夠target
指定爲該物體
:var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshLambertMaterial({color: 0x00ff00})); var light = new THREE.SpotLight(0xffff00, 1, 100, Math.PI / 6, 25); light.target = cube;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript"> var scene = null; var camera = null; var renderer = null; var cube = null; var alpha = 0; function init() { renderer = new THREE.WebGLRenderer(); renderer.setSize(800, 600); document.body.appendChild(renderer.domElement); scene = new THREE.Scene(); camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100); camera.position.set(5, 15, 25); camera.lookAt(new THREE.Vector3(0, 0, 0)); scene.add(camera); // 平面 var plane = new THREE.Mesh(new THREE.PlaneGeometry(8, 8, 16, 16), new THREE.MeshLambertMaterial({ color: 0xcccccc })); plane.rotation.x = -Math.PI / 2; plane.position.y = -1; plane.receiveShadow = true; scene.add(plane); // 立方體 cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshLambertMaterial({ color: 0x00ff00, })); cube.position.x = 2; scene.add(cube); // 聚光燈 var light = new THREE.SpotLight(0xffff00, 1, 100, Math.PI / 6, 25); light.position.set(2, 5, 3); light.target = cube; scene.add(light); // 環境光 var ambient = new THREE.AmbientLight(0x666666); scene.add(ambient); requestAnimationFrame(draw); } function draw() { alpha += 0.01; if (alpha > Math.PI * 2) { alpha -= Math.PI * 2; } cube.position.set(2 * Math.cos(alpha), 0, 2 * Math.sin(alpha)); renderer.render(scene, camera); requestAnimationFrame(draw); } </script> </head> <body onload="init();"> </body> </html>
明暗是相對的,陰影的造成也就是由於比周圍得到的光照更少。所以,要造成陰影,光源必不可少。
THREE.DirectionalLight
與THREE.SpotLight
;而相對地,能表現陰影效果的材質只有THREE.LambertMaterial
與THREE.PhongMaterial
。於是在設置光源和材質的時候,必定要注意這一點。renderer.shadowMapEnabled = true;
// 上面的案例,產生陰影的物體是正方體 cube.castShadow = true;
// 接收陰影的物體是平面 plan.receiveShadow = true;
平面
上有一個正方體
,想要讓聚光燈照射在正方體上,產生的陰影投射在平面上,那麼就須要對聚光燈和正方體調用castShadow = true
,對於平面調用receiveShadow = true
。聚光燈
,須要設置shadowCameraNear
、shadowCameraFar
、shadowCameraFov
三個值,類比咱們在第二章學到的透視投影照相機,只有介於shadowCameraNear
與shadowCameraFar
之間的物體將產生陰影,shadowCameraFov
表示張角。平行光
,須要設置shadowCameraNear
、shadowCameraFar
、shadowCameraLeft
、shadowCameraRight
、shadowCameraTop
以及shadowCameraBottom
六個值,至關於正交投影照相機的六個面。一樣,只有在這六個面圍成的長方體內的物體纔會產生陰影效果。light.shadowCameraVisible = true
。shadowDarkness
,該值的範圍是0
到1
,越小越淺。Shadow Mapping
,即陰影是做爲渲染前計算好的貼圖貼上去的,於是會受到貼圖像素大小
的限制。因此能夠經過設置shadowMapWidth
與shadowMapHeight
值控制貼圖的大小,來改變陰影的精確度。軟陰影
的效果,能夠經過renderer.shadowMapSoft = true;
方便地實現。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script type="text/javascript" src="js/three.js"></script> <script type="text/javascript"> var scene = null; var camera = null; var renderer = null; var cube = null; var alpha = 0; function init() { renderer = new THREE.WebGLRenderer(); renderer.setSize(800, 600); document.body.appendChild(renderer.domElement); renderer.shadowMapEnabled = true; scene = new THREE.Scene(); camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100); camera.position.set(5, 15, 25); camera.lookAt(new THREE.Vector3(0, 0, 0)); scene.add(camera); var plane = new THREE.Mesh(new THREE.PlaneGeometry(8, 8, 16, 16), new THREE.MeshLambertMaterial({ color: 0xcccccc })); plane.rotation.x = -Math.PI / 2; plane.position.y = -1; plane.receiveShadow = true; scene.add(plane); cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshLambertMaterial({ color: 0x00ff00 })); cube.position.x = 2; cube.castShadow = true; scene.add(cube); var light = new THREE.SpotLight(0xffff00, 1, 100, Math.PI / 6, 25); light.position.set(2, 5, 3); light.target = cube; light.castShadow = true; light.shadowCameraNear = 2; light.shadowCameraFar = 10; light.shadowCameraFov = 30; light.shadowMapWidth = 1024; light.shadowMapHeight = 1024; light.shadowDarkness = 0.3; scene.add(light); // ambient light var ambient = new THREE.AmbientLight(0x666666); scene.add(ambient); requestAnimationFrame(draw); } function draw() { alpha += 0.01; if (alpha > Math.PI * 2) { alpha -= Math.PI * 2; } cube.position.set(2 * Math.cos(alpha), 0, 2 * Math.sin(alpha)); renderer.render(scene, camera); requestAnimationFrame(draw); } </script> </head> <body onload="init();"> </body> </html>
<span id = "jump"></span>
npm install -g live-server
全局安裝在此處打開命令窗口
live-server
回車