一、重要概念
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data);顶点数据是一系列顶点的集合。
一个顶点(Vertex)是一个3D坐标的数据的集合。而顶点数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据。
顶点着色器是图形渲染管线中的一个阶段,它负责处理输入顶点数据并将其转换为裁剪空间(Clip Space)或者屏幕空间(Screen Space)的坐标。
顶点着色器通常是图形渲染中的第一个阶段,在渲染过程中,每个顶点都会经过顶点着色器的处理。
片段着色器的主要目的是计算一个像素的最终颜色,这也是所有
OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的
数据(比如光照、阴影、光的颜色等等),这些数据可以被用来
计算最终像素的颜色。
在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶
段,我们叫做Alpha测试和混合(Blending)阶段。
这个阶段检测片段的对应的深度(和模板(Stencil))值,用它们来
判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。
这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)
并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来
了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜
色也可能完全不同。
二、顶点输入
OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;
OpenGL仅当3D坐标在3个轴(x、y和z)上-1.0到1.0的范围内时
才处理它。所有在这个范围内的坐标叫做标准化设备坐标
(Normalized Device Coordinates),此范围内的坐标最终显示在屏
幕上(在这个范围以外的坐标则不会显示)。
通过使用由glViewport函数提供的数据,进行视口变换(Viewport
Transform),标准化设备坐标(Normalized Device Coordinates)会
变换为屏幕空间坐标(Screen-space Coordinates)。所得的屏幕空
间坐标又会被变换为片段输入到片段着色器中。 定义这样的顶点
数据以后,我们会把它作为输入发送给图形渲染管线的第一个处
理阶段:顶点着色器。它会在GPU上创建内存用于储存我们的顶
点数据,还要配置OpenGL如何解释这些内存,并且指定其如何
发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶
点。
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内
存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用
这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡
上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较
慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数
据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问
顶点,这是个非常快的过程。
三、链接顶点属性
//顶点数据:
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
顶点数据会被解析为下面这样子:
- 位置数据被储存为32位(4字节)浮点值。
- 每个位置包含3个这样的值。
- 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
- 数据中第一个值在缓冲开始的位置。
使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上):
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
- 第一个参数指定我们要配置的顶点属性。在顶点着色器中使用
layout(location = 0)
定义了position顶点属性的位置值,它可以把顶点属性的位置值设置为0
。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
。 - 第二个参数指定顶点属性的大小。顶点属性是一个
vec3
,它由3个值组成,所以大小是3。 - 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中
vec*
都是由浮点数值组成的)。 - 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
- 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个
float
之后,我们把步长设置为3 * sizeof(float)
。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔(这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。 - 最后一个参数的类型是
void*
,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。
//关键代码如下
#include <iostream>
#include <QDebug>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
int main(int argc, char *argv[])
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //mac os
#endif
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
qDebug() << "Failed to create GLFW window";
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
qDebug() << "Failed to initialize GLAD";
return -1;
}
//glViewport(0, 0, 800, 600);
// 0. 当我们渲染一个物体时要使用着色器程序
//顶点着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
qDebug() << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog;
}
//片段着色器
unsigned int fragmentShader;
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);
qDebug() << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog;
}
//把顶点和片段着色器附加到程序对象上
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success)
{
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
qDebug() << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//顶点数据:
float vertices[] = {
-0.5f, -0.5f, 0.0f,// left
0.5f, -0.5f, 0.0f,// right
0.0f, 0.5f, 0.0f,// top
};
// 1. 复制顶点数组到缓冲中供OpenGL使用
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 2. 设置顶点属性指针
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
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);
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
/* Render here */
glClear(GL_COLOR_BUFFER_BIT);
// 3. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
// optional: de-allocate all resources once they've outlived their purpose:
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
glfwTerminate();
return 0;
}
代码下载:点击跳转
觉得有帮助的话,打赏一下呗。。