前言
经过上一小节,基础场景已经有了,现在我们来探索Three.js的一些功能。
让三维场景中的3D对象发生变换,有很多方式,甚至不需要操作3D对象本身。比如在前一小节中,我们已经通过使相机向后移动camera.position.z = 3
来实现了立方体的缩小。
任意的3D对象都有4个用于变换的属性
position
(在三个轴向上移动)
scale
(在三个轴向上缩放)
rotation
(在三个轴向上旋转)
quaternion
(四元数,也是用于处理旋转的)
所有继承自Object3D的子类都具有这些属性,比如PerspectiveCamera
或Mesh
之类也都有。我们可以从Three.js的文档中看到类的继承关系。
这些属性最终将被转换成我们对应的矩阵数值。Three.js
,WebGL
和GPU
内部都使用矩阵Matrix来进行变换。不过还好,我们并不需要自己去计算矩阵,只需修改前面提到的属性即可。
准备工作
打开上一小节中最后的项目,可以看见在漆黑的场景中有一个红色的立方体,虽然它现在看起来只是一个正方形。
移动
position
位置属性又有3个基本变量,x,y和z。这些是在3D空间中用于定位的3个轴向。
每个轴的方向并不能单纯的用水平垂直纵深去描述,因为它可以根据环境而变化,比如旋转。在Three.js中采用右手笛卡尔坐标系,y轴向上,z轴向后,x轴向右。
这些变量的值,是向量单位,也就是1到底代表多少完全由我们自己决定。1可以是1厘米,1米,甚至1公里。
现在让我们来随意的修改一下这个立方体的position属性,没事,尽管玩,但每一次修改尽量只改变一个轴的值,方便我们观测这个值产生的效果。
由于我们看见的画面都是经过调用渲染器渲染出来的,所以要确保在进行变换后调用一次渲染器的渲染方法render(...)
mesh.position.x = 0.7
mesh.position.y = - 0.6
mesh.position.z = 1
position熟悉除了x,y,z以外还有很多实用的方法
比如通过length方法可以得到一个向量的长度:
console.log(mesh.position.length())
比如通过distanceTo计算和另一个向量坐标的距离:
console.log(mesh.position.distanceTo(camera.position))
再比如通过normalize归一化来仅保留方向特性:
console.log(mesh.position.normalize())
或者通过set方法来一次性更改x,y,z三个值:
mesh.position.set(0.7, - 0.6, 1)
轴辅助工具
在三维空间中,要确切的知道一个3D对象的轴向并不简单,尤其当我们旋转移动过相机之后。
不过,好在我们可以使用Three.js提供的轴辅助工具 AxesHelper。
AxesHelper 将始终显示与x,y和z轴相对应的3个轴向指示,每一个轴向的指示都从场景的中心开始并沿相应的方向延伸。
创建AxesHelper,并将其添加到场景中。我们可以设置轴向指示的长度,比如2:
/**
* Axes Helper
*/
const axesHelper = new THREE.AxesHelper(2)
scene.add(axesHelper)
创建后,我们应该看能到一条绿线y轴和红线x轴。还有一条蓝色的线z轴,不过由于目前它和相机的位置完全对其,所以我们看不见它。
一般情况下,我们不会使用这个轴辅助工具,当我们在三维世界中迷失方向的时候,才会用它来提供视觉辅助。
缩放
缩放也是一个具有x,y,z三个变量的向量对象。在创建3D对象时,默认的缩放比例x,y和z皆为1,就是没有缩放的意思。如果将设置某一个轴的值为0.5,则对象在该轴上将是原大小的一半,如果设置为2,则在该轴上将是原大小的2倍。
mesh.scale.x = 2
mesh.scale.y = 0.25
mesh.scale.z = 0.5
旋转
有两种处理旋转的方法,使用rotation
很简单直观,而使用quaternion
会相对麻烦一些。但使用任意方法旋转时,两种方法对应的值都会自动更新。
使用rotation
rotation属性也具有x,y和z三个变量,和移动、缩放不同,这里的值是旋转角度。让我们逐个改变三个轴向的旋转角度,然后对照轴辅助工具来观察旋转是如何生效的。
mesh.rotation.x = Math.PI * 0.25
mesh.rotation.y = Math.PI * 0.25
挺容易的对不对?但是这里面有个坑,当我们同时旋转多个轴时可能会得到一些意想不到的结果。因为,当你旋转x轴时,也会改变其他轴的方向。因而我们可以通过使用reorder(...)
方法对象来更改旋转轴的应用顺序。比如rotation.reorder('yxz')
,就将先执行y轴的旋转。
虽然rotation使用起来更容易理解,但这个顺序问题挺讨厌的。这就是为什么大多数引擎和3D软件使用另一种名为Quaternion的解决方案的原因。
使用quaternion
quaternion
四元数这个属性也用于旋转,但它是一种更加数学的方式,并且能解决顺序问题。
不过在本课程中,我们并不会学习quaternion
四元数的工作原理,但请记住,当我们更改rotation时,四元数也会更新。我们可以随意使用两者中的任何一个。
使用lookAt
Object3D这个类有一个名为lookAt(...)
的方法,这个方法可太好用了。它可以让指定的3D物体自动旋转朝向一个坐标,不需要我们去计算角度。
我们可以使用它轻而易举的将相机转向某个3D物体,或在游戏中将大炮面向敌人,亦或将角色的视野移到某个对象上。
这个方法接受一个vector3的对象作为参数,也就是三维坐标:
camera.lookAt(new THREE.Vector3(0, - 1, 0))
这个立方体看上去移动到了更高的位置,但实际上,相机的视点正位于立方体的下方。
当然我们也可以使用任何现有的3D对象的position作为参数:
camera.lookAt(mesh.position)
组合应用变换
我们可以任意组合位置、旋转 (或四元数) 和缩放。结果和赋值这些变量的顺序无关。试着同时应用前面学到的几个变换属性吧:
mesh.position.x = 0.7
mesh.position.y = - 0.6
mesh.position.z = 1
mesh.scale.x = 2
mesh.scale.y = 0.25
mesh.scale.z = 0.5
mesh.rotation.x = Math.PI * 0.25
mesh.rotation.y = Math.PI * 0.25
成组
大部分真正的业务场景中,不会只有一个立方体。比如我们在三维世界里搭建一所房子,其中有墙壁,门,窗户,屋顶等各种3D对象。假设我们觉得房子有点小的时候,是否需要重新缩放每个对象并重新设置它们的坐标呢?如果是这样,那也太麻烦了。
这个时候就需要Group成组,也可以把它理解为一个单纯的容器。
所以,当我们想对很多3D对象同时进行缩放时,将所有这些3D对象都放到一个Group中,再对这个Group进行缩放即可。
实例化一个Group并将其添加到Scene场景中。当我们再创建新的3D对象时,可以直接将它直接add (...)
到刚刚创建的Group中,而不是将其添加到场景中。
由于Group类也继承自Object3D类,因此前面提到的属性和方法,例如位置,比例,旋转,四元数和lookAt都可以作用在Group上。
/**
* Objects
*/
const group = new THREE.Group()
group.scale.y = 2
group.rotation.y = 0.2
scene.add(group)
const cube1 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
cube1.position.x = - 1.5
group.add(cube1)
const cube2 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
cube2.position.x = 0
group.add(cube2)
const cube3 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
cube3.position.x = 1.5
group.add(cube3)
现在我们知道如何变化3D对象了,下一小节我们将学习如何创建动画。