您的位置:首页 > 运维架构

转‘OpenGl鼠标控制摄像机camera实现三维漫游

2013-12-17 12:30 441 查看
目录(?)[+]

提要

      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

[cpp] view
plaincopy

#ifndef EIGEN_H  

#define EIGEN_H  

  

#include "eigen3/Eigen/Dense"  

#include "eigen3/Eigen/LU"  

#include "eigen3/Eigen/Core"  

  

#endif // EIGEN_H  

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

看类声明:glcamera.h

[cpp] view
plaincopy

#ifndef GLCAMERA_H  

#define GLCAMERA_H  

#include "eigen.h"  

#include <GL/glu.h>  

#include <iostream>  

  

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轴的旋转,如下图:



下面是相机的实现:

[cpp] view
plaincopy

#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;  

    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

[cpp] view
plaincopy

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的时候,设置当前矩阵:

[cpp] view
plaincopy

glLoadIdentity();  

   camera->setModelViewMatrix();  

在resizeGL中调整视角:

[cpp] view
plaincopy

camera->setShape(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);  

添加相应的鼠标事件:

[cpp] view
plaincopy

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 有点大,耐心等待):



参考

openGL中camera类的设计以及使用 - http://blog.csdn.net/hobbit1988/article/details/7956838
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: