Skip to content

上一节我们学习了坐标系变换的基本变换的原理以及算法实现,模型空间通过基本变换会转变到世界空间,转变到世界空间之后,就需要将世界空间映射到观察空间,本节我们学习世界空间向观察空间转换的算法实现。

什么是观察空间

观察空间是从人眼或者摄像机看到的 3D 空间,是整个3D 世界的一部分,观察变换的主要作用是将模型的顶点坐标从世界坐标系变换到观察坐标系中。

观察坐标系也可以理解为相机坐标系。

还记得上一节我们求解变换矩阵的思路吗?

  • 求出新坐标系原点在原坐标系中的位置。
  • 求出新坐标系基向量在原坐标系中的表示。

视图变换

世界空间转变到观察空间这个过程通常称为视图变换,变换矩阵称为视图矩阵。在做视图变换之前,我们会在世界坐标系里指定摄像机或者人眼的位置eyePosition,以及摄像机头顶方向向量upDirection,然后我们要根据这两个条件计算出视图变换矩阵。

按照矩阵求解步骤进行分析。

新坐标系原点在原坐标系中的位置

首先我们要分清哪个是新坐标系,哪个是原坐标系。 在世界空间变换到观察空间的过程中,新坐标系是观察坐标系,原坐标系是世界坐标系,我们已经知道的是世界坐标系下的顶点位置 P0,需要求出顶点在观察坐标系中的位置 P1。

$P1 = M \times P0。$

其中 M 是世界坐标系变换到观察坐标系的观察矩阵。

为了求M,我们需要知道世界坐标系的原点在观察坐标系中的位置,还需要知道世界坐标系基向量在观察坐标系中的表示。

我们目前的已知量有如下三个:

  • 1、观察坐标系原点在世界坐标系中的位置。
  • 2、观察坐标系的 Z 轴在世界坐标系中的表示。
  • 3、顶点在世界坐标系中的位置P0。

根据 1、2 两项可以得出观察坐标系到世界坐标系的观察矩阵 M 。

但是我们需要求出的是 世界坐标系到观察坐标系的视图矩阵 N。

逆变换

大家还记得在数学章节,我们学习了逆矩阵的含义与算法实现。在坐标系变换中,逆矩阵有着重要的意义。

假设有一顶点在坐标系A中坐标 P0,经过矩阵变换 M 后顶点在坐标系 B 中的坐标 P1,

$P1 = M \times P0$

又有

$M \times M^{-1} = I $

$I \times P1 = P1$

所以

$M \times M^{-1} \times P1 = M \times P0$

两边约去 M,可得

$M^{-1} \times P1 = P0$

什么意思呢?

意思就是顶点 P1 经过矩阵 M 的逆矩阵就可以变换回顶点 P0。我们看下逆矩阵在视图变换中如何应用。

假设顶点在世界坐标系中的坐标 P0,观察坐标系变换到世界坐标系的变换矩阵为 M,那么顶点在观察坐标系中的坐标 P1 为:

$P0 = M \times P1 $

$\Longrightarrow M \times M^{-1} \times P0 = M \times P1$

$\Longrightarrow M^{-1} \times P0 = P1$

同样地,我们只要求出观察坐标系变换到世界坐标系 的逆矩阵即可求出物体在观察坐标系中的坐标。

在此,我们定义观察坐标系变换到世界坐标系的矩阵为相机矩阵 E,世界坐标系变换到观察坐标系的矩阵为视图矩阵 V,其中 E 和 V 互逆。

求解相机矩阵

求解相机矩阵仍然按照上一节的求解步骤。

我们已经知道的几个条件:

  • 假设相机在世界坐标系中的位置 Pe (ex, ey, ez)
  • 看向目标位置为T (tx,ty,tz)
  • 摄像机上方方向向量 upDirection 为(ux, uy, uz)

接下来我们要根据这些条件求出相机矩阵。

1、相机坐标系的原点在世界坐标系的位置

由上面条件可知,相机坐标系原点在世界坐标系中的位置如下:

$ P_e = (ex, ey, ez) $

2、 求解相机坐标系的基向量在世界坐标系中的表示

从相机位置看像目标位置的方向称为观察方向,观察方向可以看做相机坐标系的 Z 轴方向,那么世界坐标系的 Z 轴基向量方向可以这样求出:

$ \begin{aligned} zAxis &= P_e - T \
& =(ex - tx, ey - ty, ez - tz) \end{aligned} $

有了 Z 轴方向向量zAxis和临时 Y 轴 方向upDirection,我们就可以利用向量叉乘来计算 X 轴方向了。

$ xAxis = zAxis \times upDirection $

计算出 X 轴方向之后,我们需要将 xAxiszAxis 归一化,得到它们的基向量,这时还需要求一遍 Y 轴的方向向量,因为 upDirection 是我们一开始假想的,只是为了求解 X 轴方向,upDirectionzAxis 不一定是垂直关系。

仍然利用向量叉乘求解 Y 轴方向:

$ yAxis = zAxis \times xAxis $

将 xAxis(xx, xy, xz)、yAxis(yx, yy, yz)、zAxis(zx, zy, zz)以及相机位置 Pe(ex, ey, ez) 代入矩阵变换框架,可以求得相机坐标系变换到世界坐标系的矩阵 E

$ \begin{aligned} \begin{pmatrix} xx & yx & zx & ex \
xy & yy & zy & ey \
xz & yz & zz & ez \
0 & 0 & 0 & 1 \end{pmatrix} \end{aligned} $

这就是相机矩阵E,有了它 ,我们利用逆矩阵的算法求出 E 的逆矩阵 $E^{-1}$,即视图矩阵。

之后就可以利用视图矩阵左乘顶点在世界坐标系的坐标,计算出顶点在观察坐标系中的坐标,也就完成了世界坐标系到观察坐标系的变换。

算法实现

有了上面的分析,我们的算法就很容易实现了。

1、 首先,求出Z 轴基向量,即观察方向:

javascript
function lookAt(cameraPosition, target, upDirection){
    var zAxis  = (Vector3.subtractVectors(cameraPosition, target)).normalize();
}

2、 其次,根据 zAxis 和 upDireciton 求出 X 轴基向量:

javascript
var xAxis = (Vector3.cross(upDirection, zAxis)).normalize();

3、处理 zAxis 和 upDirection 平行的情况:

javascript
if(xAxis.length() == 0){
    if (Math.abs(upDirection.z == 1)) {
      zAxis.x += 0.0001;
    } else {
      zAxis.z += 0.0001;
    }
    zAxis.normalize();
    xAxis = Vector3.cross(upDirection, zAxis).normalize();
}

4、接着,根据 zAxis 和 xAxis ,重新计算Y轴基向量 yAxis:

javascript
var yAxis = (Vector3.cross(zAxis, xAxis)).normalize();

5、最后,将各个值代入矩阵框架

javascript
var target = new Float32Array(16);

// 第一列,x 轴基向量
target[0] = xAxis.x;
target[1] = xAxis.y;
target[2] = xAxis.z;
target[3] = 0;

// 第二列,y 轴基向量
target[4] = yAxis.x;
target[5] = yAxis.y;
target[6] = yAxis.z;
target[7] = 0;

// 第三列,z 轴基向量
target[8] = zAxis.x;
target[9] = zAxis.y;
target[10] = zAxis.z;
target[11] = 0;

// 第四列,坐标系原点位置
target[8] = cameraPosition.x;
target[9] = cameraPosition.y;
target[10] = cameraPosition.z;
target[11] = 1;

return target;

使用方法

有了上面的算法,我们构造一个观察矩阵就轻而易举了。

  • 首先定义摄像机的位置:
javascript
var cameraPosition = new Vector3(0, 0, 10);
  • 接着指定视线看向的点:
javascript
var target = new Vector3(0, 0, 0);
  • 假定一个方向向量代表摄像机上方:
javascript
var upDirection = new Vector3(0, 1, 0);
  • 将这三个参数代入 lookAt 方法,求出相机矩阵:
javascript
var cameraMatrix = matrix.lookAt(cameraPosition, target, upDirection);
  • 最后一步,求相机矩阵的逆矩阵,即观察矩阵。
javascript
var viewMatrix = matrix.inverse(cameraMatrix);

效果演示

接下来,我们利用上面的观察矩阵,演示轨道摄像机和第一人称摄像机的效果。

轨道摄像机

我们创建一个围绕场景中心的圆形摄像机轨道,半径为10,观察处于场景中心的一个立方体。其中,摄像机在圆形轨道上移动,并且观察方向始终看向场景中心的立方体,

这是圆形轨道摄像机的观察效果,咋看之下,它给我们的感觉是立方体自己在动。其实是我们的摄像机在绕着圆形轨道移动,立方体的位置并没有变化。

移动摄像机靠近物体等价于移动物体、远离摄像机的观察效果。

跟踪摄像机

在一些第一人称游戏中,摄像机往往是跟随人物而动的,接下来我们模拟人物走动的视觉效果。

我们将摄像机的位置和人物的位置保持一致,摄像机看向的目标点始终保持在人物前方 20 单位处,于是就产生了下面的效果:

回顾

本节我们讲解了如何实现视图矩阵的推导,以及视图矩阵的简单应用,推导过程仍然涉及了很多数学知识的应用,在此建议大家一定要多加练习,熟练掌握这些数学知识。

下一节,我们学习投影矩阵的推导以及应用。