背景
首先简单介绍一下three.js
,three.js
是用javascript写的基于webGL的第三方3D库,通过它可以在网页中进行3D建模,结合上TweenMax.js
动画库,在网页中实现3D动画效果就变得很简单了。
这是three.js建模的简单流程图例:
用通俗的话来讲,首先可以用js创建各种形状的几何体,或者从外部导入建好的模型文件,然后为几何体添加材质(纹理、颜色等),就组成了一个网格模型mesh,我们可以创建很多的模型,塞进场景容器里,还要为场景加入灯光,通过“打光”照相机才能拍到场景里面的物体,当要拍摄动态场景时,就需要照相机不断的拍摄然后通过渲染器不停地渲染到屏幕上(渲染循环),最后屏幕展现的就是一个3D动态场景。
既然是通过照相机去拍摄场景,让我们在屏幕上可以看到,那么移动照相机用不同的角度拍摄这个世界,自然就可以看到不一样的世界了。
three.js中的camera
three.js
中的camera分为两种,一种是正投影相机,一种是透视投影相机,两种的大致区别是:
(图片来自webGL中文网)
在透视投影下,同样大小的物体,在屏幕下远处的物体会比近处的物体小,而正投影下两者是一样大小的。
在本例demo中,我们用的是透视投影,实例化代码如下:
camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
复制
四个参数分别表示视角,看到的窗口宽度和高度的比例,能看到的最近处的距离,和能看到的最远处的距离。视角越大,拍摄到的场景范围就越大,能看到的物体就显得越小。
接着便是camera位置的设置,在初始化camera时,一般都会先设置好它的3个属性,position/up/lookAt.以demo为例讲解一下这三个属性。在这之前,我们先了解一下three.js的坐标系,使用的是右手坐标系,如下图所示:
就是这么有气质的手势~~大拇指指向x轴正方向,食指指向y轴正方向,中指指向z轴正方向。
再回来看看camera的初始化代码:
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 500;
camera.up.x = 0;
camera.up.y = 1;
camera.up.z = 0;
camera.lookAt({x : 0 ,y : 0, z : 0});
复制
- camera.position: 顾名思义,就是camera在坐标系中的所在位置,处于z轴正方向上离原点500处。
- camera.up:比较难理解,指的是camera“头顶”的方向和向量(0,1,0)同向,即和y轴正方向同向。画个图理解一下:
- camera.lookAt: 指的就是camera面向哪里,这里是直接面向原点。
另外,camera.up设置的方向必须与camera位置和lookAt的连线不能平行,否则拍不到场景,这个大家自己可以摆摆手势体会一下~
demo实现过程-计算
1.准备过程
在初始化three.js后,创建我们需要的物体,在这里我随机创建了几个正方体,它们的大小和位置都是随机的,面向屏幕的一面加载了一张图片纹理,作为正面,如下图所示:
如果从y轴正方向往y轴负方向看,示意图大致是这样子的(蓝色代表正方体,有粗线加箭头的一面代表正方体有图片的正面,黑色的圆柱体代表照相机,箭头指明拍摄方向):
接下来便是动画过程,需要注意的是,接下来的都是物体只绕y方向旋转,x/z方向只做位移,这样就把一个三维空间的运动转化为二维空间了~~
2.将照相机移到y轴上,旋转正方体和照相机使之正对,如下图所示:
在初始化时,我们便记录了正方体的坐标值(x,y,z),正方体从面向屏幕到面向y轴要旋转多少角度,我这里用了初中数学方法——反三角函数算出。如下图所示分别为四个象限物体需要旋转的角度值。
旋转了正方体后,照相机只要和正方体旋转同样的角度,并坐标中的y值移到和正方体同向,就可以拍摄到正方体正面了。
- 因为之前所说的正方体是大小不一的,需要移动照相机使得照相机和正方体的距离与正方体的大小保持一定的比例,这样看到每个正方体的大小才是一致的。所以需要计算照相机的位移,如下图:
我们已知正方体的边长a,假设照相机与正方体中心点的距离设置为3a,利用相似三角形边长等比例的原理得出照相机的该移到的地方(x2,z2)坐标,公式如下:
- 为了让正方体具备随机摆放的感觉,还可以让正方体再绕y方向随机转动一定角度,照相机再绕正方体的中心点旋转到正对正方体正面的位置:
计算方法如下:
如上图所示,照相机原来是在正方体中心和原点的连线上的坐标(x,z),绕正方体的中心点(rx,rz)旋转随机角度θ后得到的(x',z')的计算公式如下:
以上的角度计算过程,都可以用js中强大的Math函数搞定,包括Math.atan()\math.cos()等等,具体代码就不贴了。
demo实现过程-动画
上面过程中的第2、3步,直接调用TweenMax.js的动画库,控制camera.position/camera.rotation就可以了。但是第4步由于不能想css一样直接设定transform-origin为正方体中心点,所以必须自己实现。例如控制位移:
TweenMax.to(
camera.position, //指明控制的属性
2, //动画时间
{x:x2,y: mesh[index].position.y,z:z2,delay:0,ease:Cubic.easeIn} //需要移动的距离
);
复制
这里用到的是requestAnimationFrame()来实现,通过一点点地改变camera的旋转和位置的移动,直到达到角度θ。
var rate; //旋转频率
function cameraRoAni(){
var ani;
//当物体的旋转角度大于0
if (rotateMesh[index-1] > 0) {
rotateRate = rotateRate + rate;
if(rotateRate <= rotateMesh[index-1]){
var newX = (camera.position.x - mesh[index-1].position.x)*Math.cos(-rate) - (camera.position.z - mesh[index-1].position.z)*Math.sin(-rate) + mesh[index-1].position.x;
var newZ = (camera.position.x - mesh[index-1].position.x)*Math.sin(-rate) + (camera.position.z - mesh[index-1].position.z)*Math.cos(-rate) + mesh[index-1].position.z;
camera.position.x = newX;
camera.position.z = newZ;
camera.rotation.y += rate;
ani = requestAnimationFrame(cameraRoAni);
}else{
cancelAnimationFrame(ani);
}
//当物体的旋转角度小于0
}else{
... //代码和大于0时相似
}else{
cancelAnimationFrame(ani);
}
}
if (rate>0.01) {
rate = rate - 0.0001;
}
}