(本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2020-1-30 ,并在 2021-8-10 进行了更新)
0.前言
LearnOpenGL 教程目前使用的是 glfw+glad 的方式来使用 OpenGL,而在 Qt Widgets 框架中也封装了相应的类。QOpenGLWidget 相当于 glfw,QOpenGLFunctions 相当于 glad,配合其他 Qt 工具类,操作起来更加方便。
1.如何实现
在 Qt 旧版本中,提供有 QGLWidget 类,从 Qt5.4 开始提供了 QOpenGLWidget 类,只需要继承并实现相应接口,就可以使用 OpenGL 绘制一个 Qt 组件(其中使用 OpenGL 函数还要借助QOpenGLFunctions 相关的类,可以继承,也可以作为成员变量):
class GLTriangle : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{}
继承 QOpenGLWidget 后,只需要实现三个虚函数接口,就可以实现绘制:
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
QOpenGLWidget 是继承自 QWidget 的,所以做好的组件类像其他 Widget 类那样使用就行了。
在教程中,是自己封装着色器程序类,Qt 已经封装好了一个 QOpenGLShaderProgram,我们可以直接用它。此外还提供了 QOpenGLVertexArrayObject、QOpenGLBuffer 等封装好的类供我们使用。剩下的,就是参照 LearnOpenGL 教程配合 Qt 封装的类来使用 OpenGL。
2.实现代码
(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git)
我的GLTriangle类和GLElement类效果图:
其中三角形的实现代码:
(2020-05-10 修改)将 QSurfaceFormat 设置为 3.3 及以上会出现 gl_FragColor报错:
'variable' : is removed in Forward Compatible context gl_FragColor
因为 OpenGL3.3 的 glsl 移除了这个变量,可以自己声明一个 out vec4 fragColor 。
(2021-08-10修改)析构时判断 QOpenGLWidget::isValid() ,只有显示过才会初始化,而没初始化就 glDelete 会异常。
OpenGL4.5 接口示例:
#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Compatibility>
//OpenGL4.5画三角
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLTriangle
: public QOpenGLWidget
, protected QOpenGLFunctions_4_5_Compatibility
{
public:
explicit GLTriangle(QWidget *parent = nullptr);
~GLTriangle();
protected:
//【】继承QOpenGLWidget后重写这三个虚函数
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
private:
void checkShaderError(GLuint id, const QString &type);
private:
//还没使用Qt的封装
GLuint shaderProgram = 0;
GLuint vao = 0;
GLuint vbo = 0;
};
#include "GLTriangle.h"
#include <QDebug>
GLTriangle::GLTriangle(QWidget *parent)
: QOpenGLWidget(parent)
{
}
GLTriangle::~GLTriangle()
{
//initializeGL在显示时才调用,释放未初始化的会异常
if(!isValid())
return;
//QOpenGLWidget
//三个虚函数不需要makeCurrent,对应的操作已由框架完成
//但是释放时需要设置当前上下文
makeCurrent();
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteProgram(shaderProgram);
doneCurrent();
}
void GLTriangle::initializeGL()
{
//QOpenGLFunctions
//为当前上下文初始化opengl函数解析
initializeOpenGLFunctions();
//着色器代码
//in输入,out输出,uniform从cpu向gpu发送
const char *vertex_str=R"(#version 450
layout (location = 0) in vec3 vertices;
void main() {
gl_Position = vec4(vertices,1.0);
})";
const char *fragment_str=R"(#version 450
uniform vec3 myColor;
out vec4 fragColor;
void main() {
fragColor = vec4(myColor,1.0);
})";
//顶点着色器
//创建着色器对象
//GLuint glCreateShader(GLenum shaderType);
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
//设置着色器对象中的的代码
//void glShaderSource(GLuint shader, GLsizei count,
// const GLchar **string, const GLint *length);
//参数1指定着色器对象
//参数2指定字符串个数
//参数3指定字符串二维数组指针
//参数4指定字符串长度数组,为NULL则以'\0'为字符串终止符
glShaderSource(vertex_shader, 1, &vertex_str, NULL);
//编译着色器对象
//void glCompileShader(GLuint shader);
glCompileShader(vertex_shader);
//检测着色器是否异常
checkShaderError(vertex_shader, "vertex");
//片段着色器
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_str, NULL);
glCompileShader(fragment_shader);
checkShaderError(fragment_shader,"fragment");
//着色器程序
//创建一个空的程序对象并返回一个可以被引用的非零值
//GLuint glCreateProgram(void);
shaderProgram = glCreateProgram();
//将着色器对象附加到程序对象
//void glAttachShader(GLuint program, GLuint shader);
glAttachShader(shaderProgram, vertex_shader);
glAttachShader(shaderProgram, fragment_shader);
//链接程序对象
//void glLinkProgram(GLuint program);
glLinkProgram(shaderProgram);
checkShaderError(shaderProgram, "program");
//删除着色器,它们已经链接到我们的着色器程序对象了
//void glDeleteShader(GLuint shader);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
//三角形的三个顶点
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
//【1】2014年8月12日,Khronos发布了OpenGL 4.5标准规范,
//其中ARB_direct_state_access扩展进入核心,
//其允许直接访问和修改OpenGL对象而无需绑定OpenGL对象(bind操作,例如glBindBuffer),
//提高应用程序和中间件的效率。
//【2】glGen*系列函数生成的id,内部并没有初始化那个对象的状态。只有到了glBind*的时候才会初始化。
//而Core/ARB的DSA直接提供了glCreate*系列函数,可以一步到位地建立id和初始化。
//生成顶点数组对象
//void glCreateVertexArrays(GLsizei n, GLuint *arrays);
glCreateVertexArrays(1, &vao);
//生成缓冲区对象
//void glCreateBuffers(GLsizei n, GLuint *buffers);
glCreateBuffers(1, &vbo);
//分配size个存储单元存储数据或索引
//glNamedBufferStorage(GLuint buffer, GLsizeiptr size,
// const void *data, GLbitfield flags);
//参数1缓冲区对象
//参数2数据块大小
//参数3如果为NULL则是size个为初始化的数据,否则以data拷贝初始化
//参数4数据相关的用途
glNamedBufferStorage(vbo, sizeof(vertices), vertices, GL_DYNAMIC_STORAGE_BIT);
//vbo绑定到vao
//glVertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex,
// GLuint buffer, GLintptr offset, GLsizei stride);
//参数1顶点数组对象
//参数2vbo在vao的索引
//参数3顶点缓冲对象
//参数4缓冲区第一个元素的偏移
//参数5缓冲区顶点步进,三角形一个点3个float
glVertexArrayVertexBuffer(vao, 0, vbo, 0, 3*sizeof(float));
//启用通用顶点 attribute 数组的 index 索引,对应layout location
//glEnableVertexArrayAttrib(GLuint vaobj, GLuint index);
glEnableVertexArrayAttrib(vao, 0);
//指定顶点数组的组织
//glVertexArrayAttribFormat(GLuint vaobj, GLuint attribindex,
// GLint size, GLenum type,
// GLboolean normalized, GLuint relativeoffset);
//参数1顶点数组对象
//参数2通用顶点 attribute 数组,对应layout location
//参数3每个顶点几个数据
//参数4存储类型
//参数5是否归一化
//参数6顶点步进
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0);
//关联顶点 attribute 属性和顶点缓冲区的绑定
//glVertexArrayAttribBinding(GLuint vaobj, GLuint attribindex,
// GLuint bindingindex);
//参数1顶点数组对象
//参数2属性 attribute 索引,对应layout location
//参数3vbo在vao的索引
glVertexArrayAttribBinding(vao, 0, 0);
}
void GLTriangle::paintGL()
{
//清屏设置
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//安装所指定的程序对象程序作为当前再现状态的一部分
glUseProgram(shaderProgram);
//传递值
glUniform3f(glGetUniformLocation(shaderProgram, "myColor"), 1.0f, 0.0f, 0.0f);
//绑定数组对象
glBindVertexArray(vao);
//使用当前激活的着色器和顶点属性配置和VBO(通过VAO间接绑定)来绘制图元
//void glDrawArrays(GLenum mode, GLint first, GLsizei count);
//参数1为图元类型
//参数2指定顶点数组的起始索引
//参数3指定顶点个数
glDrawArrays(GL_TRIANGLES, 0, 3);
}
void GLTriangle::resizeGL(int width, int height)
{
glViewport(0,0,width,height);
}
void GLTriangle::checkShaderError(GLuint id, const QString &type)
{
int check_flag;
char check_info[1024];
if(type != "program"){
glGetShaderiv(id, GL_COMPILE_STATUS, &check_flag);
if(!check_flag){
glGetShaderInfoLog(id, 1024, NULL, check_info);
qDebug() << type << " error:" << check_info;
}
}else{
glGetShaderiv(id, GL_LINK_STATUS, &check_flag);
if(!check_flag){
glGetProgramInfoLog(id, 1024, NULL, check_info);
qDebug() << type << " error:" << check_info ;
}
}
}
Qt 接口示例:
#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Compatibility>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
//OpenGL Qt封装的类画三角
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLTriangleQt
: public QOpenGLWidget
, protected QOpenGLFunctions_4_5_Compatibility
{
public:
explicit GLTriangleQt(QWidget *parent = nullptr);
~GLTriangleQt();
protected:
//【】继承QOpenGLWidget后重写这三个虚函数
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
private:
//使用Qt提供的便捷类
QOpenGLShaderProgram shaderProgram;
QOpenGLVertexArrayObject vao;
QOpenGLBuffer vbo;
};
#include "GLTriangleQt.h"
GLTriangleQt::GLTriangleQt(QWidget *parent)
: QOpenGLWidget(parent)
{
}
GLTriangleQt::~GLTriangleQt()
{
//initializeGL在显示时才调用,释放未初始化的会异常
if(!isValid())
return;
//QOpenGLWidget
//三个虚函数不需要makeCurrent,对应的操作已由框架完成
//但是释放时需要设置当前上下文
makeCurrent();
vao.destroy();
vbo.destroy();
doneCurrent();
}
void GLTriangleQt::initializeGL()
{
//QOpenGLFunctions
//为当前上下文初始化opengl函数解析
initializeOpenGLFunctions();
//着色器代码
//in输入,out输出,uniform从cpu向gpu发送
const char *vertex_str=R"(#version 450
layout (location = 0) in vec3 vertices;
void main() {
gl_Position = vec4(vertices,1.0);
})";
const char *fragment_str=R"(#version 450
uniform vec3 myColor;
out vec4 fragColor;
void main() {
fragColor = vec4(myColor,1.0);
})";
//顶点着色器
//可以直接add着色器代码,也可以借助QOpenGLShader类
bool success=shaderProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,vertex_str);
if(!success){
qDebug()<<"compiler vertex failed!"<<shaderProgram.log();
}
//片段着色器
success=shaderProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fragment_str);
if(!success){
qDebug()<<"compiler fragment failed!"<<shaderProgram.log();
}
success = shaderProgram.link();
if(!success){
qDebug()<<"link shader failed!"<<shaderProgram.log();
}
//三角形的三个顶点
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
vao.create();
vao.bind();
vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
vbo.create();
vbo.bind();
vbo.allocate(vertices,sizeof(vertices));
//setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
int attr=0;
attr=shaderProgram.attributeLocation("vertices");
//setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
shaderProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 3);
shaderProgram.enableAttributeArray(attr);
vbo.release();
vao.release();
}
void GLTriangleQt::paintGL()
{
//清屏设置
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
shaderProgram.bind();
shaderProgram.setUniformValue("myColor",QVector3D(0.0f,0.0f,1.0f));
{
QOpenGLVertexArrayObject::Binder vao_bind(&vao); Q_UNUSED(vao_bind);
//使用当前激活的着色器和顶点属性配置和VBO(通过VAO间接绑定)来绘制图元
//void glDrawArrays(GLenum mode, GLint first, GLsizei count);
//参数1为图元类型
//参数2指定顶点数组的起始索引
//参数3指定顶点个数
glDrawArrays(GL_TRIANGLES, 0, 3);
}
//只有一个着色器,可以不用release
shaderProgram.release();
}
void GLTriangleQt::resizeGL(int width, int height)
{
glViewport(0,0,width,height);
}