zoukankan      html  css  js  c++  java
  • 【OpenGL】GLSL中的函数和子程序(subroutines)

    这篇文章里讲一下在GLSL如何使用函数和子程序(subroutines)。


    在GLSL中使用函数

    GLSL支持函数,它们的语法结构和C很相似。但是调用约定会有所不同。下面,我们以一个普通的ADS(ambient,diffuse,specular)shader为例,熟悉一下GLSL中函数的用法。

    Vertex Shader:

    #version 400
    layout (location = 0)in vec3 VertexPosition;
    layout (location = 1)in vec3 VertexNormal;
    out vec3LightIntensity;
    struct LightInfo {
        vec4 Position; // Light position in eyecoords.
        vec3 La; // Ambient light intensity
        vec3 Ld; // Diffuse light intensity
        vec3 Ls; // Specular light intensity
    };
    uniform LightInfoLight;
    struct MaterialInfo {
        vec3 Ka; // Ambient reflectivity
        vec3 Kd; // Diffuse reflectivity
        vec3 Ks; // Specular reflectivity
        float Shininess; // Specular shininessfactor
    };
    uniform MaterialInfoMaterial;
    uniform mat4ModelViewMatrix;
    uniform mat3NormalMatrix;
    uniform mat4ProjectionMatrix;
    uniform mat4 MVP;
    void getEyeSpace( outvec3 norm, out vec4 position )
    {
        norm = normalize( NormalMatrix *VertexNormal);
        position = ModelViewMatrix *vec4(VertexPosition,1.0);
    }
    vec3 phongModel( vec4position, vec3 norm )
    {
        vec3 s = normalize(vec3(Light.Position -position));
        vec3 v = normalize(-position.xyz);
        vec3 r = reflect( -s, norm );
        vec3 ambient = Light.La * Material.Ka;
        float sDotN = max( dot(s,norm), 0.0 );
        vec3 diffuse = Light.Ld * Material.Kd *sDotN;
        vec3 spec = vec3(0.0);
        if( sDotN > 0.0 )
            spec = Light.Ls * Material.Ks *
                pow( max( dot(r,v), 0.0 ),Material.Shininess );
        return ambient + diffuse + spec;
    }
    void main()
    {
        vec3 eyeNorm;
        vec4 eyePosition;
        // Get the position and normal in eye space
        getEyeSpace(eyeNorm, eyePosition);
        // Evaluate the lighting equation.
        LightIntensity = phongModel( eyePosition,eyeNorm );
        gl_Position = MVP * vec4(VertexPosition,1.0);
    }


    上面的shader略微有点长……没事,我们一点一点来看。

    layout (location = 0)in vec3 VertexPosition;
    layout (location = 1)in vec3 VertexNormal;
    out vec3LightIntensity;
    struct LightInfo {
        vec4 Position; // Light position in eyecoords.
        vec3 La; // Ambient light intensity
        vec3 Ld; // Diffuse light intensity
        vec3 Ls; // Specular light intensity
    };
    uniform LightInfoLight;
    struct MaterialInfo {
        vec3 Ka; // Ambient reflectivity
        vec3 Kd; // Diffuse reflectivity
        vec3 Ks; // Specular reflectivity
        float Shininess; // Specular shininessfactor
    };


    上面代码的前两行使用了顶点属性来向shader传递信息,具体请见之前的文章:http://blog.csdn.net/candycat1992/article/details/8830894#t1
    之后定义了两个结构体LightInfo和MaterialInfo,并各自声明了一个变量,Light和Material,来表示灯光信息和材质信息。这部分内容也请见之前的文章:http://blog.csdn.net/candycat1992/article/details/8830894#t4

    uniform MaterialInfoMaterial;
    uniform mat4ModelViewMatrix;
    uniform mat3NormalMatrix;
    uniform mat4ProjectionMatrix;
    uniform mat4 MVP;


    这几行代码也没什么好说的,就是使用了uniform变量来向shader传递数据。接下来,就是我们这次第一次看到的GLSL中的函数了。

    void getEyeSpace( outvec3 norm, out vec4 position )
    {
        norm = normalize( NormalMatrix *VertexNormal);
        position = ModelViewMatrix *vec4(VertexPosition,1.0);
    }
    vec3 phongModel( vec4position, vec3 norm )
    {
        vec3 s = normalize(vec3(Light.Position -position));
        vec3 v = normalize(-position.xyz);
        vec3 r = reflect( -s, norm );
        vec3 ambient = Light.La * Material.Ka;
        float sDotN = max( dot(s,norm), 0.0 );
        vec3 diffuse = Light.Ld * Material.Kd *sDotN;
        vec3 spec = vec3(0.0);
        if( sDotN > 0.0 )
            spec = Light.Ls * Material.Ks *
                pow( max( dot(r,v), 0.0 ),Material.Shininess );
        return ambient + diffuse + spec;
    }
    


    学过C或其他计算机语言的人基本都可以看懂,和C的程序很像。这里只对它们之间的不同进行说明。在GLSL的函数中,参数都是按值传递的,也就是说传递的是对象的复制品。函数参数可以用限定词in和out,以及inout。对于输入参数(被标记为in或者inout),它们被复制给对应的参数;对于输出参数(被标志为out),在函数结束时,它们被复制给对应的参数。如果参数类型没有使用任何标记,那么它们默认的标记为in。

    我们以上面那个较短的函数getEyeSpace()为例。它接受两个输出参数,norm和position。它在main函数中被这样调用:

        vec3 eyeNorm;
        vec4 eyePosition;
        // Get the position and normal in eye space
        getEyeSpace(eyeNorm, eyePosition);


    也就是说,当函数结束后,eyeNorm和eyePosition就会得到函数计算的结果。

    当然,我们可以给in参数进行赋值,只是这样在函数结束后是没有任何效果的。

     

     

    const标识符

    const标识符可用于只读参数(标识符in,而不是out或inout)。这意味着该参数在函数内部不可以被赋值。这点和C一样。

     

     

    函数重载

    GLSL允许函数重载,这点和C也很类似,也就是说,两个同名的函数具有不同的参数类型、参数个数、以及返回值都是允许的。

     

     

    传递数组或结构体

    GLSL支持这么做,但是我们应该知道GLSL中的函数是按值传递的,因此如果我们使用参数传递了一个非常大的数组或结构体,那么就会产生大量的赋值操作,而这很有可能是我们不希望看到的。因此,另一种比较好的方法是使用全局变量。

     

     

    怎么,还是很简单的吧!下面我们来看一个更高级的GLSL语法。

     

     

    在GLSL中使用子程序

    有时候,我们可能希望根据某个变量的值来决定使用不同的函数调用。例如,当使用shadow mapping时,我们需要根据深度信息判断当前片段是否在阴影中,如果在就只使用环境光渲染,如果不在就使用正常的ADS渲染。在GLSL中,子程序(subroutines)就可以帮助我们实现这样的功能。它根据一个变量的值,将一个函数调用绑定到一系列函数定义上。这和C++中的函数指针很类似。这时,一个uniform变量被当成一个函数指针,并且可以被用于调用一个函数。它可以被OpenGL赋值,从而绑定到其中某一个函数上。所有的子程序不需要具有相同的函数名字,但是必须有相同的参数列表和函数返回值。

    通过使用子程序,我们可以不需要动态更换shader,或者在shader用根据一个uniform的值使用if判断句。要知道,在shader中,性能是非常重要的。而一个判断语句或者shader更替是非常耗性能的。

    下面就举例说明如何使用子程序。在下面的程序里,我们想要用两种方法渲染一个茶壶,一个程序使用正常的ADS渲染,一个只使用diffuse渲染。shader的主要部分和上面的程序基本一样,只是用到了子程序来选择渲染方式。

    Vertex Shader:

    #version 400
    subroutine vec3shadeModelType( vec4 position, vec3 normal);
    subroutine uniformshadeModelType shadeModel;
    layout (location = 0)in vec3 VertexPosition;
    layout (location = 1)in vec3 VertexNormal;
    out vec3LightIntensity;
    struct LightInfo {
        vec4 Position; // Light position in eyecoords.
        vec3 La; // Ambient light intensity
        vec3 Ld; // Diffuse light intensity
        vec3 Ls; // Specular light intensity
    };uniform LightInfoLight;
    struct MaterialInfo {
        vec3 Ka; // Ambient reflectivity
        vec3 Kd; // Diffuse reflectivity
        vec3 Ks; // Specular reflectivity
        float Shininess; // Specular shininessfactor
    };uniformMaterialInfo Material;
    uniform mat4ModelViewMatrix;
    uniform mat3NormalMatrix;
    uniform mat4ProjectionMatrix;
    uniform mat4 MVP;
    void getEyeSpace( outvec3 norm, out vec4 position )
    {
        norm = normalize( NormalMatrix *VertexNormal);
        position = ModelViewMatrix *vec4(VertexPosition,1.0);
    }
    subroutine(shadeModelType )
    vec3 phongModel( vec4position, vec3 norm )
    {
        // The ADS shading calculations go here(see: "Using
        // functions in shaders," and"Implementing
        // per-vertex ambient, diffuse and specular(ADS) shading")
        …
    }
    subroutine(shadeModelType )
    vec3 diffuseOnly(vec4 position, vec3 norm )
    {
        vec3 s = normalize( vec3(Light.Position -position) );
        return Light.Ld * Material.Kd * max( dot(s,norm), 0.0 );
    }
    void main()
    {
        vec3 eyeNorm;
        vec4 eyePosition;
        getEyeSpace(eyeNorm, eyePosition);
        // Evaluate the shading equation. This willcall one of
        // the functions: diffuseOnly orphongModel.
        LightIntensity = shadeModel(eyePosition, eyeNorm );
        gl_Position = MVP *vec4(VertexPosition,1.0);
    }


    首先,前两行定义了一个子程序类型,然后声明了一个子程序类型的uniform变量,并把它命名为shaderModel:

    subroutine vec3shadeModelType( vec4 position, vec3 normal);
    subroutine uniformshadeModelType shadeModel;


    和C程序中的函数声明很像,一个子程序类型声明包含了子程序类型名称、参数列表(可选)以及返回值。shaderModel被当成一个函数指针,并在之后的OpenGL代码中被赋值到其中一个函数上。

    随后,我们定义了两个子函数,这是通过在它们的函数定义前添加前缀:

    subroutine (shadeModelType )


    使用这个前缀表明,下面的函数应当和子函数类型声明中的声明相匹配(包括参数列表以及返回值,名字是任意的)。然后,我们在main函数中使用shadeModel调用了其中一个函数。那么我们究竟在哪里指明该调用哪个函数呢?答案是,在我们的OpenGL代码里,通常也就是我们的C++代码里。下面是这个例子中使用的OpenGL代码:

    GLuint adsIndex =glGetSubroutineIndex( programHandle, GL_VERTEX_SHADER,"phongModel" );
    GLuint diffuseIndex =glGetSubroutineIndex(programHandle, GL_VERTEX_SHADER, "diffuseOnly");
    glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &adsIndex);
    ... // Render theleft teapot
    glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &diffuseIndex);
    ... // Render theright teapot
    // Get the positionand normal in eye space


    为了在OpenGL代码里给一个子程序uniform变量赋值,我们需要按照下面的步骤。

    首先,使用glGetSubroutineIndex得到每个子程序的索引:

    GLuint adsIndex =glGetSubroutineIndex( programHandle, GL_VERTEX_SHADER,"phongModel" );

     

    函数的第一个参数是shader程序句柄,第二个参数是shader等级,因为这里我们是在vertex shader中定义的,因此使用GL_VERTEX_SHADER。第三个参数是子程序的名字。这样,我们就可以得到两个子程序的索引,并把它们存储在adsIndex和diffuseIndex中。

     

    然后,为了给shadowModel赋值,我们调用glUniformSubroutinesuiv来选定使用的子程序:

    glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &adsIndex);
    ... // Render theleft teapot
    glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &diffuseIndex);
    ... // Render theright teapot


    这个函数被用于同时给多个子程序uniform变量赋值。函数的第一个参数是shader等级,这里仍然使用GL_VERTEX_SHADER。第二个参数是赋值的uniform变量数。第三个参数是一个数组的指针,它指向需要赋值的uniform变量的索引。因为这里我们只有一个子程序uniform变量,因此是需要对adsIndex和diffuseIndex取地址即可。但是,当我们真的有很多子程序uniform变量需要赋值时,就应该使用一个真正的数组。通常,数组的第i个值被赋给索引为i的子程序uniform变量。因为我们只提供一个值,因此我们设置子程序uniform索引为0。

    但是,我们怎么知道我们的子程序uniform变量索引为0呢?在调用glUniformSubroutinesuiv之前,我们可没有查询它的索引!这是因为,我们默认OpenGL将会自动从0开始连续地为我们的子程序进行索引。如果我们有多个子程序uniform变量,我们可以(也应该)使用glGetSubroutineUniformLocation来查询它们的索引,然后再据此给我们的数组变量排序。

     

    其他

    同一个子程序可用于多个子程序类型。我们只需要使用逗号隔开不同的子程序类型即可,即使用下面的标识符:

    subroutine( type1,type2 )

     这里补充一点,subroutine功能是在OpenGL 4.0 版本里才增加的,也就是说4.0以前版本,包括OpenGL 3.3都是不支持的。如果你发现你的程序报错说,需要支持扩展ARB_shader_subroutine,那么你就应该更新你的显卡了。唉,我好像我的更新不了了诶。


    更多关于OpenGL发展历史,请详见Wikipedia

     

  • 相关阅读:
    Lua环境
    WebKit
    Net线程间通信的异步机制
    Cucumber入门1 传统流程下的使用
    Windows Server 2008中安装IIS7.0
    WebCore
    百度云计算平台Python环境试用
    认识ASP.NET MVC的5种AuthorizationFilter
    浅谈java中常见的排序
    go语言中goroutine的使用
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6314726.html
Copyright © 2011-2022 走看看