本篇文章不按照代码的顺序编写,而按照数据的走向流程编辑。
1 顶点输入
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
使用GLfloat数组记录顶点数据(数据在-1~1之间)
2 将数据发送到GPU内存
(1)需要顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,顶点数组对象(Vertex Array Object, VAO)储存顶点属性调用
一个顶点数组对象(VAO)会储存以下这些内容:
glEnableVertexAttribArray和glDisableVertexAttribArray的调用。(是否启用顶点调用)
通过glVertexAttribPointer设置的顶点属性配置。
通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
初始化顶点数组对象VAO, 顶点缓冲对象VBO。
GLuint VBO, VAO;
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
使用glBindVertexArray绑定VAO,从绑定之后绑定和配置对应的VBO和属性指针,解绑VAO。这样便可再以后之后使用VAO来配置相同的顶点数据和属性配置。
glBindVertexArray(VAO);//绑定VAO
/*************************/
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);//解绑VBO
/**************************/
glBindVertexArray(0);//解绑VAO
在绑定和解绑VAO之间的数据将被储存在VAO里
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。
glVertexAttribPointer(0,~)以及glEnableVertexAttribArray(0)中的0都是顶点属性索引(由location = 指定),这将在顶点着色器中看到。
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
上方代码为循环中绘制图元的代码
上方函数定义
函数定义:
void glGenBuffers(GLsizei n,GLuint * buffers);
n:指定要生成的缓冲区对象名称的数量。
buffers:指定存储生成的缓冲区对象名称的数组。
函数定义:
void glBindBuffer(GLenum target,GLuint buffer);
target:指定缓冲区对象绑定的目标。
符号常量必须为 GL_ARRAY_BUFFER ????
或 GL_ELEMENT_ARRAY_BUFFER ????
buffer:指定缓冲区对象的名称(ID)。
函数定义:**
void glBufferData(GLenum target,GLsizeiptr size,const GLvoid * data,GLenum usage);
参数: 1指定目标缓冲区对象。
2指定缓冲区对象的新数据存储的大小(以字节为单位)。
3指定将复制到数据存储区以进行初始化的数据的指针,如果不复制数据,则指定NULL。
4指定数据存储的预期使用模式。 符号常量必须为
GL_STATIC_DRAW :数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变。
函数定义:**
void glVertexAttribPointer(GLuint index, 指定要修改的通用顶点属性的索引。
GLint size, 指定每个通用顶点属性的组件数。 必须为1,2,3或4.初始值为4。
GLenum type, 指定数组中每个组件的数据类型。
接受符号常量GL_BYTE,GL_UNSIGNED_BYTE,GL_SHORT,GL_UNSIGNED_SHORT,GL_FIXED或GL_FLOAT。
初始值为GL_FLOAT。
GLboolean normalized, 指定在访问定点数据值时是应将其标准化(GL_TRUE)还是直接转换为定点值(GL_FALSE)。
GLsizei stride, 指定连续通用顶点属性之间的字节偏移量。
如果stride为0,则通用顶点属性被理解为紧密打包在数组中的。
初始值为0。
const GLvoid * pointer); 指定指向数组中第一个通用顶点属性的第一个组件的指针。
初始值为0。
顶点数组vertices通过glBufferData将数据传入GPU内存,这块内存由VBO管理,由VAO管理VBO及相关的顶点属性配置(连接的管线)
(2)需要顶点缓冲对象(VBO)管理点集,索引缓冲对象(管理索引),顶点数组对象(VAO)储存所有属性。
/*使用索引数据存储点的顺序,用IBO索引缓冲对象管理*/
GLfloat vertices1[] = {
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 indices1[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
GLuint VBO1,EBO1,VAO1;
/*生成三类对象,不分先后顺序*/
glGenVertexArrays(1, &VAO1);
glGenBuffers(1, &VBO1);
glGenBuffers(1, &EBO1);
/*绑定三类对象*/
glBindVertexArray(VAO1);//VAO必须为第一
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);
/*绑定缓冲数据*/
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1), vertices1, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices1), indices1, GL_STATIC_DRAW);
/*设置数据传入管线的形式*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//管线位置为0,每次传入三个float数据,是否标准化,连续的顶点属性组之间的间隔,偏移量
/*启动顶点属性*/
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//不可解绑EBO
glBindVertexArray(0);
while循环里
glBindVertexArray(VAO1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
补充(2021.8.11):VAO存储的所有顶点属性需在一次全部录入,如若需要改变VAO中顶点属性,则需对全部顶点属性重新全部录入,不可只录入改变的顶点属性。VAO的录入不是补充修改,而是对之前录入内容的覆盖。
3 数据通过图形渲染管线
第一个处理阶段::顶点着色器
#version 330 core //设置版本,与OpenGL版本一致
layout (location = 0) in vec3 position; //输入变量为三维向量,命名为position
//layout (location = 0) ?????????
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);
//main函数的最后,gl_Position设置的值会成为该顶点着色器的输出.
}
在c++、python编程中需要将GLSL语言以字符串的方式输入,例如:
const GLchar* vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\0";
编译着色器
GLuint vertexShader;//创建对象
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//指定要创建的着色器的类型只能是GL_VERTEX_SHADER顶点着色器
// 或GL_FRAGMENT_SHADER片段着色器。
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//替换着色器对象中的源代码
参数: 1.要被替换源代码的着色器对象的句柄(ID)。
2.指定字符串和长度数组中的元素数。
3.指定指向包含要加载到着色器的源代码的字符串的指针数组。
4.指定字符串长度的数组。
glCompileShader(vertexShader);//编译
检验是否编译成功
引自 LearnOpenGL-CN
.
你可能会希望检测在调用glCompileShader后编译是否成功了,如果没成功的话,你还会希望知道错误是什么,这样你才能修复它们。检测编译时错误可以通过以下代码来实现:GLint success; GLchar infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
首先我们定义一个整型变量来表示是否成功编译,还定义了一个储存错误消息(如果有的话)的容器。然后我们用glGetShaderiv检查是否编译成功。如果编译失败,我们会用glGetShaderInfoLog获取错误消息,然后打印它。
if(!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << >infoLog << std::endl; }
第二个处理阶段::形状装配(不可编程)
第三个处理阶段::几何着色器(可选)
第四个处理阶段::光栅化(不可编程)
第五个处理阶段::片段着色器
同顶点着色器
const GLchar* fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n" //输出一个四分量向量,表示最终颜色。
"void main(){\n"
"FragColor = vec4(1.0f,0.5f,0.2f,1.0f);\n"
"}\n";
FragColor为片段着色器输出的最终颜色。
GLuint 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);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
第六个处理阶段::测试与混合(不可编程)
最终阶段::链接为着色器程序对象,即合成管线。
GLuint shaderProgram;
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::PROGRAM::SHADER::LINK_FAILED\n" << infoLog << std::endl;
}
//把着色器对象链接到程序对象以后,我们就不再需要它们了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
最终代码
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
//glad需要在glfw之前,因为GLAD的头文件包含了正确的OpenGL头文件,glfw依赖于OpenGL
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
/*着色器定义*/
const GLchar* vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 position;\n"//这儿有分号
"void main()\n"
"{\n"
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\0";
const GLchar* fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n" //输出一个四分量向量,表示最终颜色。
"void main(){\n"
"FragColor = vec4(1.0f,0.5f,0.2f,1.0f);\n"
"}\n";
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
//通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。
glfwSetKeyCallback(window, key_callback);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//通过GLFW注册我们的函数至合适的回调
//glad初始化(GLAD是用来管理OpenGL的函数指针的)
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
int width, height;
glfwGetFramebufferSize(window, &width, &height);
//获取帧缓冲大小,即设置窗口的大小(这里为800*600)。
glViewport(0, 0, width, height);
//设置视口(viewport)
/*使用顶点着色器*/
GLuint vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//判断编译是否成功
GLint success;
GLchar 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;
}
/*使用片段着色器*/
GLuint 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);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
/*链接为着色器程序对象*/
GLuint shaderProgram;
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::PROGRAM::SHADER::LINK_FAILED\n" << infoLog << std::endl;
}
//把着色器对象链接到程序对象以后,我们就不再需要它们了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
/*使用顶点缓冲将顶点储存在显卡的内存中,用VBO顶点缓冲对象管理*/
GLfloat vertices[] = {
-1.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f
};
GLuint VBO, VAO;
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
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);//启用顶点属性
/*使用索引数据存储点的顺序,用IBO索引缓冲对象管理*/
GLfloat vertices1[] = {
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 indices1[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
GLuint VBO1,EBO1,VAO1;
//生成三类对象,不分先后顺序
glGenBuffers(1, &EBO1);
glGenBuffers(1, &VBO1);
glGenVertexArrays(1, &VAO1);
//绑定三类对象
glBindVertexArray(VAO1);//VAO必须为第一
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
//绑定缓冲数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1), vertices1, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices1), indices1, GL_STATIC_DRAW);
//设置数据传入管线的形式
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//设置为线框模式
//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//设置为填充模式(默认)
//设置为一类,在后续将一直为该类,直到再次修改
while (!glfwWindowShouldClose(window))//检查glfw是否被要求退出
{
glfwPollEvents();
//检查有没有触发什么事件,然后调用对应的回调函数.
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);//要用在循环里
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//设置为填充模式(默认)
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//设置为线框模式
glBindVertexArray(VAO1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window);
//交换颜色缓冲
}
//Game Loop
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteVertexArrays(1, &VAO1);
glDeleteBuffers(1, &VBO1);
glDeleteBuffers(1, &EBO1);
glDeleteProgram(shaderProgram);
glfwTerminate();
//释放GLFW分配的内存。
return 0;
}
//函数实现
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
// 当用户按下ESC键,我们设置window窗口的WindowShouldClose属性为true
// 关闭应用程序
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}