一、EBO / IBO
1、元素缓冲对象, Element Buffer Object / 索引缓冲对象, Index Buffer Object。
2、OpenGL主要处理三角形。
3、若绘制一个矩形,需要两个三角形组成一个矩形,需要6个顶点,如下。但其中2个顶点是重复的,这产生了50%额外开销,若模型顶点更多,浪费也更多。
float vertices[] =
{
// 第一个三角形
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, 0.5f, 0.0f, // 左上角
// 第二个三角形
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
4、解决办法是,只存储不同的顶点,并设定绘制这些顶点的顺序,这个顺序就是顶点数组元素(顶点)的索引。
float vertices[] =
{
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
unsigned int indices[] =
{
// 注意索引从0开始!
// 此例的索引(0,1,2,3)就是顶点数组vertices的顶点元素下标!
// 这样可以由下标代表顶点组合成矩形!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
5、EBO管理一片显存(缓冲区),这片显存(缓冲区)用于存储OpenGL要绘制哪些顶点的索引,这就是索引绘制(Indexed Drawing)。
6、我们用glGenBuffers()创建元素缓冲对象/索引缓冲对象。
7、我们用glBindBuffer()将EBO/IBO绑定到GL_ELEMENT_ARRAY_BUFFER目标位置。
8、我们用glBufferData()把索引从内存复制到EBO显存中。
9、注意,VAO会跟踪EBO的绑定,VAO的最后一个指针是指向EBO的。
二、void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid * indices);
1、从索引缓冲区渲染模型,即从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取其索引。
2、mode指定要绘制的图元类型。
3、count指定要绘制的顶点个数。
4、type指定索引数据类型。
5、indices即可以指定EBO的偏移量(使用EBO),也可以传递一个索引数组(不使用EBO,不建议)。
三、void glPolygonMode(GLenum face, GLenum mode)
0、控制OpenGL如何绘制图元。
1、face确定显示模式将适用于所有图元的正面和反面:
GL_FRONT : 显示模式适用于所有图元的前面。
GL_BACK : 显示模式适用于所有图元的背面。
GL_FRONT_AND_BACK : 显示模式适用于所有图元的所有面。
2、mode参数确定选中的物体的面以何种方式显示:
GL_POINT : 表示图元用点显示(只显示顶点)。
GL_LINE : 表示图元用轮廓显示(只显示边框)。
GL_FILL :表示图元用填充显示。
四、注意:
1、VAO的最后一个指针指向着EBO,所以当目标是GL_ELEMENT_ARRAY_BUFFER时,VAO会储存glBindBuffer的函数调用,EBO的绑定和解绑会被VAO偷偷记录下来。
2、所以EBO绑定前,VAO要先绑定才能记住EBO; EBO解绑前,VAO要先解绑,这样VAO才会一直记住此EBO。
3、VBO虽然不似2,但最好和EBO保持一致。
4、绑定VAO,会间接绑定VBO和EBO。
五、代码:
//MyOpenGLWidget.h
#pragma once
//需要两个头文件
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
class MyOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
MyOpenGLWidget(QWidget * parent = nullptr);
~MyOpenGLWidget();
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
private:
unsigned int VAO;
unsigned int VBO;
unsigned int EBO;
unsigned int shaderProgram;
};
//MyOpenGLWidget.cpp
#include "MaLanOpenGLWidget.h"
#include <QDebug>
MyOpenGLWidget::MyOpenGLWidget(QWidget * parent) : QOpenGLWidget(parent)
{
}
MyOpenGLWidget::~MyOpenGLWidget()
{
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
//创建VAO、VBO、EBO,并赋予ID
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//绑定VAO、VBO、EBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //注意, 因为VAO已经绑定,此句会被VAO的最后一个指针偷偷记录下来, 指向EBO。
//顶点数据
float vertices[] =
{
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//索引数据
unsigned int indices[] =
{
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//配置VAO
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
//解绑VAO、VBO、EBO
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //注意,因为VAO已经解绑了,所以此句不会被VAO记录下来,VAO的最后一个指针依然指向EBO,这样好处是绘制前,只要再绑定VAO即可,EBO、VBO都不用再绑定。
//顶点着色器源码
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"
"}\0";
//构建和编译顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//检查顶点着色器编译是否有错
int success = 0;
char infoLog[512] = { 0 };
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
qDebug() << "ERROR:SHADER:VERTEX:COMPILATION_FAILED\n" << infoLog;
}
//构建和编译片段着色器
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
//检查片段着色器编译是否有错
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
qDebug() << "ERROR:SHADER:FRAGMENT:COMPILATION_FAILED\n" << infoLog;
}
//链接顶点着色器和片段着色器
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
//检查链接是否有错
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
qDebug() << "ERROR:PROGRAM:LINKING_FAILED\n" << infoLog;
}
//删除不用的两个着色器
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//用线段表示多边形
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
}
void MyOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAO);
glUseProgram(shaderProgram);
//glDrawArrays(GL_TRIANGLES, 0, 6);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}