天天看点

《OpenGL编程指南》一2.5 着色器的编译

本节书摘来自华章出版社《opengl编程指南》一书中的第2章,第2.5节,作者 bill licea-kane ,更多章节内容可以访问云栖社区“华章计算机”公众号查看

opengl着色器程序的编写与c语言等基于编译器的语言非常类似。我们使用编译器来解析程序,检查是否存在错误,然后将它翻译为目标代码。然后,在链接过程中将一系列目标文件合并,并产生最终的可执行程序。在程序中使用glsl着色器的过程与之类似,只不过编译器和链接器都是opengl api的一部分而已。

《OpenGL编程指南》一2.5 着色器的编译

图2-1给出了创建glsl着色器对象并且通过链接来生成可执行着色器程序的过程。

对于每个着色器程序,我们都需要在应用程序中通过下面的步骤进行设置。

对于每个着色器对象:

1)创建一个着色器对象。

2)将着色器源代码编译为对象。

3)验证着色器的编译是否成功。

然后需要将多个着色器对象链接为一个着色器程序,包括:

1)创建一个着色器程序。

2)将着色器对象关联到着色器程序。

3)链接着色器程序。

4)判断着色器的链接过程是否成功完成。

5)使用着色器来处理顶点和片元。

为什么要创建多个着色器对象?这是因为我们有可能在不同的程序中复用同一个函数,而glsl程序也是同一个道理。我们创建的通用函数可以在多个着色器中得到复用。因此不需要使用大量的通用代码来编译大量的着色器资源,只需要将合适的着色器对象链接为一个着色器程序即可。

调用glcreateshader()来创建着色器对象。

gluint glcreateshader(glenum type);

分配一个着色器对象。type必须是gl_vertex_shader、gl_fragment_shader、gl_tess_control_shader、gl_tess_evaluation_shader或者gl_geometry_shader中的一个。返回值可能是一个非零的整数值,如果为0则说明发生了错误。

当我们使用glcreateshader()创建了着色器对象之后,就可以将着色器的源代码关联到这个对象上。这一步需要调用glshadersource()函数。

void glshadersource(gluint shader, glsizei count, const glchar* string, const glint length);

将着色器源代码关联到一个着色器对象shader上。string是一个由count行glchar类型的字符串组成的数组,用来表示着色器的源代码数据。string中的字符串可以是null结尾的,也可以不是。而length可以是以下三种值的一种。如果length是null,那么我们假设string给出的每行字符串都是null结尾的。否则,length中必须有count个元素,它们分别表示string中对应行的长度。如果length数组中的某个值是一个整数,那么它表示对应的字符串中的字符数。如果某个值是负数,那么string中的对应行假设为null结尾。

如果要编译着色器对象的源代码,需要使用glcompileshader()函数。

void glcompileshader(gluint shader);

编译着色器的源代码。结果查询可以调用glgetshaderiv(),并且参数为gl_compile_status。

这里与c语言程序的编译类似,需要自己判断编译过程是否正确地完成。调用glgetshaderiv()并且参数为gl_compile_status,返回的就是编译过程的状态。如果返回为gl_true,那么编译成功,下一步可以将对象链接到一个着色器程序中。如果编译失败,那么可以通过调取编译日志来判断错误的原因。glgetshaderinfolog()函数会返回一个与具体实现相关的信息,用于描述编译时的错误。这个错误日志的大小可以通过调用glgetshaderiv()(带参数gl_info_log_length)来查询。

void glgetshaderinfolog(gluint shader, glsizei bufsize, glsizei length, char infolog);

返回shader的最后编译结果。返回的日志信息是一个以null结尾的字符串,它保存在infolog缓存中,长度为length个字符串。日志可以返回的最大值是通过bufsize来定义的。

如果length设置为null,那么将不会返回infolog的大小。

当创建并编译了所有必要的着色器对象之后,下一步就是链接它们以创建一个可执行的着色器程序。这个过程与创建着色器对象的过程类似。首先,我们创建一个着色器程序,以便将着色器对象关联到其上。这里用到了glcreateprogram()函数。

gluint glcreateprogram(void);

创建一个空的着色器程序。返回值是一个非零的整数,如果为0则说明发生了错误。

当得到着色器程序之后,下一步可以将它关联到必要的着色器对象上,以创建可执行的程序。关联着色器对象的步骤可以通过调用glattachshader()函数来完成。

void glattachshader(gluint program, gluint shader);

将着色器对象shader关联到着色器程序program上。着色器对象可以在任何时候关联到着色器程序,但是它的功能只有经过程序的成功链接之后才是可用的。着色器对象可以同时关联到多个不同的着色器程序上。

与之对应的是,如果我们需要从程序中移除一个着色器对象,从而改变着色器的操作,那么可以调用gldetachshader()函数,设置对应的着色器对象标识符来解除对象的关联。

void gldetachshader(gluint program, gluint shader);

移除着色器对象shader与着色器程序program的关联。如果着色器已经被标记为要删除的对象(调用gldeleteshader()),然后又被解除了关联,那么它将会被即时删除。

当我们将所有必要的着色器对象关联到着色器程序之后,就可以链接对象来生成可执行程序了。这一步需要调用函数gllinkprogram()。

void gllinkprogram(gluint program);

处理所有与program关联的着色器对象来生成一个完整的着色器程序。链接操作的结果查询可以调用glgetprogramiv(),且参数为gl_link_status。如果返回gl_true,那么链接成功;否则,返回gl_false。

由于着色器对象中可能存在问题,因此链接过程依然可能会失败。我们可以调用glgetprogramiv()(带参数gl_link_status)来查询链接操作的结果。如果返回gl_true,那么链接操作成功,然后我们可以指定着色器程序来处理顶点和片元数据了。如果链接失败,即返回结果为gl_false,那么我们可以通过调用glgetprograminfolog()函数来获取程序链接的日志信息并判断错误原因。

void glgetprograminfolog(gluint program, glsizei bufsize, glsizei length, char infolog);

返回最后一次program链接的日志信息。日志返回的字符串以null结尾,长度为length个字符,保存在infolog缓存中。log可返回的最大值通过bufsize指定。如果length为null,那么不会再返回infolog的长度。

如果我们成功地完成了程序的链接,那么就可以调用函数gluseprogram(),并且参数设置为程序对象的句柄来启用顶点或者片元程序。

void gluseprogram(gluint program);

使用链接过的着色器程序program。如果program为零,那么所有当前使用的着色器都会被清除。如果没有绑定任何着色器,那么opengl的操作结果是未定义的,但是不会产生错误。

如果已经启用了一个程序,而它需要关联新的着色器对象,或者解除之前关联的对象,那么我们需要重新对它进行链接。如果链接过程成功,那么新的程序会直接替代之前启用的程序。如果链接失败,那么当前绑定的着色器程序依然是可用的,不会被替代,直到我们成功地重新链接或者使用gluseprogram()指定了新的程序为止。

当着色器对象的任务完成之后,我们可以通过gldeleteshader()将它删除,并且不需要关心它是否关联到某个活动程序上。这一点与c语言程序的链接是相同的,当我们得到可执行程序之后,就不再需要对象文件了,直到我们再次进行编译为止。

void gldeleteshader(gluint shader);

删除着色器对象shader。如果shader当前已经链接到一个或者多个激活的着色器程序上,那么它将被标识为“可删除”,当对应的着色器程序不再使用的时候,就会自动删除这个对象。

与此类似,如果我们不再使用某个着色器程序,也可以直接调用gldeleteprogram()删除它。

void gldeleteprogram(gluint program);

立即删除一个当前没有在任何环境中使用的着色器程序program,如果程序正在被某个环境使用,那么等到它空闲时再删除。

最后,为了确保接口的完整性,还可以调用glisshader()来判断某个着色器对象是否存在,或者通过glisprogram()判断着色器程序是否存在。

glboolean glisshader(gluint shader);

如果shader是一个通过glcreateshader()生成的着色器对象的名称,并且没有被删除,那么返回gl_true。如果shader是零或者不是着色器对象名称的非零值,则返回gl_false。

glboolean glisprogram(gluint program);

如果program是一个通过glcreateprogram()生成的程序对象的名称,并且没有被删除,那么返回gl_true。如果program是0或者不是着色器程序名称的非零值,则返回gl_false。

为了简化应用程序中使用着色器的过程,我们在示例中使用一个loadshaders()函数来辅助载入和创建着色器程序。我们已经在第1章的第一个程序中用到了这个函数来加载简单的着色器代码。

继续阅读