threejs行星運動小demo總結


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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM