下图是 图形渲染管线的每个阶段的抽象展示。
图形渲染管线
其中,蓝色部分代表的是我们可以注入自定义的着色器的部分。
顶点着色器(Vertex Shader),几何着色器(Geometry Shader),片段着色器(Fragment Shader)。
在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器。
顶点着色器(Vertex Shader):处理3维顶点;
片段着色器(Fragment Shader):光栅化后处理每个像素的颜色,很多特效在这里完成。
下面是一个大段从别的文章引用的,我感觉写的很好,如果知道的人完全没有必要看。
但是,我经常会忘记,而且这点写的清晰明了,我希望以后自己看笔记的时候,再看一遍,所以记录下载。
转为文章的链接地址:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
有需要的人可以去查看。真是写的很清晰明确的。我也都是学习后,理解提炼了自己有用的来做的笔记。
详细说明引用原文如下:
我们会概括性地解释一下渲染管线的每个部分,让你对图形渲染管线的工作方式有个大概了解。
首先,我们以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data);顶点数据是一系列顶点的集合。一个顶点(Vertex)是一个3D坐标的数据的集合。而顶点数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据,但是简单起见,我们还是假定每个顶点只由一个3D位置(译注1)和一些颜色值组成的吧。
为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去指定这些数据所表示的渲染类型。我们是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?做出的这些提示叫做图元(Primitive),任何一个绘制指令的调用都将把图元传递给OpenGL。这是其中的几个:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP。
图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。
图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。
几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。
片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
可以看到,图形渲染管线非常复杂,它包含很多可配置的部分。然而,对于大多数场合,我们只需要配置顶点和片段着色器就行了。几何着色器是可选的,通常使用它默认的着色器就行了。
在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)。出于这个原因,刚开始学习现代OpenGL的时候可能会非常困难,因为在你能够渲染自己的第一个三角形之前已经需要了解一大堆知识了。在本节结束你最终渲染出你的三角形的时候,你也会了解到非常多的图形编程知识。
引用完结,引用文章链接
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
我喜欢从上向下说,比较有目的性。
//各种渲染和绘制操作指令
// draw our first triangle
glUseProgram(shaderProgram);//使用着色器程序
// seeing as we only have a single VAO there's no need to bind it every time,
//but we'll do so to keep things a bit more organized
glBindVertexArray(VAO);//绑定VAO
glDrawArrays(GL_TRIANGLES, 0, 3);//绘制顶点三角形
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//绘制索引
其中:
shaderProgram是 着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。
需要编译的 顶点shader和片段shader,然后链接到shader的程序中。
const char *vertexShaderSource = "#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 *fragmentShaderSource = "#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";
// vertex shader 顶点着色器
int vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建顶点shader
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//加载顶点shader
glCompileShader(vertexShader);//编译shader
// check for shader compile errors
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// fragment shader 片段着色器
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//创建
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//加载
glCompileShader(fragmentShader);//编译
// check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// link shaders 链接shader到程序
int shaderProgram = glCreateProgram();//创建程序
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);//链接
// check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
//已经链接的shader就可以删除了。
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
创建顶点数据和解析顶点数据。具体代码如下:
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
// 创建并配置输入的顶点数据
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s),
//and then configure vertex attributes(s).
glBindVertexArray(VAO);//这里绑定后所有的顶点数据和属性就都开始记录到VAO中了。直到解绑或绑定另外一个VAO
unsigned int VBO;
glGenBuffers(1, &VBO);
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(float), (void*)0);//设置顶点解析属性
glEnableVertexAttribArray(0);//以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。
unsigned int EBO;//索引缓冲
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//拷贝数据到当前EBO中
// 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
glBindBuffer(GL_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.
glBindVertexArray(0);//解绑
// uncomment this call to draw in wireframe polygons.线框显示模式设置
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
其中:
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
当指代这三个东西的时候,可能使用的是全称,也可能用的是英文缩写,翻译的时候和原文保持的一致。由于没有英文那样的分词间隔,中文全称的部分可能不太容易注意。但请记住,缩写和中文全称指代的是一个东西。
平:还有一个几何着色器没有添加。
--------------本节完-------------