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