前言
在前面的小节中我们已经用过PerspectiveCamera
来创建相机,在本小节中我们来系统的学习Three.js中各种类型的相机以及学习如何添加相机控制器。
Camera相机
Camera相机类就是我们所说的抽象类,它用于定义相机的基本属性和方法,我们不应该直接使用它,一般情况下我们直接使用以下这些继承Camera类的相机。
ArrayCamera阵列相机
ArrayCamera可以帮我们方便的在一张画布上渲染多个相机视角,这有点像在监控室的大屏幕上看多个摄像头或者是在同一台电脑上玩多人游戏时的分屏显示。
StereoCamera立体相机
StereoCamera立体相机,这个相机很好玩,我们可以很简单的将场景渲染出需要带VR / 3D眼镜才可以观看到的立体效果。
CubeCamera立方体相机
CubeCamera必须配合WebGLCubeRenderTarget一起使用,这个相机主要用于将当前场景的画面实时渲染成一个六面图(全景图的一种),用于创建类似镜面反射的效果。
OrthographicCamera正交相机
三维世界遵循近大远小的规则,但我们有的时候希望建立一种类似2.5D游戏的效果,比如暗黑破坏神,疯狂医院等等。这个时候就可以使用正交相机。
PerspectiveCamera透视相机
PerspectiveCamera透视相机前面我们已经用过了,这个相机遵循真实物理世界近大选小的透视效果。在大部分需要3D的业务场景,主要都会使用这个透视相机。
透视相机
在基础场景一节中我们已经创建过了透视相机,创建时会用到4个参数。
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 100)
这四个参数分别是什么意思呢?
1. 视角FOV
第一个参数叫视角( Field of View )
,数值越大,可视角度就越大,和现实生活中的广角镜头是一样的。我们使用透视相机时比较常用的视角数值为45或75。
2. 宽高比
第二个参数叫做宽高比,一般情况下用画面的宽度除以高度即可。
3. Near 和 far
第3和第4个参数分别影响相机可视的近视距离和远视距离。相机的近视值我们一般设置为0.1,而远视值将影响渲染性能,因为三维场景中的所有3D对象,如果不在这个近和远的范围里,就不会在渲染时计算进去。所以在真实的业务场景中,我们根据需求合理的设置这个数值。
正交相机
虽然本课程中也很少使用这个相机类型,但如同前面介绍时所描述的,正交相机适用于一些特别的场景,所以还是带大家用用它。
在使用正交相机时,无论物体与相机的距离如何,物体都将具有相同的尺寸,没有近大远小这个特性。创建正交相机时所需要的参数和透视相机也有很大不同。
我们必须提供4个方向参数 (左面,右面,顶面和底面) 上可以看到的距离。然后是近视距离和远视距离。
const camera = new THREE.OrthographicCamera(- 1, 1, 1, - 1, 0.1, 100)
没有近大远小,这个立方体的边是平行的,但我们明明创建的是一个各边长都一样的立方体,看上去却像一个矮的方盒子呢?
这是由于我们为左面,右面,顶面和底面提供的值为1或-1,这意味着我们渲染一个正方形区域,但由于画布不是正方形,所以这个正方形的区域被拉伸成画布的尺寸了。
我们只需要使用画布的宽高比即可修复这个问题:
const aspectRatio = sizes.width / sizes.height
const camera = new THREE.OrthographicCamera(- 1 * aspectRatio, 1 * aspectRatio, 1, - 1, 0.1, 100)
如何控制相机
无论在何种现实业务场景,控制相机都是必须要掌握的。相机就像是一双眼睛,而控制相机就相当于我们双脚。
现在回到我们的透视相机,移动相机并使用LookAt使其面向立方体:
// Camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 1000)
// const aspectRatio = sizes.width / sizes.height
// const camera = new THREE.OrthographicCamera(- 1 * aspectRatio, 1 * aspectRatio, 1, - 1, 0.1, 100)
// camera.position.x = 2
// camera.position.y = 2
camera.position.z = 3
camera.lookAt(mesh.position)
scene.add(camera)
现在我们要学习如何用鼠标来控制相机,就像3D射击游戏那样,移动鼠标的时候镜头随之移动或旋转,那么首先我们需要知道鼠标坐标,这可以使用JavaScript监听鼠标的移动事件mousemove
来实现。我们将在监听事件的回调函数参数中拿到event.clientX和event.clientY:
// Cursor
window.addEventListener('mousemove', (event) =>
{
console.log(event.clientX, event.clientY)
})
现在我们已经拿到了鼠标移动时在网页里的坐标,但和Three.js的坐标系统略有不同。网页里的坐标,左上角是原点。并且y轴是越往下值越大。我们要对现在拿到的鼠标坐标值做一些调整,让其相对Three.js画布的中心变化
// Cursor
const cursor = {
x: 0,
y: 0
}
window.addEventListener('mousemove', (event) =>
{
cursor.x = event.clientX / sizes.width - 0.5
cursor.y = event.clientY / sizes.height - 0.5
console.log(cursor.x, cursor.y)
})
通过计算,我们最终会获得一个介于-0.5到0.5之间的数值
将计算后的数值存储到一个cursor对象中,接着在tick函数中用于更新相机的位置:
const tick = () =>
{
// ...
// Update camera
camera.position.x = cursor.x
camera.position.y = cursor.y
// ...
}
由于前面提到的网页的坐标系和Three.js坐标系的不同,现在鼠标带动相机的运动在y轴上是反的,所以我们添加一个-
号来反转数值。
window.addEventListener('mousemove', (event) =>
{
cursor.x = event.clientX / sizes.width - 0.5
cursor.y = - (event.clientY / sizes.height - 0.5)
})
如果觉得这个变化的幅度不够大,我们可以在基础数值上做乘法,并调用lookAt(...)
方法使相机始终面向立方体:
const tick = () =>
{
// ...
// Update camera
camera.position.x = cursor.x * 5
camera.position.y = cursor.y * 5
camera.lookAt(mesh.position)
// ...
}
我们还可以通过使用三角函数cos
和sin
让相机进行环绕立方体移动。是的,我们又一次用到了三角函数:p
const tick = () =>
{
// ...
// Update camera
camera.position.x = Math.sin(cursor.x * Math.PI * 2) * 2
camera.position.z = Math.cos(cursor.x * Math.PI * 2) * 2
camera.position.y = cursor.y * 3
camera.lookAt(mesh.position)
// ...
}
tick()
看到效果了吗?我们花了相当篇幅来讲如何控制相机,因为这很重要,是必备的基础知识。但其实Three.js
内置了多种常见的相机控制器,开箱即用。
预置的相机控制器
下面为大家介绍一下具体有哪些内置的相机控制器,以及它们分别提供怎样的功能
陀螺仪控制器
DeviceOrientationControls,绝大多数当下主流的移动设备都支持,用倾斜旋转手机的方式来控制我们的相机镜头。利用这个控制器可以很容易的创造出类似VR眼镜那样的体验。
飞行控制器 和 第一人称控制器
FlyControls,FirstPersonControls这两个控制器提供的功能极为相似,都可以很容易的实现类似人在场景中移动的效果。在开发一些比如VR看房,虚拟展厅,虚拟世界探索游戏等需求时很常用。
指针锁定控制器
PointerLockControls,使用这个控制器会隐藏鼠标指针,相机镜头会跟随mousemove事件旋转看向不同的方位,就像真正的射击游戏那样。当我们需要创建一个第一人称的3D游戏时,这个控制器是最佳选择。
环轨控制器
OrbitControls,我们刚才其实已经手写过了这个控制器的核心逻辑。当然,这个内置的控制器比我们刚才写的要完善很多,比如让我们可以通过拖拽旋转镜头,使用滚轮放大或缩小等等。这个控制器不允许世界颠倒,这是它和下面轨迹球控制器的区别。
轨迹球控制器
TrackballControls轨迹球控制器,和环轨控制器的效果极为相似,区别是取消了垂直镜头的区间限制,允许世界颠倒。
使用环轨控制器
现在让我们修改一下代码,来用一下这个环轨控制器。
导入控制器类
虽然Three.js为我们提供了这些开箱即用的控制器,但毕竟不是所有时候都需要它们,所以为了减少Three.js库的代码量,它们并不在three.min.js中,我们需要单独引入这些控制器类。
我们可以根据路径three.js/examples/js/controls/OrbitControls.js
从Three.js项目仓库中找到它,并复制粘贴到我们的项目中,依然使用<script>
标签来引入
<script src="./OrbitControls.js"></script>
实例化时OrbitControls需要传递两个参数,相机对象和画布对象。
// Controls
const controls = new THREE.OrbitControls(camera, canvas)
现在我们可以使用鼠标左键拖拽来旋转相机,还可以使用鼠标滚轮来放大或缩小。这比我们自己手写可简单多了对吧。
target
环轨控制器默认情况下会看向场景的中心点,我们可以通过改变 target
属性来改变初始视角的位置。
controls.target.y = 2
enableDamping
当我们设置enableDamping
参数为true时,鼠标拖拽移动时会带有缓动效果,让用户交互体验更加丝滑。除了将enableDamping
设置为true,我们还应该在tick函数中添加controls.update()
,刷新控制器的数值表现。
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
// ...
const tick = () =>
{
// ...
// Update controls
controls.update()
// ...
}
拖拽放开鼠标时是不是很丝滑?
环轨控制器能帮我们快速构建很多3D展示型项目的用户体验,它还有一些属性和方法,就留给大家自行探索吧。
小结
感谢这些内置的相机控制器,它们能帮我们处理大部分常见的业务场景,不过如果你真的遇到了内置相机控制器无法处理的需求,那就自己动手做吧,在本节中我们已经小试牛刀了不是吗?