这段时间自己想弄个小引擎玩玩,发现图形界面glfw功能还是太弱了点,加上想学点主流的GUI框架,于是上手了Qt,折腾了好久,终于是弄出了这个方案
先说明一点,其实Qt早就提供了包装好的OpenGL库,VBO,VAO,shader这些都是给你封装好的,
用法与原生的API差不了多少,但是我还是想多用用原生的OpenGL练习一下
目标:窗口GUI由Qt搭建,仅有OpenGLWidget桥接OpenGL的渲染输出,渲染不用到任何Qt封装好的OpenGL库,可以看做 Qt 对 learnopengl 中 glfw的平替
以下是步骤
mainwindows 主窗口没什么好说的
这是ui,很简单,一个mainwindow 里面嵌入一个OpenGLWidget,用来显示我们的渲染结果
首先创建oglwidget类
///oglwidget.hpp
#include "renderApplication.hpp"
#include <QOpenGLWidget>
#include <memory>
class oglwidget : public QOpenGLWidget
{
Q_OBJECT
public:
explicit oglwidget(QWidget *parent = nullptr);
~oglwidget();
void setRender(std::shared_ptr<zh::RenderApplication> ptr);
protected:
void initializeGL();
void resizeGL(int width, int height);
void paintGL();
private:
std::shared_ptr<zh::RenderApplication> render; //智能指针写法
// 或者直接 Render render
};
#include "oglwidget.hpp"
oglwidget::oglwidget(QWidget *parent) :
QOpenGLWidget(parent)
{
}
oglwidget::~oglwidget()
{
}
void oglwidget::initializeGL()
{
render->init_base();
}
void oglwidget::paintGL()
{
render->update_base();
}
void oglwidget::resizeGL(int width, int height) {
}
void oglwidget::setRender(std::shared_ptr<zh::RenderApplication> ptr) {
render = ptr;
}
接下来把我们的类与ui上面的控件关联起来,右键OpenGLWidget 提升为
类和头文件就写我们刚刚创建的oglwidget类,然后提升就OK
这样控件就与我们的类关联在一起了
oglwidget继承于 QOpenGLWidget,我们需要重写三个函数
init 就是我们的初始化GPU资源那些,paintGL 就是渲染循环里面的那些
这里需要注意一点,渲染逻辑一定要用一个渲染类包装起来,Qt我们至始至终只用Widget控件部分,不涉及到任何的QOpenGL封装的api,其实就是所有glad有关的代码,不要写到这个oglwidget里,只调用我们渲染类封装好的方法。
render的内容开始画三角形
#include <glad/glad.h>
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";
class Render{
bool init()
{
//glad 初始化
if(!gladLoadGL())
{
LOG_ERROR("glad init failed!");
return false;
}
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
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;
}
// fragment shader
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
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);
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float vertices[] = {
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
unsigned int indices[] = { // note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
unsigned int VBO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound.
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
glBindVertexArray(0);
return true;
}
void update(double delta)
{
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
//glDrawArrays(GL_TRIANGLES, 0, 6);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
private:
unsigned int shaderProgram;
unsigned int VAO;
};
oglwidget 里的render类 你可以用一个render实例,也可以用一个智能指针,在初始化的时候传入
最终结果
接下来可以在Render类,专注于你原生OpenGL的代码啦!