三角形的绘制在创建窗口的基础上继续。
首先定义一下我们要画的三角形的三个顶点。比较要理解,就是xyz三个坐标。现在我们不需要深度所以z都是0。
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
};
然后我们创建一个VAO和VBO。
// 创建顶点数组对象VAO(Vertex Array Object)
unsigned int VAO;
glGenVertexArrays(1, &VAO); // 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
glBindVertexArray(VAO); // 绑定VAO
// 创建顶点缓冲对象VBO(Vertex Buffer Object)
unsigned int VBO;
glGenBuffers(1, &VBO);// 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
// 将顶点数据绑定到GL_ARRAY_BUFFER上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
其中glGenVertexArrays方法是可以创建多个VAO的,例如:
// 这只是写个例子,待会并不会写到代码里面。
unsigned int VAO[10];
glGenVertexArrays(10, VAO);
所以其实只有一个VAO的话也可以写成:
// 这只是写个例子,待会并不会写到代码里面。
unsigned int VAO[1];
glGenVertexArrays(10, VAO);
但是创建一个只有一个元素的数组显得很鸡肋,所以就像上面一样直接一个VAO,然后传入地址。glGenBuffers同理。
解释一下代码,unsigned int VAO,可以理解为声明了一个整数ID,这个ID就代表着VAO这个东西(不知道为啥不用指针,还不太理解)。另外unsigned int可以用GLuint代替,他俩是一样的。就是一个无符号的整型数据。
然后通过glGenVertexArrays把生成的一个或者多个(这里只有一个)顶点数组对象的地址赋值给VAO这个整型ID。glGenBuffers同理。
glBindVertexArray和glBindBuffer都是用来绑定VAO和VBO的。VBO可以绑定到不同的buffer上,所以这里要指定是绑定到GL_ARRAY_BUFFER上。
glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。 GL_STATIC_DRAW代表顶点不会变动,这个函数还不太理解。
OpenGL不提供顶点着色器和片元着色器,所以我们必须提供这两个着色器。它们是用GLSL语言写的,看起来和C语言很类似。下面是两个很简单着色器源码。
// 顶点着色器 vertexShader
#version 330 core // 标明使用的版本,OpenGL3.3 ,GLSL就要用330
layout (location = 0) in vec3 aPos;
// 上面这句定义从我们的VAO第0个指针位置获取数据,数据作为一个vec3,叫做aPos
// location = 0对应着等会glVertexAttribPointer方法的参数设置
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); // 不做任何处理直接将顶点输出(因为需要4元的向量,所以直接用构造方法添加一个分量上去)
}
// 片元着色器 fragmentShaer
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); // 设置颜色
}
但是GLSL并不能直接在这里编译,所以要写成字符数组,通过glfw的函数进行编译。如下。
// 顶点着色器源码
const char* vertex_shader_source =
"#version 330 core \n"
"layout(location = 0) in vec3 aPos; \n"
"void main(){ \n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"} \n";
//片元(片段)着色器源码
const char* fragment_shader_source =
"#version 330 core \n"
"out vec4 FragColor; \n"
"void main(){ \n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"} \n";
然后像前面创建VAO和VBO一样。
// 创建顶点着色器(shader)
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertex_shader_source, nullptr); // 将shader源代码绑定到shader中
glCompileShader(vertexShader); // 编译shader
// 创建片元着色器(shader)
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragment_shader_source, nullptr); // 将shader源代码绑定到shader中
glCompileShader(fragmentShader); // 编译shader
光是弄好了两个shader还不能丢到GPU里面去运行。需要创建一个着色器程序(shader program),然后把两个shader都附着上去。代码和前面的也很类似,也不用多说了。
// 创建一个着色器程序(shader program)
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader); // 附上vertexShader
glAttachShader(shaderProgram, fragmentShader); // 附上fragmentSahder
glLinkProgram(shaderProgram); // 链接起来
傅老师的视频里面这里讲的比较形象,还画图了。
而且这个0可以是0-15,这里往哪放,顶点着色器里面就从哪里取(loaction = 这里的位置)。
// VAO的设置
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
/*绘制三角形*/
glBindVertexArray(VAO); // 绑定VAO
glUseProgram(shaderProgram); // 使用我们刚才创建的shader program
glDrawArrays(GL_TRIANGLES, 0, 3); //绘制三角形,三个顶点
#include
#include
#include
using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
#pragma region shader源码
// 顶点着色器源码
const char* vertex_shader_source =
"#version 330 core \n"
"layout(location = 0) in vec3 aPos; \n"
"void main(){ \n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"} \n";
//片元(片段)着色器源码
const char* fragment_shader_source =
"#version 330 core \n"
"out vec4 FragColor; \n"
"void main(){ \n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"} \n";
#pragma endregion
int main() {
#pragma region 窗口创建以及初始化
glfwInit();// 初始化GLFW
/*glfwWindowHint用来配置GLFW,第一个参数是选项名,第二个参数是这个选项的值*/
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);// 将主版本号设为3(因为用的OpenGL版本是3.3)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 将此版本号设为3
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 使用核心模式
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);如果是MAC OS X系统需要加上这句
/*创建窗口对象*/
GLFWwindow* window = glfwCreateWindow(800, 600, "OpengGL_Testing", nullptr, nullptr);// 设置窗口大小、名字
if (window == nullptr) {
cout << "创建窗口失败" << endl;
glfwTerminate();// 销毁所有剩余的窗口和游标
return -1;
}
glfwMakeContextCurrent(window);// 设置当前的上下文(OpenGL是一个庞大的状态机,其状态被称为上下文)
/*使用GLAD管理指针*/
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
cout << "初始化GLAD失败" << endl;
return -1;
}
/*设置视口,告诉OpenGL渲染窗口的尺寸大小*/
glViewport(0, 0, 800, 600);// 前两个是窗口左下角的位置,后两个是宽和高
/*告诉GLFW,每次窗口调用的时候使用framebuffer_size_callback函数*/
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
#pragma endregion
#pragma region 绘制三角形相关
// 三角形的顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// 创建顶点数组对象VAO(Vertex Array Object)
unsigned int VAO;
glGenVertexArrays(1, &VAO); // 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
glBindVertexArray(VAO); // 绑定VAO
// 创建顶点缓冲对象VBO(Vertex Buffer Object)
unsigned int VBO;
glGenBuffers(1, &VBO);// 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
// 将顶点数据绑定到GL_ARRAY_BUFFER上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 创建顶点着色器(shader)
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertex_shader_source, nullptr); // 将shader源代码绑定到shader中
glCompileShader(vertexShader); // 编译shader
// 创建片元着色器(shader)
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragment_shader_source, nullptr); // 将shader源代码绑定到shader中
glCompileShader(fragmentShader); // 编译shader
// 创建一个着色器程序(shader program)
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader); // 附上vertexShader
glAttachShader(shaderProgram, fragmentShader); // 附上fragmentSahder
glLinkProgram(shaderProgram); // 链接起来
// VAO的设置
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
#pragma endregion
#pragma region 渲染循环
/*渲染循环*/
// glfwWindowShouldClose用来检查window是否被要求退出
while (!glfwWindowShouldClose(window)) {
processInput(window); // 处理输入
/*渲染操作*/
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// 用颜色清空屏幕
glClear(GL_COLOR_BUFFER_BIT);// 清空一个缓冲位的缓冲(还不太理解)
/*绘制三角形*/
glBindVertexArray(VAO);
glUseProgram(shaderProgram);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window); // 交换颜色缓冲
glfwPollEvents(); // 检查有没有触发事件(比如键盘输入、鼠标移动等等),更新窗口状态,并调用对应的回调函数
}
#pragma endregion
/*释放资源*/
glfwTerminate();
return 0;
}
// 回调函数,窗口大小改变的时候也应该改变视口的大小
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
// 不知道这个window有什么作用
glViewport(0, 0, 800, 600);
}
// 处理输入
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)// 判断是否按下Esc
glfwSetWindowShouldClose(window, true);// 将窗口设置为需要关闭
}
我们想画一个四边形,我们就需要四个顶点。然后OpenGL是按三角形绘制的,按照上面的方法就需要六个顶点(其中有两对是重复的)。这样是很浪费资源的,特别是在重复的顶点特别多的时候(正常情况下都是这样的情况)。
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, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
所以开始引入了EBO(索引缓冲对象,Element Buffer Object),用索引指定哪三个顶点是一个三角形。
// 三角形的顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
0.9f, 0.8f, 0.0f
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 2, // 第一个三角形
1, 2, 3 // 第二个三角形
};
可以在创建VAO和VBO的后面同样的创建EBO。
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
渲染循环里面把原来的glDrawArrays替换为glDrawElements。还有要绑上EBO。
//glDrawArrays(GL_TRIANGLES, 0, 3);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 使用框线模式渲染,也可以去掉。
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);