在渲染出来一个物体之后,就可以尝试将他进行变换和动画了
变换transform
物体的变换主要有三个方面:position位置变换,scale缩放变换和rotation旋转变换
position
position对象继承自Vector3类,vector(矢量),用这个类来定义空间中物体的位置信息
position的属性x,y,z是这个物体在坐标轴的位置mesh.position.x=0.7
position设置物体位置的简写形式:mesh.position.set(x,y,z)
position.length()方法是物体到场景中心的距离:mesh.position.length()
position.distanceTo(一个Vector3类或另一个物体)方法是两个物体之间的距离:mesh.position.distanceTo(new THREE.Vector3(0,1,2))
或mesh.position.distanceTo(camera.position)
position.normalize()方法是把物体的向量归一化,也就是把他到场景中心的向量长度减小到1,但是向量方向不发生变化:mesh.position.normalize()
,此时,mesh.position.length()
就为1了
scale
缩放就很简单了 x轴方向拉长2倍:mesh.scale.x=2
同样设置缩放也有简写形式:mesh.scale.set(2,0.5,0.5)
rotation
旋转有两种形式,一种是rotation,一种是quaternion,quaternion在three.js journey中并没有过多涉及,但是rotation变化同样或导致quaternion变化,两者是同步的
旋转也涉及x,y,z轴,可以把轴想象成一根穿过物体中心的棍子,固定物体的旋转方向,旋转值是用弧度表示的,也就是说,旋转180°是3.14159...,也可以用原生javascript的Math.PI表示mesh.rotation.y=Math.PI/2
这里要注意,当物体沿着一个轴旋转的时候,可能除了固定物体的轴之外的其他轴之外其他轴也被旋转了,当有多个方向的旋转时,默认的旋转顺序是x,y,z
如果在旋转过程中,想要沿一个轴旋转却并没有成功,那么这个轴叫做"万象节锁",解决万向节锁的方法是改变旋转轴的先后顺序,使用reorder()方法
ini
mesh.rotation.reorder("YXZ")
mesh.rotation.y=Math.PI*0.25
mesh.rotation.x=Math.PI*0.25
可以添加上reorder()方法,然后再去掉对比,会发现最终物体的方向是不同的,这就是因为物体旋转的先后顺序不同导致的
相机的lookAt()
相机看向的方向默认是场景的中心,那么,如果想要让相机看向某个物体或者某个坐标点呢?这就要用到lookAt()方法了 camera.lookAt(mesh.position)
或者camera.lookAt(new THREE.Vector3(3,0,0))
组group
在3d场景中,如果想要让一群物体整体向右移动或者整个放大,一个一个操作未免有些复杂,也不一定能控制到一样的程度,这就需要将这些物体放在一个组内,成为一个组内的成员
创建一个组:const group=new THREE.Group()
把组放在场景中:scene.add(group)
创建一些组内的成员:const cube1=new THREE.Mesh(形状,材质)
把组内成员放进组里:group.add(cube1)
这时,若要整体变换组内所有成员,可以对组进行变换:group.rotation.z=Math.PI*0.25
动画animation
先说一下动画涉及的概念:fps是每秒的帧数,也叫帧率,每个电脑因为性能的不同,帧率也不同
而动画要用到一个原生js方法,window.requestAnimationFrame(函数)
表示下一帧要执行的函数,也就是说动画的速度和帧相关,是每一帧动画做到什么行为
scss
//动画函数
const tick=()=>{
//物体每帧旋转弧度+0.01
mesh.rotation.x +=0.01
//每帧的变化都要渲染出来
renderer.render(scene.camera)
//下一帧要再次执行tick函数,让物体每帧都变化
window.requestAnimationFrame(tick)
}
tick()
帧率不同就导致一个问题,我们期望的动画是不论在什么帧率下都有一样的动画速度,可是,如果我们要移动一个物体,如果每帧移动10cm,如果帧率很高,那么物体就会移动的很快,如果帧率很低,那么物体移动缓慢,物体变换的速度在每个电脑上很有可能是不一样的,那么人的体验也是不同的,所以,就需要做一些事情,让物体移动跟帧率无关,也就是让动画适应帧率
有两种方法,在下面会写出来:原生js方法和three.js的方法
使用原生js的方法
原生js使用Data.now()能获取当前的时间戳,所以,可以获取每帧的动画开始的时间戳,时间戳的差就是每帧之间的时间差,将动画的速度和每帧的时间差相关联,那么帧率高的帧与帧之间的时间差就短,一帧的时间内变换的就少,但是帧率高,在固定时间内其实是一样的变换效果
ini
const time=Date.now()
const tick=()=>{
const currentTime=Date.now();
const deltaTime=currentTime-time;
mesh.rotation.x +=0.01*deltaTime;
renderer.render(scene,camera);
window.requestAnimationFrame(tick)
}
tick()
使用three.js的方法
three.js中有一个方法能获得一个时间段 先实例化Clock类得到一个的对象:const clock=new THREE.Clock()
使用这个对象的getElapsedTime()
方法:const elapsedTime=clock.getElapsedTime()
,这里获取的时间是从实例化Clock到现在的时间,单位是秒,物体变换的值与事件有关,也就与帧率无关了
javascript
const clock=new THREE.Clock()
const tick=()=>{
//elapsedTime这里也就是一帧的时间
const elapsedTime=clock.getElapsedTime()
//这里应该是每秒旋转一圈
mesh.rotation.y=elapsedTime*Math.PI*2
renderer.render(scene.camera)
window.requestAnimationFrame(tick)
}
tick()
注:Clock还有一个getDelta()
方法,但是不要使用他,用以出问题
如果还想要有更多的控制,比如创建一个双胞胎物体,创建时间先等,可以使用gsap这个库 npm i gsap@3.5.1
gsap有自己的tick函数,他在内部自己执行请求任务,所以我们的tick只需要不停的渲染就可以
gsap.to()方法,参数:第一个是动画的物体的position,第二个参数是一个对象,在这个对象中有不同属性的目标 gsap.to(mesh.position,{x:2,duration:1,delay:1})