关于gluLookAt函数即视图转换矩阵的推导及代码实现
视图模型转换矩阵的推导也是关于gluLookAt函数即视图转换矩阵的推导及代码实现
gluLookAt函数提供给用户完成模式变换(model-view transformation)中,在将模型坐标系转换都世界坐标系后,进行世界坐标系到照相机坐标系的转换。实际上,照相机的定位也是在世界坐标系下定义的,这里的转换,可以理解为: 从照相机的角度解释世界坐标系中物体的坐标。通过构造一个UVN坐标系来简化这一转换。
先直观感受下UVN,UVN坐标系中的照相机模型如下图所示:
借助下图正式定义UVN相机坐标系:
与UVN相关的概念包括:
形象的表达为:
gluLookAt 通过指定一个视点、表面场景中心的参考点以及up向量来构造一个视变换矩阵。
这个矩阵将代表场景中心的参考点映射到-Z轴,视点映射成为原点。当使用一个特定的投影矩阵时,场景的中心就映射到视口的中心。类似地,由up向量描述的方向投影到投影平面成为+y轴,这样它在视口中向上指向。up向量必须不能与从视点到参考点的直线平行。
那么如何确定u-v-n坐标系呢?计算公式如下:
OpenGL中使用的相机坐标系是右手坐标系,UVN坐标系是左手坐标系。在构造实际变换矩阵的过程中,OpenGL
需要将-n轴翻转为相机坐标系的+z轴,uv轴定位相机坐标系的+x和+y轴。这与推导相机变换矩阵一文最后的结果矩阵有所不同。
如何构造视变换矩阵?
视变换就是在相机坐标系下解释世界坐标系下的点。而这个变换矩阵的构造,可以看做将相机坐标系变换到与原来的世界坐标系重合。而将世界坐标系变换到与相机坐标系重合,可以看做是这个所求变换的逆过程。
将世界坐标系变换到与相机坐标系重合,实际上进行了两个步骤: 第一步将世界坐标系旋转一定角度记作变换R,再将世界坐标系平移到视点位置记作T,那么这个变换矩阵记为M=TR。要将世界坐标系的点变换到照相机坐标系下,需要使用矩阵M的逆矩阵,即: inverse(M)=inverse(R)*inverse(T)。即所求变换矩阵为inverse(M)。
平移矩阵的逆矩阵形式简单,就是取平移量(eyex,eyey,eyez)的相反数,即:
那么现在的关键是如何求出旋转矩阵R?
上面我们构造的UVN坐标系u-v-n3个基向量可以构造成矩阵:
注意这里对n轴进行了翻转,构成右手照相机坐标系。
怎么看这个矩阵A呢,矩阵A实际上代表的就是一个旋转矩阵(从矩阵形式上看出)。
旋转矩阵的一个特点就是它是正交矩阵,即有inverse(A) = transpose(A).(A^-1 = A^T)
很多教材和博客都说,这里A矩阵可以看做是将世界坐标系转换到与照相机坐标系重合时的旋转矩阵,这一点怎么理解呢?
个人理解,矩阵A第四列为0,0,0,1,可以看做是世界坐标系和照相机坐标系原点重合;矩阵前3列即变换后的基向量,那么这个基向量(都是单位向量)是如何计算出来的呢?就是通过旋转原来的世界坐标系的基向量来构造的。因此,可以说矩阵A代表的就是将世界坐标系旋转到与相机坐标系重合时的旋转矩阵R,即R = A。
则inverse(R) = inverse(A) = transpose(A) 即为:
所以gluLookAt所求变换矩阵inverse(M)为:
//计算gluLookAt矩阵
#pragma once
#include
#include "Math_3d.h"
#define GLUT_DISABLE_ATEXIT_HACK
#include /*GL_BGR*/
#include
#include
#include
float g_RotAngle = 0.6;//旋转角度
M3DMatrix44f rotateMatrix;
void userInit();
void display(void);
void keyboardAction(unsigned char key, int x, int y);
void reshape(int w, int h);
//相机坐标参数
M3DVector3d eye = { 2.0, 0.0, 1.8 };
M3DVector3d center = { 0.0, 0.0, 0.0 };
M3DVector3d vup = { 0.0, 1.0, 0.0 };
void TimerFunction(int value)
{
g_RotAngle += 1.0;
glutPostRedisplay();
glutTimerFunc(10, TimerFunction, 1);
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);//初始化GLUT
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(800, 600);
glutCreateWindow("gluLookAt推导及代码实现");
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutKeyboardFunc(keyboardAction);
glutTimerFunc(1, TimerFunction, 1);
glutMainLoop();
return 0;
}
//设置视图变换矩阵double类型
void
m3dSetViewMatrix(M3DMatrix44d viewMatrix, M3DVector3d eye,
M3DVector3d center,M3DVector3d vup)
{
//构造n轴
M3DVector3d nvec = {
center[0] - eye[0],
center[1] - eye[1],
center[2] - eye[2],
};
m3dNormalizeVector(nvec);//单位化
//n轴(相当于正z轴)
m3dNormalizeVector(vup);
//构造u轴 (相当于正x轴)
M3DVector3d uvec;
m3dCrossProduct(uvec, nvec, vup);//结果已经单位化
//构造v轴 (相当于正y轴)
M3DVector3d vvec;
m3dCrossProduct(vvec, uvec, nvec);//结果已经单位化
//设置4x4矩阵并初始化0
memset(viewMatrix, 0, sizeof(double)* 16);
viewMatrix[0] = uvec[0];
viewMatrix[4] = uvec[1];
viewMatrix[8] = uvec[2];
viewMatrix[12] = -m3dDotProduct(eye, uvec);
viewMatrix[1] = vvec[0];
viewMatrix[5] = vvec[1];
viewMatrix[9] = vvec[2];
viewMatrix[13] = -m3dDotProduct(eye, vvec);
//注意这行数据
viewMatrix[2] = -nvec[0];
viewMatrix[6] = -nvec[1];
viewMatrix[10] = -nvec[2];
viewMatrix[14] = m3dDotProduct(eye, nvec);
viewMatrix[15] = 1.0;
}
void reshape(int w, int h)
{
glViewport(0, 0, GLsizei(w), GLsizei(h));
glutFullScreen();//全屏显示
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 0.1, 500.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(0.0, 0.0, 0.0, 0.0);
glColor4f(0.1f, 0.5f, 0.8, 0.0);
}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);//清除颜色缓存
//手动构造视变换矩阵
glLoadIdentity();
M3DMatrix44d viewMatrix;
m3dSetViewMatrix(viewMatrix, eye, center, vup);
glMultMatrixd(viewMatrix);
glPushMatrix();
glRotatef(g_RotAngle, 1.0, 0.0, 0.0);
glLineWidth(2.0);
glutWireCube(1.0);
glPopMatrix();
//打印当前模视变换矩阵内容
double modelViewMat[16];
glGetDoublev(GL_MODELVIEW_MATRIX, modelViewMat);
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
{
fprintf(stdout, "%-.4f\t", modelViewMat[i + 4 * j]);
if ((j + 1) % 4 == 0) fprintf(stdout, "\n");
}
glFlush();
glutSwapBuffers();
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key) {
case 033: // Escape key
case 'q': case 'Q':
exit(EXIT_SUCCESS);
break;
case 'w':eye[2] += 0.2;glutPostRedisplay(); break;
case 's':eye[2] -= 0.2; glutPostRedisplay(); break;
case 'a':eye[0] += 0.2; glutPostRedisplay(); break;
case 'd':eye[0] -= 0.2; glutPostRedisplay(); break;
}
}
实现结果截图: