OpenGL进阶(十四):UVN Camera实现

发表于2017-09-12
评论0 2.3k浏览

提要

      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

  1. #ifndef EIGEN_H  
  2. #define EIGEN_H  
  3.   
  4. #include "eigen3/Eigen/Dense"  
  5. #include "eigen3/Eigen/LU"  
  6. #include "eigen3/Eigen/Core"  
  7.   
  8. #endif // EIGEN_H  

放在工程目录下面,使用的时候包含进来就可以了。


看类声明:glcamera.h

  1. #ifndef GLCAMERA_H  
  2. #define GLCAMERA_H  
  3. #include "eigen.h"  
  4. #include   
  5. #include   
  6.   
  7. using namespace Eigen;  
  8. class GLCamera  
  9. {  
  10. public:  
  11.     GLCamera();  
  12.     GLCamera(const Vector3d& pos, const Vector3d& target, const Vector3d& up);  
  13.     void setModelViewMatrix();  
  14.     void setShape(float viewAngle,float aspect,float Near,float Far);  
  15.     void slide(float du, float dv, float dn);  
  16.     void roll(float angle);  
  17.     void yaw(float angle);  
  18.     void pitch(float angle);  
  19.     float getDist();  
  20.   
  21. private:  
  22.     Vector3d m_pos;  
  23.     Vector3d m_target;  
  24.     Vector3d m_up;  
  25.     Vector3d u,v,n;  
  26.   
  27. };  
  28.   
  29. #endif // GLCAMERA_H  

setModelViewMatrix:加载将当前MV矩阵。

setShape:设置摄像机的视角。

roll,yaw,pitch相当于绕N,V,U轴的旋转,如下图:



下面是相机的实现:

  1. #include "glcamera.h"  
  2.   
  3. GLCamera::GLCamera()  
  4. {  
  5.   
  6. }  
  7.   
  8. GLCamera::GLCamera(const Vector3d &pos, const Vector3d &target, const Vector3d &up)  
  9. {  
  10.     m_pos = pos;  
  11.     m_target = target;  
  12.     m_up = up;  
  13.     n = Vector3d( pos.x()-target.x(), pos.y()-target.y(), pos.z()-target.z());  
  14.     u = Vector3d(up.cross(n).x(), up.cross(n).y(), up.cross(n).z());  
  15.     v = Vector3d(n.cross(u).x(),n.cross(u).y(),n.cross(u).z());  
  16.   
  17.   
  18.     n.normalize();  
  19.     u.normalize();  
  20.     v.normalize();  
  21.   
  22.     setModelViewMatrix();  
  23. }  
  24.   
  25. void GLCamera::setModelViewMatrix()  
  26. {  
  27.     double m[16];  
  28.     m[0]=u.x(); m[4]=u.y(); m[8]=u.z(); m[12]=-m_pos.dot(u);  
  29.     m[1]=v.x(); m[5]=v.y(); m[9]=v.z(); m[13]=-m_pos.dot(v);  
  30.     m[2]=n.x(); m[6]=n.y(); m[10]=n.z(); m[14]=-m_pos.dot(n);  
  31.     m[3]=0;  m[7]=0;  m[11]=0;  m[15]=1.0;  
  32.     glMatrixMode(GL_MODELVIEW);  
  33.     glLoadMatrixd(m);     //用M矩阵替换原视点矩阵  
  34. }  
  35.   
  36. void  GLCamera::setShape(float viewAngle, float aspect, float Near, float Far)  
  37. {  
  38.     glMatrixMode(GL_PROJECTION);  
  39.     glLoadIdentity();                                   //设置当前矩阵模式为投影矩阵并归一化  
  40.     gluPerspective(viewAngle,aspect, Near, Far);        //对投影矩阵进行透视变换  
  41. }  
  42.   
  43. void GLCamera::slide(float du, float dv, float dn)  
  44. {  
  45.     //std::cout<<"u.x:"<<u.x()<<std::endl;< span="">  </u.x()<<std::endl;<>
  46.     m_pos(0) = m_pos(0)   du*u.x() dv*v.x() dn*n.x();  
  47.     m_pos(1) = m_pos(1)   du*u.y()  dv*v.y() dn*n.y();  
  48.     m_pos(2) = m_pos(2)   du*u.z() dv*v.z() dn*n.z();  
  49.     m_target(0) = m_target(0) du*u.x() dv*v.x() dn*n.x();  
  50.     m_target(1) = m_target(0) du*u.y() dv*v.y() dn*n.y();  
  51.     m_target(2) = m_target(0) du*u.z() dv*v.z() dn*n.z();  
  52.     setModelViewMatrix();  
  53. }  
  54.   
  55. void GLCamera::roll(float angle)  
  56. {  
  57.     float cs=cos(angle*3.14159265/180);  
  58.     float sn=sin(angle*3.14159265/180);  
  59.     Vector3d t(u);  
  60.     Vector3d s(v);  
  61.     u.x() = cs*t.x()-sn*s.x();  
  62.     u.y() = cs*t.y()-sn*s.y();  
  63.     u.z() = cs*t.z()-sn*s.z();  
  64.   
  65.     v.x() = sn*t.x() cs*s.x();  
  66.     v.y() = sn*t.y() cs*s.y();  
  67.     v.z() = sn*t.z() cs*s.z();  
  68.   
  69.     setModelViewMatrix();          //每次计算完坐标轴变化后调用此函数更新视点矩阵  
  70. }  
  71.   
  72. void GLCamera::pitch(float angle)  
  73. {  
  74.     float cs=cos(angle*3.14159265/180);  
  75.     float sn=sin(angle*3.14159265/180);  
  76.     Vector3d t(v);  
  77.     Vector3d s(n);  
  78.   
  79.     v.x() = cs*t.x()-sn*s.x();  
  80.     v.y() = cs*t.y()-sn*s.y();  
  81.     v.z() = cs*t.z()-sn*s.z();  
  82.   
  83.     n.x() = sn*t.x() cs*s.x();  
  84.     n.y() = sn*t.y() cs*s.y();  
  85.     n.z() = sn*t.z() cs*s.z();  
  86.   
  87.   
  88.     setModelViewMatrix();  
  89. }  
  90.   
  91. void GLCamera::yaw(float angle)  
  92. {  
  93.     float cs=cos(angle*3.14159265/180);  
  94.     float sn=sin(angle*3.14159265/180);  
  95.     Vector3d t(n);  
  96.     Vector3d s(u);  
  97.   
  98.     n.x() = cs*t.x()-sn*s.x();  
  99.     n.y() = cs*t.y()-sn*s.y();  
  100.     n.z() = cs*t.z()-sn*s.z();  
  101.   
  102.     u.x() = sn*t.x() cs*s.x();  
  103.     u.y() = sn*t.y() cs*s.y();  
  104.     u.z() = sn*t.z() cs*s.z();  
  105.   
  106.     setModelViewMatrix();  
  107. }  
  108.   
  109. float  GLCamera::getDist()  
  110. {  
  111.     float dist = pow(m_pos.x(),2) pow(m_pos.y(),2) pow(m_pos.z(),2);  
  112.     return pow(dist,0.5);  
  113. }  


没什么好说的,都是矩阵的一些计算。

这样就可以将你的摄像机融入到OpenGL工程中了,比如说放进一个Qt的工程,用一个GLWifget类来显示OpenGL。

在initializeGL() 中,初始化camera

  1. Vector3d pos(0.0, 0.0, 12.0);  
  2.   Vector3d target(0.0, 0.0, 0.0);  
  3.   Vector3d up(0.0, 1.0, 0.0);  
  4.   camera = new GLCamera(pos, target, up);  

在paintGL的时候,设置当前矩阵:
  1. glLoadIdentity();  
  2.    camera->setModelViewMatrix();  

在resizeGL中调整视角:
  1. camera->setShape(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);  

添加相应的鼠标事件:
  1. void GLWidget::mousePressEvent(QMouseEvent *event)  
  2. {  
  3.     lastPos = event->pos();  
  4. }  
  5.   
  6. void GLWidget::mouseMoveEvent(QMouseEvent *event)  
  7. {  
  8.     int dx = event->x() - lastPos.x();  
  9.     int dy = event->y() - lastPos.y();  
  10.     if (event->buttons() & Qt::LeftButton)  
  11.     {  
  12.         RotateX(dx);  
  13.         RotateY(dy);  
  14.     }  
  15.     else if(event->buttons() & Qt::RightButton)  
  16.     {  
  17.         camera->roll(dx);  
  18.         //camera->pitch(dy);  
  19.         //camera->slide(0,0,-dy);  
  20.     }  
  21.     else if(event->buttons() & Qt::MiddleButton)  
  22.     {  
  23.         camera->slide(-dx,dy,0);  
  24.     }  
  25.     lastPos = event->pos();  
  26.     updateGL();  
  27. }  
  28.   
  29. void GLWidget::RotateX(float angle)  
  30. {  
  31.     float d=camera->getDist();  
  32.     int cnt=100;  
  33.     float theta=angle/cnt;  
  34.     float slide_d=-2*d*sin(theta*3.14159265/360);  
  35.     camera->yaw(theta/2);  
  36.     for(;cnt!=0;--cnt)  
  37.     {  
  38.         camera->slide(slide_d,0,0);  
  39.         camera->yaw(theta);  
  40.     }  
  41.     camera->yaw(-theta/2);  
  42. }  
  43.   
  44. void GLWidget::RotateY(float angle)  
  45. {  
  46.     float d = camera->getDist();  
  47.     int cnt=100;  
  48.     float theta=angle/cnt;  
  49.     float slide_d=2*d*sin(theta*3.14159265/360);  
  50.     camera->pitch(theta/2);  
  51.     for(;cnt!=0;--cnt)  
  52.     {  
  53.         camera->slide(0,slide_d,0);  
  54.         camera->pitch(theta);  
  55.     }  
  56.     camera->pitch(-theta/2);  
  57. }  

效果就像这样(gif 有点大,耐心等待):



如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引