Qt OpenGL 相机实现
在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_OBJECTpublic: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 corelayout (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 coreout 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应用程序创建灵活、功能丰富的相机系统。