簡介:
首先,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類的你能夠有所幫助。