• 转)shader脚本的加载编译连接讲解


     

    假设你已经编写好了一对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被从程序中排除的时候,才会被真正地删除。

  • 相关阅读:
    【leetcode】修剪二叉搜索树
    053-621
    053-620
    053-619
    053-618
    053-617
    053-616
    053-615
    053-614
    053-613
  • 原文地址:https://www.cnblogs.com/monnRedShine/p/3116645.html
Copyright © 2020-2023  润新知