Qt OpenGL 相机实现

发布于:2025-06-03 ⋅ 阅读:(23) ⋅ 点赞:(0)

在Qt中使用OpenGL实现相机功能主要涉及视图矩阵(view matrix)的操作,包括相机位置、观察方向和上向量等概念。下面我将介绍如何在Qt中实现一个基本的3D相机。

基本概念

OpenGL相机本质上是通过视图矩阵(view matrix)来实现的,它定义了从世界空间到观察空间的变换。视图矩阵可以通过以下参数构建:

  1. 相机位置(camera position)

  2. 目标位置(target position)

  3. 上向量(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相机主要涉及:

  1. 创建相机类管理视图矩阵

  2. 处理键盘和鼠标输入来控制相机

  3. 在渲染时应用视图和投影矩阵

  4. 根据需求扩展相机功能(FPS、弧球等)

通过这种方式,你可以为Qt OpenGL应用程序创建灵活、功能丰富的相机系统。


网站公告

今日签到

点亮在社区的每一天
去签到