OpenGL: EBO / IBO的使用

一、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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小马兰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值