OpenGl学习笔记(二)--Triangle
本节内容
- 第一个三角形
Hello Triangle
在本章之前首先需要明确以下几个名词
- VAO(Vertex Array Object): 顶点数组对象,用于存储顶点数据
- VBO(Vertex Buffer Object): 顶点缓冲对象,用于存储顶点数据
- EBO(Element Buffer Object): 元素缓冲对象,用于存储索引数据
- IBO(Index Buffer Object): 索引缓冲对象,用于存储索引数据
OpenGL 渲染管线
- 顶点着色器: 将顶点转换到NDC(Normalized Device Coordinates)
- 几何着色器: 处理顶点的几何变换
- 图元装配: 将顶点组成图元
- 光栅化: 将图元转换为像素
- 片段着色器: 计算每个像素的颜色
- 测试混合: 融合颜色
输入顶点数据
由于我们想要渲染一个三角形,所以首先需要指定三个顶点.
float vertices[] = { |
这里将顶点位置已经设定在了NDC上,即-1到1的范围内.
接下来我们将顶点数据发送到显卡的显存中
float vertices[] = { |
glBufferData函数第四个参数指定了显卡如何管理给定数据,有三种形式:
- GL_STATIC_DRAW: 仅在显存上进行读取,不进行写入,适用于静态数据
- GL_DYNAMIC_DRAW: 仅在显存上进行读取,并允许写入,数据经常改变
- GL_STREAM_DRAW: 仅在显存上进行读取,不进行写入,数据每次绘制都改变
顶点着色器
在CPU阶段,我们使用C++作为我们处理数据的语言.然而在显卡上则需要使用到着色器语言.OpenGL使用的是GLSL(OpenGL Shading Language)作为它的着色器语言.
这里先使用一个非常简单的着色器,将顶点数据输出给几何着色器.
#version 330 core |
编译着色器
为了方便,我们先暂时将Vertex Shader保存在一个字符串中.
const char *vertexShaderSource = |
为了让显卡可以运行这个Shader,我们需要在运行时动态编译这个着色器.
输入以下代码
unsigned int vertexShaderObject;//声明一个顶点着色器对象 |
片元着色器
为了方便,我们首先写一个输出固定颜色的片元着色器.
|
同理将其保存为一个字符串,并且编译.
const char *fragmentShaderSource = |
unsigned int fragmentShaderObject;//声明一个片段着色器对象 |
唯一不同指出在于使用GL_FRAGMENT_SHADER作为着色器类型.
着色器程序
现在我们已经拥有了两个已经编译完成的Shader Object(着色器对象),接下来我们需要将他们链接到一个Shader Program(着色器程序)中.
Shader Program是多个shader的集合,链接这一步骤就是为了确保上游数据能够正确被下游接受.当输入输出不匹配时,链接就会失败.
输入以下代码
//链接着色器程序 |
链接顶点属性
现在,我们完成了数据输入,以及数据在渲染管线中的处理方式,唯一的问题在于,OpenGL并不知道应该如何解释我们输入的数据,以及该如何把这些数据连接到顶点着色器的属性上.我们需要告诉OpenGL怎么做
首先我们需要知道OpenGl对顶点数据的解释标准:
然后我们需要告诉OpenGL如何解析顶点数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); |
这里的参数非常多,具体含义如下:
- GLuint index:指定要配置的顶点属性的索引.回顾上文的VertexShader中,使用了layout (location = 0)定义了position顶点属性.因为我们希望将position数据传递给上文的顶点着色器,所以这里我们指定0.
- GLint size:指定顶点属性大小.由于顶点属性是一个vec3,所以这里我们指定3.
- GLenum type:指定顶点属性的数据类型.由于我们的数据是float,所以这里我们指定GL_FLOAT.
- GLboolean normalized:指定顶点属性是否需要被映射到0-1之间.
- GLsizei stride(步长):指定连续两个顶点属性之间的字节数.由于我们的数据是float,所以这里我们指定3*sizeof(float),即3个float的大小.
- const GLvoid* pointer:指定顶点数据在缓冲中的起始位置.由于我们的数据是从0开始的,所以这里我们指定(void*)0,即从缓冲的开头开始.
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。由于在调用glVertexAttribPointer之前绑定的是先前定义的VBO对象,顶点属性0现在会链接到它的顶点数据。
VAO
当顶点和物体数量变得很多时,绑定正确的缓冲对象,为每个物体配置所有顶点属性就变成一件麻烦事,所以
VAO(Vertex Array Object)顶点数组对象应运而生.
VAO是一个存储了 $顶点属性配置和应该使用的VBO$ 的对象
一个VAO会存储以下数据:
- glEnableVertexAttribArray 和 glDisableVertexAttribArray: 启用和禁用顶点属性的调用
即控制顶点属性是否被使用以及顶点属性位置 - 通过glVertexAttribPointer设置的顶点属性指针
- 通过glVertexAttribPointer调用与顶点属性关联的VBO
使用以下代码创建VAO
unsigned int VAO; |
EBO
在模型中,常常会有一个顶点被多个三角形共享,如果我们单独存储每个三角形的顶点,就会造成大量重复元素造成浪费,所以我们使用EBO来存储每个三角形的顶点索引.
使用以下代码创建EBO
unsigned int EBO; |
在绘制时不再使用glDrawArrays,而是使用glDrawElements
VBO & VAO & EBO
- VBO: 存储大量顶点,因而可以利用VBO一次性发送大量数据到显卡
- VAO: 配置并告诉了OpenGL如何使用VBO,以及使用哪个VBO
- EBO: 用于指定三角形顶点的连接方式
源码
/* |