本系列文章为Learn OpenGL个人学习总结!
OpenGL入门(一)之认识OpenGL和创建Window
OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO
OpenGL入门(三)之着色器Shader
OpenGL入门(四)之纹理Texture
OpenGL入门(五)之Matrix矩阵操作和坐标系统
OpenGL进阶(一)之帧缓冲FrameBuffer
OpenGL进阶(二)之像素缓冲PixelBuffer
在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。
它们是由着色器(Shader)程序控制,且运行在GPU上!
开始绘制图形之前,我们需要先给OpenGL输入一些顶点数据。OpenGL仅当3D坐标在3个轴(x、y和z)上 -1.0到1.0 的范围内时才处理它。所有在这个范围内的坐标叫做标准化设备坐标(Normalized Device Coordinates),此范围内的坐标最终显示在屏幕上(在这个范围以外的坐标则不会显示)。
绘制一个三角形:
float vertices[] = { //三个顶点,z轴都设置为0,表示相同的深度depth
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
OpenGL的坐标系为右手坐标系,即坐标x轴向右为正,y轴向上为正,z轴屏幕朝外为正,原点为为图像中心!
定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。通过 顶点缓冲对象(Vertex Buffer Objects, VBO) 管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批cpu的数据到显卡上,而不是每个顶点发送一次。
unsigned int VBO;
glGenBuffers(1, &VBO); //生成一个vbo对象
//顶点数据的缓存类型是GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定
//将顶点数据复制到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
着色器是由着色器语言GLSL(OpenGL Shading Language)编写的!它非常类似于cpp!
一个最基本的顶点着色器:
#version 330 core //定义版本 并指定core-profile
layout (location = 0) in vec3 aPos; //指定location为顶点的第0个属性
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); //w设置默认为1
}
这里vec4的最后一个参数w,是用于透视除法(Perspective Division)!
为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position
变量,它是vec4类型!
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";
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建着色器
//第二个参数:传递的源码字符串数量,这里只有一个
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //设置源码
glCompileShader(vertexShader); //编译
片段着色器所做的是计算像素最后的颜色输出。
一个最基本的片段着色器:
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); //默认为rgba,取决于帧缓冲
}
同样的可以像顶点着色器那样编译:
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。
unsigned int shaderProgram;
shaderProgram = glCreateProgram(); //创建program
glAttachShader(shaderProgram, vertexShader); //attach
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram); //link
glUseProgram(shaderProgram);//使用
//link完成之后就可以删除了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//绘制
glDrawArrays(GL_TRIANGLES, 0, 3);
现在我们将顶点数据传到GPU中了,但是OpenGL还不知道怎么使用这些数据,我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用
这里配合上面的图,重点关注一下glVertexAttribPointer
函数:
layout (location = 0)
,所以这里写0。OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
顶点数组对象(Vertex Array Object, VAO) 可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
unsigned int VAO;
glGenVertexArrays(1, &VAO); //创建VAO
glBindVertexArray(VAO);//绑定
//绘制代码
这里mac中的OpenGL可能找不到这个函数,所以需要一个三方库来寻找具体的函数指针!
下载GLAD,去官网:https://glad.dav1d.de/
将语言(Language)设置为C/C++,在API选项中,选择3.3以上的OpenGL(gl)版本。之后将模式(Profile)设置为Core,并且保证选中了生成加载器(Generate a loader)选项。现在可以先(暂时)忽略扩展(Extensions)中的内容。都选择完之后,点击生成(Generate)按钮来生成库文件。
将下载好的头文件和glad.c添加到工程中即可!
至此,一个完整的三角形就可以被绘制出来了!
完整的代码如下:
/**********************Shader begin******************/
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 color;\n"
"void main()\n"
"{\n"
" color = vec4(1.0f, 0.5f, 0.2f, 1.0);\n"
"}\0";
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建着色器
//第二个参数:传递的源码字符串数量,这里只有一个
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //设置源码
glCompileShader(vertexShader); //编译
int success;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success){
int length;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &length);
char* infoLog = (char *)malloc(length*sizeof(char *));
glGetShaderInfoLog(vertexShader, sizeof(infoLog), NULL, infoLog);
cout<<"compile vertex failed:"<<infoLog<<endl;
}
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//创建着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
unsigned int shaderProgram;
shaderProgram = glCreateProgram(); //创建program
glAttachShader(shaderProgram, vertexShader); //attach
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram); //link
//link完成之后就可以删除了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
/**********************Shader end******************/
float vertices[] = { //三个顶点,z轴都设置为0,表示相同的深度depth
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
unsigned int VBO;
glGenBuffers(1, &VBO); //生成一个vbo对象
//顶点数据的缓存类型是GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定
//将顶点数据复制到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用
//解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//渲染
while (!glfwWindowShouldClose(window))
{
...
glUseProgram(shaderProgram);//使用
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);//绘制三角形
...
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
...
元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)!
举个例子,如果我们要绘制一个矩形,它由两个三角形组成,按照前边的例子,顶点数据需要6个,而实际矩形只需要4个顶点即可!EBO就可以解决这个问题!
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,2,3)就是顶点数组vertices的下标,
// 这样可以由下标代表顶点组合成矩形
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
unsigned int EBO;
glGenBuffers(1, &EBO);//创建EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//绑定
//将索引数据复制到缓冲内存中
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
最终绘制的时候,需要做一些修改:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
https://www.cnblogs.com/xiangqi/p/14608073.html