【OpenGL】GLSL中的函数和子程序(subroutines)

2024-02-12 10:58

本文主要是介绍【OpenGL】GLSL中的函数和子程序(subroutines),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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


在GLSL中使用函数

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

Vertex Shader:

  1. #version 400
  2. layout (location = 0) in vec3 VertexPosition;
  3. layout (location = 1) in vec3 VertexNormal;
  4. out vec3 LightIntensity;
  5. struct LightInfo {
  6. vec4 Position; // Light position in eyecoords.
  7. vec3 La; // Ambient light intensity
  8. vec3 Ld; // Diffuse light intensity
  9. vec3 Ls; // Specular light intensity
  10. };
  11. uniform LightInfo Light;
  12. struct MaterialInfo {
  13. vec3 Ka; // Ambient reflectivity
  14. vec3 Kd; // Diffuse reflectivity
  15. vec3 Ks; // Specular reflectivity
  16. float Shininess; // Specular shininessfactor
  17. };
  18. uniform MaterialInfo Material;
  19. uniform mat4 ModelViewMatrix;
  20. uniform mat3 NormalMatrix;
  21. uniform mat4 ProjectionMatrix;
  22. uniform mat4 MVP;
  23. void getEyeSpace( outvec3 norm, out vec4 position )
  24. {
  25. norm = normalize( NormalMatrix *VertexNormal);
  26. position = ModelViewMatrix *vec4(VertexPosition, 1.0);
  27. }
  28. vec3 phongModel( vec4position, vec3 norm )
  29. {
  30. vec3 s = normalize(vec3(Light.Position -position));
  31. vec3 v = normalize(-position.xyz);
  32. vec3 r = reflect( -s, norm );
  33. vec3 ambient = Light.La * Material.Ka;
  34. float sDotN = max( dot(s,norm), 0.0 );
  35. vec3 diffuse = Light.Ld * Material.Kd *sDotN;
  36. vec3 spec = vec3( 0.0);
  37. if( sDotN > 0.0 )
  38. spec = Light.Ls * Material.Ks *
  39. pow( max( dot(r,v), 0.0 ),Material.Shininess );
  40. return ambient + diffuse + spec;
  41. }
  42. void main()
  43. {
  44. vec3 eyeNorm;
  45. vec4 eyePosition;
  46. // Get the position and normal in eye space
  47. getEyeSpace(eyeNorm, eyePosition);
  48. // Evaluate the lighting equation.
  49. LightIntensity = phongModel( eyePosition,eyeNorm );
  50. gl_Position = MVP * vec4(VertexPosition, 1.0);
  51. }


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

  1. layout (location = 0) in vec3 VertexPosition;
  2. layout (location = 1) in vec3 VertexNormal;
  3. out vec3LightIntensity;
  4. struct LightInfo {
  5. vec4 Position; // Light position in eyecoords.
  6. vec3 La; // Ambient light intensity
  7. vec3 Ld; // Diffuse light intensity
  8. vec3 Ls; // Specular light intensity
  9. };
  10. uniform LightInfoLight;
  11. struct MaterialInfo {
  12. vec3 Ka; // Ambient reflectivity
  13. vec3 Kd; // Diffuse reflectivity
  14. vec3 Ks; // Specular reflectivity
  15. float Shininess; // Specular shininessfactor
  16. };


上面代码的前两行使用了顶点属性来向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 mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;


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

  1. void getEyeSpace( out vec3 norm, out vec4 position )
  2. {
  3. norm = normalize( NormalMatrix *VertexNormal);
  4. position = ModelViewMatrix *vec4(VertexPosition, 1.0);
  5. }
  6. vec3 phongModel( vec4 position, vec3 norm )
  7. {
  8. vec3 s = normalize(vec3(Light.Position -position));
  9. vec3 v = normalize(-position.xyz);
  10. vec3 r = reflect( -s, norm );
  11. vec3 ambient = Light.La * Material.Ka;
  12. float sDotN = max( dot(s,norm), 0.0 );
  13. vec3 diffuse = Light.Ld * Material.Kd *sDotN;
  14. vec3 spec = vec3( 0.0);
  15. if( sDotN > 0.0 )
  16. spec = Light.Ls * Material.Ks *
  17. pow( max( dot(r,v), 0.0 ),Material.Shininess );
  18. return ambient + diffuse + spec;
  19. }


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

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

  1. vec3 eyeNorm;
  2. vec4 eyePosition;
  3. // Get the position and normal in eye space
  4. 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:

  1. #version 400
  2. subroutine vec3shadeModelType( vec4 position, vec3 normal);
  3. subroutine uniformshadeModelType shadeModel;
  4. layout (location = 0) in vec3 VertexPosition;
  5. layout (location = 1) in vec3 VertexNormal;
  6. out vec3LightIntensity;
  7. struct LightInfo {
  8. vec4 Position; // Light position in eyecoords.
  9. vec3 La; // Ambient light intensity
  10. vec3 Ld; // Diffuse light intensity
  11. vec3 Ls; // Specular light intensity
  12. };uniform LightInfoLight;
  13. struct MaterialInfo {
  14. vec3 Ka; // Ambient reflectivity
  15. vec3 Kd; // Diffuse reflectivity
  16. vec3 Ks; // Specular reflectivity
  17. float Shininess; // Specular shininessfactor
  18. };uniformMaterialInfo Material;
  19. uniform mat4ModelViewMatrix;
  20. uniform mat3NormalMatrix;
  21. uniform mat4ProjectionMatrix;
  22. uniform mat4 MVP;
  23. void getEyeSpace( out vec3 norm, out vec4 position )
  24. {
  25. norm = normalize( NormalMatrix *VertexNormal);
  26. position = ModelViewMatrix *vec4(VertexPosition, 1.0);
  27. }
  28. subroutine(shadeModelType )
  29. vec3 phongModel( vec4 position, vec3 norm )
  30. {
  31. // The ADS shading calculations go here(see: "Using
  32. // functions in shaders," and"Implementing
  33. // per-vertex ambient, diffuse and specular(ADS) shading")
  34. }
  35. subroutine(shadeModelType )
  36. vec3 diffuseOnly(vec4 position, vec3 norm )
  37. {
  38. vec3 s = normalize( vec3(Light.Position -position) );
  39. return Light.Ld * Material.Kd * max( dot(s,norm), 0.0 );
  40. }
  41. void main()
  42. {
  43. vec3 eyeNorm;
  44. vec4 eyePosition;
  45. getEyeSpace(eyeNorm, eyePosition);
  46. // Evaluate the shading equation. This willcall one of
  47. // the functions: diffuseOnly orphongModel.
  48. LightIntensity = shadeModel(eyePosition, eyeNorm );
  49. gl_Position = MVP *vec4(VertexPosition, 1.0);
  50. }


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

  1. subroutine vec3 shadeModelType( vec4 position, vec3 normal);
  2. subroutine uniform shadeModelType shadeModel;


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

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

subroutine (shadeModelType )


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

  1. GLuint adsIndex =glGetSubroutineIndex( programHandle, GL_VERTEX_SHADER, "phongModel" );
  2. GLuint diffuseIndex =glGetSubroutineIndex(programHandle, GL_VERTEX_SHADER, "diffuseOnly");
  3. glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &adsIndex);
  4. ... // Render theleft teapot
  5. glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &diffuseIndex);
  6. ... // Render theright teapot
  7. // 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来选定使用的子程序:

  1. glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &adsIndex);
  2. ... // Render theleft teapot
  3. glUniformSubroutinesuiv(GL_VERTEX_SHADER, 1, &diffuseIndex);
  4. ... // 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。

这篇关于【OpenGL】GLSL中的函数和子程序(subroutines)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/702453

相关文章

PyTorch中cdist和sum函数使用示例详解

《PyTorch中cdist和sum函数使用示例详解》torch.cdist是PyTorch中用于计算**两个张量之间的成对距离(pairwisedistance)**的函数,常用于点云处理、图神经网... 目录基本语法输出示例1. 简单的 2D 欧几里得距离2. 批量形式(3D Tensor)3. 使用不

MySQL 字符串截取函数及用法详解

《MySQL字符串截取函数及用法详解》在MySQL中,字符串截取是常见的操作,主要用于从字符串中提取特定部分,MySQL提供了多种函数来实现这一功能,包括LEFT()、RIGHT()、SUBST... 目录mysql 字符串截取函数详解RIGHT(str, length):从右侧截取指定长度的字符SUBST

Kotlin运算符重载函数及作用场景

《Kotlin运算符重载函数及作用场景》在Kotlin里,运算符重载函数允许为自定义类型重新定义现有的运算符(如+-…)行为,从而让自定义类型能像内置类型那样使用运算符,本文给大家介绍Kotlin运算... 目录基本语法作用场景类对象数据类型接口注意事项在 Kotlin 里,运算符重载函数允许为自定义类型重

Pandas中统计汇总可视化函数plot()的使用

《Pandas中统计汇总可视化函数plot()的使用》Pandas提供了许多强大的数据处理和分析功能,其中plot()函数就是其可视化功能的一个重要组成部分,本文主要介绍了Pandas中统计汇总可视化... 目录一、plot()函数简介二、plot()函数的基本用法三、plot()函数的参数详解四、使用pl

Python的time模块一些常用功能(各种与时间相关的函数)

《Python的time模块一些常用功能(各种与时间相关的函数)》Python的time模块提供了各种与时间相关的函数,包括获取当前时间、处理时间间隔、执行时间测量等,:本文主要介绍Python的... 目录1. 获取当前时间2. 时间格式化3. 延时执行4. 时间戳运算5. 计算代码执行时间6. 转换为指

Python正则表达式语法及re模块中的常用函数详解

《Python正则表达式语法及re模块中的常用函数详解》这篇文章主要给大家介绍了关于Python正则表达式语法及re模块中常用函数的相关资料,正则表达式是一种强大的字符串处理工具,可以用于匹配、切分、... 目录概念、作用和步骤语法re模块中的常用函数总结 概念、作用和步骤概念: 本身也是一个字符串,其中

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

MySQL高级查询之JOIN、子查询、窗口函数实际案例

《MySQL高级查询之JOIN、子查询、窗口函数实际案例》:本文主要介绍MySQL高级查询之JOIN、子查询、窗口函数实际案例的相关资料,JOIN用于多表关联查询,子查询用于数据筛选和过滤,窗口函... 目录前言1. JOIN(连接查询)1.1 内连接(INNER JOIN)1.2 左连接(LEFT JOI

MySQL中FIND_IN_SET函数与INSTR函数用法解析

《MySQL中FIND_IN_SET函数与INSTR函数用法解析》:本文主要介绍MySQL中FIND_IN_SET函数与INSTR函数用法解析,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一... 目录一、功能定义与语法1、FIND_IN_SET函数2、INSTR函数二、本质区别对比三、实际场景案例分

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序