zoukankan      html  css  js  c++  java
  • 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>
  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 黑色星期五
    Java实现 蓝桥杯VIP 算法训练 比赛安排
    Java实现 蓝桥杯VIP 算法训练 比赛安排
    Java实现 蓝桥杯VIP 算法训练 斜率计算
    Java实现 蓝桥杯VIP 算法训练 斜率计算
    Java实现 蓝桥杯VIP 算法训练 整数平均值
    Java实现 蓝桥杯VIP 算法训练 整数平均值
    控件动态产生器(使用RegisterClasses提前进行注册)
    Delphi编写自定义控件以及接口的使用(做了一个TpgDbEdit)
    Log4delphi使用心得
  • 原文地址:https://www.cnblogs.com/scdisplay/p/9305230.html
Copyright © 2011-2022 走看看