Hello,Triangle!
欢迎观看我的OpenGL学习笔记
这是我第一次写博客。作为一个小白在这里记录我的学习过程并分享一些我的学习心得。
本文不会有太多理论性的东西(理论太多对小白不是很友好QAQ)
简介
参考文档https://learnopengl-cn.github.io
文档中使用的是cmake虽然他很强大但是有一些复杂,所以我不会使用。哦,对了我用的是glew不是glad
本节所需要用到的东西
顶点数组对象:Vertex Array Object,VAO
顶点缓冲对象:Vertex Buffer Object,VBO
索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
标准化坐标
原文"Once your vertex coordinates have been processed in the vertex shader, they should be in normalized device coordinates which is a small space where the x, y and z values vary from -1.0 to 1.0."
简单说就是标准化的X,Y,Z三个轴的坐标为-1.0到1.0之间的值。而且任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。
顶点数组对象(VAO)与顶点缓冲对象(VBO)
(这是文档中的图片)
(简单的解释一下)
顶点缓冲对象(Vertex Buffer Objects, VBO): 它会在GPU内存(通常被称为显存)中储存大量顶点。好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
顶点数组对象(Vertex Array Objects,VAO): VBO保存了一个模型的顶点属性信息,每次绘制模型之前需要绑定顶点的所有信息,当数据量很大时,重复这样的动作变得非常麻烦。VAO可以把这些所有的配置都存储在一个对象中,每次绘制模型时,只需要绑定这个VAO对象就可以了。
可以说VBO是对顶点属性信息的绑定,VAO是对很多个VBO的绑定。
顶点数组对象(VAO)与顶点缓冲对象(VBO)的生成
//VBO的生成
unsigned int VBO;
glGenBuffers(1, &VBO);
//VAO的生成
unsigned int VAO;
glGenVertexArrays(1, &VAO);
这只是生成一个,我们也可以生成多个VAO与VBO
unsigned int VBO[[2]];
glGenBuffers(2, VBO);
unsigned int VAO[[2]];
glGenVertexArrays(2, VAO);
顶点缓存对象(VBO)的绑定
OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲。对于不同类型的缓冲对象,我们可以使用glBindBuffer函数把新创建的缓冲绑定到目标上:
glBindBuffer(GL_ARRAY_BUFFER, VBO);
第一个参数是缓冲对象的类型,第二个参数是VBO生成的时候所定义的变量的变量名。
数据的绑定
现在我们就可以把顶点的数据绑定到VBO上面了
先定义三个顶点(三角形的三个点的坐标)
float triangle[] = {
-0.5f, -0.5f, 0.0f, //第一个点
0.5f, -0.5f, 0.0f, //第二个点
0.0f, 0.5f, 0.0f //第三个点
};
我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle), triangle, GL_STATIC_DRAW);
- 第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。
- 第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
- 第三个参数是我们希望发送的实际数据。
-
- GL_STATIC_DRAW :数据不会或几乎不会改变。 - GL_DYNAMIC_DRAW:数据会被改变很多。 - GL_STREAM_DRAW :数据每次绘制时都会改变。
顶点数组对象(VAO)的绑定
glBindVertexArray(VAO);
基本跟VBO的绑定是一样的,只不过把glBindBuffer中的Buffer(缓冲)换成了VertexArray(顶点数组),且没有第一个参数。
着色器(Shader)
做渲染的话至少需要一个顶点着色器(Vertex Shader)和一个片段着色器(Fragment Shader)这里先简但说一下用来画三角形
//顶点着色器
const char* vertexShaderSource =
“#version 330 core\n”
“layout(location = 0) in vec3 aPos;\n”
“void main(){\n”
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);}\n";
//片段着色器
const char* fragmentShaderSource =
“#version 330 core\n”
“out vec4 FragColor;\n”
“void main(){\n”
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}\n";
先不用管这些是什么,就把文档里面的cv一下就行,等正式学shader的时候再细学(一次学太多脑子容易糊awa)
编译着色器
先创建
//顶点着色器的创建
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//片段着色器的创建
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
再把着色器源码(就是上面说的我直接cv的东西)附加到着色器对象上,然后编译它:
//顶点着色器
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//片段着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
着色器程序
此时我们的着色器还不能用,我们需要着色器程序对象(Shader Program Object)把我们刚才编译的顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
创建一个着色器程序对象(Shader Program Object)
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
再把他们链接(Link)在一起
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
链接顶点属性
我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。所以,我们必须在渲染前指定OpenGL该如何解释顶点数据。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer就是用来解释顶点数据的
(参数的含义)
- 第一个参数指定我们要配置的顶点属性。(还是我上面说的直接从文档里面cv的部分)layout(location = 0)定义了position顶点属性的位置值(Location)它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
- 第二个参数指定顶点属性的大小。顶点属性是一个三维向量(即x,y,z),它由3个值组成,所以大小是3。
- 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中向量都是由浮点数值组成的)。
- 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
- 最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。
glEnableVertexAttribArray中的参数与glVertexAttribPointer的第一个参数是一致的
终于可以画三角形了
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
调用glUseProgram函数,用创建的程序对象作为它的参数,以激活这个程序对象
在glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用这个程序对象(也就是之前写的着色器)了。
在把着色器对象链接到程序对象以后,记得删除着色器对象,我们不再需要它们了:
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
最后的效果