着色器源代码需要经过编译,链接,才能生成可执行的着色器程序,但是着色器的编译器和链接器都是内置在OpenGL库中的。因此我们需要调用OpenGL函数,才能创建出可执行的着色器程序。
一、定义着色器类
定义一个着色器的c++类,将所有着色器相关的操作都封装在一起,方便我们后续的代码管理和维护。MCShader类的定义如下:
class MCShader { public: MCShader(); virtual ~MCShader(); private: //shader对象句柄 unsigned int m_Handle; };
目前这个类只有一个私有的成员变量m_Handle,用来保存创建成功之后的着色器对象句柄。此外,为了访问m_Handle成员变量,可以再增加一个成员函数:
//返回shader对象 unsigned int GetHandle() { return m_Handle; }
二、创建和编译着色器
(1)创建着色器对象
首先需要使用glCreateShader函数,创建一个着色器对象,它的函数原型如下:
GLuint glCreateShader(GLenum type)
type — 需要创建的着色器对象类型,可选的值有GL_FRAGMENT_SHADER和GL_VERTEX_SHADER
目前OpenGL3.0只支持顶点着色器和片段着色器,因此着色器类型的参数只有两种:GL_VERTEX_SHADER(顶点着色器)和GL_FRAGMENT_SHADER(片段着色器)。 调用glCreateShader函数创建着色器对象,如果创建成功,会返回一个非零的,空着色器对象的句柄。
(2)附加着色器源码
着色器对象创建成功之后,下一步还要使用glShaderSource函数,为之前创建的空着色器对象绑定着色器的源代码。
void glShaderSource( GLuint shader, GLsizei count, const GLchar** string, const GLchar* length )
shader — 着色器对象的句柄
count — 着色器源代码的数量
string — 每个着色器源代码的地址指针(数量由count决定)
length — 每个着色器源代码的长度(数量由count决定)
这个函数为绑定着色器源码,提供了一种很灵活的方式。着色器源码可以分布在多个字符串数组中,我们只需要提供这些数组的指针数组,以及一个用于存放对应源代码段长度的整型数组。但为了方便,一般整个着色器源代码仅使用一个字符串数组,源程序指针数组和和长度数组都只有一个元素。
其中参数length可以为NULL,那么着色器源代码的字符串必须是以null结尾的。
(3)编译着色器
着色器源码被绑定后,下一步就是调用glCompiler函数,编译之前绑定的着色器源代码,函数原型:
void glCompileShader(GLuint shader)
shader — 着色器对象的句柄
该函数比较简单,它没有返回值,着色器对象编译的成功与否,需要后续做进一步的编译状态查询。
下面是着色器源代码编译的整个过程:
bool MCShader::LoadShaderSource(int type, const char* shaderSource) { //第一步,创建shader对象 unsigned int shader = GL30::glCreateShader(type); if (shader == 0) { return false; } // 第二步,绑定shader源码 GL30::glShaderSource(shader, 1, &shaderSource, NULL); // 第三步,编译shader源码 GL30::glCompileShader(shader); //其它代码段… }
三、检查编译状态
(1)编译状态查询
在完成着色器编译后,首先需要检查编译是否成功。为了查询编译状态,需要借助于glGetShaderiv函数,它的原型如下:
void glGetShaderiv(GLuint shader, GLenum pname, GLint *params)
shader — 着色器对象的句柄
pname — 需要查询的信息名称, 可选的参数有:
GL_COMPILE_STATUS:编译状态
GL_DELETE_STATUS:删除状态
GL_INFO_LOG_LENGTH:Log信息长度
GL_SHADER_SOURCE_LENGTH:源代码长度
GL_SHADER_TYPE:着色器类型
params — 查询结果返回的指针地址
使用glGetShaderiv查询着色器的编译状态,pname参数需要选择GL_COMPILE_STATUS,如果编译成功,params中返回的值为GL_TURE,否则为GL_FALSE;pname参数选择GL_INFO_LOG_LENGTH,可以查询编译着色器时,生成的错误信息或者警告信息的长度。
当然,glGetShaderiv函数还可以查询着色器其它的一些信息,比如着色器对象的类型,着色器源码的长度和着色器对象是否处于删除状态。
(2)编译错误输出
如果着色器源码编译失败了,还需调用glGetShaderInfoLog函数获取错误信息。因为我们需要根据错误信息,修改着色器源代码,直至编译通过。它的函数原型如下:
void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog)
shader — 着色器对象的句柄
maxLength — 获取错误信息buffer的长度
length — 错误信息的长度(不包含null终止符),如果不需要获取长度,参数可以传NULL
infoLog — 获取错误信息的buffer指针
调用glGetShaderInfoLog函数之后,返回的错误信息是一个以null结尾的字符串,它保存在infoLog缓存中,长度为length个字符。错误信息可以返回的最大长度是通过maxLength定义的,如果maxLength定义的长度过小,则返回的错误信息将被截断。
将检查着色器编译状态的整个过程,可以封装成一个函数。下面是它的代码实现:
bool MCShader::CheckCompileStatus(unsigned int shader) { //获取编译状态 GLint compiled; GL30::glGetShaderiv(shader, GL30::GL_COMPILE_STATUS, &compiled); //编译失败时,获取Log信息 if (!compiled) { //获取Log长度 GLint infoLen; GL30::glGetShaderiv(shader, GL30::GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 0) { char* pLogMsg = new char[infoLen]; //获取编译失败时的Log信息 GL30::glGetShaderInfoLog(shader, infoLen, NULL, pLogMsg); //打印失败的Log信息 printf("Error compiling shader: \n%s\n", pLogMsg); delete[] pLogMsg; } return false; } return true; }
三、删除着色器对象
当着色器对象使用完成后,最后还需要删除着色器对象,释放系统资源。
void glDeleteShader(GLuint shader)
shader — 着色器对象的句柄
如果一个着色器对象正在使用中,那么当执行glDeleteShader函数时不会立即被删除,而是将该着色器对象标记为删除状态。当它不再被其它任何程序对象使用时,将会被自动删除。
void MCShader::DeleteShader() { if(m_Handle != 0) { GL30::glDeleteShader(m_Handle); m_Handle = 0; } }
四、函数调用
首先在OpenGL窗口类中,定义两个着色器类的对象,分别是顶点和片段着色器类对象:
private: MCShader m_vsShader; //顶点shader MCShader m_psShader; //片段shader
为了创建着色器对象,还需要提供着色器的源代码。这里我们先提供一个顶点shader源码和一个片段shader源码。至于如何编写着色器源代码,这里不做过多说明。
//顶点Shader源码 GLbyte vsShaderStr[] = "attribute vec4 vPosition; \n" "void main() \n" "{ \n" " gl_Position = vPosition; \n" "} \n"; //片段Shader源码 GLbyte psShaderStr[] = "void main() \n" "{ \n" " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" "} \n";
这两个着色器的源码字符串,定义在OpenGL窗口类的初始化函数中。下一步,就可以调用LoadShaderSource函数,编译这两段着色器源代码。如果没发生任何错误,应该都会返回ture,否则返回false。
//编译顶点shader if(!m_vsShader.LoadShaderSource(GL30::GL_VERTEX_SHADER, vsShaderStr)) { printf("the vertex shader compiled failed.\n"); } //编译片段shader if(!m_psShader.LoadShaderSource(GL30::GL_FRAGMENT_SHADER, psShaderStr)) { printf("the fragment shader compiled failed.\n"); }
五、配套源码
源码下载:CompileShader