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();
}
运行与验证
- 在Qt Creator中创建
QWidget项目,将上述三个文件加入工程,并在.pro文件中添加QT += openglwidgets。 - 构建并运行程序。
- 通过条件:窗口中出现一个彩色立方体,每个面颜色不同,且立方体轮廓清晰、无撕裂或闪烁。若立方体旋转观察(可自行添加鼠标交互),各面颜色正确对应顶点颜色插值。
核心要点
- 顶点数从24(每面4个独立顶点)减少到8个,索引数36个,内存节省约67%。
- 索引绘制使用
glDrawElements,GPU根据索引顺序组装三角形。 - 每个顶点包含位置和颜色,通过
offsetof正确设置颜色属性偏移。