参考资料:https://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-2-the-first-triangle/

一、环境配置

这里使用的QT5的opengl环境,配合CMake在MacOS下进行的,因此需要在CMakeLists.txt中进行配置:

  • 主要是进行链接库文件,一个是QT的openGL库,还有一个就是操作系统本身的GL库
  • 关于其他QT的配置,这里不再列举出来,实际上只需要配置好QT的环境后,查找opengl的库文件即可
# 查找 Qt5 和系统 OpenGL 库  
find_package(Qt5 COMPONENTS Core Gui Widgets OpenGL REQUIRED)  
find_package(OpenGL REQUIRED)

target_link_libraries(opengl1-1  
        Qt5::Core  
        Qt5::Gui  
        Qt5::Widgets  
        Qt5::OpenGL  
        OpenGL::GL  
)

二、创建一个窗口

我们的实现环境是QT,因此很多时候我们不需要自己实现窗口,也不需要自己实现一个时间循环,我们唯一需要实现的就是:实现对应的initializeGLpaintGL两个函数

  • initilizeGL主要是初始化OpenGL的函数,并且这一步骤在绘制之前将会调用
  • paintGL主要是利用窗口的重绘,实现openGL的状态机的每一帧的改变,也就是在这一个函数内部我们需要实现openGL的状态保持与改变

2.1 实现my_glwidget类

  • 为了实现自己自定义的一个opengl窗口,在QT中比较常用的操作是使用继承的操作
    • 一方面,使用public继承QOpenGLWidget窗口(为了能够在外部调用它)
    • 另一方面使用protected继承QOpenGLFunction类(为了只在内部实现它的接口,比如paintGL)

通常的写法如下:

头文件

  • 通常需要重写这三个虚函数:initializeGLpaintGLresizeGL
class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {  
public:  
    MyGLWidget(QWidget* parent = nullptr);  
    ~MyGLWidget();  
protected:  
    void initializeGL() override;  
    void paintGL() override;  
    void resizeGL(int w, int h) override;    
};

源文件

  • initilizeGL中需要初始化opengl的内部函数,否则会导致空指针,使用initializeOpenGLFunctions函数调用即可。这里也可以设置一下屏幕的清理颜色,我就使用黑色清理了(rgba(0,0,0,1))

  • paintGL主要对opengl窗口进行绘制,这里简单每一帧清除一下屏幕的颜色即可

  • resizeGL主要是实现窗口拉伸时的视口的变化,因为opengl的视口需要手动修改,这里我们假设无论怎么拉伸外部窗口,内部opengl的视口总是和窗口保持一致,因此直接设置glViewport(0, 0, w, h),其中wh是当前拉伸后的窗口宽高

#include "my_glwidget.h"  
MyGLWidget::MyGLWidget(QWidget *parent): QOpenGLWidget(parent)  
{  
   
}  
  
MyGLWidget::~MyGLWidget()  {  
}  
  
void MyGLWidget::initializeGL() {  
    initializeOpenGLFunctions();  
  
    glClearColor(0.0f,0.0f,0.0f,1.0f);  

}  
void MyGLWidget::paintGL() {  
     glClear(GL_COLOR_BUFFER_BIT);
}  
  
void MyGLWidget::resizeGL(int w, int h) {  
    glViewport(0, 0, w, h);  
}  

通常,运行后的结果如下:

在这里插入图片描述

二、顶点数组对象(VAO)

绘制三角形之前,需要创建并绑定一个VAO,比如下面的代码:

GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);

在窗口创建完毕后,需要进行上述的操作,绑定了VAO后,之后所有的操作都是基于当前绑定的VAO进行,比如后续的VBO、FBO等等操作

2.1 屏幕坐标系

三点确定一个三角形。我们常常用“顶点”(Vertex,复数vertices)来指代3D图形学中的点。一个顶点有三个坐标:X,Y和Z。您可以这样想象这三根坐标轴:

  • X轴朝右
  • Y轴朝上
  • Z轴指向您后面(没错,是后面,不是前面)

还有更形象的方法:使用右手定则

  • 拇指代表X轴
  • 食指代表Y轴
  • 中指代表Z轴。如果您的拇指指向右边,食指指向天空,那么中指将指向您的后面。

为什么Z轴方向这么奇怪呢?简言之:因为基于右手定则的坐标系统被广泛使用了100多年,这一系统涵盖了许多实用工具,唯一的缺点就是Z方向比较别扭。

补充说明一下,您的手可以自由移动,X轴,Y轴和Z轴同样也能移动(详见后文)。

我们需要三个三维点来组成一个三角形;下面来定义一个三角形:

static const GLfloat g_vertex_buffer_data[] = {  
        -1.0f, -1.0f, 0.0f,  
        1.0f, -1.0f, 0.0f,  
        0.0f,  1.0f, 0.0f  
};

第一个顶点是(-1, -1, 0)。 这意味着如果不变换该顶点,它就将显示在屏幕的(-1, -1)位置。这是什么意思呢?屏幕的原点在中间,X轴朝右,Y轴朝上。屏幕坐标如下图:

在这里插入图片描述

这是显卡内置的坐标系,无法改变。(-1, -1)是屏幕的左下角,(1, -1)是右下角,(0, 1)位于中上部。这个三角形占据了大部分屏幕。

2.2 绘制三角形

下一步,通过缓冲把三角形传给OpenGL:

这步操作仅需执行一次,也就是说,这个步骤实际上是在initializeGL函数中实现

//生成一个VBO  
glGenBuffers(1, &vertexBuffer);  
//绑定VBO  
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);  
//分配VBO内存:GL_STATIC_DRAW 不经常修改缓冲区  
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

initializedGL最后别忘记了解绑VAO,虽然在只有一个VAO的情况下这不是必备的

//解绑vao  
glBindVertexArray(0);  // 解绑VAO

paintGL中重新绑定VAO,绑定VBO,并且描述如何极细VBO,同时启动我们的layout = 0,这里与着色器有关,后续章节介绍

  
//opengl是一个状态机,每一帧都需要解绑vao再绑定vao  
glBindVertexArray(VertexArrayID);  
//描述顶点信息,绑定的layout = 0,float类型,不需要归一化 stride = 0(只有唯一的位置信息)偏移0(只有唯一的位置信息)  
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);  
  
//启动layout = 0  
glEnableVertexAttribArray(0);

描述完毕后,我们绘制三角形:

glDrawArrays(GL_TRIANGLES, 0, 3);

运行结果通常如下:
在这里插入图片描述

三、完整代码

完整代码如下:

my_glwidget.h

//  
// Created by liuhang on 2025/9/16.  
//  
#include <QOpenGLWidget>  
#include<QOpenGLFunctions>  
  
#ifndef UNTITLED_MYGLWIDGET_H  
#define UNTITLED_MYGLWIDGET_H  
  
  
class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {  
public:  
    MyGLWidget(QWidget* parent = nullptr);  
    ~MyGLWidget();  
protected:  
    void initializeGL() override;  
    void paintGL() override;  
    void resizeGL(int w, int h) override;  
  
private:  
    GLuint VertexArrayID; //VAO  
    GLuint vertexBuffer; //VBO  
};  
  
  
  
#endif //UNTITLED_MYGLWIDGET_H

my_glwidget.cpp

#include "my_glwidget.h"  
#include<iostream>  
  
  
//顶点数组  
static const GLfloat g_vertex_buffer_data[] = {  
        -1.0f, -1.0f, 0.0f,  
        1.0f, -1.0f, 0.0f,  
        0.0f,  1.0f, 0.0f  
};  
  
MyGLWidget::MyGLWidget(QWidget *parent) : QOpenGLWidget(parent)  
{  
}  
  
void MyGLWidget::initializeGL() {  
    //初始化所有opengl函数  
    initializeOpenGLFunctions();  
  
    //清理画布:颜色 RGB    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
  
    //生成一个VAO  
    glGenVertexArrays(1, &VertexArrayID);  
    //绑定VAO  
    glBindVertexArray(VertexArrayID);  
  
    //生成一个VBO  
    glGenBuffers(1, &vertexBuffer);  
    //绑定VBO  
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);  
    //分配VBO内存:GL_STATIC_DRAW 不经常修改缓冲区  
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);  
  
    //解绑vao  
    glBindVertexArray(0);  // 解绑VAO  
}  
  
void MyGLWidget::paintGL() {  
    glClear(GL_COLOR_BUFFER_BIT);  
  
    //opengl是一个状态机,每一帧都需要解绑vao再绑定vao  
    glBindVertexArray(VertexArrayID);  
    //描述顶点信息,绑定的layout = 0,float类型,不需要归一化 stride = 0(只有唯一的位置信息)偏移0(只有唯一的位置信息)  
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);  
  
    //启动layout = 0  
    glEnableVertexAttribArray(0);  
  
    std::cout << "vao = " << VertexArrayID << std::endl; //vao = 1804515168  
    std::cout << "vbo = " << vertexBuffer << std::endl; //vbo = 1  
  
    //绘制三角形(GL_TRIANGLES),顶点数组的开始索引,绘制的顶点个数  
    glDrawArrays(GL_TRIANGLES, 0, 3);  
}  
  
void MyGLWidget::resizeGL(int w, int h) {  
    glViewport(0, 0, w, h);  
}  
  
MyGLWidget::~MyGLWidget() {  
    //切换到opengl上下文  
    makeCurrent();  
  
    //删除VAO和VBO  
    glDeleteVertexArrays(1, &VertexArrayID);  
    glDeleteBuffers(1, &vertexBuffer);  
    doneCurrent();  
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐