最近遇到了一个问题,角色在Unity编辑器里运行渲染结果都是好的,打包到IOS上却发现,角色身上渲染的很黑.花了些时间查了查,又试了试,把这方面算是初步弄清楚了。

  先说出现问题的原因,由于我们把shader打包进了AssetBundle中,并且在Shader中使用了shader_feature来定义了宏。

  为了完整起见,先从unity的shader  variant说起。


ShaderVariant


  举个例子,对于一个支持法线贴图的Shader来说,用户肯定希望无论是否为材质提供法线贴图它的Shader都能正确的进行渲染处理。一般有两种方法来保证这种需求:

  1.在底层shader(GLSL,HLSL等)定义一个由外部传进来的变量(如int),有没有提供法线贴图由外部来判断并给这个shader传参,若是有则传0,否则传1,在Shader用if对这个变量进行判断,然后在两个分支中进行对应的处理。

  2.对底层shader封装,如Unity的ShaderLab就是这种,然后在上层为用户提供定义宏的功能,并决定宏在被定义和未被定义下如何处理。最终编译时,根据上层的宏定义,根据不同的组合编译出多套底层shader.

  上述两种方法,各有利弊,对于前者由于引入了条件判断,会影响最终shader在GPU上的执行效率。而后者则会导致生成的shader源码(或二进制文件)变大。Unity中内置的Shader往往采取的是后者,所以这里只讨论这种情况。

  Unity的Shader中通过multi_compile和shader_feature来定义宏(keyword)。最终编译的时候也是根据这些宏来编译成多种组合形式的Shader源码。其中每一种组合就是这个Uniy Shader的一个Variant。


MaterialShaderVariant的关系


  一个Material同一时刻只能对应它所使用的Shader的一个variant。进行切换的要使用Material.EnableKeyword()和Material.DisableKeyword()来开关对应的宏,然后Unity会根据你设定的组合来匹配响应的shader variant进行渲染。如果你是在编辑器非运行模式下进行的修改那么这些keyword的设置会被保存到材质的.mat文件中,尝试用NotePad++打开.mat文件,你应该会看到类似于下面的一段内容(需要在编辑器设置里把AssetSerializationMode设置为Force Text):

 %YAML 1.1

 %TAG !u! tag:unity3d.com,:

 --- !u! &

 Material:

   serializedVersion: 

   m_ObjectHideFlags: 

   m_PrefabParentObject: {fileID: }

   m_PrefabInternal: {fileID: }

   m_Name: New Material

   m_Shader: {fileID: , guid: 3e0be7fac8c0b7c4599935fa92c842a4, type: }

   m_ShaderKeywords: _B

   m_LightmapFlags: 

   m_CustomRenderQueue: -

   …

  其中的m_ShaderKeywords就保存了这个材质球使用了哪些宏(keyword).

  如果你手头有built-in Shader的源码可以打开里面的StandardShaderGUI.cs看一下Unity自己事怎么处理对于StandardShader的keyword设置的。

  另外Shader.EnableKeyword,和Shader.DisableKeyword是对Shader进行全局宏设置的,这里不提了。


multi_compileshader_feature的区别


  完全没接触过它们的同学可以先看官方文档的介绍,multi_compile是一直都有的,shader_feature是后来的unity版本中加入的关键字。

举例介绍一下multi_compile和shader_feature:

1.如果你在shader中添加了

 #pragma multi_compile  _A _B
#pragma multi_compile _C _D

  那么无论这些宏是否真的被用到,你的shader都会被Unity编译成四个variant,分别包含了_A _C,_A _D, _B _C,_B _D四种keyword组合的代码

2.如果是

 #pragma shader_feature _A _B
#pragma shader_feature _C _D

  那么你的shader只会保留生成被用到的keyword组合的variant,至于如何判定哪些组合被用到了,等后面提到Assetbundle时候再说。


ShaderVariantAssetbundle的关系


  我所遇到的问题正是和Assetbundle(简称AB)有关,原因是打成AB包之后shader_feature所定义的宏没有被正确包含进去。

  上面说了multi_compile定义的keyword是一定能正确的生成对应的多种组合的shaderVariant,但shader_feature不尽然,Unity引入shader_feature就是为了避免multi_compile那种完整编译所导致组合爆炸,很多根本不会被使用的shader_variant也会被生成。Unity在处理shader_feature时会判断相应的keyword组合是否被使用。需要区分一下几种情况:

  1.如果shader没有与使用它的材质打在一个AB中,那么shader_feature的所有宏相关的代码都不会被包含进AB包中(有一种例外,就是当shader_feature _A这种形式的时候是可以的),这个shader最终被程序从AB包中拿出来使用也会是错误的(粉红色).

  2.把shader和使用它的材质放到一个AB包中,但是材质中没有保存任何的keyword信息(你在编辑器中也是这种情况),shader_feature会默认的把第一个keyword也就是上面的_A和_C(即每个shader_feature的第一个)作为你的选择。而不会把_A _D,_B _C,_B _D这三种组合的代码编译到AB包中。

  3.把shader和使用它的材质放到一个AB包中,并且材质保存了keyword信息(_A _C)为例,那么这个AB包就只包含_A _C的shaderVariant.

  可以看到shader_feature所定义的keyword产生的ShaderVariant并不是全部被打包到AB中,特别是你想在游戏运行时动态的通过EnableKeyWorld函数来进行动态修改材质使用的shaderVariant,如果一开始就没有把对于variant放进AB包,自然也就找不到。


ShaderVariantCollection


  要正确的让各种variant正确的在游戏运行时正确处理,

最直接暴力的两种方法:

1.把Shader放到在ProjectSetting->Graphics->Always Include Shaders列表里,Unity就会编译所有的组合变种。

2.把Shader放到Resources文件夹下,也会正确处理,我猜也应该是全部keyword组合都编译,有知道的同学,麻烦留言告诉我。

  但是这两种情况最大的问题就是组合爆炸的问题,如果keyword比较少还好,要是多了那真是不得了,比如你把standardShader放进去,由于它有大量的keyword,全部变种都生成的话大概有几百兆。另外一个问题就是这种办法没法热更新。自然不如放到AB包里的好控制。

  放到AB包就又涉及到shader_feature的处理,为了在运行时动态切换材质的shadervariant,可以在工程里新建一堆材质,然后把每个材质设置成一种想要的keyword组合,把他们和shader放到一起打到一个AB中去,这样虽然能让shadervariant正确生成,但是这些Material是完全多余的。

  为了解决这种问题,Unity5.0以后引入了ShaderVariantCollection(下面简称SVC),这里不讲用法,只说问题,这个SVC文件可以让我指定某个shader要编译都要编译带有哪些keyword的变种。并且在ProjectSetting->Graphics界面新加了一个Preloaded Shaders列表,可以让你把SVC文件放进去,编译时指定的Shader就会按照SVC中的设置进行正确的variant生成,而不会像Always Include Shaders列表中的那样全部变种都生成。

  但是它在AB中的表现可就不尽如人意了,要让它起作用,就必须把它和对应的shader放在一个AB中,而且除了5.6以外版本,我试了几个都不能正确使用,不是一个variant都没生成,就是只生成一个shadervariant(和放一个没有设置keyword的材质效果一样).你可以自己用UnityStudio打开查看一下生成的AB内容。


写在最后


  应该正确的理解Unity提供multi_compile和shader_feature以及ShaderVariantCollection的意图,根据自己的情况来选择合理的解决方案。

  在查这个问题的过程中也google了一些,发现国外在这方面的讨论远没国内多,应该是因为老外很少使用热更新这种东西,也自然很少用AB。

  

  尊重他人智慧成果,若要转载,请注明作者esfog,原文地址http://www.cnblogs.com/Esfog/p/Shader_Variant.html

UnityShaderVariant的一些探究心得的更多相关文章

  1. Activity是如何挂载Pargment的Day35

    Activity是如何挂载Pargment的Day35 mebile5.0 1.Fragment优化早上任务 思路 一个主Activity 短信群发器 中秋给朋友发短信,都能够加上他们的姓名,这样他们 ...

  2. iOS中的预编译指令的初步探究

    目录 文件包含 #include #include_next #import 宏定义 #define #undef 条件编译 #if #else #endif #if define #ifdef #i ...

  3. windows类书的学习心得(转载)

    原文网址:http://www.blogjava.net/sound/archive/2008/08/21/40499.html 现在的计算机图书发展的可真快,很久没去书店,昨日去了一下,真是感叹万千 ...

  4. Activity栈与任务管理探究1——栈与任务的概述

    Activity栈与任务管理探究1--栈与任务的概述 内容概览 1. 前言 2. Activity中的Stack 3. Activity中的Task 4. Activity栈与任务管理基本原则 1. ...

  5. BUG心得

    在<程序员,你会从 Bug 中学习么?>一文中,我写了我是怎样追踪这些年遇到的最有趣 bug 的.最近我重新浏览了这所有的 194 个条目(历时 13 年),看看我从这些 bug 中学到了 ...

  6. sed从入门到深入的使用心得

    本人已经此系列的sed文章整理到pdf中,欢迎下载:玩透sed:探究sed原理 sed系列文章: sed修炼系列(一):花拳绣腿之入门篇sed修炼系列(二):武功心法(info sed翻译+注解)se ...

  7. windows类书的学习心得

    原文网址:http://www.blogjava.net/sound/archive/2008/08/21/40499.html 现在的计算机图书发展的可真快,很久没去书店,昨日去了一下,真是感叹万千 ...

  8. BackBone及其实例探究

      摘要 我们小组对MVC框架进行了学习.我的队友们已经在博客中对MVC的设计模式及优缺点进行了详细的探讨与分析,因此我的博客中只对MVC进行简单的介绍,而我将把重心放在Backbone MVC框架一 ...

  9. Weka 二次开发使用心得

    Weka 二次开发使用心得 一.weka数据挖掘流程 使用weka图形界面,初步尝试了下数据的预处理.分类.关联等操作,因为weka本身就是一个开源的机器学习库,于是想自己尝试下利用weka的api进 ...

随机推荐

  1. Python 安装第三方库中常见问题总结

    1.安装某个库时,例如 pip install tinify 出现Permission denied时,在安装命令的最前面添加sudo 例如: sudo pip install tinify 即可. ...

  2. jmeter 上传附件

    1.查看上传接口参数,然后写到jmeter中 2.在file upload中parameterName填写main,MIME Type中填写request中的Content-Type

  3. Dart 创建List

    list的常用的操作 1. [] ,length  获取元素,和数组长度 2. add(), insert ()添加元素 3. remove(),clear() 删除元素 4.indexOf (), ...

  4. MTLD -词汇复杂度的指标

    论文: MTLD, vocd-D, and HD-D: A validation study of sophisticated approaches to lexical diversity asse ...

  5. 20164322韩玉婷 -----EXP3 免杀原理与实践

    基础问题回答 1.杀软是如何检测出恶意代码的? (1)基于特征码的检测 特征码是能识别一个程序是一个病毒的一段不大于64字节的特征串.如果一个可执行文件包含这样的特征码则被杀毒软件检测为是恶意代码. ...

  6. centos7图形界面安装

    系统笔者采用的是centos7 可以通过/etc/inittab文件看到 yum groupinstall "X Window System" -y             # 首 ...

  7. 【OO学习】OO第一单元作业总结

    OO第一单元作业总结 在第一单元作业中,我们只做了一件事情:求导,对多项式求导,对带三角函数的表达式求导,对有括号嵌套的表达式求导.作业难度依次递增,让我们熟悉面向对象编程方法,开始从面向过程向面向对 ...

  8. centos的nginx如何访问本地共享文件夹的文件 nginx访问404,403问题

    关键挂载 sudo vmhgfs-fuse .host:/musings /home/xxx -o allow_other,uid=0,gid=0

  9. 关于笔记本安装parrot和kali的一些问题(花屏,息屏,屏幕不能休眠)

    新入手了个笔记本,还是想跟原来一样装回熟悉的kali环境中,结果我的天啊,这一路坑,简直了. 写下我遇到的问题吧,算是给大家提供一些解决方法. 1.安装kali和parrot出现无法引导的grub的情 ...

  10. 使用iSCSI服务部署网络存储