OpenGL进阶(十四):UVN Camera实现
发表于2017-09-12
提要
3D游戏中最基本的一个功能就是3D漫游了,玩家可以通过键盘或者鼠标控制自己的视角。
之前我们也学习过一个相关的函数,glLookAt,用来制定摄像机的位置,摄像机观察目标位置,还有摄像机的放置方式,我们可以通过不断地调用这个函数来实现3D漫游,但更方便的是抽象出一个摄像机类,实现一些摄像机的方法。
UVN相机
UVN使用三个相互垂直的向量来表示相机的位置与朝向:
1) 相机注视的向量N
2) 相机的上方向向量V
3) 相机的右方向向量U
如下图,是在世界坐标系下的UVN相机的向量表示:
绿色轴为N,蓝色轴为V,红色轴为U。
当要改变相机位置和朝向的时候,只需要将uvn矩阵和相应的变换矩阵相乘即可。
代码实现
这里借助了一个第三方矩阵向量库 - eigen。Ubuntu下的安装的过程非常简单,下载源码之后,解压,cd进目录:
mkdir build
cd build
cmake ..
sudo make install
写一个头文件:
eigen.h
- #ifndef EIGEN_H
- #define EIGEN_H
- #include "eigen3/Eigen/Dense"
- #include "eigen3/Eigen/LU"
- #include "eigen3/Eigen/Core"
- #endif // EIGEN_H
放在工程目录下面,使用的时候包含进来就可以了。
看类声明:glcamera.h
- #ifndef GLCAMERA_H
- #define GLCAMERA_H
- #include "eigen.h"
- #include
- #include
- using namespace Eigen;
- class GLCamera
- {
- public:
- GLCamera();
- GLCamera(const Vector3d& pos, const Vector3d& target, const Vector3d& up);
- void setModelViewMatrix();
- void setShape(float viewAngle,float aspect,float Near,float Far);
- void slide(float du, float dv, float dn);
- void roll(float angle);
- void yaw(float angle);
- void pitch(float angle);
- float getDist();
- private:
- Vector3d m_pos;
- Vector3d m_target;
- Vector3d m_up;
- Vector3d u,v,n;
- };
- #endif // GLCAMERA_H
setModelViewMatrix:加载将当前MV矩阵。
setShape:设置摄像机的视角。
roll,yaw,pitch相当于绕N,V,U轴的旋转,如下图:
下面是相机的实现:
- #include "glcamera.h"
- GLCamera::GLCamera()
- {
- }
- GLCamera::GLCamera(const Vector3d &pos, const Vector3d &target, const Vector3d &up)
- {
- m_pos = pos;
- m_target = target;
- m_up = up;
- n = Vector3d( pos.x()-target.x(), pos.y()-target.y(), pos.z()-target.z());
- u = Vector3d(up.cross(n).x(), up.cross(n).y(), up.cross(n).z());
- v = Vector3d(n.cross(u).x(),n.cross(u).y(),n.cross(u).z());
- n.normalize();
- u.normalize();
- v.normalize();
- setModelViewMatrix();
- }
- void GLCamera::setModelViewMatrix()
- {
- double m[16];
- m[0]=u.x(); m[4]=u.y(); m[8]=u.z(); m[12]=-m_pos.dot(u);
- m[1]=v.x(); m[5]=v.y(); m[9]=v.z(); m[13]=-m_pos.dot(v);
- m[2]=n.x(); m[6]=n.y(); m[10]=n.z(); m[14]=-m_pos.dot(n);
- m[3]=0; m[7]=0; m[11]=0; m[15]=1.0;
- glMatrixMode(GL_MODELVIEW);
- glLoadMatrixd(m); //用M矩阵替换原视点矩阵
- }
- void GLCamera::setShape(float viewAngle, float aspect, float Near, float Far)
- {
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity(); //设置当前矩阵模式为投影矩阵并归一化
- gluPerspective(viewAngle,aspect, Near, Far); //对投影矩阵进行透视变换
- }
- void GLCamera::slide(float du, float dv, float dn)
- {
- //std::cout<<"u.x:"<<u.x()<<std::endl;< span=""> </u.x()<<std::endl;<>
- m_pos(0) = m_pos(0) du*u.x() dv*v.x() dn*n.x();
- m_pos(1) = m_pos(1) du*u.y() dv*v.y() dn*n.y();
- m_pos(2) = m_pos(2) du*u.z() dv*v.z() dn*n.z();
- m_target(0) = m_target(0) du*u.x() dv*v.x() dn*n.x();
- m_target(1) = m_target(0) du*u.y() dv*v.y() dn*n.y();
- m_target(2) = m_target(0) du*u.z() dv*v.z() dn*n.z();
- setModelViewMatrix();
- }
- void GLCamera::roll(float angle)
- {
- float cs=cos(angle*3.14159265/180);
- float sn=sin(angle*3.14159265/180);
- Vector3d t(u);
- Vector3d s(v);
- u.x() = cs*t.x()-sn*s.x();
- u.y() = cs*t.y()-sn*s.y();
- u.z() = cs*t.z()-sn*s.z();
- v.x() = sn*t.x() cs*s.x();
- v.y() = sn*t.y() cs*s.y();
- v.z() = sn*t.z() cs*s.z();
- setModelViewMatrix(); //每次计算完坐标轴变化后调用此函数更新视点矩阵
- }
- void GLCamera::pitch(float angle)
- {
- float cs=cos(angle*3.14159265/180);
- float sn=sin(angle*3.14159265/180);
- Vector3d t(v);
- Vector3d s(n);
- v.x() = cs*t.x()-sn*s.x();
- v.y() = cs*t.y()-sn*s.y();
- v.z() = cs*t.z()-sn*s.z();
- n.x() = sn*t.x() cs*s.x();
- n.y() = sn*t.y() cs*s.y();
- n.z() = sn*t.z() cs*s.z();
- setModelViewMatrix();
- }
- void GLCamera::yaw(float angle)
- {
- float cs=cos(angle*3.14159265/180);
- float sn=sin(angle*3.14159265/180);
- Vector3d t(n);
- Vector3d s(u);
- n.x() = cs*t.x()-sn*s.x();
- n.y() = cs*t.y()-sn*s.y();
- n.z() = cs*t.z()-sn*s.z();
- u.x() = sn*t.x() cs*s.x();
- u.y() = sn*t.y() cs*s.y();
- u.z() = sn*t.z() cs*s.z();
- setModelViewMatrix();
- }
- float GLCamera::getDist()
- {
- float dist = pow(m_pos.x(),2) pow(m_pos.y(),2) pow(m_pos.z(),2);
- return pow(dist,0.5);
- }
没什么好说的,都是矩阵的一些计算。
这样就可以将你的摄像机融入到OpenGL工程中了,比如说放进一个Qt的工程,用一个GLWifget类来显示OpenGL。
在initializeGL() 中,初始化camera
- Vector3d pos(0.0, 0.0, 12.0);
- Vector3d target(0.0, 0.0, 0.0);
- Vector3d up(0.0, 1.0, 0.0);
- camera = new GLCamera(pos, target, up);
在paintGL的时候,设置当前矩阵:
- glLoadIdentity();
- camera->setModelViewMatrix();
在resizeGL中调整视角:
- camera->setShape(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);
添加相应的鼠标事件:
- void GLWidget::mousePressEvent(QMouseEvent *event)
- {
- lastPos = event->pos();
- }
- void GLWidget::mouseMoveEvent(QMouseEvent *event)
- {
- int dx = event->x() - lastPos.x();
- int dy = event->y() - lastPos.y();
- if (event->buttons() & Qt::LeftButton)
- {
- RotateX(dx);
- RotateY(dy);
- }
- else if(event->buttons() & Qt::RightButton)
- {
- camera->roll(dx);
- //camera->pitch(dy);
- //camera->slide(0,0,-dy);
- }
- else if(event->buttons() & Qt::MiddleButton)
- {
- camera->slide(-dx,dy,0);
- }
- lastPos = event->pos();
- updateGL();
- }
- void GLWidget::RotateX(float angle)
- {
- float d=camera->getDist();
- int cnt=100;
- float theta=angle/cnt;
- float slide_d=-2*d*sin(theta*3.14159265/360);
- camera->yaw(theta/2);
- for(;cnt!=0;--cnt)
- {
- camera->slide(slide_d,0,0);
- camera->yaw(theta);
- }
- camera->yaw(-theta/2);
- }
- void GLWidget::RotateY(float angle)
- {
- float d = camera->getDist();
- int cnt=100;
- float theta=angle/cnt;
- float slide_d=2*d*sin(theta*3.14159265/360);
- camera->pitch(theta/2);
- for(;cnt!=0;--cnt)
- {
- camera->slide(0,slide_d,0);
- camera->pitch(theta);
- }
- camera->pitch(-theta/2);
- }
效果就像这样(gif 有点大,耐心等待):