一、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