【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。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux7 共享盘创建,使用CentOS7建立samba文件共享服务器

1、安装samba检查samba是否已经安装[rootbak1 ~]# rpm -qa | grep samba没有任何回显则表示未安装软件包 sambayum安装samba[rootbak1 ~]# yum -y install samba samba-client samba-common再次检查安装结果[rootbak1 var]# rpm -qa | grep sambasamba-common-libs-4.6.2-12.el…

批量造数据的一种方法

实现目的:一张表Table中有A、B、C三个字段,并有很多值,开发测试需要,可以执行这样的语句批量复制插入数据: insert into Table(A,B,C)select A,B,C from Table 转载于:https://www.cnblogs.com/tianqy/p/3965292.html

【Modern OpenGL】第一个三角形

>说明:跟着learnopengl的内容学习,不是纯翻译,只是自己整理记录。>强烈推荐原文,无论是内容还是排版。 [原文链接](http://learnopengl.com/#!Getting-started/OpenGL)本文地址:http://blog.csdn.net/aganlengzi…

13c语言中的文件是一种流式文件,读写时均以字符为单位.,C语言判断题部分.doc...

C语言判断题部分?第一章C语言中,字符数据用格式说明"%c"来输出,字符串数据用格式说明"%s"来输出。TC语言中,关键字及编译预处理命令用小写字母书写。T二元运算符均可以和赋值运算符构成复合的赋值运算符。TC语言中&…

UI Automation 简介

转载,源地址: http://blog.csdn.net/ffeiffei/article/details/6637418 MS UI Automation(Microsoft User Interface Automation:UIA)是随.net framework3.0一起发布的,虽然在如今这个几乎每天都有各种新名…

【OpenGL4.0】GLSL渲染语言入门与VBO、VAO使用:绘制一个三角形

以前都是用Cg的,现在改用GLSL,又要重新学,不过两种语言很多都是相通的。下面的例子是实现绘制一个三角形的简单程序。采用了VBO(veretx buffer object)、VAO(vertex array object)等OpenGL的一些…

catia linux下载64位,CATIA V5 CATSysDemon.exe缓冲区溢出漏洞

发布日期:2014-02-24更新日期:2014-02-25受影响系统:3ds catia-v5描述:--------------------------------------------------------------------------------CATIA是数字产品定义及生命周期管理使用的CAD、CAE、CAM应用集成软件包…

eclipse 中文乱码

eclipse 中页面中中文正常预览出现中文乱码 解决方法: windows->preferences->general->content types->text(展开)选择页面我的是php页面就选择phpcontent type 在窗口下面default encoding 中输入gbk 。点击update 就可以了转载于:https://www.cnblog…

【Modern OpenGL】Shader

Shaders 正如在上一篇教程中提到的,shader是在GPU中运行的小程序。如上一个教程中实现的最简单的vertex shader和fragment shader,一个shader基本上负责图形渲染流水线中的一个阶段的功能。从根本上来说,shader就是将输入转化成输出的操作。而…

c语言编译的手机软件,C语言编译工具

软件介绍C语言编译工具app是一款功能十分强大的手机编译、更换代码的软件工具,C语言编译工具app可以快速进行代码翻译、代码查找、替换等,它支持多种代码语言,欢迎下载!C语言编译工具app内容简单方便功能强大的代码编译器&#xf…

2014.9.13模拟赛【数位和乘积】

数位和乘积(digit.cpp/c/pas) 【题目描述】 一个数字的数位和乘积为其各位数字的乘积。求所有的N位数中有多少个数的数位和乘积恰好为K。请注意,这里的N位数是可以有前导零的。比如01,02视为二位数,但是他们的数位和乘积都是0。 【输入格式】…

c语言中vector函数大全,vector :: empty()函数,以及C ++ STL中的示例

C vector :: empty()函数矢量::空()是一个库函数“载体”头,它是用来检查给定的矢量是否是一个空的载体或没有,它返回一个真,如果矢量大小为0,否则返回假。注意:要使用向量,请包含标头。vector :: empty()…

【Modern OpenGL】OpenGL简介

说明:跟着learnopengl的内容学习,不是纯翻译,只是自己整理记录。 强烈推荐原文,无论是内容还是排版。 原文链接 本文地址: http://blog.csdn.net/aganlengzi/article/details/50354162 OpenGL 在真正开始我们的探索之旅时&…

c语言课程设计修改订单流程图,C语言课程设计————写下流程图! 谢谢

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼printf("---You have success read data from file!!!---\n");return h; /*返回头指针*/}/*追加记录到文件*/void append(){FILE *fp; /*定义指向文件的指针*/STUDENT *info; /*新记录指针*/int s1,i;char infile[10]; /…

微信公众账户模拟登陆后的一系列操作

<?php header("content-type:text/html;charsetutf-8");/*** wx_mass* * 完成微信公众账户模拟登陆后的一系列操作* mass($content) 调用微信群发接口&#xff0c;群发文本信息* getUserList($page,$pagesize,$group) 获取用户信息* * 群发demo* * $userarray(ac…

【Modern OpenGL】纹理 Textures

说明&#xff1a;跟着learnopengl的内容学习&#xff0c;不是纯翻译&#xff0c;只是自己整理记录。 强烈推荐原文&#xff0c;无论是内容还是排版。 原文链接 本文地址&#xff1a; http://blog.csdn.net/aganlengzi/article/details/50421006 纹理 Textures 为了使我们创建的…

华北水利水电C 语言实验11,华北水利水电大学C语言实验11.doc

华北水利水电大学C语言实验11C语言程序设计实验报告实验11、结构体程序设计班级 2013156 学号 201315613 姓名 吴浩【实验目的】(1)掌握结构体类型的概念、定义和使用&#xff1b;(2)掌握结构体数组、结构体指针的定义和使用&#xff1b;【实验内容及步骤】1、输入5位同学的一组…

Android Activity无法启动。

在正常情况上&#xff0c;这是由于没的在AndroidManifest.xml中进行注册引起的。转载于:https://www.cnblogs.com/zhaojianwei/p/3971238.html

C语言依次显示图片,c语言能显示图片吗

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼/* Svga64k.bgi 测试文件 */#include "graphics.h"#include "Svga256.h"#include "stdio.h"#include "fcntl.h"#include "malloc.h"#include "io.h"int huge Return_…

HDU 2296 Ring AC自动机 + DP

题意&#xff1a;给你n个模式串&#xff0c;每个模式串有一个得分&#xff0c;让你构造出一个长度为N之内且分数最高的文本串;输出字典序列最小的。 解题思路&#xff1a; AC自动机 DP &#xff0c; 不过要输出字典序列最小&#xff0c;多开一个 一个三维字符串来辅助二维DP&…