Qt中使用OpenGL-Camera类

在 OpenGL 中,我们通过数学方法构建出一个视图矩阵模拟摄像机,也可以通过定义一些变量,例如当前坐标和视角朝向,然后接收用户的键盘和鼠标操作,根据操作动态的修改这些变量,最终返回给 OpenGL 一个基于这些信息的视图矩阵。

摄像机类

按键枚举

将用户移动方向进行枚举,可以避免特定与窗口系统的输入方式

1
2
3
4
5
6
7
8
9
// 这里是 WASDQE
enum Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
RIGHT,
UP,
DOWN
};

Camera.h

下面对主要使用的函数进行介绍:

有两个构造函数,表示根据不同的参数初始化视图矩阵

  • Camera(QVector3D position = QVector3D(0.0f, 0.0f, 0.0f), QVector3D up = QVector3D(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH)
    • 平时可以接调用此方法,用默认参数进行构造
  • Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch)
    • 如果有更高要求,可以用此构造函数进行自定义构造

GetViewMatrix() 函数可以直接返回当前状态下的视图矩阵。

ProcessKeyboard(Camera_Movement direction, float deltaTime) 函数用于处理键盘输入,参数为当前按键(按键枚举类)和 时间差 deltaTime

ProcessMouseMovement(float xoffset, float yoffset) 函数用于处理鼠标输入,参数为 x 方向的位移 xoffset 和 y 方向的位移 yoffset

ProcessMouseScroll(float yoffset) 函数用于处理鼠标滚轮输入,参数为滚动位移 yoffset

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#ifndef CAMERA_H
#define CAMERA_H

#include <QMatrix4x4>
#include <vector>
#include <complex>
// 移动方向枚举量,是一种抽象,以避免特定于窗口系统的输入方式
// 这里是 WASDQE
enum Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
RIGHT,
UP,
DOWN
};

// 默认值
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 1.0f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;

class Camera
{
public:
// camera attributes
QVector3D Position;
QVector3D Front;
QVector3D Up;
QVector3D Right;
QVector3D WorldUp;
// euler angles
float Yaw;
float Pitch;
// camera options
float MovementSpeed = SPEED;
float MouseSensitivity = SENSITIVITY;
float Zoom = ZOOM;

// constructor with vectors
Camera(QVector3D position = QVector3D(0.0f, 0.0f, 0.0f), QVector3D up = QVector3D(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH)
{
Position = position;
WorldUp = up;
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
// constructor with scalar values
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch)
{
Position = QVector3D(posX, posY, posZ);
WorldUp = QVector3D(upX, upY, upZ);
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}

// return the view matrix calculated using EUler Angles ant the LookAt Matrix
QMatrix4x4 GetViewMatrix()
{
QMatrix4x4 theMatrix;
theMatrix.lookAt(Position, Position + Front, Up);
return theMatrix;
}

// 处理键盘输入
void ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = MovementSpeed * deltaTime;
if(direction == FORWARD) {
Position += Front * velocity;
}
if(direction == BACKWARD) {
Position -= Front * velocity;
}
if(direction == LEFT) {
Position -= Right * velocity;
}
if(direction == RIGHT) {
Position += Right * velocity;
}
if(direction == UP) {
Position += Up * velocity;
}
if(direction == DOWN) {
Position -= Up * velocity;
}
// Position.setY(1.0f);
// updateCameraVectors();
}

// 处理鼠标输入
void ProcessMouseMovement(float xoffset, float yoffset)
{
xoffset *= MouseSensitivity;
yoffset *= MouseSensitivity;
Yaw += xoffset;
Pitch += yoffset;
// 确保超出边界时屏幕不会翻转
if (true) {
if (Pitch > 89.0f) {
Pitch = 89.0f;
}
if (Pitch < -89.0f) {
Pitch = -89.0f;
}
}
// 使用更新的Euler角度更新前、右和上矢量
updateCameraVectors();
}

// 处理鼠标滚轮输入
void ProcessMouseScroll(float yoffset)
{
Zoom -= (float)yoffset;
if (Zoom < 1.0f) {
Zoom = 1.0f;
}
if (Zoom > 75.0f) {
Zoom = 75.0f;
}
}
private:
// 根据相机的Euler角度计算前矢量
void updateCameraVectors()
{
// calculate the Front vector
float PI = 3.14159265f;
QVector3D front;
front.setX(cos(Yaw * PI / 180.0) * cos(Pitch * PI / 180.0));
front.setY(sin(Pitch * PI / 180.0));
front.setZ(sin(Yaw * PI / 180.0) * cos(Pitch * PI / 180.0));
front.normalize();
Front = front;
// 重计算右向量和上向量
Right = QVector3D::crossProduct(Front, WorldUp);
Right.normalize();
Up = QVector3D::crossProduct(Right, Front);
Up.normalize();
}
};


#endif // CAMERA_H

如何使用摄像机类

引入成员变量并初始化

  1. 在 OpenGLWidget 类中将摄像机类作为成员变量加入
1
2
private:
Camera m_camera;
  1. 定义一个相机初始位置 viewInitPos
1
QVector3D viewInitPos(0.0f, 5.0f, 10.0f);
  1. 在 OpenGLWidget 类的构造函数中设置 m_camera 的位置
1
2
3
4
OpenGLWidget::OpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
m_camera.Position = viewInitPos;
}

重写鼠标、键盘事件处理类

在 OpenGLWidget 类的 protected 中加入 wheelEvent、keyPressEvent、mouseMoveEvent、mousePressEvent 这四个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>

class OpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
OpenGLWidget(QWidget *parent);
~OpenGLWidget();
protected:
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
void wheelEvent(QWheelEvent *event);
void keyPressEvent(QKeyEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
private:
QOPenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo;
}

重写这几个方法

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
void TreeOpenGLWidget::wheelEvent(QWheelEvent *event)
{
m_camera.ProcessMouseScroll(event->angleDelta().y() / 120);
}

void TreeOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
float deltaTime = timeOutmSec / 50.0f;
switch (event->key()) {
case Qt::Key_W:
m_camera.ProcessKeyboard(FORWARD, deltaTime);
break;
case Qt::Key_S:
m_camera.ProcessKeyboard(BACKWARD, deltaTime);
break;
case Qt::Key_A:
m_camera.ProcessKeyboard(LEFT, deltaTime);
break;
case Qt::Key_D:
m_camera.ProcessKeyboard(RIGHT, deltaTime);
break;
case Qt::Key_Q:
m_camera.ProcessKeyboard(UP, deltaTime);
break;
case Qt::Key_E:
m_camera.ProcessKeyboard(DOWN, deltaTime);
break;
case Qt::Key_Space:
m_camera.Position = viewInitPos;
break;
default:
break;
}
}

void TreeOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
makeCurrent();
if(event->buttons() & Qt::RightButton) {
auto currentPos = event->pos();
QPoint deltaPos = currentPos - lastPos;
lastPos = currentPos;
m_camera.ProcessMouseMovement(deltaPos.x(), -deltaPos.y());
}
doneCurrent();
}

void TreeOpenGLWidget::mousePressEvent(QMouseEvent *event)
{
makeCurrent();
lastPos = event->pos();
doneCurrent();
}

绘制前获取信息

1
2
3
4
// 从 m_camera 获取当前缩放大小设置投影矩阵
m_projection.perspective(m_camera.Zoom, (float)width()/height(),0.1,100);
// 从 m_camera 获取视图矩阵
m_view = m_camera.GetViewMatrix();

现在就可以使用键盘、鼠标、滚轮控制显示的视图了~