0%

新版OpenGL学习入门(二)——绘制图形

教程链接:你好,三角形

这一章学的东西超级多,学完也算基本入门啦

那就从最基础的开始吧

顶点输入

首先是坐标轴,它是高中数学学的直角坐标系的坐标轴,理解特别简单。

对应的数值需要在-1和1之间,大概类似百分比吧,最后的f代表浮点数。

和顶点对应的是顶点缓冲对象VBO,先是创建一个unsigned int来储存id,然后创建顶点缓冲对象,再是绑定缓冲对象

最后把顶点数据缓冲进去

这里glBufferData最后一个参数是显卡管理给定数据的模式

变量 意义
GL_STATIC_DRAW 数据几乎不会改变
GL_DYNAMIC_DRAW 数据会改变很多
GL_STREAM_DRAW 数据每次绘制都会改变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//顶点坐标x,y,z,需要在-1到1之间,标准数学坐标轴
//如果是2d的话z变为0
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
};

unsigned int VBO; //VBO顶点缓冲对象的id
glGenBuffers(1, &VBO); //生成顶点缓冲对象,给VBO这个id。类型为GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO); //把新建的缓冲绑定到GL_ARRAY_BUFFER

//把顶点数据复制到缓冲的内存中
//显卡管理给定数据的模式:GL_STATIC_DRAW数据几乎不会改变,GL_DYNAMIC_DRAW数据会改变很多,GL_STREAM_DRAW数据每次绘制都会改变
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //顶点缓冲对象,传送数据大小,发送的实际数据,显卡管理给定数据的模式

顶点着色器

着色器相关的在后面有详细介绍,这里主要是为了能够编译使用

顶点着色器

着色器代码

这是一个非常基础的GLSL顶点着色器

1
2
3
4
5
6
7
#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

在着色器的使用过程中,一般是另创建文档来使用的,因此在这边为了简单使用,用字符串记录

注意这个是写在main函数之前的

1
2
3
4
5
6
7
// GLSL顶点着色器,一般来说着色器会写在其它页面
const char *vertexShaderSource = "#version 330 core\n" //GLSL版本,与OpenGL版本对应
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" //可以把vec3的数据看作vec4构造器的参数,最后一个是透视除法(?)
"}\0";

编译顶点着色器

这个和VBO类似,先是着色器id,然后创建着色器,再编译着色器,最后判断是否成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 编译着色器
unsigned int vertexShader; //创建顶点着色器对象
vertexShader = glCreateShader(GL_VERTEX_SHADER); //创建顶点着色器
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //要编译的着色器,传递的源码字符串数量,顶点着色器真正的源码,(未知)
glCompileShader(vertexShader);
//判断编译是否成功
int sucess; //获取着色器编译是否成功的参数
char infoLog[512]; //如果编译失败获取失败的内容
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &sucess); //检查编译是否成功
if (!sucess) //如果编译失败,输出信息
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPLIATION_FILED\n" << infoLog << std::endl;
}

片段着色器

这个几乎和顶点着色器一模一样

着色器代码

源码

1
2
3
4
5
6
7
#version 330 core
out vec4 FragColor;

void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

写在同一个.cpp下的话

1
2
3
4
5
6
7
// 片段着色器同上
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" //RGB色,最后一个alpha透明度
"}\n\0";

编译片段着色器

这里比上面少了两行报错内容的声明

1
2
3
4
5
6
7
8
9
10
11
12
// 编译片段着色器,原理基本同上
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);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}

着色器程序

着色器程序是类似加载顶点着色器和片段着色器,代码一如既往的相似

顺便最后因为着色器程序全部get啦,那么顶点着色器和片段着色器就不需要留着数据了

其实从某种意义上感觉片段着色器和顶点着色器对于着色器程序做的是一个封装处理,这样易于管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 着色器程序
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);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 删除不需要的着色器
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

最后如果要使用着色器的话,需要再while循环函数里写

1
glUseProgram(shaderProgram);             //激活程序对象

链接顶点属性

它主要是告诉编译器如何解析顶点一类的

1
2
3
4
//设置顶点属性(对应顶点着色器中layout(location=0)),顶点属性大小(vec3对应大小为3),指定数据的类型
//是否被标准化(?和是否有符号有关),步长(每个顶点的间隔),强制类型转换+起始点的偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); //用来解析顶点
glEnableVertexAttribArray(0); //以顶点属性位置值为参数,启用顶点属性

顶点数组对象(VAO)

VAO是用来记录顶点属性的

1
2
3
4
unsigned int VAO;
glGenVertexArrays(1, &VAO);

glBindVertexArray(VAO);

源代码有把VAO和VBO合在一起,所以来一个合起来的版本吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int VAO, VBO;                 //VAO顶点数组对象,VBO顶点缓冲对象的id
glGenVertexArrays(1, &VAO); //生成顶点数组对象,给VAO这个id
glGenBuffers(1, &VBO); //生成顶点缓冲对象,给VBO这个id。类型为GL_ARRAY_BUFFER

glBindVertexArray(VAO); //绑定VAO

glBindBuffer(GL_ARRAY_BUFFER, VBO); //把新建的缓冲绑定到GL_ARRAY_BUFFER
//把顶点数据复制到缓冲的内存中
//显卡管理给定数据的模式:GL_STATIC_DRAW数据几乎不会改变,GL_DYNAMIC_DRAW数据会改变很多,GL_STREAM_DRAW数据每次绘制都会改变
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //顶点缓冲对象,传送数据大小,发送的实际数据,显卡管理给定数据的模式


//设置顶点属性(对应顶点着色器中layout(location=0)),顶点属性大小(vec3对应大小为3),指定数据的类型
//是否被标准化(?和是否有符号有关),步长(每个顶点的间隔),强制类型转换+起始点的偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); //用来解析顶点
glEnableVertexAttribArray(0); //以顶点属性位置值为参数,启用顶点属性

索引缓冲对象(EBO)

EBO又名IBO,可以设置顶点如何生成三角形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 // 第二个三角形
};

unsigned int EBO;
glGenBuffers(1, &EBO);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

又是整合版的

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
unsigned int VAO, VBO, EBO;            //VAO顶点数组对象,VBO顶点缓冲对象,EBO索引缓冲对象
glGenVertexArrays(1, &VAO); //生成顶点数组对象,给VAO这个id
glGenBuffers(1, &VBO); //生成顶点缓冲对象,给VBO这个id。类型为GL_ARRAY_BUFFER
glGenBuffers(1, &EBO); //生成索引缓冲对象,给EBO这个id。类型为GL_ELEMENT_ARRAY_BUFFER

glBindVertexArray(VAO); //绑定VAO

glBindBuffer(GL_ARRAY_BUFFER, VBO); //把VBO绑定到缓冲区GL_ARRAY_BUFFER
//把顶点数据复制到缓冲的内存中
//显卡管理给定数据的模式:GL_STATIC_DRAW数据几乎不会改变,GL_DYNAMIC_DRAW数据会改变很多,GL_STREAM_DRAW数据每次绘制都会改变
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //顶点缓冲对象,传送数据大小,发送的实际数据,显卡管理给定数据的模式

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //把EBO绑定到缓冲区GL_ELEMENT_ARRAY_BUFFER
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //设置缓冲区类型



//设置顶点属性(对应顶点着色器中layout(location=0)),顶点属性大小(vec3对应大小为3),指定数据的类型
//是否被标准化(?和是否有符号有关),步长(每个顶点的间隔),强制类型转换+起始点的偏移量
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);

生成图形

最后只要在while里面运行一下就好啦

生成三角形

这里是用glDrawArrays运行的

1
2
3
glUseProgram(shaderProgram);             //激活程序对象
glBindVertexArray(VAO); //每次需要重新绑定一下VAO
glDrawArrays(GL_TRIANGLES, 0, 3); //设置绘制三角形,有3个顶点

生成矩形

生成矩形需要通过两个三角形来绘制

OpenGL这里给人的感觉好蠢。。为什么不能直接生成矩形,想哭。。

1
2
3
glUseProgram(shaderProgram);             //激活程序对象
glBindVertexArray(VAO); //每次需要重新绑定一下VAO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

代码总汇

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include <glad/glad.h>
#include <glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height); //窗口回调函数
void processInput(GLFWwindow *window); //输入控制

// settings 初始化设置
const unsigned int SCR_WIDTH = 800; //初始宽度
const unsigned int SCR_HEIGHT = 600; //初始高度

// GLSL顶点着色器,一般来说着色器会写在其它页面
const char *vertexShaderSource = "#version 330 core\n" //GLSL版本,与OpenGL版本对应
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" //可以把vec3的数据看作vec4构造器的参数,最后一个是透视除法(?)
"}\0";

// 片段着色器同上,这个是管理颜色的
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.8f, 1.0f);\n" //RGB色,最后一个alpha透明度
"}\n\0";



int main()
{
// glfw: initialize and configure 不需要做任何改动
glfwInit(); //初始化GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号为3
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号为3
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //核心模式

#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 对于OS X
#endif


// glfw 窗口对象 不需要改动
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "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); //告诉glfw窗口大小会根据改变


// glad: load all OpenGL function pointers 加载系统相关的OpenGL函数指针地址的函数 不需要改动
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}


// 编译顶点着色器
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::COMPLIATION_FILED\n" << infoLog << std::endl;
}

// 编译片段着色器,原理基本同上
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);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}

// 着色器程序
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);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 删除不需要的着色器
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);





//顶点坐标x,y,z,需要在-1到1之间,标准数学坐标轴
//如果是2d的话z变为0
float vertices[] = { //储存的点是x,y,z;x,y,z这样循环的
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 VAO, VBO, EBO; //VAO顶点数组对象,VBO顶点缓冲对象,EBO索引缓冲对象
glGenVertexArrays(1, &VAO); //生成顶点数组对象,给VAO这个id
glGenBuffers(1, &VBO); //生成顶点缓冲对象,给VBO这个id。类型为GL_ARRAY_BUFFER
glGenBuffers(1, &EBO); //生成索引缓冲对象,给EBO这个id。类型为GL_ELEMENT_ARRAY_BUFFER

glBindVertexArray(VAO); //绑定VAO

glBindBuffer(GL_ARRAY_BUFFER, VBO); //把VBO绑定到缓冲区GL_ARRAY_BUFFER
//把顶点数据复制到缓冲的内存中
//显卡管理给定数据的模式:GL_STATIC_DRAW数据几乎不会改变,GL_DYNAMIC_DRAW数据会改变很多,GL_STREAM_DRAW数据每次绘制都会改变
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //顶点缓冲对象,传送数据大小,发送的实际数据,显卡管理给定数据的模式

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //把EBO绑定到缓冲区GL_ELEMENT_ARRAY_BUFFER
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //设置缓冲区类型



//设置顶点属性(对应顶点着色器中layout(location=0)),顶点属性大小(vec3对应大小为3),指定数据的类型
//是否被标准化(?和是否有符号有关),步长(每个顶点的间隔),强制类型转换+起始点的偏移量
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);


// render loop 渲染循环,这样可以保持一直运行
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);

//渲染指令
glClearColor(0.1f, 0.3f, 0.3f, 1.0f); //设置清空屏幕所用的颜色,每次循环重新渲染,因此需要清空,也因此这是屏幕的背景色。RGB色
glClear(GL_COLOR_BUFFER_BIT); //清楚颜色缓冲


glUseProgram(shaderProgram); //激活程序对象
glBindVertexArray(VAO); //每次需要重新绑定一下VAO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //6个顶点

// 检查并调用事件、交换缓冲
glfwSwapBuffers(window); //交换颜色缓冲
glfwPollEvents(); //检查事件触发
}


// glfw: terminate, clearing all previously allocated GLFW resources.
glfwTerminate(); //终止glfw
return 0;
}

// 输入控制
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) //判断是否按下Esc
glfwSetWindowShouldClose(window, true); //如果时Esc,那么glfw需要关闭窗口
}


// glfw: whenever the window size changed (by OS or user resize) this callback function executes 根据窗口大小改变显示大小
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height); //窗口左下角的坐标x、y
}

搬运自CSDN:https://blog.csdn.net/yueyue200830/article/details/84675512