当前位置: 首页 > news >正文

unity shader 变种(多重编译 multi_compile)

unity shader 变种(多重编译 multi_compile)

https://www.jianshu.com/p/f34d896dde5d

一、定义

在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使用第一个宏。

二、使用



作者:zzqlb
链接:https://www.jianshu.com/p/f34d896dde5d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

一、定义

在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

查看变体数量.png

 

上面的组合会产生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

查看变种数量.png

// 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.png

 

优点:根据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.png

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

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

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

生成方式:

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

 

ShaderVariantCollection.png

 

选择需要生成的变种.png

 

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

 

自动生成.png

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

ShaderVariantCollection加载:

启动时预加载:

 

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

预加载.png


代码加载:

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

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

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

 

 

 

 

 

 

 

 

 

相关文章:

  • Unity3d爬坑篇(2)之Assetbundle、Shader和Keyword
  • Unity AssetBundle Shader
  • Shader变体使用策略
  • AndroidStudio Gradle Plugin 下载失败
  • [原创]安卓U3D逆向从Assembly-CSharp到il2cpp
  • Unity Android il2cpp
  • Unity 找到原因了,如果你在Update里Instantiate一个prefab,上面的脚本会在这个update后执行start,然后执行lateupdate,这帧的update会被跳过
  • Unity -- Error
  • Unity iOS 删除 UIWebView
  • Jenkins 自己使用
  • Jenkins Pipeline语法(中)
  • Unity小技巧——设置代码编译时机
  • Unity 使用遇到的不足问题
  • 在eclipse中安装groovy插件详细步骤
  • Groovy 语言快速入门
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Angular2开发踩坑系列-生产环境编译
  • docker容器内的网络抓包
  • Linux下的乱码问题
  • MobX
  • Mysql优化
  • node学习系列之简单文件上传
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • Python 基础起步 (十) 什么叫函数?
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • 从0到1:PostCSS 插件开发最佳实践
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 一天一个设计模式之JS实现——适配器模式
  • ​flutter 代码混淆
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • #{}和${}的区别?
  • #if 1...#endif
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • $.ajax()方法详解
  • (AngularJS)Angular 控制器之间通信初探
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (二十三)Flask之高频面试点
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (九十四)函数和二维数组
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (算法二)滑动窗口
  • (五)c52学习之旅-静态数码管
  • (转)ABI是什么
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • .equals()到底是什么意思?
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .net 微服务 服务保护 自动重试 Polly
  • .NET轻量级ORM组件Dapper葵花宝典
  • .NET设计模式(8):适配器模式(Adapter Pattern)
  • .Net转Java自学之路—基础巩固篇十三(集合)
  • @JsonFormat与@DateTimeFormat注解的使用
  • @RequestParam,@RequestBody和@PathVariable 区别