4. 使用索引缓冲绘制立方体

任务目标

在Qt+OpenGL环境中,使用QOpenGLBuffer::IndexBuffer绘制一个彩色立方体。核心目标是:仅定义8个顶点(而非24个),通过索引缓冲复用顶点数据,从而减少内存占用并理解索引绘制的原理。

环境依赖

  • Qt 5.12+(含OpenGL模块)
  • OpenGL 3.3 Core Profile
  • 编译器:MSVC 2017+ 或 MinGW

关键完整代码

// cube_widget.h
#ifndef CUBE_WIDGET_H
#define CUBE_WIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>

class CubeWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    explicit CubeWidget(QWidget *parent = nullptr);
    ~CubeWidget() override;

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

private:
    QOpenGLShaderProgram m_program;
    QOpenGLBuffer m_vbo{QOpenGLBuffer::VertexBuffer};
    QOpenGLBuffer m_ebo{QOpenGLBuffer::IndexBuffer};
    QOpenGLVertexArrayObject m_vao;
};

#endif // CUBE_WIDGET_H
// cube_widget.cpp
#include "cube_widget.h"
#include <QVector>

CubeWidget::CubeWidget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    setMinimumSize(400, 300);
}

CubeWidget::~CubeWidget()
{
    makeCurrent();
    m_vao.destroy();
    m_vbo.destroy();
    m_ebo.destroy();
    doneCurrent();
}

void CubeWidget::initializeGL()
{
    initializeOpenGLFunctions();

    // 1. 着色器
    const char *vertexSrc = R"(
        #version 330 core
        layout(location = 0) in vec3 aPos;
        layout(location = 1) in vec3 aColor;
        out vec3 vColor;
        void main() {
            gl_Position = vec4(aPos, 1.0);
            vColor = aColor;
        }
    )";
    const char *fragmentSrc = R"(
        #version 330 core
        in vec3 vColor;
        out vec4 fragColor;
        void main() {
            fragColor = vec4(vColor, 1.0);
        }
    )";

    m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexSrc);
    m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentSrc);
    m_program.link();

    // 2. 顶点数据:8个顶点(位置+颜色)
    struct Vertex {
        QVector3D pos;
        QVector3D color;
    };
    QVector<Vertex> vertices = {
        // 位置                 // 颜色
        {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},  // 0 前左下
        {{ 0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},  // 1 前右下
        {{ 0.5f,  0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}},  // 2 前右上
        {{-0.5f,  0.5f, -0.5f}, {1.0f, 1.0f, 0.0f}},  // 3 前左上
        {{-0.5f, -0.5f,  0.5f}, {1.0f, 0.0f, 1.0f}},  // 4 后左下
        {{ 0.5f, -0.5f,  0.5f}, {0.0f, 1.0f, 1.0f}},  // 5 后右下
        {{ 0.5f,  0.5f,  0.5f}, {1.0f, 1.0f, 1.0f}},  // 6 后右上
        {{-0.5f,  0.5f,  0.5f}, {0.5f, 0.5f, 0.5f}}   // 7 后左上
    };

    // 3. 索引数据:36个索引(12个三角形)
    QVector<GLuint> indices = {
        // 前面
        0, 1, 2,  0, 2, 3,
        // 后面
        4, 6, 5,  4, 7, 6,
        // 左面
        0, 3, 7,  0, 7, 4,
        // 右面
        1, 5, 6,  1, 6, 2,
        // 上面
        3, 2, 6,  3, 6, 7,
        // 下面
        0, 4, 5,  0, 5, 1
    };

    // 4. 创建VAO/VBO/EBO
    m_vao.create();
    m_vao.bind();

    m_vbo.create();
    m_vbo.bind();
    m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(Vertex));

    m_ebo.create();
    m_ebo.bind();
    m_ebo.allocate(indices.constData(), indices.size() * sizeof(GLuint));

    // 5. 设置顶点属性指针
    int stride = sizeof(Vertex);
    m_program.enableAttributeArray(0);
    m_program.setAttributeBuffer(0, GL_FLOAT, 0, 3, stride);
    m_program.enableAttributeArray(1);
    m_program.setAttributeBuffer(1, GL_FLOAT, offsetof(Vertex, color), 3, stride);

    m_vao.release();
}

void CubeWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void CubeWidget::paintGL()
{
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    m_program.bind();
    m_vao.bind();
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, nullptr);
    m_vao.release();
    m_program.release();
}
// main.cpp
#include <QApplication>
#include "cube_widget.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    CubeWidget widget;
    widget.show();
    return app.exec();
}

运行与验证

  1. 在Qt Creator中创建QWidget项目,将上述三个文件加入工程,并在.pro文件中添加QT += openglwidgets。
  2. 构建并运行程序。
  3. 通过条件:窗口中出现一个彩色立方体,每个面颜色不同,且立方体轮廓清晰、无撕裂或闪烁。若立方体旋转观察(可自行添加鼠标交互),各面颜色正确对应顶点颜色插值。

核心要点

  • 顶点数从24(每面4个独立顶点)减少到8个,索引数36个,内存节省约67%。
  • 索引绘制使用glDrawElements,GPU根据索引顺序组装三角形。
  • 每个顶点包含位置和颜色,通过offsetof正确设置颜色属性偏移。