Qt OpenGL 相机实现

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

基本概念

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

  1. 相机位置(camera position)

  2. 目标位置(target position)

  3. 上向量(up vector)

实现步骤

1. 包含必要的头文件

cpp

cpp 复制代码
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QMatrix4x4>
#include <QVector3D>
#include <QKeyEvent>

2. 定义相机类

Camera.h

cpp 复制代码
#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

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

cpp 复制代码
#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

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

cpp 复制代码
#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

cpp 复制代码
#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.主窗口使用

cpp 复制代码
#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

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

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

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应用程序创建灵活、功能丰富的相机系统。

相关推荐
扶尔魔ocy11 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行并进行态势标绘(四)
qt·opengl·osg
:mnong12 天前
Web 三维可视化技术对比:ParaView Web vs 本项目 wasm + Three.js
前端·webgl·opengl·opencl·科学可视化
扶尔魔ocy13 天前
【qt openGL入门笔记】OSG实现显示飞机模型飞行的点迹(二)
qt·opengl·osg
扶尔魔ocy13 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行(三)
opengl·osg
扶尔魔ocy14 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行
qt·opengl·osg
扶尔魔ocy18 天前
【qt openGL入门笔记】FPS相机
qt·opengl
扶尔魔ocy18 天前
qt openGL入门笔记】FPS相机和轨道相机
qt·opengl
扶尔魔ocy20 天前
【qt openGL入门笔记】基础光照
qt·opengl
扶尔魔ocy20 天前
【qt openGL入门笔记】坐标系统和摄像机
qt·opengl
茉莉玫瑰花茶23 天前
OpenGL [ 纹理 ]
开发语言·c++·opengl