TA-Shader-文件 Unity Shader的内置变量与常量内存
发布于
2022-4-11
1398
1
TA资源类型
TA资源类型: 算法思路 
shader资源类型: 其它 
适用引擎: unity 
资源介绍: 转自 明明 https://zhuanlan.zhihu.com/p/453216279

您需要 登录 才可以下载或查看,没有账号?注册

x
本帖最后由 DiGiToTo 于 2022-4-11 06:11 编辑

image.png

通常来说,我们利用ShaderLab实现一个Unity Shader,需要声明一些变量,它可以是全局变量,也可以是局部变量。比方说如下Bling-Phong逐片元光照:
  1. Shader "Custom/BlingPhong" {
  2.     Properties{
  3.         _Diffuse("Diffuse", Color) = (1, 1, 1, 1)
  4.         _Specular("Specular", Color) = (1, 1, 1, 1)
  5.         _Gloss("Gloss", Range(8.0, 256)) = 20
  6.     }
  7.     Subshader{
  8.         Pass {

  9.             Tags{ "LightMode" = "ForwardBase" }

  10.             CGPROGRAM
  11.             #pragma vertex vert
  12.             #pragma fragment frag
  13.             #include "UnityCG.cginc"
  14.             #include "Lighting.cginc"

  15.             fixed4 _Diffuse;
  16.             fixed4 _Specular;
  17.             float _Gloss;

  18.             struct a2v {
  19.                 float4 vertex:POSITION;
  20.                 float3 normal:NORMAL;
  21.             };

  22.             struct v2f {
  23.                 float4 pos:SV_POSITION;
  24.                 float3 worldNormal:TEXCOORD0;
  25.                 float3 worldPos:TEXCOORD1;
  26.             };

  27.             v2f vert(a2v v) {
  28.                 v2f o;
  29.                 o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
  30.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
  31.                 o.pos = mul(UNITY_MATRIX_VP, float4(o.worldPos, 1.0));
  32.                 return o;
  33.             }

  34.             fixed4 frag(v2f i) : SV_Target {
  35.                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

  36.                 //Transfrom the normal from object space to world space;
  37.                 fixed3 worldNormal = normalize(i.worldNormal);

  38.                 //get the lightdirection from world space
  39.                 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  40.                 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
  41.                 fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
  42.                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
  43.                 fixed3 halfDir = normalize(worldLightDir + viewDir);
  44.                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal, halfDir)), _Gloss);
  45.                 return fixed4(ambient + diffuse + specular,1.0);
  46.             }
  47.             ENDCG
  48.         }
  49.     }
  50.     FallBack "Diffuse"
  51. }
点击此处复制文本
这个代码写的人太多了,这里就不多解释。可以注意到,我们用的一些变量既没有在局部进行声明,也没有在全局进行声明,比如说unity_ObjectToWorld... 慢着,真的没有声明过吗?要不咱看看"UnityCG.cginc"?
v2-961cdd8d33b93a7f89008797aaa3f91a_720w.jpg
打开"UnityCG.cginc",能看到22行包含了一个叫做"UnityShaderVariables.cginc"的头文件,再打开瞧瞧。搜索unity_ObjectToWorld。找到如下代码片段
  1. CBUFFER_START(UnityPerDraw)
  2.     float4x4 unity_ObjectToWorld;
  3.     float4x4 unity_WorldToObject;
  4.     float4 unity_LODFade; // x is the fade value ranging within [0,1]. y is x quantized into 16 levels
  5.     float4 unity_WorldTransformParams; // w is usually 1.0, or -1.0 for odd-negative scale transforms
  6.     float4 unity_RenderingLayer;
  7. CBUFFER_END
点击此处复制文本
原来,这些变量是在头文件中声明的,那么猜想这个CBUFFER_START可能是某种修饰符。最终在HLSLSupport.cginc中找到了如下代码片段
  1. <blockquote>#if defined(SHADER_API_D3D11) || defined(UNITY_ENABLE_CBUFFER) || defined(SHADER_API_PSSL)
点击此处复制文本
看来这个cbuffer是一个值得研究的小课题。搜搜看cbuffer是不是某种简写呢?答案是肯定的。
Shader Constants (HLSL) - Win32 apps&#8203;docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-constants
这里面提到的constant buffer就是cbuffer的全称,也就是常量内存。笔者依稀记得这个名词在CUDA里提过,所以复习了一下本科选过的一门课。常量内存是一种容量很小(64KB)但是访问很快的GPU端只读、CPU端可写内存,很适合多个GPU线程同时访问。
CUDA存储模型,图片取自下方链接。

废话时间:看上图。橙底表示的存储为片外存储,红底表示的存储为片上存储。片外存储是指存储位于GPU板卡上的专用GDRAM,特点是容量大,CPU端可直接访问(除了Local Memory),但是访问延时很高;片内存储指GPU芯片内的存储,特点是容量小,访问延时低。CPU端只能先将数据写到三种存储当中。那为什么Constant Memory会比Global Memory快呢?我以我二半吊子的FPGA设计经验的理解是,由于这个访问在线程中是单向的,所以我们只需要在片外显存中存放常量内存,然后将数据缓存到片内。片内缓存可以看作是一组寄存器或者BRAM,且只有从片外->片内这一种数据流向,缓存好数据后,读取只要做译码,非常快。这也解释了常量寄存器为什么比较小,因为现在的GPU虽然面积已经很大了,但毕竟片上资源还是寸土寸金,此外额外扩容会涉及某些地址线的变动,改动较大,所以暂时还没扩容(可能也没必要扩容)。
CUDA Memory Model&#8203;[url]www.3dgep.com/cuda-memory-model/[/url]
那么我们也许会思考几个问题。这里为什么用到常量内存?或者说用常量内存有什么好处?我们应该什么时候使用它?带着这些问题继续阅读"UnityShaderVariables.cginc"。重点先放在自己需要用到的两个片段:
  1. <blockquote>CBUFFER_START(UnityPerFrame)
点击此处复制文本
  1. CBUFFER_START(UnityPerDraw)
  2. float4x4 unity_ObjectToWorld;
  3. float4x4 unity_WorldToObject;
  4. float4 unity_LODFade; // x is the fade value ranging within [0,1]. y is x quantized into 16 levels
  5. float4 unity_WorldTransformParams; // w is usually 1.0, or -1.0 for odd-negative scale transforms
  6. float4 unity_RenderingLayer;
  7. CBUFFER_END
点击此处复制文本

注意到它们在命名上有一定的差异,一个叫UnityPerFrame,另一个叫UnityPerDraw,因此可以反过来推出,cbuffer实际上是有生命周期的。这一点也不奇怪 ,前面说过,常量内存虽然快,但是容量非常小(64KB),得省着点用。不然把顶点数据和纹理都存到常量内存里,岂不妙哉?这些数在底层应该是根据绘制的状态,由CPU发送给GPU的(一个猜想,不一定对,欢迎指正)。譬如说这个UnityPerFrame,看名字和它里面包含的变量,很显然这些变量在每一帧绘制都保持不变。那这下一切就明朗了:虽然某一帧里可能提交了很多次draw call,但是UnityPerFrame里的常量其实只需要设置一次,并且多个线程都会用到这些变量,这能加快显存访问速度,降低CPU到GPU的吞吐。UnityPerDraw也是同理,不过为什么是PerDraw而不是PerObject,这个应该与合批或者是Instancing相关。
回看到最开始的那个Bling-Phong的代码,实际上我也使用了三个全局变量:_Diffuse,_Specular和_Gloss。那能不能把用户定义的全局变量也用常量内存缓存起来呢?毕竟如果这个shader对应的材质用得很多的话,貌似能省不少。那就直接百度一下,看到有个人这么写
【SRP Batcher】How to make a Default Unlit Shader compatible?&#8203;forum.unity.com/threads/srp-batcherhow-to-make-a-default-unlit-shader-compatible.877435/
  1. CBUFFER_START(UnityPerMaterial)
  2.     fixed4 _Color;
  3. CBUFFER_END
点击此处复制文本
所以说有个UnityPerMaterial的名称,用来标识这是在每个材质中共用的全局变量。其实在Unity的Inspector中一直有提示,不过我之前没看到
加上了CBUFFER之后
有意思,原来这和Batching有关。对于相同材质的多个几何体,现在就只需要设置一次_Diffuse,_Specular和_Gloss,这能带来性能上的提升。在相应的代码里打开SRP Batching即可。
  1. GraphicsSettings.useScriptableRenderPipelineBatching = true;
点击此处复制文本
如图绘制八个球
在利用CBUFFER做优化前,绘制顺序如图
当我们将材质相关的变量用CBUFFER包起来后,新的绘制顺序如图
Draw Call减少了吗?其实没有,事实上SRP Batch只是优化了Draw Call的顺序,以减少CBUFFER的设置,从而提高性能。

收模型,杂志,教程,插图,插件,软件,拿来卖。

使用道具 举报 登录

回复 <
MJ漫步  发表于 2022-4-11 23:00:41  
2#
很棒的资源
回复 收起回复
使用道具

快来发表你宝贵的意见吧!

DiGiToTo 实名

通过了实名认证的内容创造者

大自然的搬运工

主题
1347
精华
588
超神
7
扩散
0
微金
2090
智慧
166
余额
454
在线时间
7790 小时

翡翠剑 【绝】红龙战甲 学徒法袍 赤铁剑 英雄盾 铁剑 钢盾 元素之盾 钢剑 长剑 水晶剑 死亡刃 阿鼻剑 鬼道剑 修罗剑 天子剑 天道剑 元素铜币 元素银币 元素金币 元素秘币 元素圣币 短杖 圣杖-【安东尼达斯】 【绝】珍珠戒指 女皇之泪 长枪 双杀 巨浪 龙枪 深渊 战神 魔神战甲 元素之铠 黄金圣衣 相对时空 征战护手 【绝】结界玄晶 神界之石 六界传音 【绝】大天使权杖 微光灵石 圣盾-【瓦雷利亚】 【绝】盘古斧 魔镜 裁决 神速靴 火元素 暴风之眼 元素之钻 秘法之钻 血精之钻 波塞冬之叉 圣光之钻 小苹果 大菠萝 再造药水 黄色药水 蓝色药水 赤色药水 微库VIP 翠晶之钻 紫晶之钻 扩散者 钢铁矿镐 秘银矿镐 黄金矿镐 彗星钛晶矿 阿尔法晶矿 泰坦高能矿 守望者【EX】 守望者【赤炼】 守望者【天使】 【绝】守望者【死神】 希望人没事 吃鸡头盔 吉普车 吃鸡甲 守望者【猎空】 生命之缘 自然之怒 原初葫芦 冰晶剑 玫瑰金项链 烈-红龙战甲 【绝】真-红龙战甲 【绝】千人纪念徽章 橙色药水 青色药水 一星珠 二星珠 七星珠 赛博空间 天体物理 宏观物质 无色原始矿 绿母翠晶矿 超星赤辉矿 怀特紫星矿 飞雷靴 现实宝石 灵魂宝石 心灵宝石 时间宝石 力量宝石 粒粒皆辛苦 【绝】赤影战盔 【绝】赤影战斧 莆田鞋 魔法扫帚 朝阳之光 光明勋章 魔法帽 【绝】烈龙战甲 【绝】魔焰斧 【绝】碧焰指环 烈火长枪 烈火匕首 粉御之钻 青戒之钻 黄金沙鹰 金阳之水 紫雷之水 青息之水 粽子 天蓝靴 光虹靴 紫霞之秋 源力法杖 【魔剑】龙葵之魂 绿色药水 紫色药水 光之杖 水晶杖 血晶杖 守望者【D.Va兔子】

快速回复 返回顶部 返回列表