1.動畫構思
就是中間有個紅太陽,外面有幾個行星球體環繞着太陽在各自軌道上做圓周運動。下面是效果圖
2.基本要素
使用threejs的基本構件包括:渲染器(renderer),相機(camera),場景(scene),光線(light)。首先將這些基本構件都分別初始化一下。
2.1初始化渲染器
渲染器可以理解為畫布,用於繪制照相機觀察到的畫面,一般使用WebGLRenderer這種類型的渲染器,其余還有CanvasRenderer等別的類型,這里不做介紹了。
function initThree(){ width=window.innerWidth; height=window.innerHeight; renderer=new THREE.WebGLRenderer({ antialias : true //開啟抗鋸齒 }); renderer.setSize(width,height);//設置畫布大小 renderer.setClearColor(0x000000);//設置畫布背景顏色 document.body.appendChild(renderer.domElement);//將畫布追加到html文檔中
}
2.2初始化照相機
照相機分為兩種,透視相機(PerspectiveCamera)和正交相機(OrthographicCamera)。
透視相機符合人的視覺直接,近大遠小。新建一個透視相機
new THREE.PerspectiveCamera( 45,//視野角度 width/height,//縱橫比=>視野形狀長形還是方形 1,//近景距離 10000);//遠景距離
正交相機,物體大小不隨距離遠近變化,常用於建築方面,新建一個正交相機視景體
new THREE.OrthographicCamera( //正交攝像機=>視景體 window.innerWidth / - 2, //左平面距離視點距離 window.innerWidth / 2, //右平面距離視點距離 window.innerHeight / 2, //上平面距離視點距離 window.innerHeight / - 2, //下平面距離視點距離 10, //近平面距離視點距離 1000 );//遠平面距離視點距離
這里我使用透視相機,初始化相機代碼
function initCamera(){ camera=new THREE.PerspectiveCamera(45,width/height,1,10000); camera.position.x=100; camera.position.y=100; camera.position.z=100; camera.up.x=0; camera.up.y=1; camera.up.z=0; camera.lookAt(0,0,0);
}
camera.position設置了相機的位置,這里是在空間坐標系中的(100,100,100)這個位置。camera.up設置了相機頭部朝向方向,這里設置了y=1,就是(0,0,0)和(0,1,0)連線作為上方,就是y軸向上,整個空間坐標系以此方向來放置。
camera.lookAt設置了相機視點方向,就是照相機往哪里拍的問題。
2.3初始化場景
只需要scene=new THREE.Scene();場景是存放光線,物體等各類構件的容器,只有在場景中的物體才能被相機拍到。
2.4初始化光源
光源一共有好多種,各類光源都有自身的特性。這里介紹幾種光源:
環境光(AmbientLight):各個位置的光線相同,無法確定光源。例:new THREE.AmbientLight( 0xff0000 );//參數:顏色;
方向光(DirectionLight):來自某一方向的光源。例:new THREE.DirectionalLight(0xFF0000,1); //參數:顏色,強度(0~1);
點光源(PointLight):光線來自某一點。例:new THREE.PointLight( 0xff0000,1,0 );//參數:顏色,光源強度(0~1),距離(從最大值衰減到0,需要的距離,默認為0,不衰減);
聚光燈(SpotLight):例:new THREE.SpotLight( 0xff0000,1,0,30,0 );//參數:顏色,光源強度(0~1),距離(從最大值衰減到0,需要的距離,默認為0,不衰減),角度(聚光燈着色的角度,用弧度作為單位,這個角度是和光源的方向形成的角度),exponent(光源模型中,衰減的一個參數,越大衰減約快。)
這里我們使用方向光:
function initLight(){ light=new THREE.DirectionalLight(0xffffff,1); light.position.set(10,10,3);//設置光源方向 scene.add(light);//將光源添加到場景中 }
2.5畫個球體
首先需要繪制一個幾何形狀,threejs里面有很多的二維和三維形狀,比如長方體(cubeGeometry),平面(PlaneGeometry),球體(SphereGeometry),圓形(CircleGeometry),圓柱體(CylinderGeometry)等,這里不展開了。
這里需要繪制個球形(SphereGeometry):
THREE.SphereGeometry( radius, //半徑 segmentsWidth, //經度上的切片數 (在起始經度和經度跨度的范圍內,像切西瓜從上往下切,切的片數) segmentsHeight, //緯度上的切片數 (在起始緯度和緯度跨度的范圍內,切西瓜從左往右切,切出來的層數) phiStart, //經度開始的弧度 phiLength, //經度跨過的弧度 thetaStart, //緯度開始的弧度 thetaLength //緯度經過的弧度 )
初始化球體代碼
var sunMesh;//太陽 function initSun(){ var geometry=new THREE.SphereGeometry(10,28,22);//球體半徑為10,經度切為28份,緯度切為22份(根據需要自行設置) var material=new THREE.MeshLambertMaterial({color:0xff0000})//使用Lambert材質,設為紅色 sunMesh=new THREE.Mesh(geometry,material);//使用網格創建物體 sunMesh.position.set(0,0,0);//設置球體位置 scene.add(sunMesh);//添加到場景中 }
關於材質這里介紹三種:
基本材質(BasicMaterial):渲染后物體的顏色始終為該材質的顏色,而不會由於光照產生明暗、陰影效果。如果沒有指定材質的顏色,則顏色是隨機的。
Lambert材質(LambertMaterial):是符合 Lambert 光照模型的材質。Lambert 光照模型的主要特點是只考慮漫反射而不考慮鏡面反射的效果,因而對於金屬、鏡子等需要鏡面反射效果的物體就不適應,對於其他大部分物體的漫反射效果都是適用的。
Phong材質(PhongMaterial):是符合 Phong 光照模型的材質。和 Lambert 不同的是,Phong 模型考慮了鏡面反射的效果,因此對於金屬、鏡面的表現尤為適合。
構造函數都要傳入配置項options,常用屬性包括:
visible :是否可見,默認為 true side :渲染面片正面或是反面,默認為正面 THREE.FrontSide ,可設置為反面THREE.BackSide ,或雙面 THREE.DoubleSide wireframe :是否渲染線而非面,默認為 false color :十六進制 RGB 顏色,如紅色表示為 0xff0000 map :使用紋理貼圖
2.6,繪制多個不同顏色的球體
使用循環體循環上面的代碼,注意根據需要修改一些參數,比如半徑和位置信息,然后將生成的球體存放到數組中方便后面動畫中使用。
生成16進制顏色
function getColor(){ //定義字符串變量colorValue存放可以構成十六進制顏色值的值 var colorValue="0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f"; //以","為分隔符,將colorValue字符串分割為字符數組["0","1",...,"f"] var colorArray = colorValue.split(","); var color="#";//定義一個存放十六進制顏色值的字符串變量,先將#存放進去 //使用for循環語句生成剩余的六位十六進制值 for(var i=0;i<6;i++){ //colorArray[Math.floor(Math.random()*16)]隨機取出 // 由16個元素組成的colorArray的某一個值,然后將其加在color中, //字符串相加后,得出的仍是字符串 color+=colorArray[Math.floor(Math.random()*16)]; } return color; }
循環新建球體
var balls=[]; function initball(){ for(var i=2;i<6;i++){ var geometry=new THREE.SphereGeometry(2+i/2,22,16); var material=new THREE.MeshLambertMaterial({color:getColor()}) ball=new THREE.Mesh(geometry,material); ball.position.set(10*i,0,0); scene.add(ball); balls.push(ball) } }
2.7繪制圓環
一種簡單的方式是使用幾何形狀圓形來繪制,缺點是會有多余的切片線。
var circles=[] function initCycle(){ //用畫二維圖形的方式畫圓 for(var i=2;i<6;i++){ var radius=10*i;//設置同心圓,只有半徑不一樣 var geometry=new THREE.CircleGeometry(radius,10);//半徑,分段數 var material=new THREE.MeshBasicMaterial({color:0xffa500,wireframe:true }) var cycleMesh=new THREE.Mesh(geometry,material); cycleMesh.position.set(0,0,0); cycleMesh.rotateX(Math.PI/2);//默認是繪制在xy平面的,所以這里要旋轉下放到xz平面 scene.add(cycleMesh); circles.push(radius) } }
另一種方式是用畫線的方式來畫圓THREE.Line
function initCycle2(){ //用畫線方式畫圓 for(var j=2;j<6;j++){ var radius=10*j; var lineGeometry=new THREE.Geometry(); for(var i=0;i<2*Math.PI;i+=Math.PI/30){ lineGeometry.vertices.push(new THREE.Vector3(radius*Math.cos(i),0,radius*Math.sin(i),0)) } var material=new THREE.LineBasicMaterial({color:0xffa500 }) var cycleMesh=new THREE.Line(lineGeometry,material); cycleMesh.position.set(0,0,0); scene.add(cycleMesh); circles.push(radius) } }
首先定義一個幾何形狀,在幾何形狀的vertices中push進每一個點(Vector3),點坐標是根據角度計算得出的。弧度制中一整個圓就是2PI的弧度,PI/180代表1角度,分成60段,每段就是PI/30的角度。再由sin和cos計算出坐標值。
現在運行下可以看到效果啦
function init(){ initThree(); initCamera(); initScene(); initLight(); initSun(); initball(); // initCycle(); initCycle2(); renderer.render( scene, camera ); }
3.讓畫面動起來
我們希望不同的球體以不同的速度做圓周運動。這里的速度應該是角速度,也就是不同的球體要在每一幀中轉過不同的角度。然后根據每個球體所在圓環的半徑來重新設置球體的坐標,球體就會運動起來了。
首先設置統一的起始角度,設置在x軸上,那么角度就是PI/2。然后設置每次轉動的角度。
var deg=Math.PI/2; function ballAnim(){ deg+=1/6*Math.PI/180;//每次轉動1/6度 balls.forEach((ball,index)=>{ var ballDeg=3*deg/(index+1);//根據索引值設置每個球體轉動不同的角度 ball.position.x=Math.sin(ballDeg)*circles[index]; ball.position.z=Math.cos(ballDeg)*circles[index]; }) }
最后是使用requestAnimationFrame來不停的刷新畫面,從而產生動畫。
function anim(){ ballAnim(); renderer.render( scene, camera ); requestAnimationFrame(anim); } //執行即可 init() anim()
4.性能監控(FPS)
引入stats.js,新建一個實例,在動畫函數anim中調用其update方法。
function setStats(){ stats=new Stats(); stats.domElement.style.position='absolute'; stats.domElement.style.left='0'; stats.domElement.style.top='0'; document.body.appendChild(stats.domElement); }
修改anim方法
function anim(){ ballAnim(); renderer.render( scene, camera ); stats.update() requestAnimationFrame(anim); }
5.完整代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo-sun</title> <style> canvas{width:100%;height:100%;} </style> <script src="./three.min.js"></script> <script src="./stats.js"></script> </head> <body> <script> var renderer,camera,scene,stats,light; var width,height; function initThree(){ width=window.innerWidth; height=window.innerHeight; renderer=new THREE.WebGLRenderer({ antialias : true //開啟抗鋸齒 }); renderer.setSize(width,height);//設置畫布大小 renderer.setClearColor(0x000000);//設置畫布背景顏色 document.body.appendChild(renderer.domElement);//將畫布追加到html文檔中 } function setStats(){ stats=new Stats(); stats.domElement.style.position='absolute'; stats.domElement.style.left='0'; stats.domElement.style.top='0'; document.body.appendChild(stats.domElement); } function initCamera(){ camera=new THREE.PerspectiveCamera(45,width/height,1,10000); camera.position.x=100; camera.position.y=100; camera.position.z=100; camera.up.x=0; camera.up.y=1; camera.up.z=0; camera.lookAt(0,0,0); } function initScene(){ scene=new THREE.Scene(); } function initLight(){ light=new THREE.DirectionalLight(0xffffff,1); light.position.set(10,10,3); scene.add(light); } var sunMesh;//太陽 function initSun(){ var geometry=new THREE.SphereGeometry(10,28,22);//球體半徑為10,經度切為28份,緯度切為22份(根據需要自行設置) var material=new THREE.MeshLambertMaterial({color:0xff0000})//使用Lambert材質,設為紅色 sunMesh=new THREE.Mesh(geometry,material); sunMesh.position.set(0,0,0);//設置球體位置 scene.add(sunMesh);//添加到場景中 } var balls=[]; function initball(){ for(var i=2;i<6;i++){ var geometry=new THREE.SphereGeometry(2+i/2,22,16); var material=new THREE.MeshLambertMaterial({color:getColor()}) ball=new THREE.Mesh(geometry,material); ball.position.set(10*i,0,0); scene.add(ball); balls.push(ball) } } function getColor(){ //定義字符串變量colorValue存放可以構成十六進制顏色值的值 var colorValue="0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f"; //以","為分隔符,將colorValue字符串分割為字符數組["0","1",...,"f"] var colorArray = colorValue.split(","); var color="#";//定義一個存放十六進制顏色值的字符串變量,先將#存放進去 //使用for循環語句生成剩余的六位十六進制值 for(var i=0;i<6;i++){ //colorArray[Math.floor(Math.random()*16)]隨機取出 // 由16個元素組成的colorArray的某一個值,然后將其加在color中, //字符串相加后,得出的仍是字符串 color+=colorArray[Math.floor(Math.random()*16)]; } return color; } var circles=[] function initCycle(){ //用畫二維圖形的方式畫圓 for(var i=2;i<6;i++){ var radius=10*i;//設置同心圓,只有半徑不一樣 var geometry=new THREE.CircleGeometry(radius,10);//半徑,分段數 var material=new THREE.MeshBasicMaterial({color:0xffa500,wireframe:true }) var cycleMesh=new THREE.Mesh(geometry,material); cycleMesh.position.set(0,0,0); cycleMesh.rotateX(Math.PI/2);//默認是繪制在xy平面的,所以這里要旋轉下放到xz平面 scene.add(cycleMesh); circles.push(radius) } } function initCycle2(){ //用畫線方式畫圓 for(var j=2;j<6;j++){ var radius=10*j; var lineGeometry=new THREE.Geometry(); for(var i=0;i<2*Math.PI;i+=Math.PI/30){ lineGeometry.vertices.push(new THREE.Vector3(radius*Math.cos(i),0,radius*Math.sin(i),0)) } var material=new THREE.LineBasicMaterial({color:0xffa500 }) var cycleMesh=new THREE.Line(lineGeometry,material); cycleMesh.position.set(0,0,0); scene.add(cycleMesh); circles.push(radius) } } var deg=Math.PI/2; function ballAnim(){ deg+=1/6*Math.PI/180;//每次轉動1/6度 balls.forEach((ball,index)=>{ var ballDeg=3*deg/(index+1);//根據索引值設置每個球體轉動不同的角度 ball.position.x=Math.sin(ballDeg)*circles[index]; ball.position.z=Math.cos(ballDeg)*circles[index]; }) } function init(){ initThree(); setStats(); initCamera(); initScene(); initLight(); initSun(); initball(); // initCycle(); initCycle2(); } function anim(){ ballAnim(); renderer.render( scene, camera ); stats.update() requestAnimationFrame(anim); } init() anim() </script> </body> </html>