unity shader 变种(多重编译 multi_compile)

unity shader 变种(多重编译 multi_compile)
让我们好好聊聊Unity Shader中的multi_complie

这篇文章比较全面的介绍了Unity中shader的multi_compile与shader_feature的定义和使用以及限制。


一、定义

在unity中我们可以通过使用#pragma multi_compile或#pragma shader_feature指令来为shader创建多个稍微有点区别的shader变体。这个Shader被称为宏着色器(mega shader)或者超着色器(uber shader)。实现原理:根据不同的情况,使用不同的预处理器指令,来多次编译Shader代码。
在运行时,Unity从Material宏Material.EnableKeyword和Shader.DisableKeyword或全局着色器宏Shader.EnableKeyword和Shader.DisableKeyword中选择适当的着色器变体。如果这两个宏都未启用,则Unity使用第一个宏。

二、使用
//定义两个TEST_1,TEST_2两个宏
#pragma multi_compile TEST_1 TEST_2
//在shader中使用
#ifdef TEST_1
    //Todo      
#endif
#ifdef TEST_2
    //Todo      
#endif

上面这个命令会产生2种着色器变种:TEST_1,TEST_2。
要生成未定义预处理器宏的着色器变体,请添加一个仅为下划线(__)的名称。这是避免使用两个宏的常用技术,因为对项目中可以使用的宏数量有限制,例如:

#pragma multi_compile __ TEST_1

在脚本中控制使用:

//使用TEST_1变种
Shader.EnableKeyword ("TEST_1");
Shader.DisableKeyword ("TEST_2");
三、组合
#pragma multi_compile TEST_1 TEST_2
#pragma multi_compile TEST_3 TEST_4 TEST_5

它产生总共六个着色器变体(TEST_1_TEST_3,TEST_1_TEST_4,TEST_1_TEST_5,TEST_2_TEST_3,TEST_2_TEST_4,TEST_2_TEST_5)。
所以如果有10行multi_compile,每行2个选项,那么将一共产生1024个着色器变体。
请记住,着色器变体数量将以这种方式疯狂增长。

四、#pragma shader_feature

shader_feature非常相似multi_compile。唯一的区别是Unity shader_feature在最终版本中不包含未使用的着色器变体。所以shader_feature适用于在我们在编辑器中,选中材质,设置它使用的shader的宏,如果在程序中动态的去设置可能无效(原因下面说明)。而对于multi_compile,会把所有的变体都编译进程序里,所以适合需要在程序运行中动态改变状态的宏,适合全局设置 。
材质中设置位置截图:

五、宏限制

在unity中限制了全局的宏个数为265个,而unity内部使用了大约60个,所以在多个不同的着色器中定义全局宏时需要注意宏数量不要超过限制。
使用本地宏替代一部分全局宏:使用shader_feature_local和multi_compile_local。
shader_feature_local:类似于shader_feature,但枚举宏是本地的。
multi_compile_local:类似于multi_compile,但枚举宏是本地的。
在项目中除非是希望通过全局API启用的那些特定宏,否则应尽量使用本地宏,
使用更多本地宏和更少的全局宏,以减少每个着色器的宏总计数。如果存在具有相同名称的全局和本地宏,则Unity会优先使用local宏。
注意:
(1)不能将本地宏与进行全局宏更改的API一起使用(例如Shader.EnableKeyword或CommandBuffer.EnableShaderKeyword)。
(2)每个着色器最多有64个唯一的本地宏。
(3)如果Material启用了本地宏,并且其着色器更改为不再声明的宏,则Unity会创建一个新的全局宏。

六、内置multi_compile快捷方式

unity中提供一些内置的宏用于编译多个着色器变体。这些主要用于处理Unity中不同的光照,阴影和光照贴图类型。
multi_compile_fwdbase:编译PassType.ForwardBase所需的所有变体。变体处理不同的光照贴图类型,并启用或禁用主方向光的阴影。
multi_compile_fwdadd:为PassType.ForwardAdd编译变体。这将编译变体以处理Directional,Spot或Point Light类型及其变体与Cookie纹理。
multi_compile_fwdadd_fullshadows:同样multi_compile_fwdadd,但也包括灯具有实时阴影的能力。
multi_compile_fog:扩展为多个变体以处理不同的雾类型(off / linear / exp / exp2)。

大多数内置快捷方式都会产生许多着色器变体。如果您知道项目不需要它们,您可以使用#pragma skip_variants跳过编译它们中的一些。例如:

#pragma multi_compile_fwdadd
#pragma skip_variants POINT POINT_COOKIE

该指令跳过包含POINT或POINT_COOKIE的所有变体。

七、查看shader变种数量
#pragma multi_compile TEST_1 TEST_2 TEST_3
#pragma multi_compile TEST_4 TEST_5
#pragma multi_compile TEST_6 TEST_7
查看变体数量

上面的组合会产生3x2x2=12种变体,我们可以点击show查看具体的变体组合名称。

// Total snippets: 1
// -----------------------------------------
// Snippet #0 platforms ffffffff:
Keywords always included into build: TEST_1 TEST_2 TEST_3 TEST_4 TEST_5 TEST_6 TEST_7

12 keyword variants used in scene:

TEST_1 TEST_4 TEST_6
TEST_1 TEST_4 TEST_7
TEST_1 TEST_5 TEST_6
TEST_1 TEST_5 TEST_7
TEST_2 TEST_4 TEST_6
TEST_2 TEST_4 TEST_7
TEST_2 TEST_5 TEST_6
TEST_2 TEST_5 TEST_7
TEST_3 TEST_4 TEST_6
TEST_3 TEST_4 TEST_7
TEST_3 TEST_5 TEST_6
TEST_3 TEST_5 TEST_7

这里查看的是所有会被编译的变体的数量,也就是#pragma multi_compile声明的宏的全部组合。

#pragma multi_compile TEST_1 TEST_2 TEST_3
#pragma multi_compile TEST_4 TEST_5
#pragma shader_feature TEST_6 TEST_7
查看变种数量
// Total snippets: 1
// -----------------------------------------
// Snippet #0 platforms ffffffff:
Keywords stripped away when not used: TEST_6 TEST_7
Keywords always included into build: TEST_1 TEST_2 TEST_3 TEST_4 TEST_5

6 keyword variants used in scene:

TEST_1 TEST_4 TEST_6
TEST_1 TEST_5 TEST_6
TEST_2 TEST_4 TEST_6
TEST_2 TEST_5 TEST_6
TEST_3 TEST_4 TEST_6
TEST_3 TEST_5 TEST_6

上面的组合会产生3x2x1=6种变体,#pragma shader_feature没有特别处理的话只有会默认包括第一个宏。

八、编译
(1)material的ShaderKeywords

Material所包含的Shader Keywords表示启用shader中对应的宏,Unity会调用当前宏组合所对应的变体来为Material进行渲染。在Editor下,可以通过将material的inspector调成Debug模式来查看当前material定义的Keywords,也可在此模式下直接定义Keywords,用空格分隔Keyword。

设置ShaderKeywords

优点:根据material中的ShaderKeywords自动生成变体。无需额外设置
缺点:多个不同的material包中可能存在相同的shader变体,造成资源冗余。若在程序运行时动态改变material的keyword其变体可能并没有被生成
如上图设置:如果ShaderKeywords中没有设置TEST_6,这是如果我们想在程序中通过代码动态使用TEST_6这个宏(Shader.EnableKeyword("TEST_6"))。可能不能得到想要的效果,因为TEST_6这个变种没有生成。

(2)把Shader加入到Always Include Shaders列表里

找到ProjectSetting->Graphics->Always Include Shaders列表,将我们需要的shader添加到里面,这样unity将会把这个shader的所有的变种都生成出来。

AlwaysIncludeShaders

优点:我们不用担心项目发布出去以后有些变种没有生成,不能在程序中动态的去控制我们的宏。
缺点:生成的变体数量庞大,导致发布时间变长,游戏包体过大。比如你把standardShader放进去,由于它有大量的keyword,全部变种都生成的话大概有几百兆。

(3)使用ShaderVariantCollection是生成指定变体

ShaderVariantCollection是unity5.x以后用来记录shader的哪些变体需要被生成。这样做的好处就是在shader_feature与multi_compile结合使用时,能够设置生成何种变体,从而避免生成不必要的变体;shader不必和material打在一个包中,避免了多个包中存在相同的变体资源;明确直观的显示了哪些变体是需要生成的。

生成方式:

(1)通过Create->Shader-> Shader Variant Collection,就可以新建一个shader variant collection文件,手动添加需要编译的变种

ShaderVariantCollection
选择需要生成的变种

(2)通过Edit->Project Settings->Graphics中的save to asst...按钮,生成unity帮我们自动收集的,使用到的变种信息。

自动收集变种信息

这时候只需要先Clear一下,然后依次打开我们的所有场景,把需要的物体都显示一遍,Unity就会自动记录下来哪些着色器的哪些着色器变体已经被使用到。统计完后只需点击下面的保存按钮就可以生成我们所需要的ShaderVariantCollection资源。当然你也可以为你的每一个场景或者按需生成足够多的ShaderVariantCollection资源。自动收集的功能不一定百分百可靠,最好事后多检查。

ShaderVariantCollection加载:

启动时预加载:

最简单最粗暴的使用方式就是在游戏启动的瞬间就直接加载ShaderVariantCollection资源并编译里面的着色器变体,Unity已经为我们做好这一步了,依然还是在图形设置面板里,只需把需要启动是就编译的ShaderVariantCollection添加在Preloaded Shaders里面

预加载ShaderVariants

代码加载:

由于ShaderVariantCollection也是一种资源,可以跟纹理、模型等等资源一起打包和加载等,只需在加载之后调用一句WarmUp。

ShaderVariantCollection shaderVariantCollection = Resources.Load <ShaderVariantCollection>( "MainShaderVariant");
if (shaderVariantCollection )
      shaderVariantCollection.WarmUp ();

也可以把ShaderVariantCollection放在Resources目录下,好像会被自动加载(未验证)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343