天天看點

openGL中camera類的設計以及使用

簡介:

       首先,camera類是什麼?它相當于錄影機鏡頭,通過設定合适的位置和角度可以實作對3D繪制圖形的觀察,而camera類的優勢就在于可以圍繞繪制的物體進行漫遊,唯一需要完成的工作就是如何使用該類的操作完成有效的漫遊。

       很多帶3D圖形顯示的軟體其實都有自己的camera類,很多時候我們可以使用滑鼠即可完成3D圖形的各個方向360度的觀察,并且最好還可以拉近或者拉遠攝像頭來實作對模型的縮放功能。本章内容通過對自己近期的實踐展示源自《計算機圖形學》中基于openGL的一個camera類以及操作方法來實作便捷的繞物體各方位觀察和縮放。

工作内容:

       使用了openGL的相關庫,對書中類的函數進行的重排版實作,在QT環境中運作對已有模型進行了測試,運作效果良好。雖然是在QT中測試的,但是移植到其他環境中隻要加入相關的openGL庫都是沒有問題的。

       本文預設你已經對camera的屬性有一定了解,其中eye指的是攝像頭在世界坐标系中的坐标,look指的是攝像頭所望的方向的一點,up指的是攝像頭上方一坐标,初始化的時候設定好這三個點,然後計算出初始的u、v、n機關三個向量。camera類可以實作的操作有平移和旋轉,以攝像頭為中心有上述三個兩輛垂直的向量作為坐标軸,是以一共有三個平移和三個旋轉操作,也就是六個自由度。roll對應的是翻滾角,yaw對應的是偏航角,pitch對應的是俯仰角。每種操作有兩個方向,是以其實一共12種可能的變化。如果用鍵盤操作則需要12個鍵盤,這相當不好控制。

       是以本文使用滑鼠的兩個鍵即實作了上述功能,并具有較小局限性,具體原理将在後面介紹。下面貼出camera.h和camera.cpp的代碼,其中有詳細注釋。

camera.h檔案

#ifndef CAMERA_H
#define CAMERA_H

#include <QtOpenGL>
#include <math.h>

class Point3
{
public:
    float x,y,z;
    void set(float dx,float dy,float dz)
    {
        x=dx; y=dy; z=dz;
    }
    void set(Point3& p)
    {
        x=p.x; y=p.y; z=p.z;
    }
    Point3(float xx,float yy,float zz)
    {
        x=xx; y=yy; z=zz;
    }
    Point3()
    {
        x=y=z=0;
    }
    void build4tuple(float v[])
    {
        v[0]=x; v[1]=y; v[2]=z; v[3]=1.0f;
    }
};

class Vector3
{
public:
    float x,y,z;
    void set(float dx,float dy,float dz)
    {
        x=dx; y=dy; z=dz;
    }
    void set(Vector3& v)
    {
        x=v.x; y=v.y; z=v.z;
    }
    void flip()
    {
        x=-x; y=-y; z=-z;
    }
    void setDiff(Point3& a,Point3& b)
    {
        x=a.x-b.x; y=a.y-b.y; z=a.z-b.z;
    }
    void normalize()
    {
        float base=pow(x,2)+pow(y,2)+pow(z,2);
        x=x/pow(base,0.5);
        y=y/pow(base,0.5);
        z=z/pow(base,0.5);
    }
    Vector3(float xx, float yy, float zz)
    {
        x=xx; y=yy; z=zz;
    }
    Vector3(Vector3 &v)
    {
        x=v.x; y=v.y; z=v.z;
    }
    Vector3()
    {
        x=0; y=0; z=0;
    }

    Vector3 cross(Vector3 b)
    {
        float x1,y1,z1;
        x1=y*b.z-z*b.y;
        y1=z*b.x-x*b.z;
        z1=x*b.y-y*b.x;
        Vector3 c(x1,y1,z1);
        return c;
    }

    float dot(Vector3 b)
    {
        float d=x*b.x+y*b.y+z*b.z;
        return d;
    }
};

class Camera
{
public:
/* 構造函數和析構函數 */
Camera();
~Camera();

/* 設定錄影機的位置, 觀察點和向上向量 */
void setCamera( float eyeX, float eyeY, float eyeZ,
                float lookX, float lookY, float lookZ,
                float upX, float upY, float upZ);
void roll(float angle);
void pitch(float angle);
void yaw(float angle);
void slide(float du, float dv, float dn);
float getDist();
void setShape(float viewAngle,float aspect,float Near,float Far);


private:
/* 錄影機屬性 */
Point3         eye,look,up;
Vector3        u,v,n;
float          viewAngle, aspect, nearDist, farDist;
void           setModelViewMatrix();

};

#endif //__CAMERA_H__
           

說明:其中inlude的QtOpenGL檔案是QT中封裝的opengl類,如果不是在QT下運作,則需要inlude "gl\glut.h" 。Point3和Vector3類的定義主要是考慮camera中坐标點和向量的使用而設計。

camera.cpp檔案:

#include "camera.h"


 /* 構造函數 */
Camera::Camera()   {}

Camera::~Camera()  {}

/* 設定錄影機的位置,朝向和向上向量 */
void Camera::setCamera( float eyeX, float eyeY, float eyeZ,
                        float lookX, float lookY, float lookZ,
                        float upX, float upY, float upZ)
{
/* 構造向量 */
    eye.set(eyeX, eyeY, eyeZ);
    look.set(lookX, lookY, lookZ);
    up.set(upX, upY, upZ);
    Vector3 upvec(up.x-eye.x,up.y-eye.y,up.z-eye.z);

/* 計算n、u、v并歸一化*/
    n.set(eye.x-look.x, eye.y-look.y, eye.z-look.z);
    u.set(upvec.cross(n).x,upvec.cross(n).y,upvec.cross(n).z);
    v.set(n.cross(u).x,n.cross(u).y,n.cross(u).z);

    u.normalize();
    v.normalize();
    n.normalize();
    setModelViewMatrix();
}
/* 擷取eye到坐标原點的距離 */
float Camera::getDist()
{
    float dist=pow(eye.x,2)+pow(eye.y,2)+pow(eye.z,2);
    return pow(dist,0.5);
}
/* 計算變換後的視點矩陣*/
void Camera::setModelViewMatrix()
{
    float m[16];
    Vector3 eVec(eye.x, eye.y,eye.z);
    m[0]=u.x; m[4]=u.y; m[8]=u.z; m[12]=-eVec.dot(u);
    m[1]=v.x; m[5]=v.y; m[9]=v.z; m[13]=-eVec.dot(v);
    m[2]=n.x; m[6]=n.y; m[10]=n.z; m[14]=-eVec.dot(n);
    m[3]=0;  m[7]=0;  m[11]=0;  m[15]=1.0;
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(m);     //用M矩陣替換原視點矩陣
}
/* 錄影機繞n、v、u軸旋轉的計算函數*/
void Camera::roll(float angle)

{
    float cs=cos(angle*3.14159265/180);
    float sn=sin(angle*3.14159265/180);
    Vector3 t(u);
    Vector3 s(v);
    u.set(cs*t.x-sn*s.x, cs*t.y-sn*s.y, cs*t.z-sn*s.z);
    v.set(sn*t.x+cs*s.x, sn*t.y+cs*s.y, sn*t.z+cs*s.z);
    setModelViewMatrix();          //每次計算完坐标軸變化後調用此函數更新視點矩陣
}

void Camera::yaw(float angle)
{
    float cs=cos(angle*3.14159265/180);
    float sn=sin(angle*3.14159265/180);
    Vector3 t(n);
    Vector3 s(u);
    n.set(cs*t.x-sn*s.x, cs*t.y-sn*s.y, cs*t.z-sn*s.z);
    u.set(sn*t.x+cs*s.x, sn*t.y+cs*s.y, sn*t.z+cs*s.z);
    setModelViewMatrix();
}

void Camera::pitch(float angle)

{
    float cs=cos(angle*3.14159265/180);
    float sn=sin(angle*3.14159265/180);
    Vector3 t(v);
    Vector3 s(n);
    v.set(cs*t.x-sn*s.x, cs*t.y-sn*s.y, cs*t.z-sn*s.z);
    n.set(sn*t.x+cs*s.x, sn*t.y+cs*s.y, sn*t.z+cs*s.z);
    setModelViewMatrix();
}
/* 錄影機繞三個軸平移的計算函數*/
void Camera::slide(float du, float dv, float dn)
{
    eye.x+=du*u.x+dv*v.x+dn*n.x;
    eye.y+=du*u.y+dv*v.y+dn*n.y;
    eye.z+=du*u.z+dv*v.z+dn*n.z;
    look.x+=du*u.x+dv*v.x+dn*n.x;
    look.y+=du*u.y+dv*v.y+dn*n.y;
    look.z+=du*u.z+dv*v.z+dn*n.z;
    setModelViewMatrix();
}
/* 錄影機初始化*/
void Camera::setShape(float viewAngle, float aspect, float Near, float Far)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();                                   //設定目前矩陣模式為投影矩陣并歸一化
    gluPerspective(viewAngle,aspect, Near, Far);        //對投影矩陣進行透視變換
}
           

使用方法:

       初始化:假設物體坐标中心在世界坐标原點,那麼可以把攝像頭設定在物體以外稍遠的地方讓方向朝向物體即可。調用cam.setCamera和cam.setShape函數來對錄影機進行初始化。

        如何用滑鼠實作對類的使用呢?這裡基于QT中滑鼠的事件控制,按下滑鼠左鍵沿着螢幕左右移動實作沿着u軸slide和繞着v軸yaw;上下移動實作沿着v軸slide和繞着u軸pitch。注意,每次移動滑鼠都要使攝像頭繞這物體的中心點(一般設定為look)點旋轉,即eye和look點的距離保持不變。這需要把每次移動看作多次平移和旋轉的結合,下面的代碼給出每次微小平移和旋轉的幾何關系,此關系讀者可以通過畫圖分析得到。

        通過右鍵的左右實作roll,上下實作沿着n軸的slide也就是縮放。

首先給出QT中滑鼠事件的代碼:

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)
    {
        cam.roll(dx);
        cam.slide(0,0,-dy);
    }
    lastPos = event->pos();
}
           

RotateX()和RotateY()函數實作環繞物體漫遊的操作,代碼如下:

void GLWidget::RotateX(float angle)
{
    float d=cam.getDist();
    int cnt=100;
    float theta=angle/cnt;
    float slide_d=-2*d*sin(theta*3.14159265/360);
    cam.yaw(theta/2);
    for(;cnt!=0;--cnt)
    {
        cam.slide(slide_d,0,0);
        cam.yaw(theta);
    }
    cam.yaw(-theta/2);
}

void GLWidget::RotateY(float angle)
{
    float d=cam.getDist();
    int cnt=100;
    float theta=angle/cnt;
    float slide_d=2*d*sin(theta*3.14159265/360);
    cam.pitch(theta/2);
    for(;cnt!=0;--cnt)
    {
        cam.slide(0,slide_d,0);
        cam.pitch(theta);
    }
    cam.pitch(-theta/2);
}
           

通過上面兩個函數可以實作對物體任意角度的觀察,其中d是eye和物體中心點的距離,這裡假設物體的中心點就是坐标原點。

希望該文對需要使用camera類的你能夠有所幫助。