首页 > 其他分享 >OpenGL: VAO和VBO的使用

OpenGL: VAO和VBO的使用

时间:2025-01-16 13:31:46浏览次数:3  
标签:VAO OpenGL VBO 顶点 GL 着色器

一、VAO
    1、顶点数组对象, Vertex Array Object。
    2、顶点着色器允许开发者指定任何以顶点属性为形式的输入,如:"layout(location = 0) in vec3 aPos;","location = 0"即顶点属性指针的序号。
    3、VAO并不保存实际数据,而是存放顶点数组结构定义,即指定OpenGL该如何解释顶点数据。
    4、OpenGL核心模式要求我们使用VAO,因为它知道该如何处理VBO的顶点数组里的顶点数据,如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
    5、VAO中有很多属性指针,如attribute pointer 0、attribute pointer 1、...,VBO的顶点数组里的每一个项都对应VAO的一个属性指针,属性如:位置Position、颜色Color、纹理ST等。
       VAO / VBO                          VERTEX1              VERTEX2              VERTEX3        ......
                                    X  Y  Z  R  G  B  A  X  Y  Z  R  G  B  A  X  Y  Z  R  G  B  A  ......    
                                    0  4  8  12 16 20 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 ......
       vertexAttribPointer0(可以作为位置属性指针, 解释关于顶点位置XYZ的数据)
       vertexAttribPointer1(可以作为颜色属性指针,解释关于顶点颜色RGBA的数据)
              ......
    6、我们用glGenVertexArrays()创建VAO。
    7、我们用glBindVertexArray()绑定VAO,因为OpenGL中只有这一种类型的顶点数组对象,所以绑定时不需要指定类型(或目标位置)。
    8、绑定VAO后,VAO会偷偷记录以下内容:
       * glVertexAttribPointer();
       * glEnableVertexAttribArray();
       * glDisableVertexAttribArray();
       * 目标为GL_ELEMENT_ARRAY_BUFFER时的glBindBuffer()(目标为GL_ARRAY_BUFFER时VAO不会记录glBindBuffer())。
    9、通过glVertexAttribPointer()来配置属性指针,假设序号0作为位置属性指针,可配置每个顶点包含3个位置数据(XYZ),每个位置数据类型GL_FLOAT,是否标准化位置数据,到下个顶点位置数据的步长28,第一个位置数据偏移0。
    10、注意:
       * 属性指针默认是Disable的,通过glEnableVertexAttribArray()来开启。
       * 还有最后一个指针是指向EBO的,下一章细讲。
    11、使用VAO好处是在不同顶点数据和属性配置之间切换变得非常简单,只要绑定不同的VAO即可。

二、VBO
    1、顶点缓冲对象, Vertex Buffer Objects。
    2、我们定义的顶点数据是在内存上的,需要发送到显存(显卡的内存)上。
    3、通过VBO管理显卡上用于存储所有顶点数据的内存,glGenBuffers()可用于创建VBO。
    4、使用顶点缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。因为从内存发送数据到显存,这个过程是漫长的,所以尽可能一次性把数据发送过去。
    5、OpenGL有很多缓冲对象类型,顶点缓冲对象类型(或目标位置)是GL_ARRAY_BUFFER,可以用glBindBuffer()将VBO绑定到GL_ARRAY_BUFFER,接下来所有对GL_ARRAY_BUFFER的缓冲调用都是配置当前绑定的VBO的。
    6、我们用glBufferData()告诉OpenGL如何把顶点数据复制到VBO的内存(显存)中。
    


三、图形渲染管线(Graphics Pipeline)
    1、事物处在3D空间中,而屏幕和窗口是2D像素数组,所以OpenGL的大部分工作都是关于把3D坐标转变为适应屏幕的有色2D像素的,此过程由图形渲染管线管理。
    2、理解为:一堆原始图形数据途径一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。
    3、可以分为两个主要部分:
       把3D坐标转换为2D坐标。
       把2D坐标转变为实际的有颜色的像素。
    4、图形渲染管线可以划分为六个阶段,每个阶段将会把前一个阶段的输出作为输入。

四、2D坐标和2D像素是不同的
    1、2D坐标精确表示一个点在2D空间中的位置。
    2、2D像素是这个点的近似值,2D像素受到屏幕/窗口分辨率的限制。

五、着色器(Shader)
    1、图形渲染管线每个阶段都是高度专门化的,很容易并行执行。
    2、大多数显卡都有成千上万的小处理核心,它们在GPU上为每个阶段运行各自的小程序,从而在管线中快速处理你的数据,这些小程序叫做着色器(Shader)。
    3、OpenGL着色器是用OpenGL着色器语言(GLSL, OpenGL Shading Language)写的。
    4、有些着色器,可以允许开发者用自己写的代替默认的。
    


六、顶点数据Vertex Data[]-->[顶点着色器Vertex Shader-->形状(图元)装配Shape(Primitive) Assembly-->几何着色器Geometry Shader-->光栅化Rasterization-->片段着色器Fragment Shader-->测试与混合Tests and Blending]--->显示图像

七、顶点输入
    1、顶点数据Vertex Data, 是一系列顶点的集合, 例如表示一个三角形的三个顶点。
    2、一个顶点Vertex, 是一个3D坐标等数据的集合,例如一个顶点由一个3D位置和一些颜色值组成。[一个位置,它代表在一个空间中所处地点,而空间代表一种坐标系]
    3、OpenGL是3D图形库,我们输入的顶点坐标都是3D坐标(x、y和z)。
    4、为了让OpenGL知道这些顶点(坐标和颜色值)构成的到底是什么,OpenGL需要开发者在调用绘制指令时指定渲染类型(图元类型),如GL_POINTS,GL_TRIANGLES、GL_LINE_STRIP。

八、顶点着色器Vertex Shader
    1、顶点着色器会在GPU上创建内存, 用于存储我们的顶点数据。
    2、顶点着色器把一个单独的顶点作为输入, 从VBO管理的内存中拿顶点数据非常快速。
    3、顶点着色器主要目的是把3D坐标转为另一种3D坐标(标准化设备坐标), 同时允许我们对顶点属性进行一些基本处理。
    4、用glCreateShader(GL_VERTEX_SHADER), 创建一个顶点着色器对象。
    5、用void glShaderSource(GLuint shader, GLsizei count, const GLchar ** string, GLint * length), 绑定顶点着色器源码。
    6、用glCompileShader(), 编译顶点着色器源码。
    7、可以用glGetShaderiv()检查顶点着色器编译是否有错。
    8、可以用glGetShaderInfoLog()获取具体的顶点着色器编译错误信息。
    9、gl_Position是顶点着色器的固定输出变量,vec4类型。
    10、简单示例:
       #version 330 core
       layout (location = 0) in vec3 aPos;
       void main()
       {
           gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
       }
    11、一般会把顶点着色器源码存放在一个单独的文件中, 然后通过Qt的封装加载即可。

九、形状(图元)装配Shape(Primitive) Assembly
    1、图元装配将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,就是一个顶点),并所有的点装配成指定图元的形状。

十、几何着色器Geometry Shader
    1、几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或其它的)图元来生成其它形状。

十一、光栅化Rasterization
    1、光栅化阶段会把图元映射为像素(视口变换+其它),生成供片段着色器使用的片段Fragment,还会执行裁切Clipping。
    2、裁切会丢弃超出视图以外的所有像素,用来提升执行效率。
    3、OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。

十二、片段着色器Fragment Shader
    1、片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。
    2、通常片段着色器包含3D场景的数据(如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
    3、用glCreateShader(GL_FRAGMENT_SHADER),创建一个片段着色器对象。
    4、用void glShaderSource(GLuint shader, GLsizei count, const GLchar ** string, GLint * length),绑定片段着色器源码。
    5、用glCompileShader(),编译片段着色器源码。
    6、可以用glGetShaderiv()检查片段着色器编译是否有错。
    7、可以用glGetShaderInfoLog()获取具体的片段着色器编译错误信息。
    8、简单示例:
        #version 330 core
        out vec4 FragColor;
        void main()
        {
            FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
        }
    9、一般会把片段着色器源码存放在一个单独的文件中,然后通过Qt的封装加载即可。

十三、测试与混合Tests and Blending
    1、在所有颜色值确定以后,最终对象会传入测试与混合阶段。
    2、这个阶段会检测片段的对应深度值,判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。
    3、这个阶段会检测alpha值(alpha值定义了一个物体的透明度),并对物体进行混合。
    4、所以片段着色器计算出的一个像素颜色,在经过测试与混合后,颜色可能不同。

十四、在现代OpenGL中,我们
    1、必须定义至少一个顶点着色器和一个片段着色器,因为GPU中没有默认的顶点着色器和片段着色器。
    2、几何着色器是可选的,GPU中有默认的几何着色器,通常使用默认的即可。

十五、深度
    1、通常深度可以理解为Z坐标,它代表一个像素在空间中和你的距离。
    2、如果离你远可能被别的像素遮挡,它可能会被丢弃,以节省资源。

十六、标准化设备坐标系(Normalized Device Coordinates, NDC)
    1、标准化设备坐标系是一个x、y和z值都在-1.0到1.0的一小段空间(立方体空间)。
    2、标准化设备坐标系的原点(0,0,0)在立方体中心,y轴正方向向上,x轴正方向向右,z轴正方向朝屏幕内部,是左手坐标系。
    3、一旦顶点坐标在顶点着色器中处理过,那它们就都应该是标准化设备坐标。否则落在范围外的坐标都会被裁剪,不会显示在屏幕上。
    4、通过glViewport函数提供的数据,进行视口变换,标准化设备坐标会变换为屏幕空间坐标,所得的屏幕空间坐标又会被变换为片段会进入片段着色器中。
    [注意:刚开始学习,顶点着色器和片段着色器都尽可能简单,而顶点着色器阶段过后的顶点坐标都得是标准化设备坐标,OpenGL才去处理,这就是为什么顶点数据我们给的都是标准化设备坐标的原因。]

十七、关于glBufferData()
    1、void glBufferData(GLenum target, GLsizeiptr size, const void * data, GLenum usage);
    2、为当前绑定到target的缓冲区对象(VBO或EBO)在显存上创建一个新的数据存储(缓冲区)。
    3、如果data不是NULL,则使用此指针的数据初始化数据存储。(将用户定义的顶点数据复制到显卡缓存中)
    4、target为目标缓冲类型。
    5、size指定数据存储大小,以字节为单位。
    6、data为实际要复制的数据。
    7、usage指定显卡如何管理给定的数据,常用有:
       GL_STATIC_DRAW   数据不会或几乎不改变
       GL_DYNAMIC_DRAW  数据会被改变很多
       GL_STREAM_DRAW   数据每次绘制时都会改变 (显卡会把数据存储建在能够高速写入的显卡内存部分)
       


十八、关于glVertexAttribPointer()
    1、void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * offset);
    2、index,指定要配置的顶点属性指针的编号。
    3、size,指定一种属性(如位置、颜色、纹理)数据的分量数(1,2,3,4,类似向量的维度)。
    4、type,指定分量的数据类型,如GL_FLOAT、GL_INT、GL_BYTE等。
    5、normalized, 是否将数据标准化,如果是GL_TRUE,则所有数据会被映射到[-1,1]区间(若是unsigned,则是[0,1])。
    6、stride,步长, 指定连续两个顶点的相同属性数据之间的间隔(字节)。
    7、offset,偏移量,指定第一个该属性数据的偏移地址。

十九、颜色
    1、在计算机图形中,颜色被表示为有4个元素的数组: 红色Red、绿色Green、蓝色Blue、透明度alpha, 通常缩写为RGBA。
    2、在OpenGL或GLSL中,颜色的每个分量的强度设置在0.0到1.0之间。
    3、比如设置红分量为1.0f,绿分量为1.0f,蓝分量为0.0f,我们会得到混合色,即黄色。
    4、这三种颜色分量的不同调配可以生成超过1600万种不同的颜色。

二十、着色器程序
    1、着色器程序对象, Shader Program Object。
    2、着色器程序对象是多个着色器合并之后并最终链接完成的版本。
    3、要使用自定义着色器,必须将它们链接为一个着色器程序,然后在渲染调用前激活这个着色器程序,那这些着色器就会在渲染时被调用。
    4、链接着色器时,着色器程序对象会把每个着色器的输出链接到下个着色器的输入,当输出和输入不匹配时,会得到一个链接错误。
    5、可用glCreateProgram()创建一个着色器程序对象。
    6、可用glAttachShader()把编译好的着色器附加到着色器程序对象上。
    7、可用glLinkProgram()链接着色器。
    8、可用glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success),检测链接有无错误。
    9、可用glGetProgramInfoLog()获取日志。
    10、可用glUseProgram(shaderProgram),在渲染指令之前激活着色器程序对象。
    11、可用glDeleteShader(), 在着色器链接到着色器程序对象以后,删除着色器,因为不再需要它们了。

二十一、绘制前, 只要再次绑定VAO和shaderProgram即可, VBO都可以不用绑定了,通过VAO间接绑定。

二十二、代码:

//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 VBO;
	unsigned int VAO;
	unsigned int shaderProgram;
};




//MyOpenGLWidget.cpp

#include "MyOpenGLWidget.h"
#include <QDebug>


MyOpenGLWidget::MyOpenGLWidget(QWidget * parent) : QOpenGLWidget(parent)
{
}

MyOpenGLWidget::~MyOpenGLWidget()
{
}

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

	//创建VBO和VAO对象,并赋予ID
	glGenBuffers(1, &VBO);
	glGenVertexArrays(1, &VAO);

	//绑定VBO和VAO对象
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindVertexArray(VAO);

	//为当前绑定到target的缓冲区对象创建一个新的数据存储。
	//如果data不是NULL,则使用此指针的数据初始化数据存储。(将用户定义的内存数据复制到缓存中)
	//void glBufferData(GLenum target, GLsizeiptr size, const void * data, GLenum usage);
	float vertices[] = 
	{
		 0.0f,  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);

	//告知显卡如何解析缓冲里的属性值
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
	//开启VAO管理的第一个属性值
	glEnableVertexAttribArray(0);

	//让自定义的VBO和VAO休息,这是一个好习惯,因为程序可能有很多个VBO和VAO,防止弄混淆,绘制时再绑定VBO和VAO。
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	//顶点着色器源码
	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);
}


void MyOpenGLWidget::resizeGL(int w, int h)
{
}


void MyOpenGLWidget::paintGL()
{
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	//glBindBuffer(GL_ARRAY_BUFFER, VBO);   可以注释掉,通过VAO间接绑定。
	glBindVertexArray(VAO);
	glUseProgram(shaderProgram);

	//GL_TRIANGLES指定图元为三角形
	//0指定顶点数组的起始索引
	//3指定绘制多少个顶点
	glDrawArrays(GL_TRIANGLES, 0, 3);
}





标签:VAO,OpenGL,VBO,顶点,GL,着色器
From: https://blog.csdn.net/weixin_44629261/article/details/145173403

相关文章

  • OpenGL中Shader LOD失效
    1)OpenGL中ShaderLOD失效2)DoTween的GC优化3)开发微信小程序游戏有没有类似Debug真机图形的方法4)射线和Mesh三角面碰撞检测的算法这是第418篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地掌握和学习。UWA社区主页:community......
  • 深圳大学-计算机图形学-实验一OpenGL基本绘制
    实验目的与要求掌握VisualStudioCommunity2019集成开发环境的安装;掌握CMake跨平台构建工具的安装;掌握Git版本控制工具的安装;掌握vcpkg库管理工具的安装;掌握系统环境变量的设置;了解和掌握OpenGL的环境配置;掌握OpenGL工程项目的建立和基本设置。理解OpenGL的原理;了解和熟......
  • 详解 opengl 语法
    以下是OpenGL语法的详解,分为核心功能、常见函数的用法以及它们在OpenGL渲染管线中的位置。OpenGL是一个状态机,许多操作都是围绕上下文状态进行的。1.OpenGL基本结构OpenGL的主要功能是通过调用一系列的API,完成三维图形的绘制。主要包括以下几个步骤:初始化Open......
  • 初识 OpenGL
    OpenGL(OpenGraphicsLibrary)是一种跨平台的图形开发接口,它用于渲染2D和3D图形。OpenGL的主要功能是向开发者提供一个可以调用硬件加速的API,以绘制复杂的图形和视觉效果。它被广泛应用于游戏开发、图形设计、科学可视化和虚拟现实等领域。在初识OpenGL时,我们可以通过一......
  • OpenGL 使用记录
    多线程渲染方案方案多线程更新纹理,主线程渲染多线程FBO渲染,主线程显示线程安全性子线程只操作共享的纹理,主线程负责渲染,信号槽机制确保线程安全。子线程直接操作自己的FBO,主线程仅访问FBO的纹理,较安全。复杂性逻辑较简单,纹理数据更新逻辑独立,主线程负责完整的......
  • 【OpenGL ES】GLSL基础语法
    1前言​本文将介绍GLSL中数据类型、数组、结构体、宏、运算符、向量运算、矩阵运算、函数、流程控制、精度限定符、变量限定符(in、out、inout)、函数参数限定符等内容,另外提供了一个include工具,方便多文件管理glsl代码,实现代码的精简、复用。​Unity中Shader介......
  • Sealos Devbox 基础教程:使用 Cursor 从零开发一个 One API 替代品
    随着技术的成熟和AI的崛起,很多原本需要团队协作才能完成的工作现在都可以通过自动化和智能化的方式完成。于是乎,单个开发者的能力得到了极大的提升-借助各种工具,一个人就可以完成开发、测试、运维等整条链路上的工作,渡劫飞升成为真正的“全干工程师”。之前我们分享过一些入......
  • 麒麟系统修改配置镜像源地址并安装openGL
    1.编辑文件/etc/apt/sources.list进入目录cd /etc/apt/编辑文件(需要root权限)sudovi sources.list将镜像地址改为你指定的镜像地址#debhttp://archive.kylinos.cn/kylin/KYLIN-ALL10.1mainrestricteduniversemultiverse#debhttp://archive.kylinos.cn/kylin/KYL......
  • Opengl-状态机
    状态机AslongasyoukeepinmindthatOpenGlisbasicllyonelargestatemchine,mostofitsfunctionalitywillmakemoresense.OPengl是一个巨大的状态机,变量(描述该如何操作)的大集合。Opengl的状态被称为上下文(context)状态设置函数(State-changingFunction)状态应......
  • Cursor + Devbox 学习笔记2
     【一行代码不写搞定开发和上线|Cursor+Devbox|AI写代码|全栈开发|Docker|K8S】https://www.bilibili.com/video/BV124D5YEEAD/?share_source=copy_web&vd_source=f4634749379d1151961b5797f4268f87视频源Devbox地址:https://cloud.sealos.run/提示词文档:https://square-rave......