shader之间传递数据实在是太常用了. 下面我们总结几种shader之间传递数据的方法.

Name based matching

最简单,也是最常用的一种传递方式是依靠名字进行匹配. 例如我们从vertex shader向fragment shader传递颜色:

//vertex shader
#version 460 core
out vec4 color;
void main(void)
{
color = ...;
...
} //fragment shader
#version 460 core
in vec4 color;
out vec4 outputColor;
void main(void)
{
outputColor = color;
}

对于只有两个shader stage的程序,这种方式非常方便.

假如我们在vertex shader和fragment shader中间插入geometry shader,并且将color从vertex shader传递到geometry shader,然后再传递到fragment shader. 就会写出类似的代码:

//vertex shader
out vec4 color;
----------------------
//geometry shader
in vec4 color[];
out vec4 colorFromGeom;
----------------------
//fragment shader
in vec4 colorFromGeom;

如果我们绘制某些图元的时候不需要geometry shader,直接拿上面的vertex shader和fragment shader是没法使用的,因为color和colorFromGeom名字不相同. 那我们就只能重写一个fragment shader,仅仅把名字colorFromGeom改成color,以便与vertex shader匹配起来. 但是这样我们就必须同时维护两个几乎一样的fragment shader!

Location based matching

我们可以为变量分配一个location,只要输出变量的location与输入变量的location相同,它们就能匹配成功,即使名字不一样也没关系!举个最简单的例子:

//vertex shader
layout (location = 0) out vec3 normalOut;
layout (location = 1) out vec4 colorOut;
-----------------------------------------
//fragment shader
layout (location = 0) in vec3 normalIn;
layout (location = 1) in vec4 colorIn;

因为vertex shader中的normalOut与fragment shader中的normalIn的location都是0,所以它们能够匹配起来. colorOut和colorIn也是一样.

这样,刚刚提到的Named based matching的问题就能够得到解决.

//vertex shader
layout (location = 0) out vec3 normalOut;
layout (location = 1) out vec4 colorOut;
-----------------------------------------
//geometry shader
layout (location = 0) in vec3 normalIn[];
layout (location = 1) in vec4 colorIn[]; layout (location = 0) out vec3 normalOut; //in和out修饰的变量,即使location相同也没关系
layout (location = 1) out vec4 colorOut;
-----------------------------------------
//fragment shader
layout (location = 0) in vec3 normalIn;
layout (location = 1) in vec4 colorIn;

可以看到即使去掉geometry shader,vertex shader的normalOut、colorOut也能与fragment shader的normalIn、colorIn匹配起来.

这种方法也引入了一个新的问题,考虑如下代码:

//vertex shader
layout (location = 0) out vec3 someAttribute[2];
layout (location = 1) out vec4 colorOut; //错误,colorOut和someAttribute[1]同时占用location 1
void main(void)
{
someAttribute[0] = ...;
someAttribute[1] = ...;
colorOut = ...;
...
}

由于一个location最多存放 4*32 = 128 个字节,也就是最多能够存放4个int或者float类型的数据. someAttribute[0]会占用location 0,someAttribute[1]和colorOut会同时占用location 1. 我们需要把colorOut的location改为2,使之独享一个location.

//vertex shader
layout (location = 0) out vec3 someAttribute[2];
layout (location = 2) out vec4 colorOut; //正确,colorOut和someAttribute[2]不再同时占用相同的location

也就是说,这种方法需要我们自己推算输入输出变量的location. 如果我们把someAttribute[2]改为someAttribute[3],那colorOut的location就需要改为3. 维护这些变量的location稍微有一丢丢麻烦.

Block based matching

第三种方法是通过把输入或者输出变量打成一个组,即interface block.

//vertex shader
#version 460 core
out Data //matched by "Data"
{
vec3 normal;
float scale;
vec3 color;
vec2 texCoord;
} vs_out;
void main(void)
{
vs_out.normal = ...; //referenced by "vs_out"
vs_out.scale = ...;
}
//fragment shader
#version 460 core
in Data //matched by "Data"
{
vec3 normal;
float scale;
vec3 color;
vec2 texCoord;
} fs_in; //referenced by "fs_in"
...

interface block有点像结构体. 它们通过block name进行匹配,也就是以Data这个名字,使vertex shader输出数据和fragment shader的输入数据对应起来;然后通过instance name(vs_out,fs_in)进行引用,例如vs_out.normal.

就算中间插入一个geometry shader,那也是OK的.

//geometry shader
in Data
{
vec3 normal;
float scale;
vec3 color;
vec2 texCoord;
} gs_in;
out Data
{
vec3 normal;
float scale;
vec3 color;
vec2 texCoord;
} gs_out;

甚至,我们可以省略instance name.

#version 460 core
out Data
{
vec3 normal;
float scale;
vec3 color;
vec2 texCoord;
};
void main(void)
{
normal = ...;
scale = ...;
}

引用的时候直接用数据成员的名字. 不过这样退化成了name based matching,没有实际意义.

有一点需要注意,就是我们不仅需要匹配block name,member name也是需要匹配的. 例如,下面的代码就是错误的.

//vertex shader
out Data
{
vec3 normal;
float scale;
vec3 color;
vec2 texCoord;
} vs_out;
-------------------
//fragment shader
in Data
{
vec3 Normal; //错误,Normal无法和normal匹配
float scale;
vec3 color;
vec2 texCoord;
} fs_in;

这种方法看起来完美无缺啊. 既能够解决name based matching的代码不能复用问题,还不用维护location based matching的location. 但是,我还有杀手锏.

Block based matching with location

其实,我们也可以为interface block指定一个起始的location.

//vertex shader
layout(location = 0) out Data
{
vec3 someAttribute[2];
vec2 texCoord;
float scale;
} vs_out;
-------------------
//fragment shader
layout(location = 0) in Data
{
vec3 some Attribute[2];
vec2 texCoord;
float scale;
} fs_in;

编译器会根据起始的location为每个数据成员分配一个location.

如果我们写出类似的代码:

//vertex shader
layout(location = 0) out Data
{
vec3 someAttribute[2];
vec2 texCoord;
float scale;
} vs_out;
layout(location = 3) out vec3 color_vs_out; //错误,color_vs_out的location必须大于等于4

someAttribute占用两个location,texCoord占用一个location,scale占用一个loction,所以color_vs_out的location必须在大于等于4.

为block指定location之后,还可以为成员重新指定location.

//vertex shader
layout(location = 0) out Data
{
vec3 someAttribute[2];
layout(location = 4) vec2 texCoord;
float scale;
} vs_out;

同学们可以自行推算someAttribute和scale两个成员的location,并进行验证.

但是,不要太异想天开. 下面的代码就是错误的.

//vertex shader
out Data
{
vec3 someAttribute[2];
layout(location = 4) vec2 texCoord; //错误,其它成员没有分配location
float scale;
} vs_out;

这种写法无法为someAttribute推算出location. 所以编译器直接从语法上杜绝了此类写法,哪怕你为someAttribute[2]指定一个location也不行.

//vertex shader
out Data
{
layout(location = 0) vec3 someAttribute[2]; //错误,仍然无法为其它成员分配location
vec2 texCoord;
float scale;
} vs_out;

不过,我们倒是可以为所有的成员显式指定location,并且不用为block指定起始location.

//vertex shader
out Data
{
layout(location = 0) vec3 someAttribute[2]; //正确,为所有的成员分配了location
layout(location = 2) vec2 texCoord;
layout(location = 3) float scale;
} vs_out;

有的同学可能会问,这样做有什么意义,还不如为一个block指定location方便啊.

当然有意义,我表演给你看.

//vertex shader
out Data
{
layout(location = 0) vec3 someAttribute[2];
layout(location = 2, component = 0) vec2 texCoord;
layout(location = 2, component = 2) float scale;
layout(location = 3) vec4 color;
} vs_out;

刚才我们提到过,每个location总计4*32 = 128个字节的容量. 可以认为总共有4个component,每个component的容量是32字节. 上面的代码,texCoord和scale均占用的是location 2,texCoord占用两个component(0和1),scale占用一个component(2). 有了component关键字,我们就能够充分利用location的空间.

不过,这样我们又要保证component不能冲突了.

付出总是和回报成正比,手动挡就是比自动挡有操控感.

但是,单纯的location based matching是不能使用component关键字的,例如:

layout(location = 2, component = 0) vec2 texCoord;	//错误,component只能在interface block中使用
layout(location = 2, component = 2) float scale;

好了,不胡扯了,说了这么多,有的同学可能都蒙圈了,知道你们最喜欢看总结,我们一起来总结一下.

总结

只有两个选择:

  1. Location based matching

  2. Block based matching with location

因为从GLSL转换为SPIR-V的时候,需要为每个输入输出成员指定location. Named base matchingBlock based matching without location转换SPIR-V的时候会报错.

所以,除非你有充分的理由说服自己(例如,我不想用Vulkan). 否则,就选上面这两个.

完~

shader之间的数据传递的更多相关文章

  1. ASP.NET MVC3中Controller与View之间的数据传递

    在ASP.NET MVC中,经常会在Controller与View之间传递数据,因此,熟练.灵活的掌握这两层之间的数据传递方法就非常重要.本文从两个方面进行探讨: 一.  Controller向Vie ...

  2. Activity之间的数据传递

    最常用的Activity之间的数据传递. btnStartAty1.setOnClickListener(new View.OnClickListener() { @Override public v ...

  3. ASP.NET MVC 之控制器与视图之间的数据传递

    今天,我们来谈谈控制器与视图之间的数据传递. 数据传递,指的是视图与控制器之间的交互,包括两个方向上的数据交互,一个是把控制器的数据传到视图中,在视图中如何显示数据,一个是把视图数据传递到控制器中, ...

  4. (转载)Javascript操作表单之间的数据传递

    (转载)http://www.aspxhome.com/javascript/skills/200710/214825.htm 今天有朋友问我关于用JAVASCRIPT来进行页面各表单之间的数据传递的 ...

  5. iOS开发中视图控制器ViewControllers之间的数据传递

    iOS开发中视图控制器ViewControllers之间的数据传递 这里我们用一个demo来说明ios是如何在视图控制器之间传递重要的参数的.本文先从手写UI来讨论,在下一篇文章中讨论在storybo ...

  6. React中父组件与子组件之间的数据传递和标准化的思考

    React中父组件与子组件之间的数据传递的的实现大家都可以轻易做到,但对比很多人的实现方法,总是会有或多或少的差异.在一个团队中,这种实现的差异体现了每个人各自的理解的不同,但是反过来思考,一个团队用 ...

  7. Vue 爬坑之路(二)—— 组件之间的数据传递

    Vue 的组件作用域都是孤立的,不允许在子组件的模板内直接引用父组件的数据.必须使用特定的方法才能实现组件之间的数据传递. 首先用 vue-cli 创建一个项目,其中 App.vue 是父组件,com ...

  8. sqoop实现关系型数据库与hadoop之间的数据传递-import篇

    由于业务数据量日益增长,计算量非常庞大,传统的数仓已经无法满足计算需求了,所以现在基本上都是将数据放到hadoop平台去实现逻辑计算,那么就涉及到如何将oracle数仓的数据迁移到hadoop平台的问 ...

  9. Activity之间的数据传递-android学习之旅(四十七)

    activity之间的数据传递主要有两种,一种是直接发送数据,另一种接受新启动的activity返回的数据,本质是一样的 使用Bundle传递数据 Intent使用Bundle在activity之间传 ...

随机推荐

  1. 41-native关键字的理解

    使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++等非Java 语言实现的,并且被编译成了 DLL,由 java 去调用. (1)为什么要用 native 方法 java ...

  2. ios textView跟随键盘的移动

    实现效果: textview 能够跟随键盘的移动而移动 效果图如下: 下边贴上主要的代码: 1.创建textview @interface ViewController ()<UITextVie ...

  3. Python使用Tornado+Redis维护ADSL拨号服务器代理池

    们尝试维护过一个免费的代理池,但是代理池效果用过就知道了,毕竟里面有大量免费代理,虽然这些代理是可用的,但是既然我们能刷到这个免费代理,别人也能呀,所以就导致这个代理同时被很多人使用来抓取网站,所以当 ...

  4. Java入门到实践系列(2)——Java环境搭建

    一.上集回顾 在<Java入门到实践系列(1)--Java简介>中提到过,Java程序是运行在Java虚拟机的,也展示过下面这张图. JDK:Java程序开发工具包. JRE:Java运行 ...

  5. 刚体验完RabbitMQ?一文带你SpringBoot+RabbitMQ方式收发消息

    人生终将是场单人旅途,孤独之前是迷茫,孤独过后是成长. 楔子 这篇是消息队列RabbitMQ的第二弹. 上一篇的结尾我也预告了本篇的内容:利用RabbitTemplate和注解进行收发消息,还有一个我 ...

  6. 2020-04-13:怎么在日志里排查错误,该用哪些Linux命令

    能通过less命令打开文件,通过Shift+G到达文件底部,再通过?+关键字的方式来根据关键来搜索信息. 能通过grep的方式查关键字,具体用法是, grep 关键字 文件名,如果要两次在结果里查找的 ...

  7. Unity3D天气系统插件UniStorm插件使用说明

    一.开始使用 下载并导入UniStorm插件就可以进行使用了,该资源可以在Assets Store中搜索并下载,也可以评论我会发给你,该插件功能十分强大,内置25种天气类型,并包含音效,完全可以满足我 ...

  8. ExcelWeb脚本助手,自定义脚本,批量操作Excel与网页

    ExcelWeb脚本助手,是一款可以自定义脚本操控Excel和浏览器的工具.提供了简单实用的Excel与Browser的API调用,通过自建脚本或自建项目,随意定制. 可以非常方便的根据Excel中的 ...

  9. 【UVa1635】Irrelevant Elements - 唯一分解定理

    题意 给你 \(n\) 个数,每次求出相邻两个数的和组成新数列.经过 \(n-1\) 次操作后,得到一个数.求这个数 \(mod \ m\) 与哪些项无关. 如:当 \(m=2 \ , \ n=2\) ...

  10. MyBatis学习-使用Druid连接池将Maybatis整合到spring

    目录 前言 什么是Druid连接池 Druid可以做什么? 导入库包 连接oracle 连接mysql 导入mybatis 导入druid 导入spring-jdbc包 导入spring包 导入spr ...