实现效果:
剖析Threejs渲染原理
要想在Threejs中渲染物体,你就得有三个基本元素:
- 场景Scene
- 相机Camera
- 渲染器Renderer
场景可以理解为世界大舞台,相机即你的眼睛,渲染器就理解为大脑,用于将你的眼睛从世界大舞台看到的东西展示出来。 在敲代码之前,还需要解决一个问题。边界如何确定? 既然要做动画,那就得确定边界的宽高,使爱心超出了边界就重置来达到好的性能,实现爱心的重复利用。
计算边界
我们使用的是Threejs中的透视相机THREE.PerspectiveCamera(fov, aspect, near, far) 那么要想计算边界,那么我们需要知道透视原理:
参数说明:
- 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()
})
}
}