QT中OpenGL学习笔记1
官方学习地址
Qt主要有两种主要的UI开发方法:QtQuick 和Qt Widgets,它们的存在是为了支持不同类型的用户界面,并建立在针对每种类型进行了优化的单独图形引擎之上,可将OpenGL图形API中编码与这两种用户界面类型相结合创造出渲染后的界面;
使用模块:需要直接或通过其他依赖项链接到模块库,一些构建工具对此提供了专门的支持,例如CMake和qmake:
**使用CMake构建:**使用以下命令在包中找到所需模块组件:find_package()Qt6
find_package(Qt6 REQUIRED COMPONENTS OpenGL)
target_link_libraries(mytarget PRIVATE Qt6::OpenGL)
使用qmake构建:在工程中.pro文件中加入:
QT += opengl
现对QT示例中手动旋转带纹理的骰子进行分析:
展示了如何使用带有 Qt 的 OpenGL ES 2.0 手动旋转带有用户输入的纹理 3D 立方体。它展示了如何有效地处理多边形几何体,以及如何为可编程图形管道编写简单的顶点和片段着色器。此外,它还演示如何使用四元数来表示 3D 对象方向。
此示例是为 OpenGL ES 2.0 编写的,但它也适用于桌面 OpenGL,因为此示例足够简单,并且在大多数情况下,桌面 OpenGL API 是相同的。它也会在不支持 OpenGL 的情况下进行编译,但随后它只显示一个标签,说明需要 OpenGL 支持。
该示例由两个类组成:MainWidget扩展并包含OpenGL ES 2.0初始化和绘图以及鼠标和计时器事件处理;
GeometryEngine处理多边形几何图形,将多边形几何体传输到顶点缓冲区对象,并从顶点缓冲区对象绘制几何体;
QOpenGLShaderProgram program;//QOpenGLShaderProgram 是Qt中用于处理OpenGL着色器程序的类,它提供了一种方便的方式来编译、链接和使用顶点和片段着色器。使用这个类,开发者可以简化着色器的管理过程,避免直接处理底层的OpenGL调用。
void MainWidget::initShaders()
{
// 编译顶点着色器
if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl"))
close();
// 编译片段着色器
if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fshader.glsl"))
close();
// 链接着色器管道
if (!program.link())
close();
// 绑定着色器管道以供使用
if (!program.bind())
close();
}
QOpenGLTexture *texture;//实现将纹理加载到OpenGL纹理的方法
void MainWidget::initTextures()
{
// 加载 cube.png 图片
texture = new QOpenGLTexture(QImage(":/cube.png").mirrored());
// 为纹理缩小设置最近的过滤模式
texture->setMinificationFilter(QOpenGLTexture::Nearest);
// 设置纹理放大的双线性过滤模式
texture->setMagnificationFilter(QOpenGLTexture::Linear);
// 通过重复包裹纹理坐标
// f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2)
texture->setWrapMode(QOpenGLTexture::Repeat);//设置包装模式,该模式用于决定如何跨形状或形状边界平铺纹理。 当纹理小于其填充的形状时,纹理将平铺在形状上以填充它。
GeometryEngine *geometries;//用于测试、测量和分析空间关系的客户端几何引擎 在两个或多个 2D 几何图形之间。如果以下任何方法需要多个几何体,则 所有几何必须具有相同的空间参考
QBasicTimer timer;
void MainWidget::initializeGL()
{
initializeOpenGLFunctions();//QOpenGLFunctions类提供了跨平台访问的OpenGL ES 2.0 API,QOpenGLFunctions提供了一个在所有OpenGL系统上都可用的保证API,并在需要它的系统上负责功能解析。使用QOpenGLFunctions的推荐方法是直接继承:
glClearColor(0, 0, 0, 1);//glClearColor 的作用是,指定刷新颜色缓冲区时所用的颜色。所以,完成一个刷新过程是要 glClearColor(COLOR) 与 glClear(GL_COLOR_BUFFER_BIT) 配合使用。glClearColor(0.0, 0.0, 1.0, 1.0);//蓝色
glClear(GL_COLOR_BUFFER_BIT);
initShaders();
initTextures();
//! [2]
// 启用深度缓冲区
glEnable(GL_DEPTH_TEST);
// 启用背面剔除
glEnable(GL_CULL_FACE);
//! [2]
geometries = new GeometryEngine;
// 使用 QBasicTimer,因为它比 QTimer 更快
timer.start(12, this);
}
在 OpenGL 中渲染多边形的方法有很多种,但最有效的方法是仅使用三角形条带基元并渲染图形硬件内存中的顶点。OpenGL 具有一种机制,可以将缓冲区对象创建到此内存区域,并将顶点数据传输到这些缓冲区。在 OpenGL 术语中,这些对象称为顶点缓冲区对象 (VBO)。
OpenGL 根据顶点排序确定三角形的正面和背面。默认情况下,OpenGL 对正面使用逆时针顺序。此信息由背面剔除使用,它通过不渲染三角形的背面来提高渲染性能。这样,图形管道可以省略三角形不面向屏幕的渲染侧。
使用 创建顶点缓冲区对象并将数据传输到它们非常简单。MainWidget 确保在 OpenGL 上下文当前的情况下创建和销毁 GeometryEngine 实例。这样,我们可以在构造函数中使用 OpenGL 资源,并在析构函数中执行适当的清理。
QOpenGLBuffer arrayBuf;
QOpenGLBuffer indexBuf;
GeometryEngine::GeometryEngine()
: indexBuf(QOpenGLBuffer::IndexBuffer)
{
initializeOpenGLFunctions();
// 创建两个顶点缓冲对象
arrayBuf.create();
indexBuf.create();
// 初始化 cube 几何图形并将其传输到顶点传输对象
initCubeGeometry();
//---------------------------------------------------
GeometryEngine::~GeometryEngine()
{
arrayBuf.destroy();
indexBuf.destroy();
}
void GeometryEngine::drawCubeGeometry(QOpenGLShaderProgram *program)
{
//告诉OPENGL中哪个顶点缓冲对象可使用
arrayBuf.bind();
indexBuf.bind();
// 位置偏移
quintptr offset = 0;
// 告知 OpenGL 可编程管道如何查找顶点位置数据
int vertexLocation = program->attributeLocation("a_position");
program->enableAttributeArray(vertexLocation);
program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
// 纹理坐标的偏移量
offset += sizeof(QVector3D);
// 告知 OpenGL 可编程管道如何定位顶点纹理坐标数据
int texcoordLocation = program->attributeLocation("a_texcoord");
program->enableAttributeArray(texcoordLocation);
program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));
// 使用 VBO 1 中的索引绘制立方体几何图形
glDrawElements(GL_TRIANGLE_STRIP, 34, GL_UNSIGNED_SHORT, nullptr);//glDrawElements是OpenGL中的一个核心函数,它允许开发者从数组数据中渲染多个几何图元。这个函数通过使用索引数组来指定顶点,从而减少了重复顶点数据的需要,优化了内存使用和性能。
/*
> mode 参数指定了要渲染的图元类型,例如GL_TRIANGLES表示渲染三角形。 count 参数指定了要渲染的元素数量。
> type参数指定了索引数组中值的类型,通常是GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, 或 GL_UNSIGNED_INT。
> indices 参数是一个指向索引数组的指针,这个数组包含了用于构造图元的顶点索引。
*/
}
透视投影:此矩阵用于将顶点投影到屏幕空间:
QMatrix4x4 projection;//QMatrix4x4主要是为了获取一个4X4的矩阵,让我们所需点与这个矩阵相乘可以实现这个点的空间变换
void MainWidget::resizeGL(int w, int h)
{
// 计算纵横比
qreal aspect = qreal(w) / qreal(h ? h : 1);
// 将近平面设置为 3.0,将远平面设置为 7.0,视野 45 度
const qreal zNear = 3.0, zFar = 7.0, fov = 45.0;
// 重置投影
projection.setToIdentity();
// 设置透视投影
projection.perspective(fov, aspect, zNear, zFar);
}
3D对象的方向:四元数是表示3D对象方向的便捷方式:
QVector2D mousePressPosition;//表示2D空间中的向量或顶点
QVector3D rotationAxis;
qreal angularSpeed;
void MainWidget::mousePressEvent(QMouseEvent *e)
{
// 保存鼠标按下位置,如果没有position,可能由于版本不同有的为localPos
mousePressPosition = QVector2D(e->position());
}
//------------------------------------------------
void MainWidget::mouseReleaseEvent(QMouseEvent *e)
{
// Mouse release position (鼠标释放位置) - 鼠标按下位置
QVector2D diff = QVector2D(e->position()) - mousePressPosition;
// 旋转轴垂直于鼠标位置差异//向量
QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
// 相对于鼠标扫描的长度加速角速度
qreal acc = diff.length() / 100.0;
// 将新的旋转轴计算为加权和
rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();
// 提高角速度
angularSpeed += acc;
}
QQuaternion rotation;//空间中的QVector3D既可以代表空间中的点位置,也可以表示空间矢量。为什么要对空间矢量进行旋转呢,比如有一个空间矢量在空间中代表了镜头前进的方向(即第一人称模式),初始时该矢量指向正北方向QVector3D(0,0,1),在镜头前进过程中,镜头旋转了r度,此时镜头前进的的矢量就需要相应的旋转r度,Qt中提供了QQuaternion用于三维空间中的矢量旋转
//QBasicTimer用于为场景设置动画和更新立方体方向,旋转可以简单通过乘以四元数来连接
void MainWidget::timerEvent(QTimerEvent *)
{
// 降低角速度(摩擦力)
angularSpeed *= 0.99;
// 当速度低于阈值时停止旋转
if (angularSpeed < 0.01) {
angularSpeed = 0.0;
} else {
// 更新旋转
rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;//四元数转换
// 请求更新
update();
}
}
// 计算模型视图转换:模型视图矩阵是使用四元数和按 Z 轴移动世界来计算的。该矩阵与投影矩阵相乘,得到着色器程序的 MVP 矩阵。
QMatrix4x4 matrix;
matrix.translate(0.0, 0.0, -5.0);
matrix.rotate(rotation);
// 设置 modelview-projection 矩阵
program.setUniformValue("mvp_matrix", projection * matrix);