大龙的博客

常用链接

统计

最新评论

为GLSL脚本搭建运行环境

总览

假设你已经编写好了一对shader,一个顶点shader和一个像素shader,那么你将如何在你编写的应用程序中使用这两个shader呢?这就是本章要解决的问题。

与C语言类似,每个shader源文件都必须被独立地编译成类似于C编译器生成的目标文件,它们将被连接在一起来组成一个程序。

下图为大家展示了,一对shader(一个顶点shader和一个像素shader)是如何经过编译、连接最后被应用程序所使用的过程。

 1 

创建一个Shader

下图像大家展示了编译一个shader对象(类似与生成一个C语言目标文件)的过程。 
2 
首先,我们来创建一个容纳shader的容器,我们称之为shader容器。我们使用glCreateShader函数来完成这个工作。glCreateShader的原型如下: 
    GLuint glCreateShader(GLenum shaderType); 
这个函数只有一个参数,指定了shader容器所容纳的shader的类型。其中GL_VERTEX_SHADER代表顶点shader,GL_FRAGMENT_SHADER代表像素shader。如果调用成功的话,函数将返回一个整形数作为shader容器的句柄。

接下来,我们要在创建好的shader容器中添加shader的源代码。源代码应该以字符串数组的形式表示(如char* SourceCode[5])。当然,你也可以只用一个字符串来包含所有的源代码。然后,存储在字符串数组中的源代码将作为glShaderSource函数的参数,被设置到shader容器中。glShaderSource函数的原型如下: 
    void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lenOfStrings);
其中,shader是代表shader容器的句柄(由glCreateShader返回的整形数);numOfStrings是包含源程序的字符串数组中字符串的个数;strings是包含源程序的字符串数组;lenOfStrings是一个数组,数组中的元素代表了strings相应下标的字符串的长度,如果这个值被设置成NULL,则代表每个字符串是以NULL结尾的。

最后,我们使用glCompileShader函数来对shader容器中的源代码进行编译。glCompileShader函数的原型如下: 
    void glCompileShader(GLuint shader); 
其中,shader是代表shader容器的句柄。

创建一个程序

下图为大家展示了如何将编译后的shader连接成一个程序。

3

首先创建一个容纳程序的容器,我们称之为程序容器。我们可以通过glCreateProgram函数来创建一个程序容器。glCreateProgram函数的原型如下: 
    GLuint glCreateProgram(void);
如果函数调用成功将返回一个整形数作为程序的句柄。

接下来,我们要将shader容器添加到程序中。这时的shader容器不一定需要被编译,他们甚至不需要包含任何的代码。我们要做的只是将shader容器添加到程序中。我们使用glAttachShader函数来为程序添加shader容器。glAttachShader函数的原型如下: 
    void glAttachShader(GLuint program, GLuint shader);
其中,program是程序容器的句柄;shader是你要添加的shader容器的句柄。如果你同时拥有了,顶点shader和像素shader,你们需要分别将他们各自的两个shader容器添加的程序容器中。

最后,我们使用glLinkProgram来连接程序。glLinkProgram函数的原型如下: 
    void glLinkProgram(GLuint program);
其中,program是程序容器的句柄。在连接操作执行以后,你可以任意修改shader的源代码,对shader重新编译不会影响整个程序,除非重新连接程序。

如前面的图所示,在连接了程序以后,我们可以使用glUseProgram函数来加载并使用连接好的程序。glUseProgram函数原型如下: 
    void glUseProgram(GLuint prog);
其中,prog是你要使用的程序的句柄,你也可以将它设置为0来使用固定功能管线。如果程序已经在使用的时候,对程序进行重新编译,编译后的应用程序会自动替代以前的那个被调用,这时你不需要再次调用这个函数。

完整的源代码

void setShaders() 

    char *vs;    /* 顶点shader的源代码 */ 
    char *fs;    /* 像素shader的源代码 */

/* 创建shader容器 */
    v = glCreateShader(GL_VERTEX_SHADER); 
    f = glCreateShader(GL_FRAGMENT_SHADER);

/* 从文件读取源代码 */
    vs = textFileRead("toon.vert"); 
    fs = textFileRead("toon.frag");

    const char * vv = vs; 
    const char * ff = fs;

/* 给shader容器设置源代码 */
    glShaderSource(v, 1, &vv,NULL); 
    glShaderSource(f, 1, &ff,NULL);

    free(vs); 
    free(fs);

/* 编译shader */
    glCompileShader(v); 
    glCompileShader(f);

/* 创建程序容器 */
    p = glCreateProgram();

/* 为程序添加shader */
    glAttachShader(p,v); 
    glAttachShader(p,f);

/* 连接并加载程序 */
    glLinkProgram(p); 
    glUseProgram(p); 
}

使用InfoLog

调试一个shader是非常困难的。shader的世界里没有printf,你无法在控制台中打印调试信息。但是你可以通过一些OpenGL提供的函数来获取编译和连接过程中的信息。

在shader的编译阶段,你可以使用下面的函数来查询相关信息。 
    void glGetShaderiv(GLuint object, GLenum type, int *param); 
其中,object是一个shader的句柄;type使用GL_COMPILE_STATUS;param是返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。

在连接阶段,你可以使用下面的函数来查询相关的信息。 
    void glGetProgramiv(GLuint object, GLenum type, int *param);
其中,object是一个程序的句柄;type是GL_LINK_STATUS;param是返回值,如果一切正常返回GL_TRUE代,否则返回GL_FALSE。

上面的两个函数中的type参数还可以取其他的值来获取其他的信息,具体内容参见相关书籍。

当错误产生的时候,我们可以从InfoLog中获得更多的信息。InfoLog中存储了关于上一个操作执行时的相关信息,比如编译阶段的警告和错误,以及连接阶段产生的问题。不幸的是对于错误信息没有统一的标准,所以不同的硬件或驱动程序将提供不同的错误信息。

为了能够获得特定的shader或程序的InfoLog,我们可以调用下面的函数: 
    void glGetShaderInfoLog(GLuint object, int maxLen, int *len, char *log); 
    void glGetProgramInfoLog(GLuint object, int maxLen, int *len, char *log);
其中,object是一个shader的句柄或是一个程序的句柄;maxLen从InfoLog中获得的最大字符数;len实际从InfoLog中返回的字符数;log就是log本身。

上面的两个函数需要知道InfoLog的具体长度,以便为保存返回信息的字符数组分配空间。我们可以通过下面的函数来得到InfoLog的实际长度: 
    void glGetShaderiv(GLuint object, GLenum type, int *param); 
    void glGetProgramiv(GLuint object, GLenum type, int *param); 
其中,其中,object是一个shader的句柄或是一个程序的句柄;type使用GL_INFO_LOG_LENGTH;param是返回值,返回了InfoLog的长度。

清理


当不再需要某个shader或某个程序的时候,需要对其进行清理,以释放资源。前面,提到过如何向一个程序中添加一个shader。我们也可调用下面的函数来将一个shader从一个程序中除掉: 
    void glDetachShader(GLuint program, GLuint shader);
其中,program包含shader的程序;shader是要被排除的shader。

我们可以使用下面的函数来删除一个shader或一个程序: 
  void glDeleteShader(GLuint id); 
    void glDeleteProgram(GLuint id); 
其中,id是要删除的shader或程序的句柄。

如果,一个shader被删除之前没有从相应的程序中排除,那么这个shader不会被实际删除,而只是被标记为被删除;当shader被从程序中排除的时候,才会被真正地删除。

posted on 2011-01-09 15:43 大龙 阅读(3876) 评论(1)  编辑 收藏 引用

评论

# re: 为GLSL脚本搭建运行环境 2011-04-07 14:41 andrewhunter

挺一个  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理