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应用程序创建灵活、功能丰富的相机系统。
相关文章:
Qt OpenGL 相机实现
在Qt中使用OpenGL实现相机功能主要涉及视图矩阵(view matrix)的操作,包括相机位置、观察方向和上向量等概念。下面我将介绍如何在Qt中实现一个基本的3D相机。 基本概念 OpenGL相机本质上是通过视图矩阵(view matrix)来实现的,它定义了从世界空间到观察…...
云原生时代 Kafka 深度实践:03进阶特性与最佳实践
3.1 数据可靠性与一致性 Producer 端可靠性策略 Kafka 通过acks参数控制消息确认机制,不同设置适用于不同场景: acks0:Producer 发送消息后不等待 Broker 确认,立即返回。这种模式吞吐量最高,但可能丢失消息&#x…...
基于关联表字段映射的批量数据更新 SQL 实现方案(AIGC)
UPDATE po_upfiles u JOIN po_micro m ON u.from_id = m.ent_id_old SET u.from_id = m.ent_id; 我的提问 批量更新po_upfiles数据中from_id=ent_id_old的数据中from_id为ent_id,语句怎么写“问题重新按照适合AI的逻辑进行提问,如何修改 精确版...
Hadoop复习(二)
部署Hadoop 考试不考部署,就复习选择和大题 问题 1 单项选择 2 / 2 分 下面哪个是MapReduce的核心配置文件 core-site.xml hdfs-site.xml yarn-site.xml mapred-site.xml 问题 2 单项选择 2 / 2 分 下面哪个是HDFS的核心配置文件 core-site.xml hdf…...

C 语言开发中常见的开发环境
目录 1.Dev-C 2.Visual Studio Code 3.虚拟机 Linux 环境 4.嵌入式 MCU 专用开发环境 1.Dev-C 使用集成的 C/C 开发环境(适合基础学习),下载链接Dev-C下载 - 官方正版 - 极客应用 2.Visual Studio Code 结合 C/C 扩展 GCC/MinGW 编译器,…...

vscode命令行debug
vscode命令行debug 一般命令行debug会在远程连服务器的时候用上,命令行debug的本质是在执行时暴露一个监听端口,通过进入这个端口,像本地调试一样进行。 这里提供两种方式: 直接在命令行中添加debugpy,适用于python…...

Matlab作图之 subplot
1. subplot(m, n, p) 将当前图形划分为m*n的网格,在 p 指定的位置创建坐标轴 matlab 按照行号对子图的位置进行编号 第一个子图是第一行第一列,第二个子图是第二行第二列......... 如果指定 p 位置存在坐标轴, 此命令会将已存在的坐标轴设…...
Springboot 项目一启动就获取HttpSession
在 Spring Boot 项目中,HttpSession 是有状态的,通常只有在用户发起 HTTP 请求并建立会话后才会创建。因此,在项目启动时(即应用刚启动还未处理任何请求)是无法获取到 HttpSession 的。 方法一:使用 HttpS…...
PostgreSQL的扩展 insert_username
PostgreSQL的扩展 insert_username insert_username 是 PostgreSQL 的一个实用扩展,用于自动记录数据行的创建者和最后修改者信息。这个扩展特别适合需要审计跟踪的应用场景。 一 扩展安装与启用 1.1 安装扩展 -- 使用超级用户安装 CREATE EXTENSION insert_use…...

【机器学习基础】机器学习入门核心算法:层次聚类算法(AGNES算法和 DIANA算法)
机器学习入门核心算法:层次聚类算法(AGNES算法和 DIANA算法) 一、算法逻辑二、算法原理与数学推导1. 距离度量2. 簇间距离计算(连接标准)3. 算法伪代码(凝聚式) 三、模型评估1. 内部评估指标2. …...

Google Play的最新安全变更可能会让一些高级用户无法使用App
喜欢Root或刷机的Android用户要注意了,Google最近全面启用了新版Play Integrity API,可能会导致部分用户面临无法使用某些App的窘境。Play Integrity API是Google提供给开发者的工具,用于验证App是否在“未修改”的设备上运行。 许多重要应用…...
深度学习篇---人脸识别中的face-recognition库和深度学习
深度学习方法和使用 Python 的face_recognition库进行人脸识别在技术原理、实现方式和应用场景上有显著区别,以下从多个维度对比分析: 一、技术原理 1. 深度学习方法 核心逻辑:基于神经网络(如卷积神经网络 CNN)构建…...
(11)java+ selenium->元素定位之By_tag_name
1.简介 继续WebDriver关于元素定位,这篇介绍By ClassName。tagName是DOM结构的一部分,其中页面上的每个元素都是通过输入标签,按钮标签或锚定标签等标签定义的。每个标签都具有多个属性,例如ID,名称,值类等。就其他定位符而言在Selenium中,我们使用了标签的这些属性值来…...

React---day5
4、React的组件化 组件的分类: 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和…...

Java开发之定时器学习
面试 一、线程池实现定时器 核心代码: public static void main(String[] args) {ScheduledExecutorService scheduledExecutorService Executors.newScheduledThreadPool(5);Runnable runnable () -> System.out.println("当前线程"Thread.current…...

HealthBench医疗AI评估基准:技术路径与核心价值深度分析(上)
引言:医疗AI评估的新范式 在人工智能技术迅猛发展的当下,医疗AI系统已逐渐从实验室走向临床应用。然而,医疗领域的特殊性要求这些系统不仅需要在技术指标上表现出色,更需要在实际临床场景中展现出可靠、安全且有效的性能。长期以来,医疗AI评估领域面临着三个核心挑战:评…...

Windows+VSCode搭建小智(xiaozhi)开发环境
作为一名DIY达人,肯定不会错过最近很火的“小智AI聊天机器人”,网上教程非常丰富,初级玩家可以直接在乐鑫官方下载ESP-IDF安装包并经过简单的菜单式配置后,即可进行代码编译和烧录(详见:Docs)。…...

VueScan Pro v9.8.45.08 一款图像扫描软件,中文绿色便携版
VueScan是著名的第三方底片扫描仪驱动程序,支持市场可见绝大多数型号的底片扫描仪,可以更为灵活地控制扫描过程,更深入地发掘硬件潜力,获取色彩 完美的高质量扫描结果。VueScan支持200种以上的底片类型,在剪取图像时制…...

FreeRTOS通俗理解指南:基础概念 + 架构+ 内核组件+练手实验
RTOS 基础概念 想象一下,你是一个忙碌的厨师,在厨房里同时要完成煎牛排和煮意大利面两项任务。 1.传统单线程模式(没有RTOS) 如果你只能按顺序一项一项地做,就会是这样的过程: 先煎一会儿牛排然后去看看…...
Python后端开发实战:从0到1搭建高可用API服务
引言 Python凭借其简洁的语法和丰富的生态(如Django、Flask、FastAPI等框架),已成为后端开发的主流语言之一。本文将结合一个真实电商API项目,分享从架构设计到部署上线的完整流程,并总结开发过程中常见的坑与最佳实践。 一、实战案例:电商API开发流程 1.1 技术选型 框…...

房屋租赁系统 Java+Vue.js+SpringBoot,包括房屋信息、看房申请、租赁合同、房屋报修、收租信息、维修数据、租客管理、公告管理模块
房屋租赁系统 JavaVue.jsSpringBoot,包括房屋信息、看房申请、租赁合同、房屋报修、收租信息、维修数据、租客管理、公告管理模块 百度云盘链接:https://pan.baidu.com/s/16YRGBPsfbd4_HxXhO0jM5Q 密码:smk4 摘 要 房屋是人类生活栖息的重要…...
4、ubuntu系统 | 文本和目录操作函数
1、目录操作函数 ls(列出目录内容) 用途:列出指定目录中的文件和子目录。语法:ls [选项] [路径]常用选项: -l:以长格式显示文件详细信息(权限、所有者、大小、时间等)。-aÿ…...
docker部署ELK,ES开启安全认证
ES启动命令 docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 elasticsearch:8.17.0 es启动之后需要进入es容器,重置密码 elasticsearch-reset-password -u elastic -i 重置后的密码配置到kibana.yml中,启动kibana docker run …...

ASP.NET MVC添加视图示例
ASP.NET MVC高效构建Web应用- 商品搜索 - 京东 视图(V)是一个动态生成HTML页面的模板,它负责通过用户界面展示内容。本节将修改HelloWorldController类,并使用视图模板文件,以干净地封装生成对客户端的HTML响应的过程…...
自动驾驶中的路径跟踪:Python实现与技术解析
自动驾驶中的路径跟踪:Python实现与技术解析 一、路径跟踪是什么?为什么它至关重要? 路径跟踪(Path Tracking)是自动驾驶系统的关键部分之一,它负责确保车辆能够沿着预定义的轨迹行驶,同时稳定控制转向角度和速度。一个好的路径跟踪算法需要具备以下特点: 精准度:能…...
前端面试题目-高频问题集合
1.CSS里面水平垂直居中的方法 1.CSS里面水平垂直居中的方法弹性布局display: flex; /*先开启flex布局*/justify-content: center; /*实现水平居中*/jalign-items: center; /*实现垂直居中*/网格布局display: grid; /*先开启grid布局*/plac…...
MyBatis源码解析:从 Mapper 接口到 SQL 执行的完整链路
MyBatis源码解析:从 Mapper 接口到 SQL 执行的完整链路 一、Mapper 代理对象的创建:sqlSession.getMapper(UserMapper.class)二、接口方法的执行:mapper.selectUser("coderzpw", 18)2.1 四大核心组件解析2.1.1 Executor(…...

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Form Wave(表单label波动效果)
📅 我们继续 50 个小项目挑战!—— FormWave组件 仓库地址:https://github.com/SunACong/50-vue-projects 项目预览地址:https://50-vue-projects.vercel.app/ 🎯 组件目标 构建一个美观、动态的登录表单࿰…...

双目相机深度的误差分析(基线长度和相机焦距的选择)
全文基于针孔模型和基线水平放置来讨论 影响双目计算深度的因素: 1、基线长度:两台相机光心之间距离2、相机焦距(像素): f x f_x fx(或 f y f_y fy)为焦距 f f f和一个缩放比例的乘积。在…...

Pytorch Geometric官方例程pytorch_geometric/examples/link_pred.py环境安装教程及图数据集制作
最近需要训练图卷积神经网络(Graph Convolution Neural Network, GCNN),在配置GCNN环境上总结了一些经验。 我觉得对于初学者而言,图神经网络的训练会有2个难点: ①环境配置 ②数据集制作 一、环境配置 我最初光想…...