1、视图矩阵
视图矩阵即用于将要渲染的物体从世界坐标转换到视觉坐标的过程。即视觉坐标 = 视图矩阵 * 世界坐标。
例如指定物体的世界坐标为(0, 0, 0),设置摄像机的位置为(0, 0, 300),即转换后物体的实际坐标即以摄像机的位置(眼睛看到的)为起点(0, 0, 0),物体相对摄像机的位置即为视觉坐标(0, 0, -100)。视觉坐标除了在物体渲染时的坐标变换中用到,还有个就是在光照的时候,光照的计算都是在视觉坐标下进行计算的。
视图矩阵的推导:
视觉矩阵即正常使用的gluLookAt,gluLookAt 调用即会去构造一个视图矩阵,当我们需要渲染物体前,需要去创建一个视图矩阵,否则默认为(0,0,0)的位置。
1. 取得当前摄像机看的目标,正常我们默认设置为
mTarget(0.0, 0.0, -1.0);
即朝向屏幕里面的方向。
2. 眼睛的位置,即我们初始化设置摄像机的位置,如设置为
mEyePos(0, 0, 300);
3. 设置摄像机向上的方向,默认设置为y轴正方向:
Vector3(0.0, 1.0, 0.0)
4. 通过上述三个变量来构造视图矩阵,也就是gluLookAt调用的9个参数。
Vector3 zAxis = (target - eyePos).normalize();
取得摄像机的z轴的朝向,也就是眼睛看的朝向。
Vector3 yAxis = up.normalize();
摄像机向上的朝向。
通过以上两个坐标,可以计算x轴的朝向(即z与y的叉乘):
Vector3 xAxis = (zAxis.cross(yAxis)).normalize();
接下来通过这三个坐标轴来构造一个矩阵,由于眼睛看的z轴方向与物体的方向是相反的,所以z轴需要取反:
Matrix4 mat;
mat[0] = xAxis.x;
mat[1] = xAxis.y;
mat[2] = xAxis.z;
mat[4] = yAxis.x;
mat[5] = yAxis.y;
mat[6] = yAxis.z;
mat[8] = -zAxis.x;
mat[9] = -zAxis.y;
mat[10] = -zAxis.z;
接下来将构造出的矩阵平移摄像机的位置,即可构造出当前的视图矩阵:
mat.translate(-eyePos.x, -eyePos.y, -eyePos.z);
由于视觉坐标的移动与世界坐标是相反的,所以移动为相反的方向。
完整代码如下所示:
Matrix4 Camera::lookAt(Vector3 eyePos, Vector3 target, Vector3 up)
{
Vector3 zAxis = (target - eyePos).normalize();
Vector3 yAxis = up.normalize();
Vector3 xAxis = (zAxis.cross(yAxis)).normalize();
Matrix4 mat;
mat[0] = xAxis.x;
mat[1] = xAxis.y;
mat[2] = xAxis.z;
mat[4] = yAxis.x;
mat[5] = yAxis.y;
mat[6] = yAxis.z;
mat[8] = -zAxis.x;
mat[9] = -zAxis.y;
mat[10] = -zAxis.z;
mat.translate(-eyePos.x, -eyePos.y, -eyePos.z);
return mat;
2、投影矩阵
2.1 透视投影矩阵投影就是将物体从视觉坐标投影到屏幕上,而透视投影就是使物体看起来能产生近大远小的效果。投影后的坐标即为NDC坐标(标准设备坐标),即x、y、z的坐标皆为[-1, 1]的范围内。透视投影矩阵为:
void Camera::setFrustum(float l, float r, float b, float t, float n, float f)
{
mMatrixProjection.identity();
mMatrixProjection[0] = 2 * n / (r - l);
mMatrixProjection[2] = (r + l) / (r - l);
mMatrixProjection[5] = 2 * n / (t - b);
mMatrixProjection[6] = (t + b) / (t - b);
mMatrixProjection[10] = -(f + n) / (f - n);
mMatrixProjection[11] = -(2 * f * n) / (f - n);
mMatrixProjection[14] = -1;
mMatrixProjection[15] = 0;
}
2.2 正交投影矩阵
即物体不产生变形的效果,每个物体的大小都是等比例的,即正交投影是直接从视觉坐标中根据在-1, 1的范围内直接转换而来,而透视投影则是先将物体坐标先透视到近裁剪平面,接着再将其变换到-1和1的范围。正交投影矩阵为:
void Camera::setOrtho(float l, float r, float b, float t, float n, float f)
{
mMatrixProjection.identity();
mMatrixProjection[0] = 2/(r-l);
mMatrixProjection[3] = -(r+l)/(r-l);
mMatrixProjection[5] = 2/(t-b);
mMatrixProjection[7] = -(t+b)/(t-b);
mMatrixProjection[10] = -2 / (f - n);
mMatrixProjection[11] = -(f + n) / (f - n);
}
具体矩阵推导可参考:http://www.songho.ca/opengl/gl_projectionmatrix.html
3、坐标原点
在使用opengl开发过程中,经常会遇到坐标原点问题,最常出现的是屏幕中心为原点和屏幕左下角为原点,为什么会有这些坐标原点呢?其实是在设置矩阵变换时指定的。正常设置透视投影矩阵时,如果通过
setOrtho(float l, float r, float b, float t, float n, float f)
设置时,传入的参数为:
camera->setOrtho(0, w, 0, h, 0.1, 100);
此时则原点在屏幕左下角,具体原因即为矩阵的计算时是根据输入的前后左右来进行计算的。而如果当前输入的参数为:
camera->setOrtho(-w/2, w/2, -h/2, h/2, 0.1, 100);
则此时坐标原点则是在屏幕中心。同时,通过上述设置后,绘制时坐标也为具体的坐标,即在屏幕范围内的坐标[-w/2, w/2]。