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>
  • 相关阅读:
    数据库学习笔记5---MySQL字符串函数、日期时间函数
    关于hibernate的AnnotationConfiguration的问题
    浅谈Java web 中request的setAttribute()用法
    JAVA常见面试题之Forward和Redirect的区别
    JSP页面中<%!%>与<%%>与<%=%>
    Servlet的生命周期
    JavaEE学习路线图
    java web项目WEB-INF与META-INF的作用
    iOS-申请邓白氏编码的超详细流程介绍
    从高版本JDK换成低版本JDK报错
  • 原文地址:https://www.cnblogs.com/scdisplay/p/9305230.html
Copyright © 2011-2022 走看看