在OpenGL中我们可以借助gluLookAt()或glm::lookAt()函数设置或者获取相应的视角转换矩阵,从而将片元从世界坐标系转换到相机坐标系。接下来我们以glm::lookAt()为例讲述这一过程具体是如何实现的。首先我们可以看一下glm::lookAt()的函数声明:1
2
3
4
5
6template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER mat<4, 4, T, Q> lookAt(
vec<3, T, Q> const &eye,
vec<3, T, Q> const& center,
vec<3, T, Q> const& up
);
容易看出这是一个函数模板,接受三个三维向量,分别代表相机的位置eye、观察点center、朝上的方向up,并返回一个视角转换矩阵。在讲述原理前,我们先说明一下OpenGL中通常使用的坐标系。
如下图所示,在OpenGL中,我们通常使用右手坐标系 $\textbf{O}xyz$ ,即$\textbf{z}=\textbf{x}\times \textbf{y}$,并且希望经过视角变换后相机看向$-\textbf{z}$方向,并且相机朝上方向为$\textbf{y}$,即下图所示的$\textbf{O}ruv$:
接下来即可考虑如何根据三个输入参数eye、center、up获取上述转换矩阵。
在上面的场景中,我们先根据三个输入参数计算世界坐标系下的三个相互正交的轴(需要说明的是输入参数up未必与视线垂直,因此需要校准):
综上,转换矩阵为:
可以通过如下代码进行验证(此处默认右手系,在GLM中可以自主选择左右手系:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using namespace std;
int main()
{
glm::vec3 eye(1, 1, 1);
glm::vec3 up(0, 1, 0);
glm::vec3 center(0, 0, 0);
glm::mat4x4 lookat = glm::lookAt(eye, center, up);
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
cout << lookat[j][i] << '\t';
}
cout << endl;
}
cout << endl;
glm::vec3 v = glm::normalize(center - eye);
glm::vec3 r = glm::normalize(glm::cross(up, v));
glm::vec3 u = glm::normalize(glm::cross(v, r));
glm::vec3 t = -eye;
t = glm::vec3(glm::dot(-r, t), glm::dot(u, t), glm::dot(-v, t));
cout << -r[0] << '\t' << -r[1] << '\t' << -r[2] << '\t' << t[0] << endl;
cout << u[0] << '\t' << u[1] << '\t' << u[2] << '\t' << t[1] << endl;
cout << -v[0] << '\t' << -v[1] << '\t' << -v[2] << '\t' << t[2] << endl;
cout << 0 << '\t' << 0 << '\t' << 0 << '\t' << 1 << endl;
return 0;
}
参考文献:
Real Time Rendering (4th edition)