在Qt中使用OpenGL实现相机功能主要涉及视图矩阵(view matrix)的操作,包括相机位置、观察方向和上向量等概念。下面我将介绍如何在Qt中实现一个基本的3D相机。
基本概念
OpenGL相机本质上是通过视图矩阵(view matrix)来实现的,它定义了从世界空间到观察空间的变换。视图矩阵可以通过以下参数构建:
相机位置(camera position)
目标位置(target position)
上向量(up vector)
实现步骤
1. 包含必要的头文件
cpp
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QMatrix4x4>
#include <QVector3D>
#include <QKeyEvent>
2. 定义相机类
Camera.h
#ifndef CAMERA_H
#define CAMERA_H
#include <QVector3D>
#include <QMatrix4x4>
#include <QQuaternion>
class Camera
{
public:
Camera();
void setPosition(const QVector3D &position);
void setTarget(const QVector3D &target);
void setUpVector(const QVector3D &up);
QMatrix4x4 getViewMatrix() const;
QVector3D getPosition() const { return m_position; }
void moveForward(float distance);
void moveRight(float distance);
void moveUp(float distance);
void rotate(float yaw, float pitch);
private:
void updateVectors();
QVector3D m_position;
QVector3D m_target;
QVector3D m_up;
QVector3D m_right;
float m_yaw;
float m_pitch;
};
#endif // CAMERA_H
3. 实现相机类
Camera.cpp
#include "camera.h"
#include <QtMath>
Camera::Camera() :
m_position(0.0f, 0.0f, 3.0f),
m_target(0.0f, 0.0f, -1.0f),
m_up(0.0f, 1.0f, 0.0f),
m_yaw(-90.0f),
m_pitch(0.0f)
{
updateVectors();
}
void Camera::setPosition(const QVector3D &position)
{
m_position = position;
updateVectors();
}
void Camera::setTarget(const QVector3D &target)
{
m_target = target;
updateVectors();
}
void Camera::setUpVector(const QVector3D &up)
{
m_up = up;
updateVectors();
}
QMatrix4x4 Camera::getViewMatrix() const
{
QMatrix4x4 view;
view.lookAt(m_position, m_position + m_target, m_up);
return view;
}
void Camera::moveForward(float distance)
{
m_position += m_target * distance;
}
void Camera::moveRight(float distance)
{
m_position += m_right * distance;
}
void Camera::moveUp(float distance)
{
m_position += m_up * distance;
}
void Camera::rotate(float yaw, float pitch)
{
m_yaw += yaw;
m_pitch += pitch;
// 限制俯仰角,防止万向节死锁
if (m_pitch > 89.0f)
m_pitch = 89.0f;
if (m_pitch < -89.0f)
m_pitch = -89.0f;
updateVectors();
}
void Camera::updateVectors()
{
// 计算新的前向量
QVector3D front;
front.setX(cos(qDegreesToRadians(m_yaw)) * cos(qDegreesToRadians(m_pitch)));
front.setY(sin(qDegreesToRadians(m_pitch)));
front.setZ(sin(qDegreesToRadians(m_yaw)) * cos(qDegreesToRadians(m_pitch)));
m_target = front.normalized();
// 重新计算右向量和上向量
m_right = QVector3D::crossProduct(m_target, QVector3D(0.0f, 1.0f, 0.0f)).normalized();
m_up = QVector3D::crossProduct(m_right, m_target).normalized();
}
4. 在OpenGLWidget中使用相机
OpenGLWidget.h
#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QMatrix4x4>
#include <QVector3D>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include "camera.h"
class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit OpenGLWidget(QWidget *parent = nullptr);
~OpenGLWidget();
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
void keyPressEvent(QKeyEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
void initShaders();
void initCube(float width);
QOpenGLShaderProgram m_program;
QOpenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo;
Camera m_camera;
QMatrix4x4 m_projection;
QPoint m_lastMousePos;
bool m_firstMouse = true;
};
#endif // OPENGLWIDGET_H
5. 实现OpenGLWidget
OpenGLWidget.cpp
#include "openglwidget.h"
#include <QDebug>
OpenGLWidget::OpenGLWidget(QWidget *parent) :
QOpenGLWidget(parent),
m_lastMousePos(QPoint(width()/2, height()/2))
{
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
}
OpenGLWidget::~OpenGLWidget()
{
m_vao.destroy();
m_vbo.destroy();
}
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
initShaders();
initCube(1.0f);
glEnable(GL_DEPTH_TEST);
}
void OpenGLWidget::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
m_projection.setToIdentity();
m_projection.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f);
}
void OpenGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_program.bind();
// 设置模型、视图和投影矩阵
QMatrix4x4 model;
model.setToIdentity();
model.translate(0.0f, 0.0f, 0.0f);
m_program.setUniformValue("model", model);
m_program.setUniformValue("view", m_camera.getViewMatrix());
m_program.setUniformValue("projection", m_projection);
// 绘制立方体
m_vao.bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
m_vao.release();
m_program.release();
}
void OpenGLWidget::initShaders()
{
if (!m_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertex.glsl"))
qDebug() << "Vertex shader error:" << m_program.log();
if (!m_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragment.glsl"))
qDebug() << "Fragment shader error:" << m_program.log();
if (!m_program.link())
qDebug() << "Shader program link error:" << m_program.log();
}
void OpenGLWidget::initCube(float width)
{
float halfWidth = width / 2.0f;
QVector<QVector3D> vertices;
// 前面
vertices << QVector3D(-halfWidth, -halfWidth, halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, halfWidth);
vertices << QVector3D(halfWidth, halfWidth, halfWidth);
vertices << QVector3D(halfWidth, halfWidth, halfWidth);
vertices << QVector3D(-halfWidth, halfWidth, halfWidth);
vertices << QVector3D(-halfWidth, -halfWidth, halfWidth);
// 后面
vertices << QVector3D(-halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(halfWidth, halfWidth, -halfWidth);
vertices << QVector3D(halfWidth, halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, -halfWidth, -halfWidth);
// 左面
vertices << QVector3D(-halfWidth, halfWidth, halfWidth);
vertices << QVector3D(-halfWidth, halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, -halfWidth, halfWidth);
vertices << QVector3D(-halfWidth, halfWidth, halfWidth);
// 右面
vertices << QVector3D(halfWidth, halfWidth, halfWidth);
vertices << QVector3D(halfWidth, halfWidth, -halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, halfWidth);
vertices << QVector3D(halfWidth, halfWidth, halfWidth);
// 上面
vertices << QVector3D(-halfWidth, halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, halfWidth, halfWidth);
vertices << QVector3D(halfWidth, halfWidth, halfWidth);
vertices << QVector3D(halfWidth, halfWidth, halfWidth);
vertices << QVector3D(halfWidth, halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, halfWidth, -halfWidth);
// 下面
vertices << QVector3D(-halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, -halfWidth, halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, halfWidth);
vertices << QVector3D(halfWidth, -halfWidth, -halfWidth);
vertices << QVector3D(-halfWidth, -halfWidth, -halfWidth);
m_vao.create();
m_vao.bind();
m_vbo.create();
m_vbo.bind();
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(QVector3D));
m_program.bind();
m_program.enableAttributeArray(0);
m_program.setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(QVector3D));
m_vao.release();
m_vbo.release();
}
void OpenGLWidget::keyPressEvent(QKeyEvent *event)
{
float cameraSpeed = 0.05f;
switch(event->key()) {
case Qt::Key_W:
m_camera.moveForward(cameraSpeed);
break;
case Qt::Key_S:
m_camera.moveForward(-cameraSpeed);
break;
case Qt::Key_A:
m_camera.moveRight(-cameraSpeed);
break;
case Qt::Key_D:
m_camera.moveRight(cameraSpeed);
break;
case Qt::Key_Space:
m_camera.moveUp(cameraSpeed);
break;
case Qt::Key_Shift:
m_camera.moveUp(-cameraSpeed);
break;
}
update();
}
void OpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::RightButton) {
if (m_firstMouse) {
m_lastMousePos = event->pos();
m_firstMouse = false;
}
QPoint delta = event->pos() - m_lastMousePos;
m_lastMousePos = event->pos();
float sensitivity = 0.1f;
m_camera.rotate(delta.x() * sensitivity, -delta.y() * sensitivity);
update();
}
}
void OpenGLWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
m_lastMousePos = event->pos();
}
}
void OpenGLWidget::wheelEvent(QWheelEvent *event)
{
QPoint numDegrees = event->angleDelta() / 8;
if (!numDegrees.isNull()) {
float zoom = numDegrees.y() / 15.0f;
m_camera.moveForward(zoom);
}
event->accept();
update();
}
6.着色器代码
vertex.glsl
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
fragment.glsl
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(0.8, 0.3, 0.02, 1.0);
}
注意: 1)vertex.glsl和fragment.glsl需要添加QT工程的资源文件中。比如:shaders.qrc
<RCC>
<qresource prefix="/shaders">
<file>shaders/vertex.glsl</file>
<file>shaders/fragment.glsl</file>
</qresource>
</RCC>
2)交互功能
移动控制:W: 向前移动 S: 向后移动 A: 向左移动 D: 向右移动 Space: 向上移动 Shift: 向下移动
视角控制:按住鼠标右键并移动鼠标可以旋转视角。鼠标滚轮可以缩放视图。
7.主窗口使用
#include <QApplication>
#include "openglwidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSurfaceFormat format;
format.setVersion(3, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setDepthBufferSize(24);
QSurfaceFormat::setDefaultFormat(format);
OpenGLWidget w;
w.resize(800, 600);
w.setWindowTitle("Qt OpenGL Camera Example");
w.show();
return a.exec();
}
高级功能扩展
1. 添加FPS相机
cpp
class FPSCamera : public Camera {
public:
void update(float deltaTime);
void setMovementSpeed(float speed) { m_movementSpeed = speed; }
void setMouseSensitivity(float sensitivity) { m_mouseSensitivity = sensitivity; }
private:
float m_movementSpeed = 2.5f;
float m_mouseSensitivity = 0.1f;
};
2. 添加鼠标滚轮缩放
cpp
void OpenGLWidget::wheelEvent(QWheelEvent *event) {
QPoint numDegrees = event->angleDelta() / 8;
if (!numDegrees.isNull()) {
float zoom = numDegrees.y() / 15.0f;
m_camera.moveForward(zoom);
}
event->accept();
update();
}
3. 添加弧球相机(Arcball Camera)
cpp
class ArcballCamera : public Camera {
public:
void rotate(float angleX, float angleY);
void zoom(float distance);
void pan(float x, float y);
private:
float m_radius = 5.0f;
QVector3D m_center;
};
总结
在Qt中实现OpenGL相机主要涉及:
创建相机类管理视图矩阵
处理键盘和鼠标输入来控制相机
在渲染时应用视图和投影矩阵
根据需求扩展相机功能(FPS、弧球等)
通过这种方式,你可以为Qt OpenGL应用程序创建灵活、功能丰富的相机系统。