shader之间的数据传递
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;
好了,不胡扯了,说了这么多,有的同学可能都蒙圈了,知道你们最喜欢看总结,我们一起来总结一下.
总结
只有两个选择:
用Location based matching
用Block based matching with location
因为从GLSL转换为SPIR-V的时候,需要为每个输入输出成员指定location. Named base matching和Block based matching without location转换SPIR-V的时候会报错.
所以,除非你有充分的理由说服自己(例如,我不想用Vulkan). 否则,就选上面这两个.
完~
shader之间的数据传递的更多相关文章
- ASP.NET MVC3中Controller与View之间的数据传递
在ASP.NET MVC中,经常会在Controller与View之间传递数据,因此,熟练.灵活的掌握这两层之间的数据传递方法就非常重要.本文从两个方面进行探讨: 一. Controller向Vie ...
- Activity之间的数据传递
最常用的Activity之间的数据传递. btnStartAty1.setOnClickListener(new View.OnClickListener() { @Override public v ...
- ASP.NET MVC 之控制器与视图之间的数据传递
今天,我们来谈谈控制器与视图之间的数据传递. 数据传递,指的是视图与控制器之间的交互,包括两个方向上的数据交互,一个是把控制器的数据传到视图中,在视图中如何显示数据,一个是把视图数据传递到控制器中, ...
- (转载)Javascript操作表单之间的数据传递
(转载)http://www.aspxhome.com/javascript/skills/200710/214825.htm 今天有朋友问我关于用JAVASCRIPT来进行页面各表单之间的数据传递的 ...
- iOS开发中视图控制器ViewControllers之间的数据传递
iOS开发中视图控制器ViewControllers之间的数据传递 这里我们用一个demo来说明ios是如何在视图控制器之间传递重要的参数的.本文先从手写UI来讨论,在下一篇文章中讨论在storybo ...
- React中父组件与子组件之间的数据传递和标准化的思考
React中父组件与子组件之间的数据传递的的实现大家都可以轻易做到,但对比很多人的实现方法,总是会有或多或少的差异.在一个团队中,这种实现的差异体现了每个人各自的理解的不同,但是反过来思考,一个团队用 ...
- Vue 爬坑之路(二)—— 组件之间的数据传递
Vue 的组件作用域都是孤立的,不允许在子组件的模板内直接引用父组件的数据.必须使用特定的方法才能实现组件之间的数据传递. 首先用 vue-cli 创建一个项目,其中 App.vue 是父组件,com ...
- sqoop实现关系型数据库与hadoop之间的数据传递-import篇
由于业务数据量日益增长,计算量非常庞大,传统的数仓已经无法满足计算需求了,所以现在基本上都是将数据放到hadoop平台去实现逻辑计算,那么就涉及到如何将oracle数仓的数据迁移到hadoop平台的问 ...
- Activity之间的数据传递-android学习之旅(四十七)
activity之间的数据传递主要有两种,一种是直接发送数据,另一种接受新启动的activity返回的数据,本质是一样的 使用Bundle传递数据 Intent使用Bundle在activity之间传 ...
随机推荐
- 使用hibernate validate做参数校验
1.为什么使用hibernate validate 在开发http接口的时候,参数校验是必须有的一个环节,当参数校验较少的时候,一般是直接按照校验条件做校验,校验不通过,返回错误信息.比如以下校验 ...
- 使用Android Studio创建模拟器,安装配置Android SDK
Android Studio 一个写安卓APP应用的代码编辑器之类的?嗯,应该是... 这里只是需要用到里面的AVD Manager 创建安卓模拟器(也可以用mumu类的安卓模拟器):SDK Mana ...
- 从零搭建Spring Boot脚手架(1):开篇以及技术选型
1. 前言 目前Spring Boot已经成为主流的Java Web开发框架,熟练掌握Spring Boot并能够根据业务来定制Spring Boot成为一个Java开发者的必备技巧,但是总是零零碎碎 ...
- Layui+MVC+EF (项目从新创建开始)
最近学习Layui ,就准备通过Layui来实现之前练习的项目, 先创建一个新的Web 空项目,选MVC 新建项目 创建各种类库,模块之间添加引用,并安装必要Nuget包(EF包) 模块名称 模块 ...
- 朴素贝叶斯分类器基本代码 && n折交叉优化 2
这个代码基于上一个代码 不同的是:读取了txt文件,改变了min_ft与max_ft的参数 import re import pandas as pd import warnings import n ...
- EF框架 与 Dapper框架 调用分页存储过程
1. SqlServer创建存储过程: --创建存储过程 create proc sp_Show ( @index int, --当前页 @size int, --每页大小 @totalcount i ...
- git使用-git仓库
1.初始化版本库 git init 2.添加文件到版本库 git add git commit 3.查看仓库状态 git status 4.撤销初始化命令 rm -rf .git
- 字段解析之OopMapBlock(4)
OopMapBlock是一个简单的内嵌在Klass里面的数据结构,用来描述oop中包含的引用类型属性,即该oop所引用的其他oop在oop中的内存分布,然后就可以根据当前oop的地址找到所有引用的其他 ...
- Spring IOC 启动过程
1. 引言 本篇博文主要介绍 IOC 容器的启动过程,启动过程分为两个步骤,第一个阶段是容器的启动阶段,第二个阶段是 Bean 实例化阶段,这两个阶段各自需要执行的步骤如下图,接下来会一一介绍. 需要 ...
- Python实现迪杰斯特拉算法
首先我采用邻接矩阵法来表示图(有向图无向图皆可) 图的定义如下: class Graph: def __init__(self, arcs=[]): self.vexs = [] self.arcs ...