从GLSL着色器到VAO/VBO/EBO知识梳理

1. 着色器的含义

着色器是GPU上的小程序,为图形渲染管线的某个特定部分运行。换句话说,着色器就是将输入转化为输出的程序。
着色器是由一种叫做GLSL的类C语言编写成的。

2. GLSL

着色器的开头要声明版本、输入输出、uniform和main函数。比如顶点着色器,它的输入变量叫做顶点属性,我们能声明的顶点属性是有上限的,一般由硬件决定。OpenGL至少确保有16个包含4分量的顶点属性可用。
一个简单着色器如下:

#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;
}
2.1 数据类型
  • 默认数据类型 :int float double unit bool
  • 容器类型:向量(Vector)和矩阵(Matrix)
向量类型
2.2 输入输出

每个着色器使用关键字in和out决定输入输出,只要输出变量与下一个着色器的输入相匹配,它就会不断传递下去。顶点着色器与片段着色器会有点不一样。

  • 顶点着色器:我们必须使用location这个一元数据去指定输入变量,这样我们才可以在GPU上设置顶点属性。顶点着色器需要为它的输入变量提供额外的layout标识,这样我们才能将它链接到顶点数据
  • 片段着色器:它需要一个vec4颜色输出变量,因为片段着色器最终会生成颜色,如果你没有设置它的输出变量,那么OpenGL就会自动将你的物体渲染为黑色或白色。

所以,如果我们打算从一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。

  • 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0

out vec4 vertexColor; // 为片段着色器指定一个颜色输出

void main()
{
    gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
    vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}
  • 片段着色器
#version 330 core
out vec4 FragColor;

in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)

void main()
{
    FragColor = vertexColor;
}
  • 最终效果


    最终渲染效果
2.3 Uniform

Uniform是一种从CPU应用向GPU着色器发送数据的一种方式。Uniform是全局的,在某一着色器里声明了它,其他着色器就可以使用它。
比如下面我们将在片段着色器里声明一个Uniform

#version 330 core
out vec4 FragColor;

uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量

void main()
{
    FragColor = ourColor;
}

下面我们来看一个特殊的函数glUniform4f,4f代表什么?4f其实就是函数需要4个float作为它的值。

glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

如下是我们函数名可能有的后缀


函数名后缀

下面我们打算当每一次渲染迭代都更新Uniform

while(!glfwWindowShouldClose(window))
{
    // 输入
    processInput(window);

    // 渲染
    // 清除颜色缓冲
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // 记得激活着色器
    glUseProgram(shaderProgram);

    // 更新uniform颜色
    float timeValue = glfwGetTime();
    float greenValue = sin(timeValue) / 2.0f + 0.5f;
    int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
    glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

    // 绘制三角形
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    // 交换缓冲并查询IO事件
    glfwSwapBuffers(window);
    glfwPollEvents();
}
2.4 从文件流中读取着色器内容
Shader(const char* vertexPath, const char* fragmentPath)
{
    // 1. 从文件路径中获取顶点/片段着色器
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;
    // 保证ifstream对象可以抛出异常:
    vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    try 
    {
        // 打开文件
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;
        // 读取文件的缓冲内容到数据流中
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();       
        // 关闭文件处理器
        vShaderFile.close();
        fShaderFile.close();
        // 转换数据流到string
        vertexCode   = vShaderStream.str();
        fragmentCode = fShaderStream.str();     
    }
    catch(std::ifstream::failure e)
    {
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
    }
    const char* vShaderCode = vertexCode.c_str();
    const char* fShaderCode = fragmentCode.c_str();
    [...]

3. 顶点缓冲对象(VBO)

3.1 定义

顶点缓冲对象是在显卡里开辟一块内存,用于存储顶点的各类属性信息。在渲染时,可以直接从VBO中取出顶点数据进行渲染,由于顶点数据存储在显卡中而不是内存中,不需要从CPU传输数据,渲染效率更高。

每个VBO在OpenGL里都有对应的ID,这个ID对应VBO的显存地址,通过这个ID可以对VBO的数据进行存取

3.2 VBO的创建和配置
  • 开辟显存空间并分配VBO的ID
//创建vertex buffer object对象  
GLuint vboId;//vertex buffer object句柄  
glGenBuffers(1, &vboId);  
  • 创建后需要通过分配的ID来绑定VBO,对于同一种类型的顶点数据一次只能绑定一个VBO。
    第一个参数指的是绑定的数据类型:GL_ARRAY_BUFFER(顶点数组传值), GL_ELEMENT_ARRAY_BUFFER(索引数组传值), GL_PIXEL_PACK_BUFFER,GL_PIXEL_UNPACK_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, vboId);  
  • 将用户数据传输到当前绑定的显存缓冲区中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  
  • 顶点数据传入GPU后,还需要告诉OpenGL如何解析这些顶点数据
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);  

第一个参数指定顶点属性位置(与顶点着色器的layout(location=0)有关)
第二个参数指定顶点数据的大小
第三个参数指定顶点数据类型
第四个参数指定顶点数据是否被标准化
第五个参数是步长,指定顶点之间的间隔
第六个参数是位置数据在缓冲区起始的偏移量

顶点属性glVertexAttribPointer默认是关闭的,使用时要以顶点属性位置值为参数调用glEnableVertexAttribArray开启。如glEnableVertexAttribArray(0);

总的来讲,VBO的配置如下:

  1. 开辟显存空间并分配ID
  2. 通过分配的ID来绑定VBO
  3. 传入用户数据到绑定的显存缓冲区BO
  4. 设置OpenGL如何解析顶点数据

4. 顶点数组对象(VAO)

4.1 优点

当我们使用VBO时,每次绘制模型的时候都需要绑定顶点的所有信息,数据量大的时候就显得十分麻烦。VAO可以将所有的配置信息都保存在对象中,下一次绘制模型的时候直接绑定VAO对象就可以了。

4.2 含义

VAO保存了所有顶点属性的状态集合,它存储了顶点数据的格式以及顶点数据所需的VBO对象的引用.它不能单独使用,都是结合VBO来一起使用的

4.3 VAO的配置
  • 生成一个VAO对象并绑定VAOID,之后我们所有关于顶点数据的设置都会被存储在VAO中,在设置完成之后一般会解绑VAO,然后在需要绘制的时候启用相应的VAO对象。
//创建VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);
//设置当前VAO,之后所有操作(注意:这些操作必须是上文VAO中包含的内容所注明的调用,其他非VAO中存储的内容即使调用了也不会影响VAO)存储在该VAO中
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); //设置了VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//设置VBO中的数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); //设置顶点属性(索引为0的属性,与shader中的内容有交互)
glEnableVertexAttribArray(0); //设置开启顶点属性(索引为0的属性,与shader中的内容有交互)
glBindVertexArray(0); //解绑VAO(解绑主要是为了不影响后续VAO的设置,有点类似于C++中指针delete后置空,是个好习惯)

当我们需要绘制的时候

glUseProgram(shaderProgram);
glBindVertexArray(VAO); //绑定我们需要的VAO,会导致上面所有VAO保存的设置自动设置完成
someOpenGLFunctionThatDrawsOurTriangle();   
glBindVertexArray(0);   //解绑VAO

VAO对象中主要包含以下几个信息

  1. VAO开启或关闭的状态(glEnableVertexAttribArray和glDisableVertexAttribArray)
  2. 使用glVertexAttribPointer设置顶点属性信息
  3. 存储顶点数据的VBO对象
顶点数组与顶点对象的联系

4. 索引缓冲对象(EBO)

4.1 含义

索引缓冲对象是为了解决同一顶点重复调用的问题,可以减少内存浪费提高执行效率。当需要使用重复顶点的时候,可以通过顶点索引来调用顶点,而不是重复记录。

是不是看起来有点糊里糊涂,下面我们用一个小例子来说明索引缓存对象的用处。
比如我们在绘制一个正方体的时候,它的结构如下:

正方体

正方体的顶点只有八个,但是我们在构造它的数组的时候可以发现有许多重复的值,最好的方式应该是每个顶点只需要存储一次,当我们需要这些顶点时,只需要调用顶点的索引来引用的需要的顶点数据

GLfloat vertices[] = {
//前
-1,-1,1, //v4
1, -1, 1 //v5
1, 1, 1 //v6
-1, 1, 1 //v7
//左
-1,1,-1 //v0
-1,-1,1 //v4
-1,1,1 //v7
-1,1,-1 //v3
......
};
索引的好处
4.2 索引的使用
  • 创建EBO(与VBO类似)
GLuint eboID;
glGenBuffers(1, &eboID);
  • 绑定EBO,传入索引数据到EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //绑定EBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
 //传入索引数据到EBO中
  • 绘制几何体(glDrawElements,如果不使用EBO,就是使用glDrawArrays进行绘制)
void glDrawElements(    
    GLenum mode,    //绘制模式,可以是GL_TRIANGLES、GL_POINTS等     
    GLsizei count, //绘制顶点的次数
    GLenum type,   //索引数据的类型
    const GLvoid * indices //EBO中的偏移量(如果不使用EBO,那么indices指向的是索引数组的指针)
);

当我们使用了EBO后,VAO中也会存储EBO的信息

VAO

引用:
OpenGL缓冲区对象之VAO
OpenGL缓冲区对象之EBO
OpenGL图形渲染管线、VBO、VAO、EBO概念及用例
官方文档-着色器

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容