three.js基础学习综合复习

这是three.js学习的第20篇笔记,也是基础学习的最后一篇笔记。这篇笔记会综合复习一下已经学习过的内容。因为three.js还有很多的知识,所以这篇笔记里面还是会继续有干货出现。同时,这一篇笔记我们会重新从0到1做一个完整three.js应用。

在开始之前,新建一个叫learnThree20.html的文件,除了基本的设置,什么都不要做,也不要复制基础代码。我们真的会从零开始做。做完之后,咱们开始复习吧。

跟着这篇笔记完整走下去,会做出下面这样一个完整的应用。

  • 认识three.js应用结构

    threejs学习第一课:了解threejs的基本概念中,我们通过下图知道了three.js的基本应用结构。

    threejs应用结构图

    一个three.js应用,主要由渲染引擎Renderer, 场景Scene, 相机Camera三部分组成,而一个场景Scene则由以Mesh为基础的各种物体和光源light组成。我们最近学习的,都是这些基础的东西。

  • 认识和创建three.js应用中的renderer

    threejs学习第二课:hello three.js中,我们创建了第一个renderer

    renderer是由THREE.WebGLRenderer构造函数实例化而来。在实例化的过程中,THREE.WebGLRenderer接收一个可选参数,这个参数是一个对象。对象里面可以放一系列参数。其中,我们用到最多的是canvas。这个canvas会被three.js用来绘制内容。当然,也可以啥都不传,这时候three.js会自己创建一个canvas,然后我们可以通过实际话后的变量属性domElement获取到,比如const canvas = renderer.domElement,这样就获取到了three.js自己创建的canvas。

    虽然three.js总是能自动创建canvas,我还是更喜欢将canvas先创建好后传入THREE.WebGLRenderer来构建渲染引擎,感觉这样做对canvas的控制更灵活一点。

    现在,我们就来创建一个渲染引擎renderer吧。

    1.我们在body标签中间新建一个canvas标签,设置属性id='learnThree'.

    1
    2
    3
    <body>
    <canvas id='learnThree'></canvas>
    </body>
    1. 设置canvas的样式,让它全屏显示。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <head>
      ···
      <style>
      html,body{
      height: 100%;
      margin: 0;
      }
      #learnThree{
      display: block;
      width: 100%;
      height: 100%;
      }
      </style>
      ···
      </head>
    2. 新建一个main函数并且实例化渲染引擎
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <body>
      ···
      <script>
      function main () {
      const canvas = document.querySelector('#learnThree')
      const renderer = THREE.WebGLRenderer({canvas})
      }
      </script>
      </body>
    3. main函数内新建一个render函数用来执行渲染动作

    three.js基础学习之WebGLRenderer中,我们学习了WebGLRenderer的渲染方法render,他会通过camera相机将scene场景渲染到canvas上,从而展现出来。

    1
    2
    3
    4
    5
    6
    function main () {
    ···
    function render () {
    renderer.render(scene, camera) // scene camera后面会复习到
    }
    }
  • 认识和创建three.js应用中的相机camera

    threejs学习第二课:hello three.js我们通过PerspectiveCamera透视相机创建了一个相机。

    PerspectiveCamera透视相机是用来模拟人眼看到的景象,在创建时需要传入四个参数,分别是fov视野范围,aspect相机的长款比, near相机距离画面的最近距离,far相机可显示画面的最远距离, 这四个参数一起构成了视锥,如下图所示。

    threejs中的视锥

    详细的可以点击 threejs学习第二课:hello three.js回去查看。

    现在,我们在main函数中新增一个相机

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function main () {
    ...
    const fov = 70
    const aspect = 2
    const near = 0.1
    const far = 500
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
    ...
    }
  • 认识和创建three.js应用中的场景scene

    场景就像一个戏台,提供场所,让演员来尽情唱戏。three.js应用中所有的影响,都需要添加到场景中,才能够展示出来。而构建场景非常简单,只需要实例化THREE.Scene即可。所以,我们接下增加一个场景吧。

    1
    2
    3
    4
    5
    function main () {
    ...
    const scene = new THREE.Scene()
    ...
    }

    创建完后我们可以运行main函数看看。这时候没有意外的话,应该会看一片漆黑。这是因为咱们现在还什么都没放进去。

  1. 材质material
  2. 纹理贴图Texture

    通过这些学习,我们知道,一个3d物体想展示出来,需要通过mesh将图元geometry和材料Material结合起来。

    现在,我们就来创建three.js的3D世界吧。
    1.使用PlaneGeometry 创建一个平面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function main () {
    ...
    {
    const width = 10
    const height = 10
    const planeGeometry = new THREE.PlaneGeometry(width, height)
    const planeMaterial = new THREE.MeshBasicMaterial({
    color: 0xffffff
    })
    cosnt plane = new THREE.Mesh(planeGeometry, planeMaterial)
    scene.add(plane)
    }
    ...
    }

    这个时候,我们刷新查看,发现还是黑漆漆的一片,啥都看不见。这是什么原因呢?

    因为我们three.js中新构建的物体,不管是camera还是light,还是其它形状,默认位置都在(0,0,0)处。因此,我们刚刚新建的平面和camera重叠在了一起。要解决这个问题,我们只需要移动相机或者plane即可。

    2.利用Group统一管理形状
    在Three.js中,我们可以使用Group来统一管理一些东西。可以使用add方法添加新的对象到里面去,如果想移除,则使用remove方法。
    这里,我们新建一个Group,并且移动位置让前面新建的平面显示出来。

    function main () {

    ...
    const group = new THREE.Group()
    group.position.z = -10
    group.position.y = -1
    {
        const width = 15
        const height = 15   
        const planeGeometry = new THREE.PlaneGeometry(width, height)
        const planeMaterial = new THREE.MeshBasicMaterial({
            color: 0xffffff
        })
        cosnt plane = new THREE.Mesh(planeGeometry, planeMaterial)
        group.add(plane)
    }
    scene.add(group)
    ...
    

    }

    1. 调整旋转平台,让平面变成一个平台
      1
      2
      3
      4
      5
      6
      7
      ...
      cosnt plane = new THREE.Mesh(planeGeometry, planeMaterial)
      plane.rotation.x = Math.PI * -0.5
      plane.position.z = -3
      plane.position.y = -4
      group.add(plane)
      ...
      现在,能够看到一个平台,不过平台上充满了锯齿,改变屏幕大小形状也会变的乱七八糟。我们接着解决问题吧
  • three.js响应式设计开发之屏幕适配和自适应

    在(threejs响应式设计开发)[/three/learnThree3.html]中,我们了解到,canvas的展示和图像的展示差不多,当图片的尺寸小于实际展示尺寸时,就会出现虚化和锯齿的现象。因此为了让canvas能清晰的将图像显示出来,我们需要确保显示的canvas和canvas本身大小一致。为了考虑视网膜屏幕,我们在确保显示canvas大小和实际大小的同时,还得考虑屏幕像素比。下面我们来写自适应的代码。

    1.检测是否需要调整canvas尺寸

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function main () {
    ...
    function checkNeedResize () {
    const pixelRatio = window.devicePixelRatio;
    const needShowWidth = canvas.clientWidth * pixelRatio;
    const needShowHeight = canvas.clientHeight * pixelRatio;
    const width = canvas.width;
    const height = canvas.height;
    return needShowWidth !== width || needShowHeight !== height
    }
    ...
    }
    1. 如果需要调整屏幕尺寸,在渲染之前调整
      three.js基础学习之WebGLRenderer中,我们学到过一个renderer.setSize(width,height,updateStyle)方法,现在,如果需要调整尺寸,我们就用它。

      当我们调整完canvas的尺寸后,摄像机的长款比aspect也应该进行对应的调整。而相机的大多是属性调整后,我们都应该调用updateProjectionMatrix方法来使它生效。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      function main () {
      ...
      function checkNeedResize () {
      const pixelRatio = window.devicePixelRatio;
      const needShowWidth = canvas.clientWidth * pixelRatio;
      const needShowHeight = canvas.clientHeight * pixelRatio;
      const width = canvas.width;
      const height = canvas.height;
      return needShowWidth !== width || needShowHeight !== height
      }

      function render () {
      if (checkNeedResize()) {
      const pixelRatio = window.devicePixelRatio;
      const width = canvas.clientWidth * pixelRatio
      const height = canvas.clientHeight * pixelRatio
      renderer.setSize(width, height, false)
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
      }
      renderer.render(scene, camera) // scene camera后面会复习到
      }
      ...
      }

      做完这些后,我们已经得到了一个高清的平面了,现在,我们在它上面搭建点东西吧。

  • 用three.js建立一个圆柱子

    three.js学习之常见几何体圆柱和球我们学习了圆柱的建立方法,现在我们就在白板上新建一个圆柱吧。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function main () {
    ...
    {
    const radiusTop = 0.3;
    const radiusBottom = 0.3;
    const height = 6
    const cylinderGeometry = new THREE.CylinderGeometry(radiusTop, radiusBottom, height);
    const cylinderMaterial = new THREE.MeshBasicMaterial({color: 0xFFEC8B})
    const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial)
    group.add(cylinder)
    }
    ...
    }

    现在,我们做出来的东西已经像是在一个广场上放置了一根柱子,接下来咱们再在上面放一个顶吧。

  • 用three.js建立一个圆形拱顶

    three.js学习之常见几何体平面圆形和圆锥中,我们学习了如何建立一个圆锥。接下来,咱们一起给圆柱安一个顶子吧。

    1. 利用ConeGeometry,创建一个圆形拱顶
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      function main () {
      ...
      {
      const radius = 5;
      const height = 2
      const radialSegments = 100;
      const heightSegments = 5;
      const coneGeometry = new THREE.ConeGeometry(radius, height, radialSegments, heightSegments, true, 0 , 2 * Math.PI);
      const coneMaterail = new THREE.MeshBasicMaterial({color: 0x8B814C})
      const cone = new THREE.Mesh(coneGeometry, coneMaterail)
      cone.position.y = 2.5
      group2.add(cone)
      }
      ...
      }
    2. 给圆拱加上线条让它看起来美观点
      three.js学习之盒子中我们学习了如何将一个物体用线条显示出来,如何在一个物体上显示线条。接下来我们给圆顶加上金色线条吧。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      function main () {
      ...
      {
      const radius = 5;
      const height = 2
      const radialSegments = 100;
      const heightSegments = 5;
      const coneGeometry = new THREE.ConeGeometry(radius, height, radialSegments, heightSegments, true, 0 , 2 * Math.PI);
      const coneMaterail = new THREE.MeshBasicMaterial({color: 0x8B814C})
      const cone = new THREE.Mesh(coneGeometry, coneMaterail)
      cone.position.y = 2.5

      const lineMaterial = new THREE.MeshBasicMaterial({
      color: 0xFFEC8B
      })
      const lineGeometry = new THREE.WireframeGeometry(coneGeometry)

      const line = new THREE.Line(lineGeometry, lineMaterial)
      line.position.y = 2.5

      group2.add(cone, line)
      }
      ...
      }
  • 让three.js应用动起来

    虽然线条已经加进去了,但还是看起来有点僵硬,咱们加一个动画让它转起来吧。
    threejs学习第二课:hello three.jsz中,我们学习了通过requestAnimationFrame()来为three.js添加动画。

    1. 新建分组group2,并将圆柱和圆形顶都放到group2中,将group2添加到scene中
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      ...
      const group = new THREE.Group()
      group.position.z = -10
      group.position.y = -1
      const group2 = new THREE.Group()
      group2.position.z = -10
      group2.position.y = -1
      ...

      group2.add(cylinder)
      ...
      group2.add(circle, line)
      ...
      scene.add(group2)
      ...
    2. 添加动画

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
                function main () {
      ...
      function render (time) {
      if (checkNeedResize()) {
      const pixelRatio = window.devicePixelRatio;
      const width = canvas.clientWidth * pixelRatio
      const height = canvas.clientHeight * pixelRatio
      renderer.setSize(width, height, false)
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
      }
      time = time * 0.0001
      group2.rotation.y = time
      renderer.render(scene, camera)
      requestAnimationFrame(render)
      }
      requestAnimationFrame(render)
      ...
      }
      ```
      现在我们已经做出了一个旋转的类似伞的东西,接下来再在伞下面加一个随着伞转动的简易四轮车吧。

      * ## three.js创建一个简易四轮车

      1. 创建一个车的整体
      就像我们的伞一样,车也是一个整体,所以我们在这个里利用`Object3D`创建一个car实体。因为我们想要车跟着伞的边缘一起转圈,所以将car放到group2中去。

      function main () {

      const group2 = new THREE.Group()
      group2.position.z = -10
      group2.position.y = -1

      const car = new THREE.Object3D()
      group2.add(car)

      }

      1
      2. 利用BoxGeometry制造车体

      function main () {

      ...
      { // 车整体的作用域
          car.position.set(3.5,-3,0)
          {
              const carWidth = 1
              const carHeight = 0.5
              const carDepth = 1.5
              const carBodyGeometry = new THREE.BoxGeometry(carWidth, carHeight, carDepth) 
      
              const curMaterial = new THREE.MeshBasicMaterial({color: 0x87CEFA})
      
              const carBody = new THREE.Mesh(carBodyGeometry, curMaterial)
              car.add(carBody)
          }
      }
      ...
      

      }

      1
      2
      3
      4
       现在,刷新代码,应该能看到一个蓝色的方块在跟着伞转了。要是汽车真是个盒子,那估计没几天就废。接下来加个轮子吧

      3. 利用TorusGeometry创建轮子

      function main () {

      ...
      { // 车整体的作用域
          ...
          {
              function makeRing (x, y, z) {
                      const radius = 0.2;
                      const tube = 0.05;
                      const radialSegments = 50
                      const tubularSegments = 50
                      const torusGeometry = new THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments)
                      const torusMaterial = new THREE.MeshBasicMaterial({color: 0x000000})
                      const carRing = new THREE.Mesh(torusGeometry, torusMaterial)
                      carRing.rotation.y = 0.5 * Math.PI
                      carRing.position.set(x, y, z)
                      car.add(carRing)
                  }
                  makeRing(-0.55, 0.11, 0.3)
                  makeRing(-0.55, 0.11, -0.5)
                  makeRing(0.55, 0.11, -0.5)
                  makeRing(0.55, 0.11, 0.3)
              }
      
          }
      }
      ...
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
          现在,我们的小车已经绕着亭子转了。接下来我们给底部的平面加个纹理贴图,感觉起来亭子在草地上,车在草地上跑。

      * ## three.js纹理贴图
      * [three.js学习之纹理贴图Texture](/three/learnThree13.html)
      * [three.js学习之纹理贴图Texture(二)纹理背景的位置移动旋转和重复](/three/learnThree14.html)
      上面两篇中,我们学习了纹理贴图,现在我们去给平面贴上图。
      我找了这张图,现在我们将它放上去![](https://picture.wuhoushu.com/blog/threejs/5d00541d75311.jpeg)

      修改平面代码

      {

      const width = 15
      const height = 15
      const planeGeometry = new THREE.PlaneGeometry(width, height)
      const loader = new THREE.TextureLoader() // 新增
      const planeMaterial = new THREE.MeshBasicMaterial({
          // color: 0xffffff 删除代码
          map: loader.load('https://picture.wuhoushu.com/blog/threejs/5d00541d75311.jpeg') // 新增
      })
      const plane = new THREE.Mesh(planeGeometry, planeMaterial)
      plane.rotation.x = Math.PI * -0.5
      plane.position.z = -4
      plane.position.y = -4
      group.add(plane)
      

      }

      1
      2
      3
      4

      到这里,我们three.js基础学习就结束了。下面一如即往的奉上源码。
      大家也可以点击[https://www.91yqz.com/learnThree/learnThree20.html](https://www.91yqz.com/learnThree/learnThree20.html)这个页面查看

      <!DOCTYPE html>


      three.js学习之从0开始做一个three.js应用