errorman

不积跬步,无以至千里

导航

Qt+OpenGL(8)坐标变换

Posted on 2023-08-24 15:50  Clemens  阅读(105)  评论(0编辑  收藏  举报

一、准备工作

详情见learnopengl(8)坐标变换

该部分主要涉及5个空间的变换,它们分别为局部空间(Local Space,或者称为物体空间(Object Space))、世界空间(World Space)、观察空间(View Space,或者称为视觉空间(Eye Space),或者摄像机空间(Camera Space))、裁剪空间(Clip Space)、屏幕空间(Screen Space)。

这5个空间的变换,本质上是经过各自的坐标系统经过一系列的矩阵运算,进行坐标系统的变换。理解和计算坐标系统的变换是关键。

从局部空间,经过模型矩阵变换到世界空间,再经过观察矩阵变换到观察空间,再经过投影矩阵变换到裁剪空间,然后再经过视口变换到屏幕空间。

$$ V_{clip} = M_{projection} * M_{view} * M_{model} * V_{local} $$

顶点着色器的输出要求所有的顶点都在裁剪空间内,这就是我们上面一系列变换矩阵做的工作。然后OpenGL会对裁剪坐标执行透视除法从而将他们变换到标准坐标系。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了屏幕上的一个点。这个过程称为视口变换。

 

二、进入3D

我们在上一节的基础上,我们首先创建一个模型矩阵,模型矩阵包含了位移、缩放与旋转操作。他们会应用到所有物体的顶点上,从而将他们变换到世界空间。

我们首先将物体绕x轴顺时针旋转55度,变换到世界空间。然后将物体向后移动10单位长度,等同于将摄像机向Z轴正方向,即向后移动10单位长度。然后将透视投影矩阵的视野设置为45,这会让我们获得一个更加真实的观察效果,将平头截体的近平面设置为0.1,远平面设置为100.0.

代码请见:

void GLWidget::initializeGL(){
    ...
    // 创建一个模型矩阵,绕X轴顺时针旋转55度
    QMatrix4x4 model;
    model.rotate(-55.0, QVector3D(1.0, 0, 0));
    // 创建观察矩阵,沿z轴负方向平移10单位长度,相当于摄像机向后移动了10
    QMatrix4x4 view;
    view.translate(QVector3D(0, 0, -10));
    // 创建投影矩阵,视野为45,这会显得更真实;近距离设置为0.1,远距离设置为100
    QMatrix4x4 projection;
    projection.perspective(45, width() / height(), 0.1, 100.0);
    // 赋值给顶点着色器中的uniform变量
    m_program->setUniformValue("model", model);
    m_program->setUniformValue("view", view);
    m_program->setUniformValue("projection", projection);
    ...
}
    

修改顶点着色器,将每个矩阵作用到每个顶点上:

#version 330 core

layout(location=0) in vec3 aPos;
layout(location=1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

我们将得到如下效果:

我们可以试着变换矩阵的参数,观察他们的效果,深刻体会每个步骤的作用。

完整代码如下:

查看代码
 // glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>

class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
public:
    explicit GLWidget(QWidget *parent = nullptr);
    ~GLWidget();

protected:
    void resizeGL(int w, int h) override;
    void initializeGL() override;
    void paintGL() override;

private:
    QOpenGLVertexArrayObject *m_vao;    //顶点数组对象
    QOpenGLBuffer *m_vbo;               //顶点缓冲对象
    QOpenGLBuffer *m_ebo;               //元素缓冲对象
    QOpenGLShader *m_vshader;           //顶点着色器
    QOpenGLShader *m_fshader;           //片段着色器
    QOpenGLShaderProgram *m_program;    //着色器程序对象
    QOpenGLTexture *m_texture1 ;          //纹理对象
    QOpenGLTexture *m_texture2 ;          //纹理对象
};
#endif // GLWIDGET_H


// glwidget.cpp
#include "glwidget.h"
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QImage>
#include <QMatrix4x4>

float vertices[] = {
    // 位置                 // 纹理坐标
    0.5f,  0.5f, 0.0f,     1.0f, 1.0f, // 右上
    0.5f, -0.5f, 0.0f,     1.0f, 0.0f, // 右下
    -0.5f, -0.5f, 0.0f,    0.0f, 0.0f, // 左下
    -0.5f,  0.5f, 0.0f,    0.0f, 1.0f  // 左上
};
unsigned int indices[] = {
    // 注意索引从0开始!
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    makeCurrent();

}

GLWidget::~GLWidget()
{
    if (m_program == nullptr) { return; }
    makeCurrent();
    delete m_program;
    delete m_vshader;
    delete m_fshader;
    m_vbo->destroy();
    m_vao->destroy();
    doneCurrent();
}

//设置OpenGL视口、投影等。每当窗口部件被调整大小时调用(也在第一次显示时调用,因为所有新创建的窗口部件都自动获得调整大小事件)。
void GLWidget::resizeGL(int w, int h)
{

}

//设置OpenGL资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次。
void GLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// 设置清屏颜色
    glEnable(GL_DEPTH_TEST);    // 启用深度测试
    glEnable(GL_COLOR_BUFFER_BIT);  // 启用颜色缓冲区

    // 创建顶点着色器对象,并编译
    m_vshader = new QOpenGLShader(QOpenGLShader::Vertex);
    m_vshader->compileSourceFile(QString(":/shader/shader.vs"));
    // 创建片段着色器对象,并编译
    m_fshader = new QOpenGLShader(QOpenGLShader::Fragment);
    m_fshader->compileSourceFile(QString(":/shader/shader.fs"));
    // 创建着色器程序对象,添加顶点着色器和片段着色器,并链接它们
    m_program = new QOpenGLShaderProgram;
    m_program->addShader(m_vshader);
    m_program->addShader(m_fshader);
    m_program->link();
    // 创建顶点数组对象,并绑定到当前上下文
    m_vao = new QOpenGLVertexArrayObject;
    m_vao->create();
    QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
    // 创建顶点缓冲对象,
    m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    m_vbo->create();
    m_vbo->bind();
    // 分配显存大小,并搬运至显存
    m_vbo->allocate(vertices, sizeof(vertices));

    // 创建元素缓冲对象,
    m_ebo = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
    m_ebo->create();
    m_ebo->bind();
    // 分配显存大小,并搬运至显存
    m_ebo->allocate(indices, sizeof(indices));
    // 链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
    // 顶点属性默认是禁用的,启用顶点属性0(location=0)
    m_program->enableAttributeArray(0);
    // 纹理坐标属性
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float)));
    m_program->enableAttributeArray(1);


    // 加载纹理
    m_texture1 = new QOpenGLTexture(QImage(":/image/container.jpg").mirrored());
    // 设置纹理环绕方式
    m_texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    m_texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
    // 设置多级渐远纹理过滤方式
    m_texture1->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);

    // 加载纹理
    m_texture2 = new QOpenGLTexture(QImage(":/image/awesomeface.png").mirrored());
    // 设置纹理环绕方式
    m_texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    m_texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
    // m_texture2
    m_texture1->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);


    // 我们需要手动设置哪个采样器对应哪个纹理单元
    m_program->bind();  //在修改uniform值之前,一定要绑定着色器程序到当前激活的opengl上下文
    m_program->setUniformValue("texture1", 0);
    m_program->setUniformValue("texture2", 1);
    // 创建一个模型矩阵,绕X轴顺时针旋转55度
    QMatrix4x4 model;
    model.rotate(-55.0, QVector3D(1.0, 0, 0));
    // 创建观察矩阵,沿z轴负方向平移10单位长度,相当于摄像机向后移动了10
    QMatrix4x4 view;
    view.translate(QVector3D(0, 0, -10));
    // 创建投影矩阵,视野为45,这会显得更真实;近距离设置为0.1,远距离设置为100
    QMatrix4x4 projection;
    projection.perspective(45, width() / height(), 0.1, 100.0);
    // 赋值给顶点着色器中的uniform变量
    m_program->setUniformValue("model", model);
    m_program->setUniformValue("view", view);
    m_program->setUniformValue("projection", projection);


    m_vao->release();
    m_vbo->release();
    // VAO还在使用时,不能释放EBO
}

//渲染OpenGL场景。每当窗口部件需要更新时调用。
void GLWidget::paintGL()
{
    // 清空颜色缓冲区和深度缓冲区
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 将纹理绑定到当前激活的纹理单元
    m_texture1->bind(0);
    m_texture2->bind(1);
    // 绑定着色器程序至当前上下文,相当于调用glUseProgram()
    m_program->bind();
    // 绑定VAO,调用设置的一组状态
    QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
    // 绘制三角形
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    m_program->release();
    // 交换缓存
    auto *context = QOpenGLContext::currentContext();
    context->swapBuffers(context->surface());
    //使用swapBuffers后,必须调用makeCurrent后才能使用其他OpenGL函数。
    makeCurrent();
}

 

绘制更多的立方体

渲染一个立方体需要(6个面 x 每个面有2个三角形组成 x 每个三角形有3个顶点)36个顶点,每个顶点的位置如下:

查看代码
 float vertices[] = {
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

 

 我们为每个立方体定义一个位移向量来指定它在世界空间的位置,一共1个。

QVector3D cubePositions[] = {
    QVector3D(0.0f,  0.0f,  0.0f),
    QVector3D(2.0f,  5.0f, -15.0f),
    QVector3D(-1.5f, -2.2f, -2.5f),
    QVector3D(-3.8f, -2.0f, -12.3f),
    QVector3D(2.4f, -0.4f, -3.5f),
    QVector3D(-1.7f,  3.0f, -7.5f),
    QVector3D(1.3f, -2.0f, -2.5f),
    QVector3D(1.5f,  2.0f, -2.5f),
    QVector3D(1.5f,  0.2f, -1.5f),
    QVector3D(-1.3f,  1.0f, -1.5f)
};

 

我们使用顶点,便不再需要元素缓冲对象EBO了,此时可以删除EBO相关的代码。

因为我们绘制了多个立方体,每个立方体在世界空间中的位置不同,所以我们每帧都要计算模型矩阵,每帧都要重绘,代码如下:

void GLWidget::paintGL()
{
    ...
    // 绘制立方体,并平移到设定好的不同位置。
    for (int i = 0; i < 10 ; i++) {
        QMatrix4x4 model;
        model.translate(cubePositions[i]);
        m_program->setUniformValue("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
    ...
}

运行程序,我们可以得到如下:

可以看出,位于z轴不同位置,显示的跟我们肉眼观察到的类似,远小近大。

完整代码如下:

查看代码
 //glwidget.cpp
#include "glwidget.h"
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QImage>
#include <QMatrix4x4>

float vertices[] = {
    // 位置                 // 纹理坐标
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
QVector3D cubePositions[] = {
    QVector3D(0.0f,  0.0f,  0.0f),
    QVector3D(2.0f,  5.0f, -15.0f),
    QVector3D(-1.5f, -2.2f, -2.5f),
    QVector3D(-3.8f, -2.0f, -12.3f),
    QVector3D(2.4f, -0.4f, -3.5f),
    QVector3D(-1.7f,  3.0f, -7.5f),
    QVector3D(1.3f, -2.0f, -2.5f),
    QVector3D(1.5f,  2.0f, -2.5f),
    QVector3D(1.5f,  0.2f, -1.5f),
    QVector3D(-1.3f,  1.0f, -1.5f)
};

unsigned int indices[] = {
    // 注意索引从0开始!
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    makeCurrent();

}

GLWidget::~GLWidget()
{
    if (m_program == nullptr) { return; }
    makeCurrent();
    delete m_program;
    delete m_vshader;
    delete m_fshader;
    m_vbo->destroy();
    m_vao->destroy();
    doneCurrent();
}

//设置OpenGL视口、投影等。每当窗口部件被调整大小时调用(也在第一次显示时调用,因为所有新创建的窗口部件都自动获得调整大小事件)。
void GLWidget::resizeGL(int w, int h)
{

}

//设置OpenGL资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次。
void GLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// 设置清屏颜色
    glEnable(GL_DEPTH_TEST);    // 启用深度测试
    glEnable(GL_COLOR_BUFFER_BIT);  // 启用颜色缓冲区

    // 创建顶点着色器对象,并编译
    m_vshader = new QOpenGLShader(QOpenGLShader::Vertex);
    m_vshader->compileSourceFile(QString(":/shader/shader.vs"));
    // 创建片段着色器对象,并编译
    m_fshader = new QOpenGLShader(QOpenGLShader::Fragment);
    m_fshader->compileSourceFile(QString(":/shader/shader.fs"));
    // 创建着色器程序对象,添加顶点着色器和片段着色器,并链接它们
    m_program = new QOpenGLShaderProgram;
    m_program->addShader(m_vshader);
    m_program->addShader(m_fshader);
    m_program->link();
    // 创建顶点数组对象,并绑定到当前上下文
    m_vao = new QOpenGLVertexArrayObject;
    m_vao->create();
    QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
    // 创建顶点缓冲对象,
    m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    m_vbo->create();
    m_vbo->bind();
    // 分配显存大小,并搬运至显存
    m_vbo->allocate(vertices, sizeof(vertices));
    // 链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
    // 顶点属性默认是禁用的,启用顶点属性0(location=0)
    m_program->enableAttributeArray(0);
    // 纹理坐标属性
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float)));
    m_program->enableAttributeArray(1);


    // 加载纹理
    m_texture1 = new QOpenGLTexture(QImage(":/image/container.jpg").mirrored());
    // 设置纹理环绕方式
    m_texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    m_texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
    // 设置多级渐远纹理过滤方式
    m_texture1->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);

    // 加载纹理
    m_texture2 = new QOpenGLTexture(QImage(":/image/awesomeface.png").mirrored());
    // 设置纹理环绕方式
    m_texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    m_texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
    // m_texture2
    m_texture1->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);


    // 我们需要手动设置哪个采样器对应哪个纹理单元
    m_program->bind();  //在修改uniform值之前,一定要绑定着色器程序到当前激活的opengl上下文
    m_program->setUniformValue("texture1", 0);
    m_program->setUniformValue("texture2", 1);
    // 创建观察矩阵,沿z轴负方向平移10单位长度,相当于摄像机向后移动了10
    QMatrix4x4 view;
    view.translate(QVector3D(0, 0, -10));
    // 创建投影矩阵,视野为45,这会显得更真实;近距离设置为0.1,远距离设置为100
    QMatrix4x4 projection;
    projection.perspective(45, width() / height(), 0.1, 100.0);
    // 赋值给顶点着色器中的uniform变量
    m_program->setUniformValue("view", view);
    m_program->setUniformValue("projection", projection);


    m_vao->release();
    m_vbo->release();
    // VAO还在使用时,不能释放EBO
}

//渲染OpenGL场景。每当窗口部件需要更新时调用。
void GLWidget::paintGL()
{
    // 清空颜色缓冲区和深度缓冲区
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 将纹理绑定到当前激活的纹理单元
    m_texture1->bind(0);
    m_texture2->bind(1);
    // 绑定着色器程序至当前上下文,相当于调用glUseProgram()
    m_program->bind();
    // 绑定VAO,调用设置的一组状态
    QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
    // 绘制立方体,并平移到设定好的不同位置。
    for (int i = 0; i < 10 ; i++) {
        QMatrix4x4 model;
        model.translate(cubePositions[i]);
        m_program->setUniformValue("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
    // 交换缓存
    auto *context = QOpenGLContext::currentContext();
    context->swapBuffers(context->surface());
    //使用swapBuffers后,必须调用makeCurrent后才能使用其他OpenGL函数。
    makeCurrent();
}