记录一次使用Threejs,手写爱心雨的经历

2,534 阅读3分钟

实现效果:

image.png

剖析Threejs渲染原理

要想在Threejs中渲染物体,你就得有三个基本元素:

  • 场景Scene
  • 相机Camera
  • 渲染器Renderer

场景可以理解为世界大舞台,相机即你的眼睛,渲染器就理解为大脑,用于将你的眼睛从世界大舞台看到的东西展示出来。 在敲代码之前,还需要解决一个问题。边界如何确定? 既然要做动画,那就得确定边界的宽高,使爱心超出了边界就重置来达到好的性能,实现爱心的重复利用。

计算边界

我们使用的是Threejs中的透视相机THREE.PerspectiveCamera(fov, aspect, near, far)  那么要想计算边界,那么我们需要知道透视原理:

image.png

image.png

参数说明:

  • fov:视角,可以理解为眼睛睁开的角度,一般45到60最佳
  • aspect:宽高比
  • near:近端点
  • far:远端点

远近端点可以理解成你拿着相机去拍摄,你到相机镜头的距离是近端点,你到拍摄物体的距离是远端点。 根据透视图,会形成两个互相嵌套的等腰三角形,height就是我们的网页高度,小三角形的底边就是我们渲染后的高度。

即在Threejs的场景Scene中,是以小三角形的底边来进行计算距离的,那么我们就需要求小三角形的底边。

推导过程: 设小三角形底边为x 根据勾股定理:

tan(fov/2) = x/2/near

tan(fov/2) = height/2/far

即:

x/near = height/far => x = height/far*near

如此便可以求出Threejs中场景Scene的边界

又因为Threejs中默认相机是处于中心位置,即坐标会有正负数,为方便我们批量生成爱心,将相机移动至左上角,重新定义坐标轴。

initScene(){
  		// 页面宽高
      let w = this.$q.screen.width
      let h = this.$q.screen.height
      // 定义远近端点
      let near = 40
      let far = 1000
      // 计算渲染后宽高
      let mw = w*near/far
      let mh = h*near/far
      this.mw = mw
      this.mh = mh
  		// 定义Threejs三元素
      this.scene = new THREE.Scene()
      this.camera = new THREE.PerspectiveCamera(45,w/h,near,far)
  		// 设置相机位置,重新定义坐标轴
      this.camera.position.set(mw/2,-mh/2,near)
      this.renderer = new THREE.WebGL1Renderer()
      this.renderer.setSize(w,h)
      this.$refs.bgCanvas.appendChild(this.renderer.domElement)
      this.drawScene()
    }

批量生成爱心

在写代码之前,我们来了解一下Threejs的精灵系统: 精灵是一个总是面朝着摄像机的平面,通常含有使用一个半透明的纹理。精灵不会投射任何阴影

精灵可以通过设置图片来生成 又通过上面的操作,我们知道了渲染宽度mw,于是便可以遍历生成爱心:

// 生成三十个爱心
for (let i = 0; i < 30; i++) {
  			// 读取图片生成精灵
        new THREE.TextureLoader().load( "love.png",(image)=>{
          const material = new THREE.SpriteMaterial( { map: image } )
          const sprite = new THREE.Sprite( material )
          // 设置精灵缩放,实现大小爱心
          let zoom = randomNum(0.1,0.5)
          sprite.scale.set(zoom,zoom,zoom)
          // 根据渲染宽度mw,随机生成爱心
          let x = randomNum(0,this.mw)
          sprite.position.set(x,0.5,0)
          this.scene.add( sprite )
        })
      }

爱心掉落动画

Threejs中,有requestAnimationFrame函数为我们提供动画,一般是一秒刷新60次,即60帧。我们的确可以用setInterval,但是,requestAnimationFrame有很多的优点。最重要的一点或许就是当用户切换到其它的标签页时,它会暂停,因此不会浪费用户宝贵的处理器资源,也不会损耗电池的使用寿命。 于是,我将其封装成一个函数,达到每一个爱心都是独自的动画的效果:

drawScene(){
      for (let i = 0; i < 30; i++) {
        new THREE.TextureLoader().load( "love.png",(image)=>{
          const material = new THREE.SpriteMaterial( { map: image } )
          const sprite = new THREE.Sprite( material )
          let zoom = randomNum(0.1,0.5)
          sprite.scale.set(zoom,zoom,zoom)
          let x = randomNum(0,this.mw)
          sprite.position.set(x,0.5,0)
          this.scene.add( sprite )
          const animeScene = ()=>{
            // 动画系统
            requestAnimationFrame(animeScene)
            // 判断超出渲染高度时重置高度
            if (sprite.position.y<-this.mh){
              sprite.position.y = 0.5
            }
            // 根据缩放大小,有不同的下坠速度
            sprite.position.y-=0.2-sprite.scale.x*0.1
            this.renderer.render(this.scene,this.camera)
          }
          animeScene()
        })
      }
    }

演示网站

MyLove