先推荐几篇好的博文:
https://niyaoyao.github.io/2018/05/23/learning_opengl(es)_opengl_model_pipeline_and_practices/
https://blog.csdn.net/lance710825/article/details/78137950/
https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/04%20Hello%20Triangle/
这里不详细描述渲染管线的流程,大致写一下: 一堆顶点数据(OpenGL的坐标系),经过系列的变换,最终被GPU输出到FrameBuffer额过程.
一些基础的术语名词理解(个人理解;
这些顶点数据被放在一个数组中; 顶点数据一般是这个顶点的坐标(x,y,z);
顶点属性: 包含了一个顶点数据(顶点坐标)及其他属性, 如: 颜色(RGB),法向量(Normal),纹理坐标(Texture Coordinate)等属性
"顶点属性"意味着GPU中的shader程序每调用一次,顶点缓冲区都会为其提供一个新的顶点数据.
上图是一个包含了颜色的顶点属性
//接下来用顶点属性表示要被处理的数据; 可以认为顶点属性就是顶点数据
顶点缓冲区: 这些顶点属性都是要被复制到GPU中的内存(显存)中.
这个内存就被顶点缓冲对象(Vertex Vuffer Object, VBO)管理,我们可以认为VBO 就是一块内存区域,里面存储了一堆顶点属性;
//GLuint 无符号四字节整型,包含数值从0 到 4,294,967,295
GLuint VBO;
//生成一个VBO对象,
glGenBuffers(1, &VBO);
glGenBuffers()
意思是该函数用来生成缓冲区对象的名称。
函数原型:void glGenBuffers(GLsizei n,GLuint * buffers);
glGenBuffers()函数 仅仅是生成一个缓冲对象的名称,类似c语言中的一个指针变量,仅仅是个缓冲对象,还不是一个顶点数组缓存.
因为这时候根本没有进行内存分配, 可以理解只是声明了一个变量, 我们准备用这个变量来引用准备分配的内存.
GLuint vbo;
glGenBuffers(1,&vbo);
GLuint vbo[3];
glGenBuffers(3,vbo);
个人理解如下,可以声明一个GLuint变量,然后使用glGenBuffers后,它就会把缓冲对象保存在vbo里,当然也可以声明一个数组类型,那么创建的3个缓冲对象的名称会依次保存在数组里。
声明一个变量时,使用&VBO,声明一个数组类型,直接使用VBO; C语言, 数组名=数组首元素指针.
已经声明了缓冲对象, 那这个缓冲对象是什么类型,需要用到glBindBuffer();
函数原型:
void glBindBuffer(GLenum target,GLuint buffer);
第一个: 缓冲对象类型
第二个: 要绑定的缓冲对象的名称, 即我们在上一个函数里生成的名称.
按照我的理解 : 确定了VBO缓冲区类型.
在OpenGL红包书中给出了一个恰当的比喻:绑定对象的过程就像设置铁路的道岔开关,每一个缓冲类型中的各个对象就像不同的轨道一样,我们将开关设置为其中一个状态,那么之后的列车都会驶入这条轨道。
缓冲区的类型:
GL_ARRAY_BUFFER:数组缓冲区存储颜色、位置、纹理坐标等顶点属性,或者其它自定义属性
GL_COPY_READ_BUFFER:缓冲区用作通过glCopyBufferSubData进行复制的数据源
GL_COPY_WRITE_BUFFER:缓冲区用作通过glCopyBufferSubData进行复制的目标
GL_ELEMENT_ARRAY_BUFFER:索引数组缓冲区用于保存glDrawElements、glDrawRangeElements和glDrawElementsInstanced的索引
GL_PIXEL_PACK_BUFFER:glReadPixels之类像素包装操作的目标缓冲区
GL_PIXEL_UNPACK_BUFFER:glTexImageXD、glTexSubImageXD之类的纹理更新函数的源缓冲区
GL_TEXTURE_BUFFER:着色器可以通过纹理单元拾取来访问的缓冲区
GL_TRANSFORM_FEEDBACK_BUFFER:变换反馈顶点着色器写入的缓冲区
GL_UNIFORM_BUFFER:着色器访问的uniform值
绑定完缓冲区之后, 要把数据传递到缓冲区:
函数原型:
glBufferData(GLenum target, GLsizeiptrARB size, const void *data, GLenum usage);
glBindBuffer(GL_ARRAY_BUFFER, VBO); //VBO变成了一个顶点缓冲类型
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
以上 按照我的理解如下,不一定正确,但是适合我理解:
声明了一个变量或者一个数组, 准备用这个变量(数组)去管理一块(多块)内存(缓存)区域,这(多)块内存区域的类型 需要我们设置, 确保这块内存能干什么;
由于顶点属性(数据)有很多, 所以用一块内存来存储,这块内存由VBO管理, 可以看成VBO就是这块内存;
那么VBO也可以是多个,使用VAO(Vertex Array Object)进行管理,VAO存储这attributePointer, 即顶点属性的指针;
好处是: 当配置顶点属性指针时, 只要将那些调用执行一次,之后再绘制物体的时候只需要绑定响应的VAO;
这使得不同的顶点数据和属性配置之间切换变的非常简单,只需要绑定不同的VAO就行了,之前设置的状态都存储在VAO中了;
VAO 实际是存储顶点属性指针的数组;
EBO(Element Buffer Object,也叫IBO: index Buffer Object) 索引缓冲区对象,这个缓冲区主要用来存储顶点的索引信息
画一个四边形时, 可以看成是两个三角形组成. 那么有两个点是重复的. 这是可以省去的..
使用索引, 我们只需要基础的四个点的数据,根据索引从对应的数据中取得点 .
与VBO类似,声明完EBO的变量之后,先生成EBO的缓冲区,然后绑定,最后设置缓冲区数据,缓冲区类型是GL_ELELMENT_ARRAY_BUFFER.
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。当目标是GL_ELEMENT_ARRAY_BUFFER的时候,VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了.
所以 配置EBO时, VAO自动将EBO存储了.在内存中,EBO 位于 VAO 最后的 element buffer object
完整的缓冲绑定代码. 在绑定缓冲区之前, 必须先生成VVAO.之后进行VBO和VAO的生成,绑定和设置. 这是设置顶点属性的指针.
其中glVertexAttribPointer 函数用于向OpenGL 解释如何解析顶点数据.
顶点数据会被解析为下面的样子:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer 函数中:
生成(Generate),绑定顶点数据有关buffer的过程.
必须先生成VAO,glGenVertextArrays
绑定VAO,glBindVertexArray
生成VBO, glGenBuffers
绑定VBO, glBindBuffer
把顶点缓冲对象赋值到缓冲中区供OpenGL使用, glBufferData
生成EBO,glGenBuffers
绑定EBO, glBindBuffer
把元素缓冲对象赋值到缓冲中供OpenGL 使用, 管理glBufferData;
设定顶点属性,glVertexAttributePointer
之后可以进行绘制;
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// 2. Make the context current
//切换上下文
[[self openGLContext] makeCurrentContext];
// 3. Define and compile vertex and fragment shaders
//Shader 程序文件本质也是向应用程序提供常量字符串
GLuint vs;
GLuint fs;
const char *vss = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fss = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
//创建一个顶点着色器
vs = glCreateShader(GL_VERTEX_SHADER);
//把着色器源码附加到着色器对象上
//第一个参数: 要编译的着色器对象
//第二个: 传递的源码字符串数量
//第三个: 顶点着色器真正的源码 &vss
glShaderSource(vs, 1, &vss, NULL);
glCompileShader(vs);
//同上 编译fragment着色器
fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fs, 1, &fss, NULL);
glCompileShader(fs);
printf("vs: %i, fs: %i\n",vs,fs);
int success;
char infoLog[512];
//检查编译是否报错c
glGetShaderiv(vs, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vs, 512, NULL, infoLog);
printf("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n%s\n", infoLog);
}
glGetShaderiv(fs, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fs, 512, NULL, infoLog);
printf("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n%s\n", infoLog);
}
// 4. Attach the shaders
//创建shader Program
shaderProgram = glCreateProgram();
//俯角shader到program
glAttachShader(shaderProgram, vs);
glAttachShader(shaderProgram, fs);
//链接program
glLinkProgram(shaderProgram);
// check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
printf("ERROR::SHADER::PROGRAM::LINKING_FAILED\n%s\n", infoLog);
}
//删除shader,已经链接上了,不需要了
glDeleteShader(vs);
glDeleteShader(fs);
printf("positionUniform: %i, colourAttribute: %i, positionAttribute: %i\n",positionUniform,colourAttribute,positionAttribute);
// There is no space (or other values) between each set of 3 values. The values are tightly packed in the array.
//正方形的四个顶点
float vertices[] = {
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
//三角形的顶点索引
unsigned int indices[] = { // note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
unsigned int VBO, VAO, EBO;
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenBuffers(1, &EBO);
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);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
//解绑当前活动内存设置为空, 因为glVertexAttribPointer 已经将VBO作为顶点属性绑定到缓冲区对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
// remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound.
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
//解绑VAO
glBindVertexArray(0);
// Drawing code here.
//glViewport 函数,对视口在窗体中的 x 坐标、y 坐标、宽度、高度进行设置,最终得到在窗体中的坐标
glViewport(10, 10, 100, 100);
//设置背景颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//清空屏幕
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
// glUseProgram(shaderProgram);
// glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0);
//使用创建的shaderProgram,已经附加了shaderProgram
glUseProgram(shaderProgram);
//绑定VAO
glBindVertexArray(VAO);
//(使用了EBO)绘制图形
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
[[self openGLContext] flushBuffer];
}
代码来源就是上面贴的第一篇博客.
博客写的有点乱,因为这是自己在学OpenGL时候的记录,方便自己回顾.