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. idea中运行Tomcat后控制台出现乱码(统一设置成UTF-8)

    出现问题:运行Tomcat后控制台出现乱码,输出语句乱码(idea 2019.3版本) 解决方法: 方案1:File----->Settings..----->Editor-----> ...

  2. GLIBC_2.18 not found 之类的问题解决办法

    先到 /usr/lib64 下 ll libstdc++* 会发现 libstdc++.so.6 指向了一个新的.比如我这里之前指向6.0.22这个版本,报错 此时只要重新建立软连接 将软连接指向旧的 ...

  3. 实验06——java自动封箱、自动拆箱

    package cn.tedu.demo; /** * @author 赵瑞鑫 E-mail:1922250303@qq.com * @version 1.0 * @创建时间:2020年7月17日 上 ...

  4. Kaggle-pandas(1)

    Creating-reading-and-writing 戳我进原网站 教程 1.创建与导入 DataFrame import pandas as pd pd.DataFrame({'Yes': [5 ...

  5. C++虚函数相关内容

    样例代码 class Base{public: Base(){}; virtual ~Base(){    //若没有设置为虚函数:如果有这样的指针Base *p=new Derived();声明,则 ...

  6. 利用Python实现定时发送邮件,实现一款营销工具

    说起自动化绝对算是茶余饭后最有显B格的谈资,毕竟解放双手是从老祖先那里就流传下来的基因,都2020了,你每天上班还要登录各个邮箱账号查收邮件?快来解锁本章内容 整体思路 很多人学习python,不知道 ...

  7. SonarQube 自定义规则开发

    SonarQube 自定义规则开发 满足一些特定需求的时候,需要自己开发代码规则. 环境 和前文的演示环境一致. 步骤 开发步骤见 Writing Custom Java Rules 101,这是官方 ...

  8. 又一个小而美的Java Web框架: Solon!

    Solon 是Java世界里一个新的极易上手的Web框架.参考过 Javalin . Spring 等很多现有框架的设计. 取名自海贼王里的角色,说是希能像他一样能打 小.真的是小.最小的运行单位只有 ...

  9. C#LeetCode刷题之#344-反转字符串​​​​​​​(Reverse String)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3933 访问. 编写一个函数,其作用是将输入的字符串反转过来. 输 ...

  10. [持续更新]——关于C++的一些可能会常用的函数

    写在前面 这些函数都是我和朋友一点一点写出来的,可能部分代码会有点雷同,但大部分代码都是自我总结出来的.目前包含的函数功能分别是: 1.设置控制台颜色 2.设置控制台光标位置 3.隐藏控制台光标 4. ...