OpenGL:三角形的诞生
先放一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分:
顶点缓冲对象(VBO)#
所有的顶点数据(位置、纹理、法线等)都需要存储在GPU上,OpenGL通过顶点缓冲对象管理这个内存。其声明和绑定的代码函数如下:
unsigned int vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), positions, GL_STATIC_DRAW);
设置顶点属性#
在绑定VBO时,我们只是指定了一堆数据,但是对于其内存布局(layout)完全未知,为了让机器知道顶点属性,我们还需如下代码(这两行代码的顺序无所谓):
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
每个索引都可以绑定一种属性,比如上面的例子就是将每2个连续的float
作为一组属性绑定在索引0上,这里的索引0在后面的顶点着色器中也会使用到。
代码中并没有指明这个属性具体是什么属性,比如位置还是法线等,它只是一组属性并且被绑定在了索引上。
你可能会觉得很奇怪,没有指明哪个内存空间就直接设置属性。这也是OpenGL状态机的体现,只要在GL_ARRAY_BUFFER
上绑定的VBO,那么这个属性就对应的是这个VBO。
着色器(shader)#
着色器简单来说就是运行在显卡上的一段小程序,我们可以通过编程控制器输入输出进而控制图像的显示。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的,它是一种类C语言。
就目前而言,我们最需要了解的着色器就两个:顶点着色器和片段着色器。简单来说,我们首先在CPU上定义了顶点数据,之后将其转移到显存上(通过VBO),之后需要通过顶点着色器(一段小程序,对每个顶点都运行一次)处理顶点数据,并将必要的数据输出,再之后就是片段着色器(计算颜色等,对每个光栅块处理一次)进行处理,最后通过混合显示图像。
着色器间不能相互通信,它们之间唯一的沟通只有通过输入和输出。GLSL定义了in和out关键字专门来实现这个目的。每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它们的值就能够传递。
着色器代码如下:
#shader vertex
#version 330 core
layout(location = 0) in vec2 position;
layout(location = 1) in vec3 color;
out vec3 ourColor;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
ourColor = color;
}
#shader fragment
#version 330 core
layout(location = 0) out vec4 color;
in vec3 ourColor;
void main() {
color = ourColor;
}
在编译着色器时,将其转换为字符串进行编译,先假设其分别转换为std::string
对象为vertexShader
和fragmentShader
。
可以调用以下函数进行编译,类似于其他OpenGL对象,他们也需要一个id:
unsigned int id = glCreateShader(GL_VERTEX_SHADER); // or "GL_FRAGMENT_SHADER" for fragmentShader
glShaderSource(id, 1, &vertexShader, nullptr); // or fragmentShader
glCompileShader(id);
在编译完成后它们需要链接至程序中,代码如下:
unsigned int program = glCreateProgram();
glAttachShader(program, id);
glLinkProgram(program);
需要注意的是,在链接至程序中后即可删除编译的shader:
glDeleteShader(id);
之后可以通过以下函数选择是否启用此段程序:
glUseProgram(program); // set 0 to unused;
索引缓冲对象(元素缓冲对象,IBO)#
当我们需要用两个三角形构建正方形时,是否需要六个节点信息呢(每个三角形三个)?当然可以,但这会浪费现存,毕竟四边形一共四个顶点,有50%的定点信息时重复的。OpenGL提供了索引缓冲对象(IBO)解决这钟需要重复使用同一个节点信息的情况。
glDrawElements
函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER
目标的IBO中获取其索引。这意味着我们每次想要使用索引渲染对象时都必须绑定相应的IBO,这又有点麻烦。碰巧顶点数组对象也跟踪元素缓冲区对象绑定。在绑定VAO时,绑定的最后一个元素缓冲区对象存储为VAO的元素缓冲区对象。然后,绑定到VAO也会自动绑定该IBO。
当目标是GL_ELEMENT_ARRAY_BUFFER
的时候,VAO会储存glBindBuffer
的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个IBO配置了。
其设置代码与VBO类似,如下所示:
unsigned int ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * 2 * sizeof(unsigned int), index, GL_STATIC_DRAW);
顶点数组对象(VAO)#
我们可以将一组对应的VBO及其顶点属性共同绑定在一个VAO上,那么在每次绘制时,只需要绑定给定的VAO就能够绘制对应的顶点和属性了。这样做的好处就在于无需在绘制时额外设置一系列的layout和属性等,直接利用VAO的解绑和绑定就能够绘制图案。
值得注意的是,VAO是必须的。在OpenGL核心模式下需要自己创建VAO才能正常绑定VBO等。这也意味着下面这段代码需要在上面所示代码的最前面:
unsigned int vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
在使用时可以通过glBindVertexArray(vao)
指定特定的顶点数组对象,值得注意的时,它虽然绑定了对应的VBO和属性,但是并没有绑定shader等。在开启VAO时记得开启shader。
作者:Warren-j1an
出处:https://www.cnblogs.com/warren-j1an/p/18225096
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」