About the arrangement of Shader KeyWord

About the arrangement of Shader KeyWord

About the arrangement of Shader KeyWord. It originated from a demo I made a long time ago, and I dug it out today to sort it out.



foreword

About the arrangement of Shader KeyWord. It originated from a demo I made a long time ago, and I dug it out today to sort it out.


1. Keyword

In shader writing, we often encounter such problems. We want to manually control the opening and closing of certain display functions through code. From this variant came into being. The variant definition of Unity shader has
multi_compile
shader_feature
shader_feature_local. After unity 2019, there are
switch functions that can realize the above functions. But their scope is different. multi_compile can act globally or locally. You can use Shader.EnableKeyword("XXXX") to act globally, and use meshRender.material.EnableKeyword ("XXXX") to act locally. The same is true for shader_feature, but there is a big difference in packaging, which will be explained below.
By definition, multi_compile A will only define one variant A, and it is turned on by default and cannot be turned off. So we need to add an underscore to represent the off state
multi_compile_A.
Defining shader_feature A will define variant_ and variant A by default.
Local keywords:
The main disadvantage of shader_feature and multi_compile is that all keywords defined limit the number of global keywords for Unity (256 global keywords, plus 64 local keywords). To avoid this problem, we can use different shader variant directives: shader_feature_local and multi_compile_local.

shader_feature_local: similar to shader_feature, but only for this shader
multi_compile_local: similar to multi_compile, but only for this shader

Two, KeyWord view

We can click here to toggle Debug mode and see the variants cached on the shader. and other data.
insert image description here
insert image description here
Select the shader file and click keyword to display the variants defined by the shader.
insert image description here
Click the Compile And Show Code arrow to view the variant combination
insert image description here
insert image description here
On the frame debugger, we can also see the currently effective variants.
insert image description here

3. KeyWordDemo

Let's go directly to our demo code first. First define a shader code as follows:

Shader "Unlit/NewUnlitShader"
{
    
    

   SubShader {
    
    
		Pass {
    
    
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#pragma multi_compile MY_multi_1 
		#pragma multi_compile MY_multi_2
		#include "UnityCG.cginc"
		struct vertOut {
    
    
			 float4 pos : POSITION;
            };
		vertOut vert(appdata_base v)
		{
    
    
			vertOut o;
			o.pos = UnityObjectToClipPos(v.vertex);
			return o;
		}
		float4 frag(vertOut i):COLOR
		{
    
    
			float4 c = float4(0, 0, 0, 0);
			#if defined (MY_multi_1)
			c = float4(0, 1, 0, 0);//输出绿色
			#endif
			#if defined (MY_multi_2)
			c = float4(0, 0, 1, 0);//输出蓝色
			#endif
			return c;
		}
		ENDCG
		}	
	} 
}

The code is as above, we define two multi_compile global variants MY_multi_1, MY_multi_2. MY_multi_2 has higher priority than MY_multi_1. When MY_multi_2 is true, we output blue, when MY_multi_1 is true, and MY_multi_2 is false, we output green. When neither MY_multi_1 nor MY_multi_2 is effective, we output black directly.
We add the CS script, which controls the variant switch.

public class TestKeyWorld : MonoBehaviour
{
    
    
    public bool multi_1;
    public MeshRenderer meshRender;

    public void OnChangeJJJJ()
    {
    
    
        multi_1 = !multi_1;
       if (multi_1) {
    
    
            Shader.EnableKeyword("MY_multi_1");
            Shader.DisableKeyword("MY_multi_2");
            //meshRender.material.EnableKeyword ("MY_multi_1");
            //meshRender.material.DisableKeyword ("MY_multi_2");
        } else {
    
    
            Shader.EnableKeyword("MY_multi_2");
            Shader.DisableKeyword("MY_multi_1");
            //meshRender.material.EnableKeyword ("MY_multi_2");
            //meshRender.material.DisableKeyword ("MY_multi_1");
        }
    }
}

We define the function OnChangeJJJJ to control the switch of the variant.
In the scene, we define two facets A and B. and buttons. The click event of the button is bound to our function OnChangeJJJJ()
and the scene is as follows:
insert image description here

1.multi_compile

We can see that the patches are already showing blue. This is because our variant defines #pragma multi_compile MY_multi_1 without the underscore. Then his default value is on. And the function OnChangeJJJJ() cannot control the switch of the variant.
At this time, we modify the variant definition and add the close option "_"
#pragma multi_compile _ MY_multi_1
//#pragma multi_compile _ MY_multi_2 (comment the variant)
insert image description here
you can see that it is displayed in black, that is, MY_multi_1 is not turned on. We click the button to control the opening and closing of the variant.
insert image description here
We can see that both panels turn green together because we are using the multi_compile global variant. For variant control, we use Shader.EnableKeyword("MY_multi_1");
if we want to use multi_compile to control a single shader variant switch, we can use meshRender.material.EnableKeyword("MY_multi_1"). We modify the code shader

#pragma multi_compile _ MY_multi_1
#pragma multi_compile _ MY_multi_2

Modify the function OnChangeJJJJ

 public void OnChangeJJJJ()
    {
    
    
        multi_1 = !multi_1;
       if (multi_1) {
    
    
            //Shader.EnableKeyword("MY_multi_1");
            //Shader.DisableKeyword("MY_multi_2");
            meshRender.material.EnableKeyword("MY_multi_1");
            meshRender.material.DisableKeyword("MY_multi_2");
        } else {
    
    
            //Shader.EnableKeyword("MY_multi_2");
            //Shader.DisableKeyword("MY_multi_1");
            meshRender.material.EnableKeyword("MY_multi_2");
            meshRender.material.DisableKeyword("MY_multi_1");
        }
    }

Run the game again. We can see that the panel on the right is still green. In fact, it should be black. It should be caused by the cache of the shader. When we pack it out or restart Unity, it will become black. The left panel is functional.
insert image description here
Let's pack and try the exe
insert image description here
to see that the function is normal. We use multi_compile here, and all variants will be generated when packaging, regardless of whether they are currently used. But because this thing will generate all variant combinations, when the number of variant definitions is large, the variant combinations will grow exponentially, and the memory will explode. So we need to use it appropriately.

2.shader_feature

We try to use shader_feature to achieve the above effect. Modify the shader file

#pragma shader_feature MY_multi_1
#pragma shader_feature MY_multi_2

Modify the cs function

 public void OnChangeJJJJ()
    {
    
    
        multi_1 = !multi_1;
       if (multi_1) {
    
    
            //Shader.EnableKeyword("MY_multi_1");
            //Shader.DisableKeyword("MY_multi_2");
            meshRender.material.EnableKeyword("MY_multi_1");
            meshRender.material.DisableKeyword("MY_multi_2");
        } else {
    
    
            //Shader.EnableKeyword("MY_multi_2");
            //Shader.DisableKeyword("MY_multi_1");
            meshRender.material.EnableKeyword("MY_multi_2");
            meshRender.material.DisableKeyword("MY_multi_1");
        }
}

The variant definition #pragma shader_feature MY_multi_1 does not need to carry the _ underscore. It will be defined by default.
Running this, we found nothing unusual.
insert image description here
Let's take a look at the package.
insert image description here
We can see that it is black by default, and the buttons do not respond. Because when the shader_feature variant is packaged, only the compiled variant will be entered. The default value of shader_feature is "_", which is not enabled by default.
In order to solve the above problems, the ShaderVariants variant collector came into being.
insert image description here
insert image description here
After selecting it in the Demo,
insert image description here
put it here to preload,
insert image description here
let's try to package it again, and
insert image description here
the function is normal.
We have understood that using the variant collector ShaderVariants in conjunction with shader_feature can control the generation of variant combinations very well and exclude unnecessary variants.

Fourth, the variant collector is automatically generated

Variant generation rules are as follows:
shader_feature A
shader_feature B
Variant Group is as follows
A, B, AB
paste the code directly.

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Reflection;
using System;
using UnityEngine.Rendering;
using System.Linq;

public class ShaderCollection : EditorWindow
{
    
    
    static Dictionary<string, List<ShaderVariantCollection.ShaderVariant>> ShaderVariantDict = new Dictionary<string, List<ShaderVariantCollection.ShaderVariant>>();
    public static List<string> GetAllRuntimeDirects()
    {
    
    
        //搜索所有资源
        List<string> directories = new List<string>();
        directories.Add("Assets");
        return directories;
    }
    private ShaderVariantCollection svc;
    readonly public static string ALL_SHADER_VARAINT_PATH = "Assets/AllShaders.shadervariants";

    static List<string> allShaderNameList = new List<string>();

    [MenuItem("ShaderTool/AutoShaderVariants")]
    public static void GenShaderVariant()
    {
    
    
        ShaderVariantDict = new Dictionary<string, List<ShaderVariantCollection.ShaderVariant>>();
        //先搜集所有keyword到工具类SVC
        toolSVC = new ShaderVariantCollection();
        var shaders = AssetDatabase.FindAssets("t:Shader", new string[] {
    
     "Assets", "Packages" }).ToList();
        foreach (var shader in shaders)
        {
    
    
            ShaderVariantCollection.ShaderVariant sv = new ShaderVariantCollection.ShaderVariant();
            var shaderPath = AssetDatabase.GUIDToAssetPath(shader);
            sv.shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
            toolSVC.Add(sv);
            //
            allShaderNameList.Add(shaderPath);
        }

        var toolsSVCpath = "Assets/Tools.shadervariants";
        //防空

        File.WriteAllText(toolsSVCpath, "");
        AssetDatabase.DeleteAsset(toolsSVCpath);
        AssetDatabase.CreateAsset(toolSVC, toolsSVCpath);

        //搜索所有Mat
        var paths = GetAllRuntimeDirects().ToArray();
        var assets = AssetDatabase.FindAssets("t:Prefab", paths).ToList();
        var assets2 = AssetDatabase.FindAssets("t:Material", paths);
        assets.AddRange(assets2);
        List<string> allMats = new List<string>();

        //GUID to assetPath
        for (int i = 0; i < assets.Count; i++)
        {
    
    
            var p = AssetDatabase.GUIDToAssetPath(assets[i]);
            //获取依赖中的mat
            var dependenciesPath = AssetDatabase.GetDependencies(p, true);
            var mats = dependenciesPath.ToList().FindAll((dp) => dp.EndsWith(".mat"));
            allMats.AddRange(mats);
        }

        //处理所有的 material
        allMats = allMats.Distinct().ToList();

        float count = 1;
        foreach (var mat in allMats)
        {
    
    
            var obj = AssetDatabase.LoadMainAssetAtPath(mat);
            if (obj is Material)
            {
    
    
                var _mat = obj as Material;
                EditorUtility.DisplayProgressBar("处理mat", string.Format("处理:{0} - {1}", Path.GetFileName(mat), _mat.shader.name), count / allMats.Count);
                AddToDict(_mat);
            }

            count++;
        }

        EditorUtility.ClearProgressBar();
        //所有的svc
        ShaderVariantCollection svc = new ShaderVariantCollection();
        foreach (var item in ShaderVariantDict)
        {
    
    
            foreach (var _sv in item.Value)
            {
    
    
                svc.Add(_sv);
            }
        }

        AssetDatabase.DeleteAsset(ALL_SHADER_VARAINT_PATH);
        AssetDatabase.CreateAsset(svc, ALL_SHADER_VARAINT_PATH);
        AssetDatabase.Refresh();

    }
    public class ShaderData
    {
    
    
        public int[] PassTypes = new int[] {
    
     };
        public string[][] KeyWords = new string[][] {
    
     };
        public string[] ReMainingKeyWords = new string[] {
    
     };
    }

    //shader数据的缓存
    static Dictionary<string, ShaderData> ShaderDataDict = new Dictionary<string, ShaderData>();

   

    //添加Material计算
    static List<string> passShaderList = new List<string>();

    /// <summary>
    /// 添加到Dictionary
    /// </summary>
    /// <param name="curMat"></param>
    static void AddToDict(Material curMat)
    {
    
    
        if (!curMat || !curMat.shader) return;

        var path = AssetDatabase.GetAssetPath(curMat.shader);
        if (!allShaderNameList.Contains(path))
        {
    
    
            Debug.LogError("不存在shader:" + curMat.shader.name);
            Debug.Log(path);
            return;
        }

        ShaderData sd = null;
        ShaderDataDict.TryGetValue(curMat.shader.name, out sd);
        if (sd == null)
        {
    
    
            //一次性取出所有的 passtypes 和  keywords
            sd = GetShaderKeywords(curMat.shader);
            ShaderDataDict[curMat.shader.name] = sd;
        }

        var kwCount = sd.PassTypes.Length;
        if (kwCount > 2000)
        {
    
    
            if (!passShaderList.Contains(curMat.shader.name))
            {
    
    
                Debug.LogFormat("Shader【{0}】,变体数量:{1},不建议继续分析,后续也会跳过!", curMat.shader.name, kwCount);
                passShaderList.Add(curMat.shader.name);
            }
            else
            {
    
    
                Debug.LogFormat("mat:{0} , shader:{1} ,keywordCount:{2}", curMat.name, curMat.shader.name, kwCount);
            }

            return;
        }

      
        List<ShaderVariantCollection.ShaderVariant> svlist = null;
        if (!ShaderVariantDict.TryGetValue(curMat.shader.name, out svlist))
        {
    
    
            svlist = new List<ShaderVariantCollection.ShaderVariant>();
            ShaderVariantDict[curMat.shader.name] = svlist;
        }

        //求所有mat的kw
        for (int i = 0; i < sd.PassTypes.Length; i++)
        {
    
    
            //
            var pt = (PassType)sd.PassTypes[i];
            ShaderVariantCollection.ShaderVariant? sv = null;
            try
            {
    
    
                string[] key_worlds = sd.KeyWords[i];

                //变体交集 大于0 ,添加到 svcList
                sv = new ShaderVariantCollection.ShaderVariant(curMat.shader, pt, key_worlds);
                SetShaderVariantKeyWorld(svlist, sv);
            }
            catch (Exception e)
            {
    
    
                Debug.LogErrorFormat("{0}-当前shader不存在变体(可以无视):{1}-{2}", curMat.name, pt, curMat.shaderKeywords.ToString());
                continue;
            }

 
        }
    }

    static void SetShaderVariantKeyWorld(List<ShaderVariantCollection.ShaderVariant> svlist, ShaderVariantCollection.ShaderVariant? sv)
    {
    
    
        //判断sv 是否存在,不存在则添加
        if (sv != null)
        {
    
    
            bool isContain = false;
            var _sv = (ShaderVariantCollection.ShaderVariant)sv;
            foreach (var val in svlist)
            {
    
    
                if (val.passType == _sv.passType && System.Linq.Enumerable.SequenceEqual(val.keywords, _sv.keywords))
                {
    
    
                    isContain = true;
                    break;
                }
            }

            if (!isContain)
            {
    
    
                svlist.Add(_sv);
            }
        }
    }


    static MethodInfo GetShaderVariantEntries = null;

    static ShaderVariantCollection toolSVC = null;

    //获取shader的 keywords
    public static ShaderData GetShaderKeywords(Shader shader)
    {
    
    
        ShaderData sd = new ShaderData();
        GetShaderVariantEntriesFiltered(shader, new string[] {
    
     }, out sd.PassTypes, out sd.KeyWords, out sd.ReMainingKeyWords);
        return sd;
    }

    /// <summary>
    /// 获取keyword
    /// </summary>
    /// <param name="shader"></param>
    /// <param name="filterKeywords"></param>
    /// <param name="passTypes"></param>
    /// <param name="keywordLists"></param>
    /// <param name="remainingKeywords"></param>
    static void GetShaderVariantEntriesFiltered(Shader shader, string[] filterKeywords, out int[] passTypes, out string[][] keywordLists, out string[] remainingKeywords)
    {
    
    
        //2019.3接口
        //            internal static void GetShaderVariantEntriesFiltered(
        //                Shader                  shader,                     0
        //                int                     maxEntries,                 1
        //                string[]                filterKeywords,             2
        //                ShaderVariantCollection excludeCollection,          3
        //                out int[]               passTypes,                  4
        //                out string[]            keywordLists,               5
        //                out string[]            remainingKeywords)          6
        if (GetShaderVariantEntries == null)
        {
    
    
            GetShaderVariantEntries = typeof(ShaderUtil).GetMethod("GetShaderVariantEntriesFiltered", BindingFlags.NonPublic | BindingFlags.Static);
        }

        passTypes = new int[] {
    
     };
        keywordLists = new string[][] {
    
     };
        remainingKeywords = new string[] {
    
     };
        if (toolSVC != null)
        {
    
    
            var _passtypes = new int[] {
    
     };
            var _keywords = new string[] {
    
     };
            var _remainingKeywords = new string[] {
    
     };
            object[] args = new object[] {
    
     shader, 256, filterKeywords, toolSVC, _passtypes, _keywords, _remainingKeywords };
            GetShaderVariantEntries.Invoke(null, args);

            var passtypes = args[4] as int[];
            passTypes = passtypes;
            //key word
            keywordLists = new string[passtypes.Length][];
            var kws = args[5] as string[];
            for (int i = 0; i < passtypes.Length; i++)
            {
    
    
                keywordLists[i] = kws[i].Split(' ');
            }

            //Remaning key word
            var rnkws = args[6] as string[];
            remainingKeywords = rnkws;
        }
    }
}

insert image description here
Click here to automatically collect variant combinations.
We also want to output shader information during packaging
by implementing the interface IPreprocessShaders to
directly paste the code

using System.Collections;
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;

public class MyCustomBuildProcessor : IPreprocessShaders
{
    
    
    ShaderKeyword m_Blue;

    public MyCustomBuildProcessor()
    {
    
    
        m_Blue = new ShaderKeyword("_BLUE");
    }

    public int callbackOrder {
    
     get {
    
     return 0; } }

    public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
    {
    
    
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        sb.AppendFormat("shader={3}, passType={0}, passName={1}, shaderType={2}\n",
            snippet.passType, snippet.passName, snippet.shaderType, shader.name);

        for (int i = 0; i < data.Count; ++i)
        {
    
    
            var pdata = data[i];
            sb.AppendFormat("{0}.{1},{2}: ", i, pdata.graphicsTier, pdata.shaderCompilerPlatform);
            var ks = pdata.shaderKeywordSet.GetShaderKeywords();
            foreach (var k in ks)
            {
    
    
                sb.AppendFormat("{0}, ", k.ToString());
            }
            sb.Append("\n");
        }
        Debug.Log(sb.ToString());
    }
}

Summarize

The above is what I will talk about today. If there are any mistakes, please point them out.

Guess you like

Origin blog.csdn.net/weixin_39289457/article/details/125964988