0%

Qt+OpenGL 2.创建摄像机类

上一篇文章中,我在Qt中搭建了一个Opengl绘制框架,并绘制了一个三角形,为了让朴实无华且枯燥的三角形动起来,我决定创建一个摄像机。

摄像机类

我创建了一个openglcamera.h文件,以及对应的cpp文件,用于安放摄像机类体系。什么,类体系?没错,我先创建了一个摄像机的抽象基类,称为AbstractCamera,并由此派生出其余的具体摄像机类。学c++,还是要多锻炼自己面向对象的思想。

首先要为摄像机类定义抽象接口,摄像机类公有的功能,一是要获取矩阵,二是要处理外界事件,比如按键、鼠标移动等。因此我在抽象类中声明了以下几个纯虚函数:

  • virtual QMatrix4x4 getViewMatrix() 获取视图矩阵
  • virtual QMatrix4x4 getProjectMatrix() 获取投影矩阵
  • virtual void processEvent(Camera::Event e) 处理事件
  • virtual void processEvent(Camera::Event e,float xoffset) 重载函数,处理滚轮等事件
  • virtual void processEvent(Camera::Event e,float xoffset,float yoffset) 重载函数,处理鼠标移动等事件

其中对于processEvent()函数我重载了多个版本,因为对于不同的事件可能有不同的参数传递,比如鼠标移动事件就需要相对移动坐标参数。本来我想用可变长参数列表实现,但考虑到会使接口不是特别清晰,于是采用了重载的方法。

为了将三角形动起来,我通过上面定义的抽象摄像机,派生出一个CAD风格的摄像机类,命名为CADCamera。该类中有一些独有的成员变量,zoomSensitivity、rotateSensitivity等,用来控制摄像机的各项属性;最核心的旋转和缩放算法在processEvent()函数中,作者并不打算展开描述这些细节╮(╯3╰)╭;最后获取投影矩阵和视图矩阵则分别用到了Qt矩阵库中的QMatrix4x4::ortho()、QMatrix4x4::lookAt()函数(投影矩阵采用正投影)。

以下是代码:

openglcamera.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#ifndef OPENGLCAMERA_H
#define OPENGLCAMERA_H
#include <QMatrix4x4>
#include <QVector>
#include <QString>
#include <initializer_list>
class GLWidget;
namespace Camera{
//各种外界事件
enum Event{Up,Down,Left,Right,Mouse_Move,Mouse_Scroll};
}
//一个抽象的摄像机类
class AbstractCamera
{
public:
AbstractCamera()=default;s
AbstractCamera(GLWidget* father,QVector3D pos,QVector3D front,QVector3D up);
virtual QMatrix4x4 getViewMatrix()=0;
virtual QMatrix4x4 getProjectMatrix()=0;
//处理所有响应类事件
virtual void processEvent(Camera::Event e)=0;
//滚轮、鼠标移动之类的事件,需要位移参数
virtual void processEvent(Camera::Event e,float xoffset)=0;
virtual void processEvent(Camera::Event e,float xoffset,float yoffset)=0;
//基类的析构函数也要设成虚函数
virtual ~AbstractCamera();
protected:
GLWidget* father;
QVector3D Position;
QVector3D Front;
QVector3D Up;
QVector3D Right;
};
class CADCamera: public AbstractCamera
{
public:
CADCamera(GLWidget* father,QVector3D pos);
virtual QMatrix4x4 getViewMatrix() override;
virtual QMatrix4x4 getProjectMatrix() override;

virtual void processEvent(Camera::Event e) override;
virtual void processEvent(Camera::Event e,float xoffset) override;
virtual void processEvent(Camera::Event e,float xoffset,float yoffset) override;

private:
void ProcessMouseScroll(float yoffset);
void ProcessMouseMovement(float xoffset, float yoffset);
const float zoomSensitivity;
const float rotateSensitivity;
//正交投影矩阵的基础宽度
const float orthoWidth;
//缩放比
float Zoom;
};
#endif // OPENGLCAMERA_H
openglcamera.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include "openglcamera.h"
#include <QTransform>
#include <QMatrix4x4>
#include "glwidget.h"
AbstractCamera::AbstractCamera(GLWidget* father,QVector3D pos, QVector3D front, QVector3D up):
father(father),Position(pos),Front(front),Up(up)
{
//前方向向量和上方向向量叉乘决定右方向向量
Right = QVector3D::crossProduct(front,up);
Right.normalize();
}

AbstractCamera::~AbstractCamera()
{
}

CADCamera::CADCamera(GLWidget* father,QVector3D pos):
AbstractCamera (father,pos,QVector3D(0.0f, 0.0f, -1.0f),QVector3D(0.0,1.0,0.0f)),
zoomSensitivity(0.001),rotateSensitivity(0.3),orthoWidth(2.0f)
{
Zoom = 1.0;
}

QMatrix4x4 CADCamera::getViewMatrix()
{
QMatrix4x4 viewMat;
viewMat.lookAt(Position,Position+Front,Up);
return viewMat;
}

QMatrix4x4 CADCamera::getProjectMatrix()
{
QMatrix4x4 projectMat;
float halfWid = orthoWidth*Zoom/2;
//opengl界面的高宽比
float hw = father->height()*1.0/father->width();
//远平面和近平面的坐标看情况可以适当调大
projectMat.ortho(-halfWid,halfWid,-halfWid*hw,halfWid*hw,100.0f,-100.0f);
return projectMat;
}

void CADCamera::processEvent(Camera::Event e)
{

}

void CADCamera::processEvent(Camera::Event e, float xoffset)
{
using namespace Camera;
switch (e) {
case Mouse_Scroll: ProcessMouseScroll(xoffset);
}
}

void CADCamera::processEvent(Camera::Event e, float xoffset, float yoffset)
{
using namespace Camera;
switch (e) {
case Mouse_Move: ProcessMouseMovement(xoffset,yoffset);
}
}

void CADCamera::ProcessMouseScroll(float yoffset)
{
Zoom -= yoffset*zoomSensitivity;
if(Zoom<=0.01)
Zoom = 0.01;
}

void CADCamera::ProcessMouseMovement(float xoffset, float yoffset)
{
xoffset *= rotateSensitivity;
yoffset *= rotateSensitivity;

QMatrix4x4 rotateX;
rotateX.rotate(xoffset,Up);
QMatrix4x4 rotateY;
rotateY.rotate(yoffset,Right);
Up = QVector3D(rotateY*QVector4D(Up,1.0));
Position = QVector3D(rotateX*rotateY*QVector4D(Position,1.0));
Front = -Position;
Front.normalize();
Right = QVector3D::crossProduct(Front,Up);
Right.normalize();
//上述计算后Up会有微小误差,会形成累积误差,要修正Up,使三个方向向量保持90度关系
Up = -QVector3D::crossProduct(Front,Right);
Up.normalize();
}

新的着色器

由于加入了模型、视图、投影矩阵,所以需要新的顶点着色器。为每个矩阵设一个uniform变量,并乘到原始坐标上。

CADshader.vs
1
2
3
4
5
6
7
8
9
#version 430 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 project;
void main()
{
gl_Position = project*view*model*vec4(aPos.x, aPos.y, aPos.z, 1.0f);
}

另外,为了给这个三角形一点颜色看看(give this triangel a little color see see),我在片段着色器中加入了代表颜色的uniform变量。

CADshader.fs
1
2
3
4
5
6
7
#version 430 core
out vec4 FragColor;
uniform vec4 myColor;
void main()
{
FragColor = myColor;
}

修改GLWidget类

为了适应新加入的摄像机类,GLWidget类中需要做一些修改。第一步是加入一个摄像机的成员变量,这里需要做一点手脚。不能直接为该摄像机变量声明成具体的CADCamera类,因为如果以后有更多的摄像机类了(如果有那一天的话),GLWidget的子类可能会需要另一个摄像机类,这样就需要为摄像机对象的类型留出更改空间。这里就要祭出工厂模式啦(Factory Method),在头文件中定义一个工厂虚函数,和一个指向抽象摄像机类的智能指针成员。

1
2
virtual AbstractCamera* cameraFactory(); //“摄像机工厂”
shared_ptr<AbstractCamera> pCamera;

源文件中实现该工厂函数,在该版本的GLWidget中,工厂建立一个CADCamera对象,并以注意:工厂函数不能在GLWidget构造函数中调用,因为GLWiget的派生类在构造GLWiget类时,派生类本身没有构造完毕,因此调用的永远是GLWiget版本的cameraFactory()。

绝不在构造和析构函数中调用virtual函数——《effective c++》

1
2
3
4
5
6
7
8
9
10
11
AbstractCamera *GLWidget::cameraFactory()
{
return new CADCamera(this,QVector3D(0.0,0.0,3.0));
}
//在初始化函数中调用工厂函数
GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
father = parent;
pCamera.reset(cameraFactory());
init();
}

增加新模型类

为了迎合该摄像机,需要派生出一种新的CADModel类,类定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CADModel:public GLModel
{
public:
CADModel(GLWidget* f)
:GLModel(f){};
CADModel(GLWidget* f,vector<Vertex> v,vector<unsigned int> i=vector<unsigned int>(),vector<Texture> t=vector<Texture>())
:GLModel(f,v,i,t){};
CADModel(GLWidget* f,unsigned int vbo,vector<unsigned int> i=vector<unsigned int>(),vector<Texture> t=vector<Texture>())
:GLModel(f,vbo,i,t){};

private:
virtual void mainDraw(QOpenGLShaderProgram* shader) override;
QMatrix4x4 modelMat;
};

该类重写mainDraw()函数,在其中分配矩阵和颜色的Uniform,因为模型本身不做变换,模型矩阵即为单位矩阵。颜色上,使之随时间变化。

1
2
3
4
5
6
7
8
9
10
void CADModel::mainDraw(QOpenGLShaderProgram *shader)
{
using namespace std;
float t=clock()/200.0;
shader->setUniformValue("model",modelMat);
shader->setUniformValue("view",myCamera->getViewMatrix());
shader->setUniformValue("project",myCamera->getProjectMatrix());
shader->setUniformValue("myColor",QVector4D(sin(t)/2+1,cos(sqrt(2)*t)/2+1,sin(sqrt(3)*t+0.5)/2+1,cos(sqrt(4)*t+0.6)/2+1));
father->glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
}

效果

魔幻三角!