OpenGL学习 着色器

顶点数组对象:Vertex Array Object,VAO
顶点缓冲对象:Vertex Buffer Object,VBO
索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO(代码中用EBO表示)
(了解渲染流水线过程)
着色器分类: 顶点着色器,几何着色器,片段着色器
VAO 和 VBO 的知识链接https://www.photoneray.com/opengl-vao-vbo/看懂加深理解

VBO:
VBO本质上是一块服务端buffer(缓存),对应着client端的某份数据,在数据传输给VBO之后,client端的数据是可以删除的。系统会根据用户设置的 target 和 usage 来决定VBO最适合的存放位置(系统内存/AGP/显存)。当然,GL规范是一回事,显卡厂商的驱动实现又是另一回事了。在初始化阶段,VBO是不知道它所存储的是什么数据,而是在渲染阶段(精确说是 glVertexAttribPointer 函数)才确定数据作用类型(顶点位置、float类型、从偏移量0处开始采集数据、2个float算一个采集步长等等)。到真正绘制(glDrawArray/glDrawElement)的时候才从VBO里读取需要的数据进入渲染流水线。

// 初始化
    GLfloat vertices[] = {
        0.0f, 0.0f,
        1.0f, 0.0f, 
        0.0f, 1.0f
    }

    GLuint vbo;
    glGenBuffer(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);

    ...

    // 绘制
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2, (void*)0);
    glDrawArray(GL_TRIANGLES, 0, 3);

    ...

用一个缓冲ID来生成一个VBO对象

unsigned int VBO;
glGenBuffers(1, &VBO);

OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:

glBindBuffer(GL_ARRAY_BUFFER, VBO); 

调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中
此时我们调用的GL_ARRAY_BUFFER实际上对应着我们VBO缓冲对象(在上个代码中被绑定)

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

VAO:
VAO不是 buffer-object,所以不作数据存储;与顶点的绘制息息相关,即是说与VBO强相关。如上,VAO本质上是state-object(状态对象),记录的是一次绘制所需要的信息,包括数据在哪,数据格式之类的信息。

 // 初始化
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);  
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0); 

    ...

    // 绘制
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
    glBindVertexArray(0);

关于以下代码的笔记
glBindVertexArray(VAO);绑定VAO
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
在这里插入图片描述
在这里插入图片描述
个人理解,因为VBO将所有顶点存储在内存中,并将其给到顶点着色器,所以自然要解释各个读取的方式,起点,方式。(既如何解析顶点数据)
以下是LearnOpenGLCN的解释

glVertexAttribPointer函数的参数非常多,所以我会逐一介绍它们:
第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。

第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。(此处我写的是vec2,和LearnOpenGL数据类型不同)

第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。

第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。

如果没有VAO的帮助,我们每次都要在渲染循环里面重复如下函数
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

用完VAO需要解除绑定
glBindVertexArray(0);
(当程序只有一个VAO的时候,似乎不接触绑定也是可以的)

EBO:和顶点缓冲对象一样,EBO也是一个缓冲,它专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引

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, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

与定义VBO类似的如下代码

unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

要注意的是,我们传递了GL_ELEMENT_ARRAY_BUFFER当作缓冲目标。最后一件要做的事是用glDrawElements来替换glDrawArrays函数,来指明我们从索引缓冲渲染。使用glDrawElements时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。

(着色器后面会编写着色器类并从从文件中读取,后面一起整理)
顶点着色器
顶点着色器的基础代码示例
其中vec3指的是三维向量,可以.x.y.z去获取三个方向的向量

#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

片段着色器
片段着色器的基础代码示例

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

跟着教程发现着色器会被封装成一个类去使用,所以不多赘述着色器的使用代码。

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。
着色器部分
https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/

#version version_number
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

int main()
{
  // 处理输入并进行一些图形操作
  ...
  // 输出处理过的结果到输出变量
  out_variable_name = weird_stuff_we_processed;
}

猜你喜欢

转载自blog.csdn.net/linsang233/article/details/103394876