• OpenGL进阶(十四)


    提要

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



    下面是相机的实现:

    #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

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



    参考

    openGL中camera类的设计以及使用 - http://blog.csdn.net/hobbit1988/article/details/7956838

  • 相关阅读:
    rpm命令详解
    Linux基础提高_系统性能相关命令
    Day004_Linux基础命令之特殊符号与正则表达式通配符
    Linux基础_网站权限规划
    Day005_Linux基础之文件权限
    Day003_linux基础_系统启动过程及系统安装后优化
    win7旗舰版安装不了mysql问题-------win7系统版本选择问题的一点探索
    Java程序结构
    NCRE Java二级备考方案
    NCRE的JAVA二级考试大纲
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3398174.html
Copyright © 2020-2023  润新知