关键词介绍:
定义:实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。
在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。
图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
下面是图形渲染管线的每个阶段的抽象展示。官方链接
要注意蓝色部分代表的是我们可以注入自定义的着色器
的部分。
这里主要涉及渲染流水线问题,就不做过多描述,因为涉及到知识点,后面会单独总结一篇。
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
顶点缓冲对象是我们在OpenGL教程中第一个出现的OpenGL对象。就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:
unsigned int VBO;
glGenBuffers(1, &VBO);
顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。
这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
一个顶点数组对象会储存以下这些内容:
创建一个VAO和创建一个VBO很类似:
unsigned int VAO;
glGenVertexArrays(1, &VAO);
顶点着色器:它属于GPU流水线中的几何阶段,是完全可编程的,它通常用于实现顶点的空间变换,顶点着色等功能。
我们需要做的第一件事是用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器,这样我们就可以在程序中使用它了。一个基础的GLSL顶点着色器的源代码:
#version 330 core //版本
layout (location = 0) in vec3 aPos; //in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);//gl_Position设置的值会成为该顶点着色器的输出
}
为了能够让OpenGL使用它,我们必须在运行时动态编译它的源代码。
...
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";
...
int main()
{
...
// 1.顶点着色器(vertex shader) 用于处理顶点
unsigned int vertexShader=glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
glCompileShader(vertexShader);
//检查着色器编译错误
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;
}
...
}
片段着色器:也叫片元着色器,它属于GPU流水线中的光栅化阶段,是完全可编程的,片段着色器所做的是计算像素最后的颜色输出。
一个基础的GLSL片元着色器的源代码:
...
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";
...
int main()
{
...
// 2.片段着色器(fragment shader) 用于处理片段/片元(一个片段是OpenGL渲染一个像素所需的所有数据)
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); // 创建片段着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); // 将着色器源码附加到着色器对象上
glCompileShader(fragmentShader); // 编译着色器
// 检查着色器编译错误
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); // 获取编译状态
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); // 获取错误信息
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
<< infoLog << std::endl;
}
...
return 0;
}
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
创建一个着色器程序,用来管理着色器:
...
// 3.着色器程序(shader program) 用来管理着色器
unsigned int shaderProgram = glCreateProgram(); // 创建着色器程序
glAttachShader(shaderProgram, vertexShader); // 将之前编译的着色器附加到程序对象上
glAttachShader(shaderProgram, fragmentShader); // 将之前编译的着色器附加到程序对象上
glLinkProgram(shaderProgram); // 链接着色器
// 检查着色器程序链接错误
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); // 获取链接状态
if (!success)
{
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); // 获取错误信息
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
<< infoLog << std::endl;
}
glDeleteShader(vertexShader); // 删除着色器
glDeleteShader(fragmentShader); // 删除着色器
...
元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)。
有了上面的知识点介绍,我们可以创建一个三角形。
创建之前,我们先整理一下OpenGL模板脚本:
#include
#include
#include
void InitGLFW();
bool CreateWindow();
bool InitGLAD();
// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);
// settings 窗口宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
GLFWwindow *window;
int main()
{
InitGLFW(); // 初始化GLFW
bool isCreated = CreateWindow(); // 创建一个窗口对象
if (!isCreated)
return -1;
bool isGLAD = InitGLAD(); // 初始化GLAD,传入加载系统相关opengl函数指针的函数
if (!isGLAD)
return -1;
//TODO:创建一个三角形,使用着色器语言GLSL编写顶点着色器和片段着色器,将它们编译链接到一个着色器程序中,并使用这个着色器程序绘制三角形。
// 循环渲染
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 检查并调用事件,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
// 释放/删除之前的分配的所有资源
glfwTerminate();
std::cout << "Hello, World!" << std::endl;
return 0;
}
void InitGLFW()
{
// 初始化GLFW
glfwInit();
// 配置GLFW 第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;
// 第二个参数接受一个整型,用来设置这个选项的值。
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
}
bool CreateWindow()
{
// 创建一个窗口对象
window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
// 创建失败,终止程序
glfwTerminate();
return false;
}
// 将我们窗口的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
// 设置窗口大小改变时的回调函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
return true;
}
bool InitGLAD()
{
// 初始化GLAD,传入加载系统相关opengl函数指针的函数
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
// 初始化失败,终止程序
return false;
}
return true;
}
// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
// 设置窗口的维度
glViewport(0, 0, width, height);
}
// 输入
void processInput(GLFWwindow *window)
{
// 当用户按下esc键,我们设置window窗口的windowShouldClose属性为true
// 关闭应用程序
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
//设置顶点数据(和缓冲区)并配置顶点属性
//1.顶点输入
float vertices[] = {
-0.5f, -0.5f, 0.0f, // 左
0.5f, -0.5f, 0.0f, // 右
0.0f, 0.5f, 0.0f // 上
};
//2.创建一个顶点缓冲对象(Vertex Buffer Objects, VBO) 顶点数组对象(Vertex Array Object, VAO)
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO); // 生成一个VAO对象
glGenBuffers(1, &VBO); // 生成一个VBO对象
//3.首先绑定顶点数组对象,然后绑定和设置顶点缓冲区,然后配置顶点属性。
glBindVertexArray(VAO); // 绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据复制到缓冲区中
//4.配置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); // 解析顶点数据
glEnableVertexAttribArray(0); // 启用顶点属性
//注意这是允许的,调用glVertexAttribPointer将VBO注册为顶点属性的绑定顶点缓冲对象,因此之后我们可以安全地解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
//你可以在之后解除对VAO的绑定,这样其他VAO调用就不会意外地修改这个VAO,但这种情况很少发生。修改其他
// VAOs无论如何都需要调用glBindVertexArray,所以当不直接需要时,我们通常不会取消绑定VAOs(也不会取消绑定vbo)。
glBindVertexArray(0);
...
// 循环渲染
while (!glfwWindowShouldClose(window))
{
...
// 绘制三角形
glUseProgram(shaderProgram); // 使用着色器程序
glBindVertexArray(VAO); // 绑定VAO
glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形
// glBindVertexArray(0); // 不需要每次都绑定VAO
// 检查并调用事件,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
...
#include
#include
#include
void InitGLFW();
bool CreateWindow();
bool InitGLAD();
// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);
// settings 窗口宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
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";
GLFWwindow *window;
int main()
{
InitGLFW(); // 初始化GLFW
bool isCreated = CreateWindow(); // 创建一个窗口对象
if (!isCreated)
return -1;
bool isGLAD = InitGLAD(); // 初始化GLAD,传入加载系统相关opengl函数指针的函数
if (!isGLAD)
return -1;
// TODO:创建一个三角形,使用着色器语言GLSL编写顶点着色器和片段着色器,将它们编译链接到一个着色器程序中,并使用这个着色器程序绘制三角形。
// 1.顶点着色器(vertex shader) 用于处理顶点
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 检查着色器编译错误
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;
}
// 2.片段着色器(fragment shader) 用于处理片段/片元(一个片段是OpenGL渲染一个像素所需的所有数据)
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); // 创建片段着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); // 将着色器源码附加到着色器对象上
glCompileShader(fragmentShader); // 编译着色器
// 检查着色器编译错误
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); // 获取编译状态
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); // 获取错误信息
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
<< infoLog << std::endl;
}
// 3.着色器程序(shader program) 用来管理着色器
unsigned int shaderProgram = glCreateProgram(); // 创建着色器程序
glAttachShader(shaderProgram, vertexShader); // 将之前编译的着色器附加到程序对象上
glAttachShader(shaderProgram, fragmentShader); // 将之前编译的着色器附加到程序对象上
glLinkProgram(shaderProgram); // 链接着色器
// 检查着色器程序链接错误
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); // 获取链接状态
if (!success)
{
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); // 获取错误信息
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
<< infoLog << std::endl;
}
glDeleteShader(vertexShader); // 删除着色器
glDeleteShader(fragmentShader); // 删除着色器
//设置顶点数据(和缓冲区)并配置顶点属性
//1.顶点输入
float vertices[] = {
-0.5f, -0.5f, 0.0f, // 左
0.5f, -0.5f, 0.0f, // 右
0.0f, 0.5f, 0.0f // 上
};
//2.创建一个顶点缓冲对象(Vertex Buffer Objects, VBO) 顶点数组对象(Vertex Array Object, VAO)
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO); // 生成一个VAO对象
glGenBuffers(1, &VBO); // 生成一个VBO对象
//3.首先绑定顶点数组对象,然后绑定和设置顶点缓冲区,然后配置顶点属性。
glBindVertexArray(VAO); // 绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据复制到缓冲区中
//4.配置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); // 解析顶点数据
glEnableVertexAttribArray(0); // 启用顶点属性
//注意这是允许的,调用glVertexAttribPointer将VBO注册为顶点属性的绑定顶点缓冲对象,因此之后我们可以安全地解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
//你可以在之后解除对VAO的绑定,这样其他VAO调用就不会意外地修改这个VAO,但这种情况很少发生。修改其他
// VAOs无论如何都需要调用glBindVertexArray,所以当不直接需要时,我们通常不会取消绑定VAOs(也不会取消绑定vbo)。
glBindVertexArray(0);
//取消此调用的注释以绘制线框多边形。
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 循环渲染
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 绘制三角形
glUseProgram(shaderProgram); // 使用着色器程序
glBindVertexArray(VAO); // 绑定VAO
glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形
// glBindVertexArray(0); // 不需要每次都绑定VAO
// 检查并调用事件,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
//可选:一旦资源超出其用途,就取消分配所有资源:
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
// 释放/删除之前的分配的所有资源
glfwTerminate();
std::cout << "Hello, World!" << std::endl;
return 0;
}
void InitGLFW()
{
// 初始化GLFW
glfwInit();
// 配置GLFW 第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;
// 第二个参数接受一个整型,用来设置这个选项的值。
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
}
bool CreateWindow()
{
// 创建一个窗口对象
window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
// 创建失败,终止程序
glfwTerminate();
return false;
}
// 将我们窗口的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
// 设置窗口大小改变时的回调函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
return true;
}
bool InitGLAD()
{
// 初始化GLAD,传入加载系统相关opengl函数指针的函数
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
// 初始化失败,终止程序
return false;
}
return true;
}
// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
// 设置窗口的维度
glViewport(0, 0, width, height);
}
// 输入
void processInput(GLFWwindow *window)
{
// 当用户按下esc键,我们设置window窗口的windowShouldClose属性为true
// 关闭应用程序
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
主要介绍比较重要的API。
说明
用于设置窗口的维度。
语法
void glViewport(
GLint x,
GLint y,
GLsizei width,
GLsizei height
);
参数
说明
一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
语法
void glGenBuffers(
GLenum target,
GLsizeiptr size,
const void *data,
GLenum usage
);
参数
说明
一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
语法
void glVertexAttribPointer(
GLuint index, GLint size,
GLenum type,
GLboolean normalized,
GLsizei stride,
const void *pointer
);
参数