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. RabbitMQ学习总结(4)-消息处理机制

    1. 正常的消息流程 上面这张图,是一个正常的消息从生产到消息流程.在上一篇文章RabbitMQ学习总结(3)-集成SpringBoot中,代码里使用消息确认,消息回退机制,现在详细说一下. 2.1 ...

  2. 企业玩转DevOps转型:由弱到强,只需7步

    [摘要] 在参考业界方法并总结客户成功故事的基础上,本文提出了“七步法”路线图,希望能帮助更多的企业顺利进行DevOps转型. 从2009年诞生,DevOps已经悄然走过了10多个年头.Gartner ...

  3. Python3.x+Fiddler抓取APP数据

    随着移动互联网的市场份额逐步扩大,手机APP已经占据我们的生活,以往的数据分析都借助于爬虫爬取网页数据进行分析,但是新兴的产品有的只有APP,并没有网页端这对于想要提取数据的我们就遇到了些问题,本章以 ...

  4. 秦九韶算法 & 三分法

    前言 今天考试出了一个题 郭郭模拟退火骗了75分 于是再次把咕咕了好久的模退提上日程 如果进展顺利 明后天应该会开爬山算法和模退的博客笔记 今天先把今天考试的正解学习一下--三分法 引入 老规矩上板子 ...

  5. Windows10 无法完全关闭Hyper-V导致VirtualBox 虚拟机无法启动

    win10本来已经安装使用了VirtualBox. 突然心血来潮决定试试系统自带的虚拟机Hyper-V.发现并没有想象中的好用.随后在启用或关闭 Windows功能中关闭了Hyper-V. 这时我发现 ...

  6. .NET Core Web APi大文件分片上传研究

    前言 前两天发表利用FormData进行文件上传,然后有人问要是大文件几个G上传怎么搞,常见的不就是分片再搞下断点续传,动动手差不多也能搞出来,只不过要深入的话,考虑的东西还是很多.由于断点续传之前写 ...

  7. Visual-platform,基于Vue的可视化大屏开发GUI框架

    visual-platform 基于Vue的可视化大屏开发GUI框架 ------ CreatedBy 漆黑小T 构建用于开发可视化大屏项目的自适应布局的GUI框架. github仓库: https: ...

  8. C#LeetCode刷题之#40-组合总和 II(Combination Sum II)

    目录 问题 示例 分析 问题 该文章已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3666 访问. 给定一个数组 candidates ...

  9. C#LeetCode刷题之#14-最长公共前缀​​​​​​​(Longest Common Prefix)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3921 访问. 编写一个函数来查找字符串数组中的最长公共前缀. 如 ...

  10. Homekit_DoHome_智能通断器

    本款通断器适用于IOS和android系统用户,苹果用户可以非常方便的使用siri进行有效控制,android用户需要下载Dohome App进行操作,同时支持市面上主流的智能音箱进行控制. 对于an ...