使用QT将learningopengl入门篇的Hello Triangle代码在QT框架下实现,绘制一个静态三角形
一、环境
操作系统:Windows 11 IDE:Microsoft Visual Studio Community 2022 (64 位) QT:5.12.12
二、代码实现
learningopengl Hello Triangle章节教程:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/#_10
GLFW框架下的代码:https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/2.1.hello_triangle/hello_triangle.cpp
对照GLFW框架下代码进行以下修改
着色器
着色器代码和glfw或qt无关,无论使用哪个框架,这部分代码都是一样的,作为全局变量直接粘贴过来
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
GLFW初始化
main函数中的第一部代码,是进行glfw初始化和窗体配置的,在QT框架下,窗体已经由QT创建,这部分代码直接删除即可。
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
GLAD管理OpenGL函数指针
接下来glad的部分,在QT中被initializeOpenGLFunctions 方法取代,在initializeGL() 方法中直接调用即可
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
替换为
this->initializeOpenGLFunctions();
编译着色器代码
这部分也没有什么不同,直接粘贴到initializeGL() 中,只需要将std::cout替换为qDebug即可,std::cout在QT中无法正常输出,原因和解决方法我还没有研究。
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
设置顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
在这部分代码中,和VAO相关的创建、绑定、解绑的方法glBindVertexArray() 和glBindVertexArray() 报错,这两个函数好像是第三方库gl3w中的,我们将他们替换成qt中的方法
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glBindVertexArray(0);
分别替换为
QOpenGLVertexArrayObject VAO;
VAO.create();
if(!VAO.isCreated()){
qDebug()<<"vao is not created.";
}
VAO.bind();
VAO.release();
并添加头文件
#include <QOpenGLVertexArrayObject>
render loop
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
这部分的while (!glfwWindowShouldClose(window)) 循环控制交给qt框架,循环体内的代码放在paintGL() 方法中,由定时器和QT框架控制循环调用
processInput(window); 是自定义的函数,用于处理按键事件,先去掉这个功能,后续用qt实现。
glUseProgram(shaderProgram); 和glBindVertexArray(VAO); 用到了前面定义的变量,将他们修改定义到类中,glBindVertexArray(VAO) 改为VAO.bind(); ,VO是否需要每次解绑暂不清楚(使用glBindVertexArray 进行绑定是不需要每次都解绑),保险起见先加上解绑的代码VAO.release();
glfwSwapBuffers(window); 和glfwPollEvents(); 分别是glut交换缓冲区进行绘制和glfw响应键盘鼠标时间的函数,直接删掉
修改后代码为
void HelloTriangleWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
VAO.bind();
glDrawArrays(GL_TRIANGLES, 0, 3);
VAO.release();
}
此时,已经可以成功显示Hello Triangle示例中的三角形,但是不会自动刷新,对于静态显示已经可以达到效果,对于动态显示还需要让程序循环调用paintGL() 方法,就像glfw框架下的while (!glfwWindowShouldClose(window))
timer刷新
gl绘制的代码写在paintGL() 方法中,这个方法只有在移动窗口或显示/隐藏窗口时会被触发,因此还需新建一个定时器定时调用update()方法,主动触发paintGL(),定时刷新。
将下面代码加在initializeGL() 方法最末尾,开启定时器每33ms刷新一次界面,刷新频率30Hz左右
timer = new QTimer();
timer->setInterval(33);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start();
此时opengl控件已经在不断刷新,但是因为绘制的内容只有一个静态三角形,看不出效果,我们修改一些paintGl() 方法让背景颜色产生变化,代码如下
void HelloTriangleWidget::paintGL()
{
static float r = 0.0, g = 0.0, b = 0.0;
r += 0.01;
g += 0.02;
b += 0.03;
if (r > 1)r = 0;
if (g > 1)g = 0;
if (b > 1)b = 0;
glClearColor(r, g, b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
VAO.bind();
glDrawArrays(GL_TRIANGLES, 0, 3);
VAO.release();
}
至此全部代码已完成,编译运行,窗口中绘制一个橙色三角形,背景颜色在不断变化
三、总结
将glfw框架下的OpenGL代码移植到qt框架下,需要进行以下修改:
- 删除glfw相关的窗体绘制的代码和键盘、鼠标响应的代码,在qt中重新实现
- 将glad的
gladLoadGLLoader 方法替换为qt的initializeOpenGLFunctions 方法 - 顶点数组对象(VAO)相关的方法
glBindVertexArray() 和glBindVertexArray() 替换为QOpenGLVertexArrayObject::create() 、QOpenGLVertexArrayObject::bind() 和QOpenGLVertexArrayObject::release() - 将render loop主循环中渲染部分的代码移动到
paintGL() 方法中,并用定时器定时调用update() ,让paintGL() 方法定时执行
四、全部代码
HelloTriangle.h
#pragma once
#ifndef __HELLO_TRIANGLE_H__
#define __HELLO_TRIANGLE_H__
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QTimer>
#include <QOpenGLVertexArrayObject>
class HelloTriangleWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
HelloTriangleWidget(QWidget* parent = 0);
~HelloTriangleWidget();
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
private:
QTimer* timer;
unsigned int shaderProgram;
QOpenGLVertexArrayObject VAO;
};
#endif
HelloTriangle.cpp
#include "HelloTriangle.h"
#include <qDebug>
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
HelloTriangleWidget::HelloTriangleWidget(QWidget* parent)
: QOpenGLWidget(parent)
{
}
HelloTriangleWidget::~HelloTriangleWidget()
{
}
void HelloTriangleWidget::initializeGL()
{
this->initializeOpenGLFunctions();
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
qDebug() << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog;
}
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
qDebug() << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog;
}
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
qDebug() << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
unsigned int VBO;
VAO.create();
if (!VAO.isCreated()) {
qDebug() << "vao is not created.";
}
glGenBuffers(1, &VBO);
VAO.bind();
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
VAO.release();
timer = new QTimer();
timer->setInterval(33);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start();
}
void HelloTriangleWidget::resizeGL(int w, int h)
{
glViewport(0.0f, 0.0f, w, h);
}
void HelloTriangleWidget::paintGL()
{
static float r = 0.0, g = 0.0, b = 0.0;
r += 0.01;
g += 0.02;
b += 0.03;
if (r > 1)r = 0;
if (g > 1)g = 0;
if (b > 1)b = 0;
glClearColor(r, g, b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
VAO.bind();
glDrawArrays(GL_TRIANGLES, 0, 3);
VAO.release();
}
qt_test.h
#pragma once
#include <QtWidgets/QMainWindow>
#include <Qlabel>
#include <QPushButton>
#include <Qopenglwidget>
#include "ui_qt_test.h"
#include "GlWidget.h"
#include "HelloTriangle.h"
class qt_test : public QMainWindow
{
Q_OBJECT
public:
qt_test(QWidget *parent = Q_NULLPTR);
void onButtonClick();
private:
Ui::qt_testClass ui;
QLabel* lab;
QPushButton* button;
HelloTriangleWidget* opengl_widget;
};
qt_test.cpp
#include "qt_test.h"
qt_test::qt_test(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
this->opengl_widget = new HelloTriangleWidget(this);
this->opengl_widget->setGeometry(this->geometry());
this->lab = new QLabel("Hello,World!", this);
this->lab->setGeometry(10, 20, 100, 20);
this->button = new QPushButton("test", this);
this->button->setGeometry(10, 40, 100, 20);
QObject::connect(this->button, &QPushButton::clicked, this, &qt_test::onButtonClick);
}
void qt_test::onButtonClick()
{
static int i = 0;
this->lab->setText(QString::number(i++));
}
main.cpp
#include "qt_test.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qt_test w;
w.show();
return a.exec();
}
相关链接和参考
https://blog.csdn.net/weixin_44518102/article/details/119306516 https://learnopengl-cn.github.io/ https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/#_10 https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/2.1.hello_triangle/hello_triangle.cpp
|