Three.js 是一个强大的 WebGL 库,用于在浏览器中创建 3D 图形和交互式体验。相机是 Three.js 中的重要组成部分,它决定了如何将 3D 场景投影到 2D 屏幕上。本文将深入探讨 Three.js 相机的数学原理、源码实现、API 使用以及实际应用。
一、相机的数学原理
相机的核心是将 3D 世界中的物体投影到 2D 屏幕上。Three.js 中的相机主要分为两种类型:透视投影相机(Perspective Camera) 和正交投影相机(Orthographic Camera) 。
1.1 投影矩阵
投影矩阵是将 3D 点投影到 2D 屏幕的核心工具。Three.js 中的投影矩阵基于线性代数中的矩阵变换。
1.1.1 透视投影矩阵
透视投影矩阵模拟了人类视觉的特性,近处的物体看起来大,远处的物体看起来小。其公式如下:
P = ( 2 n r − l 0 r + l r − l 0 0 2 n t − b t + b t − b 0 0 0 − ( f + n ) f − n − 2 f n f − n 0 0 − 1 0 ) P = \begin{pmatrix} \frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\ 0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{-(f+n)}{f-n} & \frac{-2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{pmatrix} P= r−l2n0000t−b2n00r−lr+lt−bt+bf−n−(f+n)−100f−n−2fn0
其中:
- n n n 是近裁剪面距离。
- f f f 是远裁剪面距离。
- l , r l, r l,r 是左右视界的范围。
- b , t b, t b,t 是上下视界的范围。
1.1.2 正交投影矩阵
正交投影矩阵不考虑透视效果,适用于技术绘图等场景。其公式如下:
P = ( 2 r − l 0 0 − ( r + l ) r − l 0 2 t − b 0 − ( t + b ) t − b 0 0 2 n − f − ( n + f ) n − f 0 0 0 1 ) P = \begin{pmatrix} \frac{2}{r-l} & 0 & 0 & \frac{-(r+l)}{r-l} \\ 0 & \frac{2}{t-b} & 0 & \frac{-(t+b)}{t-b} \\ 0 & 0 & \frac{2}{n-f} & \frac{-(n+f)}{n-f} \\ 0 & 0 & 0 & 1 \end{pmatrix} P= r−l20000t−b20000n−f20r−l−(r+l)t−b−(t+b)n−f−(n+f)1
1.2 视图矩阵
视图矩阵(View Matrix)是将世界坐标系中的物体变换到视图坐标系(Camera Space)中的矩阵。视图矩阵的作用是将场景中的物体相对于相机的位置进行变换,使得相机的位置和方向成为新的原点和坐标轴。
1.2.1 视图矩阵的推导
视图矩阵的推导可以通过以下步骤完成:
- 计算相机的坐标系:根据相机的位置、目标点和上方向,计算出相机的局部坐标系。
- 构建变换矩阵:将世界坐标系中的点变换到相机的局部坐标系中。
具体来说,视图矩阵可以通过以下公式计算:
V = ( u x v x n x − p x u y v y n y − p y u z v z n z − p z 0 0 0 1 ) V = \begin{pmatrix} u_x & v_x & n_x & -p_x \\ u_y & v_y & n_y & -p_y \\ u_z & v_z & n_z & -p_z \\ 0 & 0 & 0 & 1 \end{pmatrix} V= uxuyuz0vxvyvz0nxnynz0−px−py−pz1
其中:
- ( u , v , n ) (u, v, n) (u,v,n) 是相机的局部坐标系的三个单位向量。
- ( p x , p y , p z ) (p_x, p_y, p_z) (px,py,pz) 是相机的位置。
1.2.2 LookAt 函数
在 Three.js 中,视图矩阵的计算可以通过 lookAt
方法实现。lookAt
方法会根据相机的位置、目标点和上方向,自动计算出视图矩阵。
camera.lookAt(targetX, targetY, targetZ);
在 OpenGL 或其他图形库中,LookAt
函数的实现通常基于以下公式:
V = ( u x ∥ u ∥ v x ∥ v ∥ n x ∥ n ∥ − p x u y ∥ u ∥ v y ∥ v ∥ n y ∥ n ∥ − p y u z ∥ u ∥ v z ∥ v ∥ n z ∥ n ∥ − p z 0 0 0 1 ) V = \begin{pmatrix} \frac{u_x}{\|u\|} & \frac{v_x}{\|v\|} & \frac{n_x}{\|n\|} & -p_x \\ \frac{u_y}{\|u\|} & \frac{v_y}{\|v\|} & \frac{n_y}{\|n\|} & -p_y \\ \frac{u_z}{\|u\|} & \frac{v_z}{\|v\|} & \frac{n_z}{\|n\|} & -p_z \\ 0 & 0 & 0 & 1 \end{pmatrix} V= ∥u∥ux∥u∥uy∥u∥uz0∥v∥vx∥v∥vy∥v∥vz0∥n∥nx∥n∥ny∥n∥nz0−px−py−pz1
其中:
- u u u 是右方向向量。
- v v v 是上方向向量。
- n n n 是前方向向量。
1.2.3 视图矩阵的作用
视图矩阵的作用是将场景中的物体从世界坐标系变换到相机坐标系,使得相机的位置和方向成为新的原点和坐标轴。视图矩阵的计算是三维图形渲染中的关键步骤之一。
二、Three.js 相机源码分析
Three.js 的相机实现主要集中在 Camera
类及其子类中。
2.1 Camera 类
Camera
是所有相机的基类,定义了相机的基本功能,包括:
matrixWorld
:相机的世界变换矩阵。updateMatrixWorld
:更新相机的变换矩阵。
2.2 PerspectiveCamera 类
PerspectiveCamera
实现了透视投影相机。其构造函数如下:
constructor(fov, aspect, near, far) {
super();
this.fov = fov;
this.aspect = aspect;
this.near = near;
this.far = far;
this.updateProjectionMatrix();
}
updateProjectionMatrix
方法根据 fov、aspect、near 和 far 计算投影矩阵。
2.3 OrthographicCamera 类
OrthographicCamera
实现了正交投影相机。其构造函数如下:
constructor(left, right, top, bottom, near, far) {
super();
this.left = left;
this.right = right;
this.top = top;
this.bottom = bottom;
this.near = near;
this.far = far;
this.updateProjectionMatrix();
}
updateProjectionMatrix
方法根据 left、right、top、bottom、near 和 far 计算投影矩阵。
2.4 CameraHelper 类
CameraHelper
是一个辅助类,用于可视化相机的视锥体。它可以帮助开发者调试相机的参数设置。
const cameraHelper = new CameraHelper(camera);
scene.add(cameraHelper);
2.5 Shader 中的视图矩阵
在 Shader 中,视图矩阵和投影矩阵通常用于将模型矩阵变换到屏幕空间。以下是 Three.js 中 Shader 的示例代码:
2.5.1 顶点着色器
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
void main() {
// 将模型矩阵、视图矩阵和投影矩阵相乘
gl_Position = u_ProjectionMatrix * u_ViewMatrix * modelViewMatrix * vec4(position, 1.0);
}
2.5.2 片元着色器
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
void main() {
// 将模型矩阵、视图矩阵和投影矩阵相乘
gl_Position = u_ProjectionMatrix * u_ViewMatrix * modelViewMatrix * vec4(position, 1.0);
}
2.5.3 逆矩阵的应用
在 Shader 中,逆矩阵通常用于将屏幕空间中的点变换回世界空间。以下是逆矩阵的应用示例:
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
void main() {
// 将屏幕空间中的点变换回世界空间
vec4 worldPos = inverse(u_ProjectionMatrix * u_ViewMatrix) * vec4(gl_FragCoord, 1.0);
}
2.5.4 视图矩阵的更新
在 Three.js 中,视图矩阵的更新可以通过以下代码实现:
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
三、Three.js 相机 API 使用
Three.js 提供了丰富的 API 来控制相机的行为。
3.1 基本操作
3.1.1 设置相机位置和方向
camera.position.set(x, y, z); // 设置相机位置
camera.lookAt(x, y, z); // 设置相机目标点
camera.up.set(x, y, z); // 设置相机的上方向
3.1.2 更新投影矩阵
camera.updateProjectionMatrix(); // 更新投影矩阵
3.2 常用方法
3.2.1 计算屏幕坐标
const vector = new THREE.Vector3();
vector.setFromMatrixPosition(object.matrixWorld).applyProjection(camera.projectionMatrix);
const x = (vector.x + 1) * (window.innerWidth / 2);
const y = (-vector.y + 1) * (window.innerHeight / 2);
3.2.2 计算世界坐标
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mousePosition, camera);
const intersects = raycaster.intersectObjects(scene.children);
3.3 相机控制库
Three.js 社区提供了许多相机控制库,例如 OrbitControls
和 TrackballControls
,方便开发者快速实现相机的交互控制。
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = true;
controls.enablePan = true;
controls.enableRotate = true;
3.4 常用 API 表格
以下是 Three.js 相机中常用 API 的汇总:
API | 描述 | 参数 | 示例代码 | 应用场景 |
---|---|---|---|---|
position.set(x, y, z) | 设置相机的位置。 | x , y , z :相机在世界坐标系中的位置。 | camera.position.set(0, 5, 10); | 初始化相机位置或动态调整相机位置。 |
lookAt(x, y, z) | 设置相机的目标点,使相机朝向该点。 | x , y , z :目标点的坐标。 | camera.lookAt(0, 0, 0); | 初始化相机视角或动态调整相机视角。 |
up.set(x, y, z) | 设置相机的上方向向量。 | x , y , z :上方向向量的坐标。 | camera.up.set(0, 1, 0); | 初始化相机的上方向或动态调整相机的上方向。 |
updateProjectionMatrix() | 更新投影矩阵。 | 无 | camera.updateProjectionMatrix(); | 在修改相机参数(如 fov、aspect、near、far)后调用。 |
fov | 设置或获取相机的视场角(仅适用于透视投影相机)。 | fov :视场角,以度为单位。 | camera.fov = 75; | 初始化相机的视场角或动态调整相机的视场角。 |
aspect | 设置或获取相机的宽高比。 | aspect :宽高比。 | camera.aspect = window.innerWidth / window.innerHeight; | 初始化相机的宽高比或窗口大小变化时更新宽高比。 |
near | 设置或获取相机的近裁剪面距离。 | near :近裁剪面距离。 | camera.near = 0.1; | 初始化相机的近裁剪面或动态调整相机的近裁剪面。 |
far | 设置或获取相机的远裁剪面距离。 | far :远裁剪面距离。 | camera.far = 1000; | 初始化相机的远裁剪面或动态调整相机的远裁剪面。 |
getWorldDirection() | 获取相机的朝向向量。 | 无 | const direction = camera.getWorldDirection(); | 计算相机的朝向向量,用于物理模拟或交互控制。 |
Viewport | 设置相机的视口区域。 | x , y , width , height :视口区域的坐标和尺寸。 | camera.viewport = new THREE.Vector4(x, y, width, height); | 在多相机渲染或分屏渲染时使用。 |
四、相机的实际应用
4.1 游戏开发
在游戏开发中,相机用于控制玩家的视角。例如,第一人称射击游戏通常使用透视投影相机,而第三人称游戏可能使用自由视角相机。
4.2 VR/AR 应用
在 VR/AR 应用中,相机用于模拟用户的视角。Three.js 结合 WebXR API 可以实现沉浸式的 VR 体验。
4.3 3D 建模工具
在 3D 建模工具中,相机用于观察和编辑 3D 模型。正交投影相机常用于技术绘图,而透视投影相机用于展示模型的效果。
五、总结
Three.js 相机是 3D 开发中的核心工具,理解其数学原理、源码实现和 API 使用对开发者来说至关重要。通过本文的介绍,希望读者能够更好地掌握 Three.js 相机的使用方法,并在实际项目中灵活应用。