1 #include <GL/glew.h>
2 #include <GLFW/glfw3.h>
3 #include <iostream>
4
5 using namespace std;
6
7 const int numVAOs = 1;
8 GLuint renderingProgram;
9 GLuint vao[numVAOs];
10
11 // 当 GLSL 代码编译失败时,显示 OpenGL 日志内容。
12 void printShaderLog(GLuint shader)
13 {
14 int len = 0;
15 int chWrittn = 0;
16 char* log;
17 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
18 if (len > 0)
19 {
20 log = (char*)malloc(len);
21 glGetShaderInfoLog(shader, len, &chWrittn, log);
22 cout << "Shader Info Log: " << log << endl;
23 free(log);
24 }
25 }
26
27 // 当 GLSL 链接失败时,显示 OpenGL 日志内容
28 void printProgramLog(int prog) {
29 int len = 0;
30 int chWrittn = 0;
31 char* log;
32 glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
33 if (len > 0) {
34 log = (char*)malloc(len);
35 glGetProgramInfoLog(prog, len, &chWrittn, log);
36 cout << "Program Info Log: " << log << endl;
37 free(log);
38 }
39 }
40 // 检查 OpenGL 错误标志,即是否发生 OpenGL 错误
41 bool checkOpenGLError() {
42 bool foundError = false;
43 int glErr = glGetError();
44 while (glErr != GL_NO_ERROR) {
45 cout << "glError: " << glErr << endl;
46 foundError = true;
47 glErr = glGetError();
48 }
49 return foundError;
50 }
51
52
53 /**
54 * @brief 创建着色器,返回一个整数ID作为索引号,便于后续引用
55 * OpenGL中要使用一个shader需要如下几步:
56 * 1.使用glCreateShader()方法创建一个空的着色器对象,用来接收源代码并进行编译
57 * 2.使用glShaderSource()方法将着色器源码传递给着色器对象,以便保留该源代码的副本
58 * 3.使用glCompileShader()方法对着色器对象中包含的任何源代码进行编译
59 * 4.使用glCreateProgram()方法创建一个着色器程序,我们可以将着色器对象附加到该对象
60 * 5.使用glAttachShader()方法将着色器附加到着色器程序
61 * 6.使用glLinkProgram()方法将所有附加到程序对象的着色器对象链接在一起
62 * 7.使用glDeleteShader()方法删除着色器对象。一旦着色器链接到一个程序对象,程序将包含二进制代码,不再需要着色器了
63 **/
64 GLuint createShaderProgram()
65 {
66 GLint vertCompiled;
67 GLint fragCompiled;
68 GLint linked;
69
70 // 定义定点着色器源码
71 const char* vshaderSource =
72 // 声明着色器版本,与OpenGL版本同步
73 "#version 430 \n"
74 // 在顶点着色器,给 gl_Position指定out标签不是必需的,因为 gl_Position 是预定义的输出变量
75 //"out vec4 gl_Position; \n"
76 // main函数
77 "void main(void) \n"
78 // 用内置变量gl_Position在源码中硬编码一个顶点位置
79 "{ gl_Position = vec4(0.0, 0.0, 0.0, 1.0);}";
80
81 // 定义片段着色器源码
82 const char* fshaderSource =
83 // 声明着色器版本,与OpenGL版本同步
84 "#version 430 \n"
85 // out 标签表明color变量是输出变量
86 "out vec4 color; \n"
87 // main函数
88 "void main(void) \n"
89 // 设置颜色分量(rgba)
90 "{ if (gl_FragCoord.x < 295) color = vec4(0.0, 1.0, 0.0, 1.0); else color = vec4(0.0, 0.0, 1.0, 1.0);}";
91
92 // 创建顶点着色器
93 GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
94 // 创建片段着色器
95 GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
96 // 载入顶点着色器源码
97 glShaderSource(vShader, 1, &vshaderSource, nullptr);
98 // 载入片段着色器源码
99 glShaderSource(fShader, 1, &fshaderSource, nullptr);
100 // 编译顶点着色器
101 glCompileShader(vShader);
102 checkOpenGLError();
103 glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
104 if (vertCompiled != 1)
105 {
106 cout << "vertex compilation failed" << endl;
107 printShaderLog(vShader);
108 }
109 // 编译片段着色器
110 glCompileShader(fShader);
111 checkOpenGLError();
112 glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
113 if (fragCompiled != 1)
114 {
115 cout << "fragment compilation failed" << endl;
116 printShaderLog(fShader);
117 }
118 // 创建程序对象,OpenGL程序对象包含一系列编译过的着色器
119 GLuint vfProgram = glCreateProgram();
120 // 加入顶点着色器
121 glAttachShader(vfProgram, vShader);
122 // 加入片段着色器
123 glAttachShader(vfProgram, fShader);
124 // 链接程序
125 glLinkProgram(vfProgram);
126 checkOpenGLError();
127 glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
128 if (linked != 1)
129 {
130 cout << "linked failed" << endl;
131 printProgramLog(vfProgram);
132 }
133 // 删除着色器
134 glDeleteShader(vShader);
135 glDeleteShader(fShader);
136 return vfProgram;
137 }
138
139 void init(GLFWwindow* window) {
140 renderingProgram = createShaderProgram();
141 // 当准备将数据集发送给管线时,数据集是以缓冲区形式发送的。这些缓冲区最后都会被存入顶点数组对象
142 // 此案例我们在顶点着色器中硬编码一个点,因此不需要缓存区
143 // 即使如此,OpenGL依然需要创建一个VAO
144 glGenVertexArrays(numVAOs, vao);
145 glBindVertexArray(vao[0]);
146 }
147
148 void display(GLFWwindow* window, double currentTime)
149 {
150 // 指定颜色缓冲区清除后填充的值
151 glClearColor(1.0, 0.0, 0.0, 1.0);
152 // 用指定颜色清除(填充)颜色缓存区
153 glClear(GL_COLOR_BUFFER_BIT);
154 // 将含有两个已编译着色器的程序载入OpenGL管线阶段,并没有运行着色器
155 glUseProgram(renderingProgram);
156 // 设置绘制点的大小,便于观察
157 glPointSize(50);
158 // 启动管线处理过程,开始绘制
159 glDrawArrays(GL_POINTS, 0, 1);
160 }
161
162 int main(void)
163 {
164 // 如果glfw初始化失败则返回
165 if (!glfwInit()) { exit(EXIT_FAILURE); }
166 // 设置OpenGL程序的主版本号
167 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
168 // 设置OpenGL程序的副版本号
169 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
170 // 借助GLFW创建一个窗口
171 GLFWwindow* window = glfwCreateWindow(600, 600, "Graphics Program With OpenGL", nullptr, nullptr);
172 // 将GLFW创建的窗口与当前OpenGL的上下文关联起来
173 glfwMakeContextCurrent(window);
174 // 如果glew初始化失败则返回,glew负责调用OpenGL相关的函数
175 if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
176 // 交换间隔指示了直到交换缓冲区前需要等待多少帧,通常被理解为垂直同步。
177 // 默认情况下,交换间隔为0,意味着缓冲区交换会立即发生。
178 // 在一些快速的机器上,因为屏幕保持以典型的60 - 75次每秒的速度更新,许多帧会永远看不到,所以这会浪费许多CPU和GPU周期。
179 // 而且,因为缓冲区可能会在屏幕更新的中途被交换,导致画面撕裂。
180 // 因此,应用需要代表性地设置交换间隔为1。
181 glfwSwapInterval(1);
182
183 init(window);
184
185 // 当用户点击关闭窗口按钮时会返回true
186 while (!glfwWindowShouldClose(window))
187 {
188 display(window, glfwGetTime());
189 // GLFW默认使用了双缓冲技术。这意味着每个窗口会有两个渲染缓冲区,一个前置缓冲区和一个后置缓冲区。
190 // 前置缓冲区会在屏幕上显示而后置缓冲区是你渲染的目标。
191 // 当整个帧已经渲染完毕时,两个缓冲区需要进行交换,所以后置缓冲区会变成前置缓冲区,反之亦然。
192 glfwSwapBuffers(window);
193 // 处理窗口相关事件
194 glfwPollEvents();
195 }
196 // 销毁窗口
197 glfwDestroyWindow(window);
198 // 终止运行
199 glfwTerminate();
200
201 exit(EXIT_SUCCESS);
202 }