在了解了opengl渲染流水线并且配置好环境以后就可以绘制第一个三角形了。
一、基本概念
1.顶点数组对象(Vertex Array Object,VAO)
VAO记录数据的存储和如何使用的细节信息。使用VAO的优势是,如果有多个物体需要绘制,那么我们设置一次绘制物体需要的顶点数据、数据解析方式等信息,然后通过VAO保存起来后,后续的绘制操作不再需要重复这一过程,只需要将VAO设定为当前VAO,那么opengl则会使用这些状态信息。
2.顶点缓冲对象(Vertex Buffer Object,VBO)
顶点缓冲对象VBO是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据,由于VBO在显存而不是在内存中,不需要从CPU传输数据,处理效率更高。
所以可以理解为VBO就是显存中的一个存储区域,可以保持大量的顶点属性信息。并且可以开辟很多个VBO,每个VBO在OpenGL中有它的唯一标识ID,这个ID对应着具体的VBO的显存地址,通过这个ID可以对特定的VBO内的数据进行存取操作。
3.顶点索引对象(Element Buffer Object,EBO或者Index Buffer Object,IBO)
索引缓冲对象EBO相当于OpenGL中的顶点数组的概念,是为了解决同一个顶点多次重复调用的问题,可以减少内存空间浪费,提高执行效率。当需要使用重复的顶点时,通过顶点的位置索引来调用顶点,而不是对重复的顶点信息重复记录,重复调用。
EBO中存储的内容就是顶点位置的索引indices,EBO跟VBO类似,也是在显存中的一块内存缓冲器,只不过EBO保存的是顶点的索引。
3.渲染流水线
这一部分解释可以参考opengl渲染管线
二、三角形绘制
此部分参考 https://blog.csdn.net/fatfish_/article/details/82453765
1、顶点输入
OpenGL处理的是-1.0到1.0范围之间的3D坐标,并且绘制图形的顺序是逆时针方向,我们定义一个float类型的三角形顶点数组,其中Z坐标为0,使得最终的三角形是2D的。
float vertice[3]={
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
2.VAO与VBO
(1)产生并绑定VAO
产生VAO
void glGenVertexArrays(GLsizei n,GLuint *array);
其中 n:VAO对象数量,arrays:VAO对象指针
绑定VAO
void glBindVertexArray(GLuint array);
array:要绑定数组的名称
(2)产生VBOs并绑定VBO
void glGenBuffers(GLsizei n,GLuint *buffers);
n:VBO对象数量,buffers:VBO对象指针
void glBindBuffer(GLenum target,GLuint buffer);
target:GL_ARRAY_BUFFER,GL_ELEMENT_ARRAY_BUFFER,GL_PIXEL_PACK_BUFFER,or GL_PIXEL_UNPACK_BUFFER
(3)给VBO分配数据
void glBufferData(GLenum target,GLsizeiptr size,const GLvoid *data,GLenum usage);
target:GL_ARRAY_BUFFER(表示顶点数据),GL_ELEMENT_ARRAY_BUFFER(表示索引数据),GL_PIXEL_PACK_BUFFER(表示从opengl获取的像素数据),or GL_PIXEL_UNPACK_BUFFER(表示传递给OpenGL的像素数据。
size:缓存对象字节数
data:指向数据对象的指针,或者是NULL,表示暂时不分配数据。
(4)指定需要启用的顶点属性数组的索引并给对应的VAO指定数据
void glEnableVertexAttaibArray(GLuint index);
void glVertexAttribPointer(Gluint index,GLint size,GLenum type,GLboolean normalized,const GLvoid *pointer);
index: 数据存储的VBO下标
size: 数据量
type: 数据类型
normalized: 是否需要归一化
pointer: 指向数组中的第一个顶点属性的第一个数据
(5)渲染时绑定对应的VAO
glBindVertexArray(vAOHandle);
(6)使用完毕清除绑定
glBindVertexArray(0);
3.顶点着色器
(1)创建顶点着色器
顶点着色器(Vertex Shader)是可编程着色器之一,它是使用GLSL(OpenGL Shading Language)编写的。
#version 330 core
layout(location=0) in vec3 aPos;
void main()
{
gl_Position=vec(aPos.x,aPos.y,aPos.z,1.0);
}
#version 330 core:GLSL版本号和OpenGL的版本号是匹配的,GLSL330版本对应于OpenGL 3.3,并且同样使用核心模式。
layout(location=0) in vec3 aPos; 这一句使用in关键字,在顶点着色器中声明所有的输入顶点属性(此处为3D位置属性),创建一个vec3输入变量aPos,layout(location=0)设定输入变量的位置值。
gl_Position=vec(aPos.x,aPos.y,aPos.z,1.0); 为了设置顶点着色器的输出,把位置数据赋给预定义的vec4变量gl_Position,所以把vec3类型的aPosition数据作为vec4构造器的参数,并把w分量设置为1.0f.
(2)编译着色器
a.创建着色器对象
把需要创建的着色器类型以参数形式提供给glCreateShader,则将顶点着色器存储为unsigned int类型
unsigned int vertexShader;
vertexShader=glCreateShader(GL_VERTEX_SHADER);
b.编译着色器对象
glShaderSource(glShaderSource(GLuint shader,GLsizei count,const GLchar * const *string,const GLint *length););
shader:要被替换源代码的着色器对象的句柄(ID)
count:指定字符串和长度数组中的元素数
string:指定指向包含要加载到着色器的源代码的字符串的指针数组
length:指定字符串长度
glCompileShader(vertexShader);
检测在调用glCompileShader后编译是否成功
4.片段着色器
a.创建片段着色器对象
片段着色器的功能是计算像素的最后的颜色输出。
#version 330 core
out vec FragColor;
void main()
{
FragColor=vec4(1.0f,0.5f,0.2f,1.0f);
}
out vec FragColor;使用out关键字声明命名为FragColor的输出变量。并将一个alpha值为1.0(1.0代表完全不透明)的橘黄色的vec4赋值给颜色输出。
b.编译着色器对象
编译片段着色器的过程与顶点着色器类似,区别是使用GL_FRAGMENT_SHADER常来那个作为着色器类型。
unsigned int fragmentShader;
fragmentShader=glCreatShader(GL_fragment_shader);
glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
glCompileShader(fragmentShader);
5.着色器程序
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本,如果要使用前面编译的着色器,必须把它们链接作为一个着色器对象,然后再渲染对象的时候激活这个着色器程序。已激活的着色器程序的着色器在我们发送渲染调用时被使用。
当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下一个着色器的输入。当输出和输入不匹配是,发生连接错误。
a.创建程序对象
unsigned int shaderProgram;
shaderProgram=glCreateProgram();
glCreateProgram():创建一个程序,并返回新创建程序对象的ID引用。
b.链接
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(shaderProgram);
c.激活程序对象
glUseProgram(shaderProgram);
d.删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
6.三角形绘制
#include <glad/glad.h>
#include <GLFW/glfw3.h>//包含头文件时要确保GLAD头文件是在GLFW的头文件之前。
#include<iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
//顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
//顶点着色器
const GLchar* 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 GLchar* 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()
{
glfwInit(); //初始化GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号为3
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); //次版本号为3
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //使用核心模式
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //Mac OS X系统
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
unsigned int VAO, VBO;
//创建并绑定VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//创建并绑定VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//给VBO分配数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//指定顶点属性数组索引
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//创建着色器
unsigned int vertexShader;
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;
}
//编译片段着色器
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;
}
//链接着色器
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;
}
//删除着色器
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//渲染循环
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//画三角形
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
7.EBO(索引缓冲对象)
当绘制一个矩形时时,需要两个三角形(六组顶点)来组成一个矩形,热一个矩形只需要四个顶点就可以了,这个时候会有浪费。使用索引缓冲可以解决这一问题。
(1)输入数据
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 // 第二个三角形
};
(2)创建索引缓冲对象
unsigned int EBO;
glGenBuffer(1,&EBO);
(3)绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
(4)使用当前索引绘制
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
#include <glad/glad.h>
#include <GLFW/glfw3.h>//包含头文件时要确保GLAD头文件是在GLFW的头文件之前。
#include<iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
//顶点数据
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 // 左上角
};
//顶点着色器
const GLchar* 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 GLchar* 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";
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
int main()
{
glfwInit(); //初始化GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号为3
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); //次版本号为3
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //使用核心模式
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //Mac OS X系统
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
unsigned int VAO, VBO,EBO;
//创建并绑定VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//创建并绑定VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//EBO
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//给VBO分配数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//指定顶点属性数组索引
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//创建着色器
unsigned int vertexShader;
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;
}
//编译片段着色器
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;
}
//链接着色器
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;
}
//删除着色器
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//渲染循环
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//画三角形
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
结果如下: