OpenGL 笔记 <2> Compiling and Linking a shader program
Preface
这一节所有的主要内容都在一个OpenGL库文件中<LoadShaders.h> ,只需要用LoadShader()函数进行加载即可。但是由于老是出错,所以自己实现了一下,也用以提供给有兴趣了解着色器的编译、连接等原理的人。
因为程序基本都是自己实现的,所以,只需要包含最基本的OpenGL头文件即可运行。
效果如下:
Background
由于固定管线编程过程中涉及的大量计算(当然,上个例子并没有多少计算量)都是再CPU上进行的,而这些可能影响CPU处理其他事情的效率,所以不妨运用一种编程技法,将这些繁重的运算交予GPU去处理, 进而腾出CPU的时间。于是就引进了我们今天的前提——可编程管线,其对应的语言为GLSL(OpenGL Shading Language)。
先说一下博文撰写原则,博文撰写涉及到的背景知识只阐述与本次主题相关的,至于整合的那种完整的理论原理综述,如果需要的话会在后面做统一的阐述,而单篇有主题的文章中不做总结,不然可能会喧宾夺主。
看完这一篇可能会有几个疑问:着色器到底干了啥,它是怎么导致图形效果的,以及它里面每一个关键字都是干什么的,等等
上述问题,可能考虑下一篇讲,有人会问为什么不先弄清楚上面的再写下面的,因为我就是那样过来的,只有空洞的理论和语法,这种还是先看到效果,然后追究其原理更合适一点,这是我的一个安排和考虑,理解万岁。
Getting ready
觉得还是需要简单说明一下可编程渲染管线的流程(超级精简版):
对于顶点属性数据,如:位置、颜色、纹理等等,他们都需要进行某些操作,比如,位置可能需要平移,颜色可能需要渐变等,而这些一定都是矩阵运算,而不同的处理就对应着不同的着色器,嗯,就是酱紫。
而我们今天只需要用到顶点着色器和片元着色器。
以顶点着色器为例来介绍我们这次需要掌握的GLSL语法
vertex Shader (顶点着色器)
将下面的内容写到一个名为basic.vert的文件中 (emmm,最后一行可能要有一个空行,这个不是语法要求,这个是该程序所需,后面会讲到)
#version 430 layout (location=0) in vec3 VertexPosition; layout (location=1) in vec3 VertexColor; out vec3 Color; void main() { Color = VertexColor; gl_Position = vec4(VertexPosition,1.0); }
顶点着色器:对于绘制命令传输的每一个顶点,OpenGL都会调用一个顶点着色器来处理顶点相关的属性数据。
顶点着色器的复杂与否与其他着色器的活跃与否有关,所以,它可能会非常简单,如:值将数据复制并传递到下一个阶段,这类似于pass-through shader,就是,走一下形式,数据简单流过,无任何操作。它也可能非常复杂,如:执行大量的计算来得到顶点在屏幕上的位置,或者通过光照的计算来判断顶点的颜色。
注:一个复杂应用程序可能有多个顶点着色器,但统一时刻只能有一个顶点着色器起作用
in 从应用程序(我们习惯将主程序称为应用程序)中传递进来的数据
out 从该着色器传递出去的新数据(相当于C++程序中的return 变量)
第一句#version 430指的是opengl或GLSL的版本为4.3(后面用程序获取本机的版本号)
因为可能会有很多个in 对象,而我们知道应用程序中需要获取它的位置,所以就要有Location,即下标、索引 号,而layout就是一个GLSL语言的关键字用于人工确定索引的排布方式。上一节中,提到过Location:(https://www.cnblogs.com/lv-anchoret/p/9221504.html)
vec3就是一个一维的有三个元素的数值类型(三元组)
gl_Position就是OpenGL系统内部的量,需要将处理好的量传递给它,它和out量都会传递到下面继续处理.
vertex shader takes the input attributes VertexPosition and VertexColor and passes them along to the fragment shader via the output variabes gl_Position and Color.
而关于主程序代码中,对着色器的处理与之前固定管线的结合,我们也并不需要担心太多,主程序中,单纯从代码上说它们可能交融的不是很紧密。
我们需要用一些window工具包来构建脚本,跨平台的有GLFW、GLUT、FLTK、QT或者wxWidgets。我们这里继续选用GLUT / freeglut
Compiling a shader
the diagram of this recipe
第一步 # 创建一个着色器对象 Create the shader object
vertShader = glCreateShader(GL_VERTEX_SHADER);
函数的参数为着色器类型,该函数返回一个着色器对象的映射量(GLuint型),有时称之为对象句柄(object“handle”)。
如果创建失败,将返回一个0,所以我们可以根据返回值来检验是否创建成,如果没有创建成功,则返回一个正确的信息,以用于检验程序错误。
第二步 # 拷贝脚本源程序 Copy the source code
const GLchar* shaderCode = loadShaderAsString("basic.vert");
const GLchar* codeArray[] = {shaderCode};
glShaderSource(vertShader, 1, codeArray, NULL);
loadShaderAsString(); 似乎是在flew头文件中才有的吧,我也不是很清楚,原则上应该是这么整的,但是出于,我也不知道它的头文件,所以就这个也是自己写的。
它的目的就是将以参数为名的文件信息内容读取出来。(因为我也不知道这个东西,之前一直以为是类型转换之类的,然后就出现了很多错误)
glShaderSource(); // load the source code into the shader object
因为这个函数支持一次性加载多个源代码,所以我们把读取的字符串指针放入一个数组中,即 codeArray
parameter 1:创建的着色器对象的映射值(即:shader_Obj handle)
para 2: Array 数组里面元素的个数
para 3: Array数组
para 4: 一个数组,记录Array数组中每个元素的长度。如果你读取的每个源代码字符串是以null结尾的,那么此处可以填写NULL
第三步 # 编译着色器 Compile the shader
glCompileShader(vertShader);
第四步 # 验证编译状态 Vertify the compilation status
GLint result;
glGetShaderiv(vertShader, GL_COMPILE_STATUS, &result);
if (GL_FALSE == result)
{
fprintf(stderr, "Vertex shader compilation failed!\n");
GLint logLen;
glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, &logLen);
if (logLen > 0)
{
char* log = new char[logLen];
GLsizei written;
glGetShaderInfoLog(vertShader, logLen, &written, log);
fprintf(stderr, "Shader log:\n%s", log);
delete[] log;
}
}
glGetShaderiv(shader_Obj handle,要查询的状态的枚举量,用于记录结果的参数);
之后就是逐步验证着色器编译时候的每一个状态,直到找到错误点为止,然后提示相应的错误信息
Linking a shader program
the diagram of this recipe:
我们编译好我们的着色器之后,在将它们初始化到管线之前,我们还需要将它们链接到同一个着色器程序中。
我们知道上面提到的管线过程中,着色器之间是有数据交流的,所以为了让所有在同一个着色器程序中的各个着色器能够互相通信,我们需要把它们链接到同一个着色器程序中。
其步骤分为一下几步:
第一步 # 创建一个着色器程序 Create the program object
GLuint programHandle = glCreateProgram();
第二步 # 关联着色器与着色器程序 attach the shaders to the program object
glAttachShader(programHandle, vertShader);
glAttachShader(programHandle, fragShader);
就是将着色器对象句柄与着色器程序句柄关联在一起
第三步 # 链接着色器程序 Link the progra
glLinkProgram(programHandle);
检验,如果没问题,则进行下一步
第四步 # Install the program into the OpenGL pipline
glUseProgram(programHandle);
到此,我们拥有了一个完整的OpenGL管线,然后就可以渲染了。
如果要删除句柄可以如下这么做
glDeleteProgram(programHandle); 如果正在使用该着色器程序,那么,将会当它 不再使用的时候删除
glDeleteShader 同上
代码:做了点小小的整合
顶点着色器:basic.vert
#version 430 layout (location=0) in vec3 VertexPosition; layout (location=1) in vec3 VertexColor; out vec3 Color; void main() { Color = VertexColor; gl_Position = vec4(VertexPosition,1.0); }
片元着色器:basic.frag
#version 430 in vec3 Color; out vec4 FragColor; void main() { FragColor = vec4(Color, 1.0); }
注意两者后面要空一行空行,原因在前面说过了
主程序:
与本节无关的代码,如果有什么问题,可以参见上一节 笔记<1>:https://www.cnblogs.com/lv-anchoret/p/9221504.html
//配置代码 #if _MSC_VER>=1900 #include "stdio.h" _ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned); #ifdef __cplusplus extern "C" #endif FILE* __cdecl __iob_func(unsigned i) { return __acrt_iob_func(i); } #endif /* _MSC_VER>=1900 */ //code-list //using namespace std; #include <iostream> #include <fstream> using namespace std; #include <vgl.h> GLint vertShader, fragShader; GLuint vaoHandle; float positionDate[] = { -0.8f,-0.8f,0.0f, 0.8f,-0.8f,0.0f, 0.0f,0.8f,0.0f, }; float colorDate[] = { 1.0f,0.0f,0.0f, 0.0f,1.0f,0.0f, 0.0f,0.0f,1.0f, }; void init(); void Display(); void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName); //编译着色器 void _Link_Shader_(); //链接着色器 bool readFile(const char*, string&); //读取文件内容的函数 int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize(1024, 768); glutInitWindowPosition(20, 20); glutCreateWindow("test"); if (glewInit()) { cout << "Error!" << glGetString(glewInit()) << endl; exit(EXIT_FAILURE); } cout << "GL version:" << glGetString(GL_VERSION) << endl; //查询本机OpenGL版本 init(); _Compiling_Shader_(vertShader, GL_VERTEX_SHADER,"basic.vert"); _Compiling_Shader_(fragShader, GL_FRAGMENT_SHADER, "basic.frag"); _Link_Shader_(); glutDisplayFunc(Display); glutMainLoop(); } void init() { GLuint vboHandles[2]; glGenBuffers(2, vboHandles); GLuint postionBufferHandle = vboHandles[0]; GLuint colorBufferHanle = vboHandles[1]; glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle); glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), positionDate, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle); glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), colorDate, GL_STATIC_DRAW); glGenVertexArrays(1, &vaoHandle); glBindVertexArray(vaoHandle); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr); } void Display() { glClear(GL_COLOR_BUFFER_BIT); glBindVertexArray(vaoHandle); glDrawArrays(GL_TRIANGLES, 0, 3); glutSwapBuffers(); } bool readFile(const char* filename, string& content) { ifstream infile; infile.open(filename); if (!infile.is_open())return false; char ch; infile >> noskipws; while (!infile.eof()) { infile >> ch; content += ch; } infile.close(); content += '\0'; return true; } void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName) { shaderHandle = glCreateShader(GL_Shader_type); //检查编译情况 if (0 == shaderHandle) { fprintf(stderr, "Error creating shader.\n"); exit(EXIT_FAILURE); } string ShaderCode; if (!readFile(shaderName, ShaderCode)) { cout << "readFile Error!" << endl; exit(EXIT_FAILURE); } const GLchar* shaderSource = ShaderCode.c_str(); const GLchar* codeArray[] = { shaderSource }; glShaderSource(shaderHandle, 1, codeArray, NULL); glCompileShader(shaderHandle); GLint result; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &result); if (GL_FALSE == result) { fprintf(stderr, "shader compilation failed!\n"); GLint logLen; glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLen); if (logLen > 0) { char* log = new char[logLen]; GLsizei written; glGetShaderInfoLog(shaderHandle, logLen, &written, log); fprintf(stderr, "Shader log:\n%s", log); delete[] log; } } } void _Link_Shader_() { GLuint programHandle = glCreateProgram(); if (0 == programHandle) { fprintf(stderr, "Error creating program object.\n"); exit(1); } glAttachShader(programHandle, vertShader); glAttachShader(programHandle, fragShader); glLinkProgram(programHandle); GLint status; glGetProgramiv(programHandle, GL_LINK_STATUS, &status); if (GL_FALSE == status) { fprintf(stderr, "Failed to link shader program!\n"); GLint logLen; glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen); if (logLen > 0) { char* log = new char[logLen]; GLsizei written; glGetShaderInfoLog(programHandle, logLen, &written, log); fprintf(stderr, "Program log:\n%s", log); delete[] log; } } else glUseProgram(programHandle); }
如果出现_fprintf、_sscanf或者__iob_func问题,请参见:https://www.cnblogs.com/lv-anchoret/p/8729384.html
如果没有vgl.h的,见如下
#ifndef __VGL_H__ #define __VGL_H__ // #define USE_GL3W #ifdef USE_GL3W #include <GL3/gl3.h> #include <GL3/gl3w.h> #else #define GLEW_STATIC #include <GL/glew.h> #ifdef _MSC_VER # ifdef _DEBUG # if (_MSC_VER >= 1600) # pragma comment (lib, "glew_static_vs2010_d.lib") # else # pragma comment (lib, "glew_static_d.lib") # endif # else # if (_MSC_VER >= 1600) # pragma comment (lib, "glew_static_vs2010.lib") # else # pragma comment (lib, "glew_static.lib") # endif # endif #endif #endif #define FREEGLUT_STATIC #include <GL/freeglut.h> #ifdef _MSC_VER # ifdef _DEBUG # if (_MSC_VER >= 1600) # pragma comment (lib, "freeglut_static_vs2010_d.lib") # else # pragma comment (lib, "freeglut_static_d.lib") # endif # else # if (_MSC_VER >= 1600) # pragma comment (lib, "freeglut_static_vs2010.lib") # else # pragma comment (lib, "freeglut_static.lib") # endif # endif #endif #define BUFFER_OFFSET(x) ((const void*) (x)) #endif /* __VGL_H__ */
如果下载的是9th版本的OpenGL库文件,那么vgl是不完整的
这个程序的运行效果在一开始有,就不放了,放一个错误提示,当初因为一个函数解读错误导致的
可以看出来错误定位还是非常精准的
今天就到这里
感谢您的阅读,生活愉快~