精华内容
下载资源
问答
  • shaderVariant的缺失,可能会造成不同平台的差异(unity工程editor模式和真机模式表现不一致) 例如:如果你在unity中editor模式下表现正常,但是打包到真机后出现黑片(lightMap未正常表现)?紫片(shader直接丢失...

    一. shaderVariant的缺失,可能会造成不同平台的差异(unity工程editor模式和真机模式表现不一致)
    例如:如果你在unity中editor模式下表现正常,但是打包到真机后出现黑片(lightMap未正常表现)?紫片(shader直接丢失)?--------------恭喜你----原因很可能是shader的变体 shaderVariant没有正常表现。
    核查问题的方法:
    (1)在editor模式下frameDebug 查看正常的表现shader的属性和使用编译情况(查看编译后的变体代码Show generated code)
    (2)真机连接frameDebug调试核查表现失常的shader的属性中是否缺失属性例如:是谁偷走了我的lightmap?? 没能切换使用正确的shaderVariant
    那么解决问题就是:首先确定 shaderVariant是否打包进去了(用bundle拆解工具 AssetStudio核查bundle内的信息),第二是否执行命令“切换到lightmap_on的shaderVariant”.
    (附加上查看bundle的工具:链接: https://pan.baidu.com/s/18YC-KZsW0Xjls8DVKle-lQ 提取码: gyjg)

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

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

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

    上述两种方法,各有利弊,对于前者由于引入了条件判断,会影响最终shader在GPU上的执行效率。而后者则会导致生成的shader源码(或二进制文件)变大。Unity采取的是后者,我们这里也只讨论Unity对后者的使用。
      Unity的Shader中通过multi_compile和shader_feature来定义宏(keyword)。
      在这里插入图片描述

    #pragma multi_compile A B
    //OR #pragma shader_feature A B
    
    //-----------------------A模块----------------------
    #if A
      return fixed4(1,1,1,1); 
    #endif 
    //---------------------------------------------------
    
    //-----------------------B模块-----------------------
    #if B
      return fixed4(0,0,0,1); 
    #endif
    //---------------------------------------------------
    

    (1)这个Shader会被编译成两个变体:一是只包含A模块代码的变体A;二是只包含B模块代码的变体B;
    (2)指定的第一个关键字是默认生效的,即默认使用变体A;在脚本里用Material.EnableKeyword或Shader.EnableKeyword来控制运行时具体使用变体A还是变体B;
    (3)它们声明的Keyword是全局的,可以对全局的包含该Keyword的不同Shader起作用;全局最多只能声明256个这样的Keyword;
    (4)请注意Keyword的数量和变体的数量之间的关系,并可能由此导致的性能开销,比如声明#pragma multi_compile A B和#pragma multi_compile D E 这样的两组Keyword会产生 2x2=4 个Shader变体,但若声明10组这样的keyword,则该Shader会产生1024个变体
    (5)区别!!!!特别注意!!!!:
    如果使用shader_feature,build时没有用到的变体会被删除,不会打出来。也就是说,在build以后环境里,运行代码Material.EnableKeyword(“B”)可能不起作用,因为没有Material在使用变体B,所以变体B没有被build出来,运行时也找不到变体B

    multi_complie_local:
    (1) local Keyword仍有数量限制,每个Shader最多可以包含64个local Keyword因为这种Keyword是局部的,
    (2)Material.EnableKeyword仍是有效的,但对Shader.EnableKeyword或CommandBuffer.EnableShaderKeyword这种全局开关说拜拜
    (3)当你既声明了一个全局的Keyword A ,同时又声明了一个同名的、局部的Keyword A,那么优先认为Keyword A是局部的。
    下面是示例说明:

    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,如果只用到—AC,那么只会产生一个variant,当你使用Material.EnableKeyword(——A_D)时是无效的,因为没有此variant
    

    定义方式:
    {
    定义方式中值得注意的是,#pragma shader_feature A其实是 #pragma shader_feature _ A的简写,下划线表示未定义宏(nokeyword)。因此此时shader其实对应了两个变体,一个是nokeyword,一个是定义了宏A的。
      而#pragma multi_compile A并不存在简写这一说,所以shader此时只对应A这个变体。若要表示未定义任何变体,则应写为 #pragma multi_compile __ A。
    }
    编辑器查看和定义keywords:
    在这里插入图片描述

    三.能够控制生成shaderVariant的3中方式:

    在上面的“二”中讲到 multi_compile 和 shader_feature会产生shaderVariant,此外还有一种:Project Settings->Graphics->Always Included Shaders中添加shader:
    在此处添加的shader,会产生该shader所有的变体打入包中(multi_compile的变体不用收集也会被全部打进包体)
    如果没有设置到GraphicsSetting-> always included shader中,那么会打包到依赖它的ab中,如果设置了就不会打包进去。而是再构建的时候,就导入到你的游戏
    在这里插入图片描述四.有必要将所有的shaderVariant全部打进包内吗?
    没有必要,我们只需要将需要用到的shaderVariant打包进去就好了!!!
    那么,怎样才能确定哪些shaderVariant是需要用到的?
    unity给我们提供了解决方案:ShaderVariantCollection!!!!!!
    (首先shader是单独打包,不会和material打包在一起,不然会造成资源冗余)

    (1)Shader Variant Collection是用来记录shader中哪些变体是实际使用的。其优点主要有:在shader_feature与multi_compile结合使用时,能够设置生成何种变体,从而避免生成不必要的变体;shader不必和material打在一个包中,避免了多个包中存在相同的变体资源;明确直观的显示了哪些变体是需要生成的(multi_compile的变体不用收集也会被全部打进包体)收集仅对shader_feature有效。
    (2)自动添加:在场景中跑工程,上图中有一个 “Save to asset”按钮,unity会自动收集shaderVariant.
    (3)手动选择:自动添加的可能会出现冗余,手动添加可以核查确保正确,shader是你写的,你肯定知道需要用到哪些宏
    

    在这里插入图片描述
    在这里插入图片描述
    (4)配置好需要生成的变体后,将collection与shader打在同一个包中,便能准确生成面板中所配置的shader变体

    五.其他注意事项

    (1).内存中ShaderLab的大小和变体成正比关系。从减少内存方面应该尽量减少变体数量,可以使用 #pragma skip_variants。

    (2).在使用ShaderVariantCollection收集变体打包时,只对shader_feature定义的宏有意义,multi_compile的变体不用收集也会被全部打进包体。

    (3).2018.2新功能OnProcessShader可以移除无用的shader变体。比#pragma skip_variants更合理。

    (4).项目前期介入美术效果制作流程,规范shader宏定义使用,防止TA为了美术效果过度使用宏定义的情况,以过往项目经验来看,到后期进行此项工作导致的资源浪费非常之大。

    (5)变体众多的shader不仅影响首次提交到硬件时的编译时间,还对内存的占用有着巨大的关系,一个变体几十个的shader可能内存占用不多,也就几十kb不到,但是一个上千变体的shader其内存则是数兆不止
    (6).ShaderLab在相关shader加入内存时就已经产生,但如果没有被渲染的话不会触发CreateGPUProgram操作,如果提前在ShaderVariantCollection中收集了相关变体并执行了warmup(预热)的话,第一次渲染时就不会再CreateGPUProgram,对卡顿会有一定好处。因此第一次加载shader先warmup一下,虽然等待的时间长但是仅此一次,当你退出游戏下次再进入也不会再执行warmup,可以有效避免渲染卡顿
    (7)!!!!!
    加入Always In Clude中,打包, ,和 把shader打成bundle,这两种方式在内存中的引用情况是不一样的。这个可以动手试一下。。。

    展开全文
  • ShaderVariant是啥,*.shadervariants 是干嘛的,#pragma shader_feature,#pragma multi_compile 有什么作用。 ShaderVariant 表示一个shader其中一个特定的变种。个人理解 有点像C 语言的预编译宏一样,shader源...

        最近工作遇到shader 打包问题,最后解决发现shader变体没有被包含进来所致。 一直以来对shader变体的了解还不够系统全面。现在觉得好好完整的总结下。

    一、概念

      ShaderVariant是啥,*.shadervariants 是干嘛的, #pragma shader_feature,#pragma multi_compile 有什么作用。

    ShaderVariant 表示一个shader其中一个特定的变种。个人理解 有点像C 语言的预编译宏一样,shader源代码写好后,编译时会根据 ShaderVariant 的关键字生成多个 最终shader。材质使用shader时,可以通过关键字使用其中那个最终shader.

    #pragma shader_feature,#pragma multi_compile 二者作用本质上是一样的,都是声明一个ShaderVariant关键字,之后Shader代码里就可以用#if 来判断此关键字是否启用,二者不同的地方就是前者在性能上做了优化。#shader_feature 声明的关键字,只在有材质球使用了其中关键字,则关键字的变体才会被包含近shader。*.shadervariants 文件可以手动编辑那些变体被包含进来。

    二、为啥要了解、使用变体。

    在实际工作,我们经常要根据外部条件的不同,对着色器有不同的处理方式。比如,如果有法线贴图的时候,会运用法线贴图,没有时候,会是一种处理逻辑,一般方法就是,在shader 属性上加个开关参数,针对有法线和没有法线的情况传入不同的值,再在shader代码里用if分支判断处理。这样虽然没有啥问题,但性能却下降很多,gpu运算都是并行的,分支结构对性能影响很大。所以要针对这两种情况,分别用两个不同shader来处理。变体,就可以针对这些情况用一份shader源代码通过使用不同关键字编译后生成不同的 最终shader。好处就是只要一份shader源代码,就可以生成多份目标shader.缺点就是,会增加 目标shader体积大小。影响最终包大小和加载变体的时间。

    三、如何正确使用变体

     1. 声明Keyword,用来产生Shader的变体(Variant)

       #pragma multi_compile A B 或者 #pragma shader_feature A B    全局变体,最多只能声明256个这样的Keyword;

      #pragma multi_complie_local 声明局部的、只在该Shader内部起作用的Keyword 每个Shader最多可以包含64个local Keyword

      local Keyword   Material.EnableKeyword仍是有效的,但对Shader.EnableKeyword 或CommandBuffer.EnableShaderKeyword这种全局开关说拜拜

        #if A

       return fixed(1,1,1,1)

       #endif

       #if B

       return fixed(1,0,0,1)

      #endif

     2. 变体组合

    变体组合 变种个数
    #pragma shader_feature A __, A  (非A 变体, A变体,) 默认:__
    #pragma shader_feature A B A,B 默认:A
    #pragma shader_feature A B C A,B,C 默认:A
    #pragma shader_feature  __ A __, A (非A 变体, A变体,) 默认:__
    #pragma multi_compile A A 默认:A
    #pragma multi_compile A B A,B 默认:A
    #pragma multi_compile A B C A, B,C 默认:A

    #pragma multi_compile  A B C

    #pragma multi_compile  D E

    AD,BD,CD,AE,BE,CE 默认:AD

    3. 通过shader 监视面板查看 ShaderVariant 包含的数量.

    3. 材质球如何选择变体组合。

     a. 默认选择第一个变体。 

      如 #pragma multi_compile A B 或者 #pragma shader_feature A B,默认选择 A

    b. 材质球监视面板,切换到Debug模式,Shader keywords 填入变体关键字组合

     

      

       c. 代码设置 

    Material. EnableKeyword(); Material. EnableKeyword();

    Shader.EnableKeyword();  Shader.DisableKeyword();

    四、shader变体打包

    1. shader_feature声明变种时,打包只会打包被资源引用的keyword变种,

    2. multi_compile声明变种时,打包会把所有变种都打进去

    3. ProjectSetting->Graphics->Always Include Shaders 列表里的shader ,打包时会把所有的变种打包进取。

    4. Resources目录的shader 打包时会把所有的变种打包进去

    5. *.shadervariantcollection 文件指定所有shader变体,会在打包时包含所有变体。

     

    五、自定义材质监视器

    1. Space、Heaer 标记
    [Space(50)][Header(MaterialPropertyDrawer)]
      Space 标记,可以带参数也可以不带,参数表示距离。

     Header 标记, 增加一个title,注意没有引号

    2. KeywordEnum 标记
    [KeywordEnum(None, Add, Multiply, Screen)] _BlendMode ("Blend Mode", Float) = 0 或者
    [Enum(None, 0, Add, 1, Multiply, 2, Screen ,3)] _BlendMode ("Blend Mode", Float) = 0

    生成一个下拉框,可以选择None, Add, Multiply, Screen

    shader 代码可以 

     

    #if _BLENDMODE_NONE , #if _BLENDMODE_ADD, #if _BLENDMODE_Multiply, #if _BLENDMODE_Screen

    命名规则为变量名的大写形式+枚举名的大写形式。而需要注意的是,Enum标记无法使用这些宏。

    3.Toggle

    [Toggle] _Invert ("Invert?", Float) = 0     //_INVERT_ON

    [Toggle(ENABLE_DOUBLE)] _Double ("Double?", Float) = 0             //ENABLE_DOUBLE

    shader 代码可以 

    #if _INVERT_ON  #if ENABLE_DOUBLE 

    命名规则是变量名的大写形式+_ON

    4. 继承ShaderGUI

    public class TestShaderGUI : ShaderGUI
    {
        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
            base.OnGUI(materialEditor, properties); // 显示默认面板

            Material targetMat = materialEditor.target as Material;

        }

    }

    展开全文
  • title: unity-shader-ShaderVariant变体及宏及材质编辑器扩展 categories: Unity3d tags: [unity, shader, 编辑器扩展, 宏] date: 2019-03-20 11:31:45 comments: false unity-shader-ShaderVariant变体及宏及材质...

    title: unity-shader-ShaderVariant变体及宏及材质编辑器扩展
    categories: Unity3d
    tags: [unity, shader, 编辑器扩展, 宏]
    date: 2019-03-20 11:31:45
    comments: false

    unity-shader-ShaderVariant变体及宏及材质编辑器扩展


    前篇


    说明

    #pragma shader_feature#pragma multi_compile 相似, 都是起到宏的作用

    不同的是 shader_feature 没有用到的不会被包含进去, 而 multi_compile 全部版本都会被包含

    所以 shader_feature 材质用, multi_compile 代码控制用.

    一般是写 XXX_OFFXXX_ON

    另外, 使用 shader_feature 时也极其容器踩坑, 参考 : 踩坑

    变体计算

    选中 shader, 可以通过减少 #pragma shader_feature#pragma multi_compile 声明的宏, 开查看. 可以看到是成倍的增减

    变体的计算, 假设原来初始有 23 个, 加入以下宏申明 ( 假设所有的 shader_feature 都被使用了 )

    #pragma shader_feature CS_BOOL
    #pragma shader_feature _BLENDMAP
    #pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY
    #pragma multi_compile _OVERLAY_NONE2 _OVERLAY_ADD2
    

    总量 = 23 * ( 2 * 3 * 2 ) = 276

    使用场景

    (非常重要!!!),shader_feature 适用于那些会在材质面板中直接设置的情况,而如果需要在脚本里通过DisableKeyword 和 EnableKeyword 来开启或关闭 keyword 的话就应该使用 multi_compile。(栽过坑啊!!!)并且不要在#pragma后面写注释!!!如果要在脚本里通过 Shader.DisableKeyword 来控制 keyword 的开关的话,不要在 Properties 里写 KeywordEnum,这样可能会导致脚本里的设置失效(可以重新建一个材质解决)。但如果是使用 material.DisableKeyword 来设置的话,就不会有这个问题,原因暂时不明。


    材质面板扩展

    shader 定义材质面板

    类别 描述
    ToggleDrawer 把一个类型为float的属性显示为一个开关,它的值要么是0要么是1。当选中它时,Unity还会设置一个名为大写属性名_ON(可以自定义名字)的shader feature(需要在shader里使用”#pragma shader_feature”来定义它),我们可以在shader里用过#if、#ifdef或者#if defined关键词来判断它当前是否被开启。
    EnumDrawer 把一个类型为float的属性显示为一个下拉列表。可以使用UnityEngine.Rendering命名空间下的各种状态来设置对应的渲染状态,例如ZWrite、ZTest、Blend等。据实验推测,这个值不可以随便定义,老老实实用UnityEngine.Rendering。
    KeywordEnum 和EnumDrawer类似,也会把一个类型为float的属性显示为一个下拉列表,但不同的是它会定义一组shader keyword(需要在shader里使用”#pragma multi_compile”来定义它们)。这些keyword的名字是大写属性名_枚举名。同样,我们可以在shader里用过#if、#ifdef或者#if defined配合#elif来判断当前选择是哪个keyword。最多同时支持9个keywords。
    PowerSliderDrawer 显示一个非线性响应的滑动条,其中PowerSliderDrawer中的参数指定了底数,然后我们再根据Range()来指定范围。其实简单说来就是滑动条上的数值不再是均匀变化了,而是xsliderxslider进行变化,但我们在shader里还是可以直接访问到一个float数值。
    Shader "Custom/Material Property Drawer Example"
    {
        Properties
        {
            // Header creates a header text before the shader property.
            [Header(Material Property Drawer Example)]
            // Space creates vertical space before the shader property.
            [Space]
    
            _MainTex ("Main Tex", 2D) = "white" {}
            _SecondTex ("Second Tex", 2D) = "white" {}
    
            // Large amount of space
            [Space(50)]
    
            // Toggle displays a **float** as a toggle. 
            // The property value will be 0 or 1, depending on the toggle state. 
            // When it is on, a shader keyword with the uppercase property name +"_ON" will be set, 
            // or an explicitly specified shader keyword.
            [Toggle] _Invert ("Invert color?", Float) = 0
    
            // Will set "ENABLE_FANCY" shader keyword when set
            [Toggle(ENABLE_FANCY)] _Fancy ("Fancy?", Float) = 0
    
            // Enum displays a popup menu for a **float** property. 
            // You can supply either an enum type name 
            // (preferably fully qualified with namespaces, in case there are multiple types), 
            // or explicit name/value pairs to display. 
            // Up to **7** name/value pairs can be specified
            [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend Mode", Float) = 1
            [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend Mode", Float) = 1
            [Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 0
            [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 0
            [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 1
    
            // KeywordEnum displays a popup menu for a **float** property, and enables corresponding shader keyword. 
            // This is used with "#pragma multi_compile" in shaders, to enable or disable parts of shader code. 
            // Each name will enable "property name" + underscore + "enum name", uppercased, shader keyword. 
            // Up to **9** names can be provided.
            [KeywordEnum(None, Add, Multiply)] _Overlay ("Overlay mode", Float) = 0
    
            // PowerSlider displays a slider with a non-linear response for a Range shader property.
            // A slider with 3.0 response curve
            [PowerSlider(3.0)] _Shininess ("Shininess", Range (0.01, 1)) = 0.08
        }
        SubShader
        {
            Tags { "Queue"="Transparent" "RenderType"="Transparent" }
            Blend [_SrcBlend] [_DstBlend]
            ZWrite [_ZWrite]
            ZTest [_ZTest]
            Cull [_Cull]
    
            Pass
            {
                CGPROGRAM
                // Need to define _INVERT_ON shader keyword
                #pragma shader_feature _INVERT_ON
                // Need to define _INVERT_ON shader keyword
                #pragma shader_feature ENABLE_FANCY
                // No comma between features
                #pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY
    
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
                sampler2D _SecondTex;
                float4 _SecondTex_ST;
                float _Shininess;
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float4 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                    o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
                    o.uv.zw = TRANSFORM_TEX(v.uv, _SecondTex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    // sample the texture
                    fixed4 col = tex2D(_MainTex, i.uv.xy);
    
                    // Use #if, #ifdef or #if defined
                    #if _INVERT_ON
                    col = 1 - col;
                    #endif
    
                    // Use #if, #ifdef or #if defined
                    #if ENABLE_FANCY
                    col.r = 0.5;
                    #endif
    
                    fixed4 secCol = tex2D(_SecondTex, i.uv.zw);
    
                    #if _OVERLAY_ADD
                    col += secCol;
                    #elif _OVERLAY_MULTIPLY
                    col *= secCol;
                    #endif
    
                    col *= _Shininess;
    
                    return col;
                }
                ENDCG
            }
        }
    }
    

    cs 脚本扩展材质面板

    using UnityEngine;
    using UnityEditor;
    using System;
    
    public class TestShaderGUI : ShaderGUI
    {
        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
            base.OnGUI(materialEditor, properties); // 显示默认面板
    
            Material targetMat = materialEditor.target as Material;
    
    		// test1, shader 中增加一个 Toggle, 来决定编辑器是否显示 _Color2 属性
    		MaterialProperty _UseTwoColors = ShaderGUI.FindProperty("_UseTwoColors", properties);
    		if (_UseTwoColors.floatValue == 1)
    		{
    			MaterialProperty _Color2 = ShaderGUI.FindProperty("_Color2", properties);
    			materialEditor.ShaderProperty(_Color2, _Color2.displayName);
    		}
    
    		// test2, 检测贴图是否有被用到, 来决定是否启用 _BLENDMAP 宏
    		MaterialProperty blendMap = ShaderGUI.FindProperty("_BlendTex", properties);
    		bool blendEnabled = blendMap.textureValue != null;
    		if (blendEnabled) {
          		targetMat.EnableKeyword("_BLENDMAP");
    		}
    		else {
    			targetMat.DisableKeyword("_BLENDMAP");
    		}
    		
    		// test3, 编辑器扩展一个 Toggle, 来决定是否启用 CS_BOOL 宏
            bool CS_BOOL = Array.IndexOf(targetMat.shaderKeywords, "CS_BOOL") != -1;
            EditorGUI.BeginChangeCheck();
            CS_BOOL = EditorGUILayout.Toggle("CS_BOOL", CS_BOOL);
    
            if (EditorGUI.EndChangeCheck())
            {
                if (CS_BOOL)
                    targetMat.EnableKeyword("CS_BOOL");
                else
                    targetMat.DisableKeyword("CS_BOOL");
            }
        }
    }
    


    踩坑

    展开全文
  • 什么是ShaderVariant 在写shader时,往往会在shader中定义多个宏,并在shader代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种组合形式的shader源码。其中每一种组合...

    基础知识介绍

    什么是ShaderVariant

    在写shader时,往往会在shader中定义多个宏,并在shader代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种组合形式的shader源码。其中每一种组合就是这个shader的一个变体(Variant)。

    Material ShaderKeywords与ShaderVariant

    Material所包含的Shader Keywords表示启用shader中对应的宏,Unity会调用当前宏组合所对应的变体来为Material进行渲染。
    在Editor下,可以通过将material的inspector调成Debug模式来查看当前material定义的Keywords,也可在此模式下直接定义Keywords,用空格分隔Keyword。
    在这里插入图片描述
    在程序中,可用Material.EnableKeyword()、Material.DisableKeyword()、Shader.EnableKeyword()、Shader.DisableKeyword()来启用/禁用相应的宏。Enable函数应与Disable函数相对应。若一个宏由Material.EnableKeyword()开启,则应由Material.DisableKeyword()关闭,Shader.DisableKeyword()无法关闭这个宏。Material中定义的Keywords由Material的函数进行设置。

    multi_compile与shader_feature

    multi_compile与shader_feature可在shader中定义宏。两者区别如下图所示:

    multi_compile shader_feature
    定义方式 #pragma multi_compile A #pragma shader_feature A
    宏的适用范围 大多数shader 一般仅针对shader自身
    变体的生成 生成所有的变体 可自定义生成何种变体
    默认定义的宏 默认定义首个宏 默认定义首个宏
    1. 定义方式
      定义方式中值得注意的是,#pragma shader_feature A其实是 #pragma shader_feature _ A的简写,下划线表示未定义宏(nokeyword)。因此此时shader其实对应了两个变体,一个是nokeyword,一个是定义了宏A的。
      而#pragma multi_compile A并不存在简写这一说,所以shader此时只对应A这个变体。若要表示未定义任何变体,则应写为 #pragma multi_compile __ A。

    2. 宏的适用范围
      multi_compile定义的宏,如#pragma multi_compile_fog,#pragma multi_compile_fwdbase等,基本上适用于大部分shader,与shader自身所带的属性无关。
      shader_feature定义的宏多用于针对shader自身的属性。比如shader中有_NormalMap这个属性(Property),便可通过#pragma shader_feature _NormalMap来定义宏,用来实现这个shader在material有无_NormalMap时可进行不同的处理。

    3. 变体的生成
      #pragma multi_compile A B C
      #pragma multi_compile D E
      则此时会生成 A D、A E、B D、B E、C D、C E这6中变体。
      shader_feature要生成何种变体可用shader variant collection进行自定义设置。

    4. 默认定义的宏
      当material中的keywords无法对应shader所生成的变体时,Unity便会默认定义宏定义语句中的首个宏,并运行相应的变体来为这个material进行渲染。
      multi_compile与shader_feature都默认定义首个宏,如下表所示:

    宏定义语句 默认定义的宏
    #pragma shader_feature A nokeyword(存在简写问题)
    #pragma shader_feature A B C A
    #pragma multi_compile A A
    #pragma multi _compile A B C A

    如何控制项目中Shader变体的生成

    项目中shader的生成方式主要有三种,其优缺点如下表所示:

    生成方式 优点 缺点
    shader与material打在一个包中 变体根据material中的keywords自动生成 多个不同的material包中可能存在相同的shader变体,造成资源冗余
    若在程序运行时动态改变material的keyword其变体可能并没有被生成
    Shader单独打包,使用multi_compile定义全部宏 全部变体都被生成,不会发生需要的变体未生成的情况 生成的变体数量庞大,严重浪费资源
    Shader单独打包,shader_feature与multi_compile结合使用 能够有效控制变体数量 如何确定哪些变体需要生成
    容易遗漏需要生成的变体

    而我们希望的结果是在保证渲染效果正确的情况下,要尽可能的控制项目中shader的变体数量,避免产生冗余资源。幸运的是,Unity已经为我们准备好了解决方案:ShaderVariantCollection。

    解决方案:ShaderVariantCollection

    ShaderVariantCollection介绍

    Shader Variant Collection是用来记录shader中哪些变体是实际使用的。其优点主要有:在shader_feature与multi_compile结合使用时,能够设置生成何种变体,从而避免生成不必要的变体;shader不必和material打在一个包中,避免了多个包中存在相同的变体资源;明确直观的显示了哪些变体是需要生成的。

    在Unity中可以通过Create->Shader-> Shader Variant Collection,就可以新建一个shader variant collection文件,shader variant collection 的使用如下图所示:
    在这里插入图片描述
    点击增加变体后,会出现变体选择窗口
    在这里插入图片描述
    配置好需要生成的变体后,将collection与shader打在同一个包中,便能准确生成面板中所配置的shader变体。

    ShaderVariantCollection生成变体规则

    除了在collection中配置的变体会被生成外,Unity还在后台为我们多生成了几个变体,这几个变体是“隐藏的”,并未在collection面板中显示。

    1. 必定生成首个宏定义开启所对应的变体。
      Shader中通过#pragma shader_feature A定义了宏A,并在collection中加入了宏A所对应的变体,如下图所示:
      在这里插入图片描述
      此时生成的变体除了collection中已经存在的ForwardBase A外,还会生成变体ForwardBase nokeyword。因为只定义单个宏时,A 为 _ A的简写。实际上首个被定义的宏为nokeyword,故 nokeyword所对应的变体必定会被生成。
      同理,以 #pragma shader_feature A B C来定义宏时,即使collection中未添加变体Forward A,这个变体也必定会被生成(当shader的PassType仅有ForwardBase)。

    2. Shader中有多个Pass时变体的生成规则

    a. 读取ShaderVariantCollection中已存在的变体,获取它们的Keywords。
    b. 将这些Keywords分别与每个Pass的多组Keywords列表求交集,取交集中Keywords数量最多得那组。
    c. 用得到的Keywords与对应的PassType生成ShaderVariant,并添加到ShaderVariantCollection中。
    d. 若得到得交集中有新的Keywords,则回到b。

    上述过程类似递归。例如:
    Shader 中有 ForwardBase、ForwardAdd、Normal 三种PassType(以下为了方便简称Base、Add、 Normal)。定义的宏如下:

    Base Add Normal
    #pragma shader_feature A
    #pragma shader_feature B
    #pragma shader_feature C
    #pragma shader_feature A
    #pragma shader_feature E
    #pragma shader_feature A
    #pragma shader_feature B
    #pragma shader_feature E

    此时若ShaderVariantCollection中包含的变体是 Base ABC,Add AE。则此时生成的变体为:这三种PassType的默认定义的宏(nokeyword)所对应的变体(3个)以及原先直接包含的Base ABC、Add AE。除此之外Unity还会额外生成Add A、Base A、Normal A、Normal AB、 Base AB、Normal AE这6个变体。

    ABC ∩ Add AE -> Add A (A is NewKeyword)
        A ∩ Base ABC -> Base A
        A ∩ Normal ABE -> Normal A
    ABC ∩ Normal ABE -> Normal AB (AB is NewKeyword)
        AB ∩ Base ABC -> Base AB
    AE ∩ Normal ABE -> Normal AE

    变体的调用规则

    当collection将变体准确生成后,便能在运行时通过修改material中的keywords来实现对不同变体的调用。
    假设某collection生成的变体只有Forward ABC,Forward ABE,Forward nokeyword这三种,则此时调用关系如下:

    Material中的Keywords 调用的变体 解释
    A B C Forward A B C 正常匹配
    A B Forward nokeyword 没有匹配的变体,调用默认被定义的宏 所对应的变体
    A B C D Forward A B C 调用交集中keyword数量多的变体
    ABCD ∩ ABC = ABC
    ABCD ∩ ABE = AB
    A B C E Forward A B C 交集中keyword数量相同,在collection中谁在前就调用谁
    A B C E Forward A B C 与在material中的定义顺序无关

    在这里插入图片描述
    以上规则均为根据测试总结归纳出来的规则,若有错误之处还请严加指正!

    项目中对Shader Variant的管理

    项目中变体的添加

    那么项目中是如何确定哪些变体是需要加到collection中的呢?我们的做法是:

    1. 遍历每一个Material,提取其shader keywords。
    2. 将获得的keywords与shader的每个PassType所包含的宏定义做交集,并将其结果添加到collection中。

    举个简单的例子,Material中的Keywords为A B C D,则shader的PassType、PassType中所定义的宏、需要往collection中添加的变体则如下表所示:

    PassType 定义的宏 需要往collection中添加的变体
    ForwardBase #pragma shader_feature A
    #pragma shader_feature B
    Forward A B
    ((ABCD ∩ AB = AB))
    ForwardAdd #pragma shader_feature _ C D Add C
    (ABCD ∩ C = C,ABCD ∩ D = D,但C的定义在D前,故只添加C)
    Normal #pragma shader_feature _ E F Normal NoKeyword
    (ABCD ∩ E = NoKeyword)
    (ABCD ∩ F = NoKeyword)

    需要说明的是,我们自己的代码里为了降低变体生成逻辑的复杂度、保持collection面板上变体的直观性,不将Unity为我们额外生成的那几个变体添加到collection面板中,但要记得Unity是会为我们生成额外的变体的。

    Shader编写规范

    1. 建议使用shader_feature时将定义语句写成完整模式,并且不要在一个语句中定义多个宏。
      完整模式:#pragma shader_feature _ A,不建议写成#pragma shader_feature A。
      不建议在一个语句中定义多个宏,如: #pragma shader_feature _ A B C,若一定要定义多个宏,请务必将其写成完整模式,不使用完整模式在切换shader时可能会与想要的效果不一致,具体原因尚未测得。

    2. 若在shader中使用shader_feature,请为这个shader指定一个CustomEditor
      每个使用shader_feature来定义Keyword的shader都需要再末尾加个 CusomEditor “xxxx”,并在代码中实现类xxxx(需继承自UnityEditor.ShaderGUI),用来对Keywords定义进行设定。
      这么做是因为Material中的部分Keyword是由shader中的属性(Properties)所控制的。比如shader中含有_NormalMap的属性并且定义了与_NormalMap相关的Keyword,这个Keyword需要在Material含有NormalMap时添加,不含NormalMap时移除。这个功能可由自定义的CustomEidtor实现。
      具体如何写这个CustomEditor类可参考Unity builtin_shaders\Editor\StandardShaderGUI.cs。该文件可去Unity官网下载,下载时选择内置着色器即可。
      在这里插入图片描述

    3. 如果需要在代码中开关宏,请使用multi_compile来定义这个宏,以免变体丢失。

    展开全文
  • Unity技术分享连载(64)|Shader Variant Collection|Material.SetPassFast https://www.codetd.com/article/8857415 Unity技术分享连载(64)|Shader Variant Collection|Material.SetPassFast ...
  • Unity技术分享(64)|Shader Variant Collection|打包方式 http://www.sohu.com/a/225673118_100123347 原文链接:https://blog.uwa4d.com/archives/TechSharing_64.html 我们将从日常技术交流中精选若干个开发...
  • shader variants shader变体 在写shader的时候,会写一些小段代码,通过关键字去开启或者关闭它们,当Unity去编译他们的时候,会根据关键字开关的组合去生成不同的shader程序,每一个单独的程序就叫做shader v.
  • 1. 首先在CGPROGRAM 下 申明 需要多版本编译的 几个版本的key,Material同一时刻只能对应它所使用的Shader的一个variant。 #pragma multi_compile A B 如果C#代码没指定,Unity会默认使用 A这个编译版本。 当B...
  • 打包成exe后法线变体收集器没起作用 注意如果没有包含进去的话需要手动 再alwaysInclude里取包含 这样打包的程序才会有作用,不知是不是bug
  • 举例: ...Shader "Mihayou/SmokeDissolve" { Properties { _Maintex ("Maintex", 2D) = "white" {} _DisolveMap01 ("DisolveMap01", 2D) = "white" {} _Min ("Min", Range(0, 1)) = 0.25641...
  • Shader变体收集与打包

    2020-01-28 13:05:53
    Shader变体收集与打包 ... Shader变体收集与打包 作者:小明 Shader变体收集与打包 基础知识 ... Material ShaderKeywords与ShaderVariant multi_compile与shader_feature 1. 定义方式...
  • 什么是ShaderVariant 在写shader时,往往会在shader中定义多个宏,并在shader代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种组合形式的shader源码。其中每一种组合...
  • Shader变体收集与打包作者:小明基础知识什么是ShaderVariant在写shader时,往往会在shader中定义多个宏,并在shader代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种...
  • <div><p>This is a step towards converting the text run shader to be a brush shader. Like brush shaders, we now determine whether to apply pixel snapping based on a conditional, rather than a new ...
  • Unity的Shader加载编译优化

    千次阅读 2019-05-23 20:47:13
    参考官方关于Shader...最近项目中引入了ShaderVariant机制,将部分shader的变体放到了variant中,但是在加载variant执行warmUp的时候发现卡的时间特别长,连接真机profiler看到如下 之前看到过文章,在Un...
  • bing unity ShaderVariantCollection.ShaderVariant 一种Shader变体收集和打包编译优化的思路 https://www.jianshu.com/p/0bd9b16496d0 一、什么是变体 引用Unity官方文档的解释:ShaderVariant In Unity, ...
  • Unity中的Shader模板 Standard Surface Shader 包含了标准光照模型的表面着色器 Unlit Shader 不包含光照(包含雾效)的基本顶点/片元着色器 Image Effect Shader ... Shader Variant Collection 减少Shader变体,...
  • Better MRTK Standard Shader

    2021-01-10 08:53:58
    This makes the shader a lot more complicated, with more variants for the same shader, where there could be different shaders limiting those variant growing exponentially. Plus, it's get ahrder to ...
  • ShaderVariant 在shader最终编译时,根据上层的宏定义,根据不同的组合编译出多套底层shader. Unity的Shader中通过multi_compile和shader_feature来定义宏(keyword)。最终编译的时候也是根据这些宏来编译成多种组合...
  • EE r600_state_common.c:761 r600_shader_select - Failed to build shader variant (type=0) -12 </code></pre> <p>Apparently the error is not logged to <code>daemon.log</code> but is printed to the ...
  • 2.还有一个问题是没有使用Shader Variant Collection的情况下,打包到安卓真机上发现所有用来生成变体的预处理宏都不见了。用adb连电脑查看frame debug,确实shader中的宏都没有了。问题在于使用的是 shader_feature...

空空如也

空空如也

1 2 3 4 5 6
收藏数 119
精华内容 47
关键字:

shadervariant