本文最后更新于 2024年7月15日 晚上
为终身之路踏上第一步。
入门
OpenGL可以被看作是一个大的状态机。API中的一些函数会根据当前OpenGL的状态的不同,而产生不同的效果,这些函数被称为状态函数。
OpenGL的工作流:
创建对象->绑定对象到上下文(Bind Gen出来的Object到OpenGL上下文的内置属性)->设置已绑定对象的选项->解绑对象
PS:解绑对象只是让对象和上下文之间断开联系。实际上选项已经改变,与解绑无关。当要获取之前那个对象的信息时,只需要重新绑定那个上下文变量就行了。比如说我们有一些作为3D模型数据(一栋房子或一个人物)的容器对象,在我们想绘制其中任何一个模型的时候,只需绑定一个包含对应模型数据的对象就可以了。拥有数个这样的对象允许我们指定多个模型,在想画其中任何一个的时候,直接将对应的对象绑定上去,便不需要再重复设置选项了。
1 2 3 4 5 6 7 8 9 10
| unsigned int objectId = 0; glGenObject(1, &objectId);
glBindObject(GL_WINDOW_TARGET, objectId);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800); glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
glBindObject(GL_WINDOW_TARGET, 0);
|
环境配置
GLFW
下载源代码,使用Clion打开源代码根目录作为项目。
点击构建-构建项目,完成后在cmake-build-output/src文件夹下找到glfw3.dll文件。
新建空项目,新建libs和include文件夹,将GLFW源代码中include文件夹的内容拖入新include文件夹,将编译完成的glfw3.dll拖入libs文件夹。同时,也要把glfw3.dll拖入cmake-build-output文件夹下
修改CMakeList.txt内容如下:
1 2 3 4 5 6 7 8 9 10
| cmake_minimum_required(VERSION 3.28) project(LearnOpenGL)
set(CMAKE_CXX_STANDARD 17)
add_executable(LearnOpenGL main.cpp)
INCLUDE_DIRECTORIES(include) link_directories(libs) target_link_libraries(LearnOpenGL libs/glfw3.dll)
|
重新加载CMake即可。可新建cpp文件,输入#include <GLFW\glfw3.h>
,若未报错则链接成功。
GLAD
打开http://glad.dav1d.de/,按照以下规则配置:
Language:C/C++
Specification:OpenGL
API-gl:3.3 and newer
Opentions - Generate a loader:打勾
点击Generate按钮,下载glad.zip,解压,将include内的文件拖动到工程的include文件里,glad.c则放到工程根目录。使用#include <glad\glad.h>
验证是否配置成功。
范例工程
头文件
1 2
| #include <glad/glad.h> #include <GLFW/glfw3.h>
|
注意:glad头文件必须在GLFW之前include
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include <iostream> #include <glad/glad.h> #include <GLFW/glfw3.h> using namespace std;
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); }
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", NULL, NULL); if (window == NULL) { cout << "Failed to create GLFW window" << endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){ cout << "Failed to initialize GLAD" << endl; return -1; } glViewport(0, 0, 800, 600); glfwSetFramebufferSizeCallback(window,framebuffer_size_callback); while(!glfwWindowShouldClose(window)){ glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); processInput(window); glfwSwapBuffers(window); glfwPollEvents(); } glfwDestroyWindow(window); glfwTerminate(); return 0; }
|
通常的OpenGL范例程序:
初始化GLFW->设置GLFW信息,如OpenGL版本号->生成窗口->设置线程上下文->使用GLAD获取GL函数->定义渲染视口大小->注册各类回调函数->进入渲染循环->销毁资源
渲染循环内部:
清空缓存->处理输入->进行渲染操作(在后缓冲上绘制)->检查并调用事件,交换缓冲(使后缓冲的内容显示到画面上)
GLFW函数可分为两类:状态设置函数和状态使用函数。前者用于设置状态量,后者用于借助已经设置完毕的状态来改变程序行为。
对象绘制
基础
顶点数组对象:Vertex Array Object,VAO
顶点缓冲对象:Vertex Buffer Object,VBO
元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
图形渲染管线可以被分为两个部分:
- 第一部分负责将3D坐标转换为2D坐标
- 第二部分负责将2D坐标转换为有颜色的,实际屏幕上的像素。
2D坐标与像素不同。像素是2D坐标的近似值,受分辨率影响。
着色器是运行在GPU上的处理程序。每个小核心负责一个着色器的计算。
Vertex Shader、Geometry Shader和Fragment Shader可以由开发者自定义。

Vertex Shader把单独的顶点作为输入,将局部坐标系下的顶点坐标转换到标准化设备坐标(NDC),同时对顶点属性(Vertex Attribute)进行基本处理。
经Vertex Shader处理过的坐标必定是NDC,范围为[-1, 1]
Geometry Shader把一个图元(包括点、线、三角形三种类型)的顶点作为输入,根据需要处理这些顶点,也可以生成新的顶点,用于构建新的形状。
这个过程可选。
Shape Assembly将前阶段的所有顶点作为输入,并将其装配为指定图元的形状。
Rasterization把图元映射为屏幕上的像素,生成Fragment, 并剔除View以外的所有像素。
glViewport函数定义了视口信息。视口变换(Viewport Transform)将NDC变换为屏幕空间坐标。
屏幕空间坐标被变换为Fragment,输入到Fragment Shader中。
Fragment Shader用于计算一个像素的最终颜色。该阶段包含3D场景的数据,如光照、阴影等。
Test and Blending阶段,首先检测所有像素的深度值、模板值,用于判断像素是正面还是背面,并据此决定是否丢弃。随后,根据像素的alpha值,进行blend操作。
顶点输入
顶点数据首先被送到顶点着色器。这些数据以顶点缓冲对象(Vertex Buffer Objects, VBO)的状态存储在GPU内存(即显存)中。
使用VBO的优点在于,可以一次发送一大批数据到GPU上,而非一个顶点传送一次。
CPU到GPU的传输速度较慢,所以要尽量减少传输次数,一次发送尽可能多的数据。
通过**glGenBuffers(int count, unsigned int* VBO)**函数生成VBO对象。生成完毕后,变量VBO将存储VBO实例的id。随后,进行绑定操作。
1 2 3
| unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
任何对象在生成以后都需要与GL上下文中的特定目标进行绑定,才能生效。
每个缓冲区目标都只能同时绑定一个对象。
GL_ARRAY_BUFFER存储的对象通常是:需要在CPU和GPU之间传输的顶点相关数据。
**glBufferData(CONTEXT_TARGET, int data_len, float[] data, DRAW_FORM)**用于向当前绑定的缓冲区存入用户定义数据。
1 2 3 4 5 6
| float verticals[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; glBufferData(GL_ARRAY_BUFFER, sizeof(verticals),verticals, GL_STATIC_DRAW);
|
DRAW_FORM参数用于指定显卡如何管理存入的数据。
- GL_STATIC_DRAW:存入的数据几乎不会发生变化
- GL_DYNAMIC_DRAW:存入的数据时常会有发生变化
- GL_STREAM_DRAW:存入的数据每时每刻都在变化
至此,顶点已完成了输入,此刻的顶点数据以VBO的形式存储在显存中。
顶点着色器
1 2 3 4 5 6 7 8
| #version 330 core //定义版本号和PROFILE模式
layout (location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos, 1.0); }
|
location变量用于绑定顶点属性的特定位置索引。一般而言,顶点属性都是用float类型存储的。而存放顶点数据的vertical数组又是一维而非二维的。因此,可能出现前N个数据里,数据[0,N-M]是位置数据,而[N-M+1,N]是颜色数据。通过设置不同的location变量,可以解明顶点数据的具体含义。
gl_Position的值将会成为顶点着色器的输出。
在实际的顶点着色器中,往往还需要经过坐标变换到NDC的过程。
C++源码文件无法直接嵌入GLSL代码。所以需要在运行时动态编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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";
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
|
通过glGetShaderiv
函数可以检测编译是否成功。
1 2 3 4 5 6 7 8 9 10 11 12
| 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; }
|
片元着色器
片元着色器用于计算像素最后的颜色输出。
1 2 3 4 5 6
| #version 330 core out vec4 FragColor; void main() { FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); }
|
随后进行编译。
1 2 3 4 5 6 7 8 9 10
| 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"; unsigned int fragmentShader; fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader);
|
着色器程序
完成着色器编译后,还需要编写着色器程序。整个渲染管线就像一个链表,着色器是其中的一个个节点,而着色器程序负责把这些节点连接(Link)起来,并负责数据的输入输出。
当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,你会得到一个连接错误。
1 2 3 4 5 6 7
| unsigned int shaderProgram; shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
|
借助glGetProgramiv
函数,可以判断链接是否出错。
1 2 3 4 5
| glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if(!success) { glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); ... }
|
链接完毕后,使用glUseProgram
函数激活着色器程序对象,同时,删除先前定义的着色器,以释放内存。
1 2 3
| glUseProgram(shaderProgram); glDeleteShader(vertexShader); glDeleteShader(fragmentShader);
|
完成这一系列操作以后,我们完成了以下内容:
- 发送顶点数据,让其以VBO的状态存储在显存。
- 编写了Vertex Shader和Fragment Shader,并指示GPU该如何使用这些着色器处理顶点数据。
链接顶点属性
我们输入的verticals数组是一个一维float数组。在这个数组里,每3个元素代表着一个顶点的位置数据。但OpenGL不知道,所以我们要告诉OpenGL,数组的哪些位置代表着哪个顶点的什么属性。
1 2 3 4 5 6 7 8 9 10 11 12
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glUseProgram(shaderProgram);
|
顶点数组对象
所以,每次绘制一个物体,我们都必须经历以下步骤:
- 生成VBO对象
- 绑定VBO对象到GL_ARRAY_BUFFER
- 将顶点数据(float数组)传入GL_ARRAY_BUFFER
- 设置顶点属性指针
- 启用顶点属性
- 使用着色器程序
- 绘制物体
非常繁琐。为了减少工作量,我们引入顶点数组对象(Vertex Array Object,VAO)的概念。
一个顶点数组对象会储存以下这些内容:
- glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
- 通过glVertexAttribPointer设置的顶点属性配置。
- 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
一般,完整的渲染代码可以表示为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
unsigned int VAO; glGenVertexArrays(1, &VAO); unsigned int VBO; glGenVertexArrays(1, &VBO);
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);
glUseProgram(shaderProgram); glBindVertexArray(VAO); someOpenGLFunctionThatDrawsOurTriangle();
|
绘制
glDrawArray
函数使用当前激活的着色器和VAO(包含VBO信息)来绘制图元(点、线、三角)。
1 2 3 4 5 6
| glUseProgram(shaderProgram); glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| #include <iostream> #include <glad/glad.h> #include <GLFW/glfw3.h> using namespace std;
float verticals[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f };
const char* vertex_shader="#version 330 core\n" "layout(location=0) in vec3 aPos;\n" "void main()\n" "{\n" "gl_Position = vec4(aPos,1.0);" "}\n";
const char* fragment_shader="#version 330 core\n" "out vec4 Fragcolor;\n" "void main()\n" "{\n" "Fragcolor = vec4(1.0f, 0.5f, 0.2f, 1.0f);" "}\n";
void process_input(GLFWwindow* window, int width, int height){ glViewport(0,0,width,height); }
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",NULL,NULL); if(window==NULL){ cout<<"Failed to create GLFW Window!"<<endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){ cout<<"Failed to load proc"<<endl; return -1; } glViewport(0,0,800,600); glfwSetFramebufferSizeCallback(window,process_input); unsigned int VBO; glGenBuffers(1,&VBO); glBindBuffer(GL_ARRAY_BUFFER,VBO); glBufferData(GL_ARRAY_BUFFER,sizeof(verticals),verticals,GL_STATIC_DRAW); unsigned int VAO; glGenVertexArrays(1,&VAO); glBindVertexArray(VAO); glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0); glEnableVertexAttribArray(0); unsigned int vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex,1,&vertex_shader,NULL); glCompileShader(vertex); int success; char infoLog[512]; glGetShaderiv(vertex, GL_COMPILE_STATUS, &success); if(!success) { glGetShaderInfoLog(vertex, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; } unsigned int fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment,1,&fragment_shader,NULL); glCompileShader(fragment); glGetShaderiv(fragment, GL_COMPILE_STATUS, &success); if(!success) { glGetShaderInfoLog(fragment, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; } unsigned int shader_program = glCreateProgram(); glAttachShader(shader_program,vertex); glAttachShader(shader_program,fragment); glLinkProgram(shader_program); glUseProgram(shader_program); glDeleteShader(vertex); glDeleteShader(fragment); while(!glfwWindowShouldClose(window)){ glClearColor(0.2f,0.3f,0.3f,1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shader_program); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES,0,3); glfwSwapBuffers(window); glfwPollEvents(); } }
|
元素缓冲对象
当图元存在共用顶点的情况时,传统的绘制方法会把共用的顶点绘制两次,导致额外开销。
为此,元素缓冲对象(Element Buffer Object, EBO)提供了一种方式,用于存储OpenGL用来决定要绘制哪些顶点的索引。
使用EBO时,顶点数据必须是不重复的顶点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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, 1, 3, 1, 2, 3 }; unsigned int EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
|
使用EBO的完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| #include <iostream> #include <glad/glad.h> #include <GLFW/glfw3.h> using namespace std;
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, 1, 3, 1, 2, 3 };
const char* vertex_shader="#version 330 core\n" "layout(location=0) in vec3 aPos;\n" "void main()\n" "{\n" "gl_Position = vec4(aPos,1.0);" "}\n";
const char* fragment_shader="#version 330 core\n" "out vec4 Fragcolor;\n" "void main()\n" "{\n" "Fragcolor = vec4(1.0f, 0.5f, 0.2f, 1.0f);" "}\n";
void process_input(GLFWwindow* window,int width, int height){ glViewport(0,0,width,height); }
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",NULL,NULL); if(window==NULL){ cout<<"Failed to Create Window"<<endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if(!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)){ cout<<"Failed to Load GLAD proc"<<endl; glfwTerminate(); return -1; } glViewport(0,0,800,600); glfwSetFramebufferSizeCallback(window,process_input);
unsigned int vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex,1,&vertex_shader,NULL); glCompileShader(vertex); int success; char info[512]; glGetShaderiv(vertex,GL_COMPILE_STATUS,&success); if(!success){ glad_glGetShaderInfoLog(vertex,512,NULL,info); cout<<"VERTEX SHADER COMPILE ERROR:"<<info<<endl; } unsigned int fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment,1,&fragment_shader,NULL); glCompileShader(fragment); glGetShaderiv(fragment,GL_COMPILE_STATUS,&success); if(!success){ glad_glGetShaderInfoLog(fragment,512,NULL,info); cout<<"FRAGMENT SHADER COMPILE ERROR:"<<info<<endl; } unsigned int shader_program = glCreateProgram(); glAttachShader(shader_program,vertex); glAttachShader(shader_program,fragment); glLinkProgram(shader_program); glGetProgramiv(shader_program, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shader_program, 512, NULL, info); std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << info << std::endl; } glDeleteShader(vertex); glDeleteShader(fragment);
unsigned int VBO; glGenBuffers(1,&VBO); glBindBuffer(GL_ARRAY_BUFFER,VBO); glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW); unsigned int VAO; glGenVertexArrays(1,&VAO); glBindVertexArray(VAO); glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0); glEnableVertexAttribArray(0); unsigned int EBO; glGenBuffers(1,&EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); while(!glfwWindowShouldClose(window)){ glClearColor(0.5f,0.4f,0.3f,1.0f); glClear(GL_COLOR_BUFFER_BIT); glBindVertexArray(VAO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO); glUseProgram(shader_program); glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER,0); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteBuffers(1,&VBO); glDeleteProgram(shader_program); glDeleteVertexArrays(1,&VAO); glfwTerminate(); return 0; }
|