GameTech/URP 渲染实战
源码 / Ch29/Editor/CookbookVariantStripper.cs
Ch29/Editor/CookbookVariantStripper.cs
// 案例 29.3:自定义 Shader 变体剥离(IPreprocessShaders)。
// 打包时 Unity 对每个 Shader 的每个 Pass/阶段调用一次 OnProcessShader,data 里是即将编译的变体列表,
// 从列表里删掉的变体就不会被编译、也不会进包。
// 规则要根据项目实际情况写:剥掉“确定用不到”的,宁可少剥也不要错剥(错剥的变体运行时会退回到别的变体,表现出错)。
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;

namespace Cookbook.Ch29
{
    class CookbookVariantStripper : IPreprocessShaders, IPreprocessBuildWithReport, IPostprocessBuildWithReport
    {
        // ---- 项目约定(示例):根据你的项目改 ----
        const bool k_ProjectUsesLightmaps = false;          // 全实时光照,没有烘焙光照贴图
        const bool k_ProjectUsesScreenSpaceShadows = false; // 没有加 Screen Space Shadows Feature
        const bool k_ProjectUsesVertexAdditionalLights = false; // 附加光一律逐像素
        const string k_OnlyShadersWithPrefix = "";          // 只处理某些 Shader(空 = 全部)

        static readonly ShaderKeyword s_LightmapOn = new ShaderKeyword("LIGHTMAP_ON");
        static readonly ShaderKeyword s_DynLightmapOn = new ShaderKeyword("DYNAMICLIGHTMAP_ON");
        static readonly ShaderKeyword s_DirLightmap = new ShaderKeyword("DIRLIGHTMAP_COMBINED");
        static readonly ShaderKeyword s_ShadowsShadowmask = new ShaderKeyword("SHADOWS_SHADOWMASK");
        static readonly ShaderKeyword s_LightmapShadowMixing = new ShaderKeyword("LIGHTMAP_SHADOW_MIXING");
        static readonly ShaderKeyword s_ScreenShadows = new ShaderKeyword("_MAIN_LIGHT_SHADOWS_SCREEN");
        static readonly ShaderKeyword s_VertexLights = new ShaderKeyword("_ADDITIONAL_LIGHTS_VERTEX");
        static readonly ShaderKeyword s_DebugDisplay = new ShaderKeyword("DEBUG_DISPLAY");

        static int s_Before, s_After;
        static readonly Dictionary<string, int> s_RemovedPerShader = new Dictionary<string, int>();

        // 数字越小越早执行。URP 自己的剥离器也实现了这个接口,顺序不影响结果(都是从同一个列表里删)
        public int callbackOrder => 10;

        bool ShouldStrip(ShaderSnippetData snippet, ShaderCompilerData data)
        {
            var keys = data.shaderKeywordSet;
            if (!k_ProjectUsesLightmaps &&
                (keys.IsEnabled(s_LightmapOn) || keys.IsEnabled(s_DynLightmapOn) || keys.IsEnabled(s_DirLightmap) ||
                 keys.IsEnabled(s_ShadowsShadowmask) || keys.IsEnabled(s_LightmapShadowMixing)))
                return true;
            if (!k_ProjectUsesScreenSpaceShadows && keys.IsEnabled(s_ScreenShadows)) return true;
            if (!k_ProjectUsesVertexAdditionalLights && keys.IsEnabled(s_VertexLights)) return true;
            // 发布版不需要 Rendering Debugger 的调试变体
            if (!EditorUserBuildSettings.development && keys.IsEnabled(s_DebugDisplay)) return true;
            // Meta Pass 给光照烘焙/实时 GI 提取反照率和自发光用;项目完全不用 GI 时才剥
            if (!k_ProjectUsesLightmaps && snippet.passType == PassType.Meta) return true;
            return false;
        }

        public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
        {
            if (k_OnlyShadersWithPrefix.Length > 0 && !shader.name.StartsWith(k_OnlyShadersWithPrefix)) return;
            int before = data.Count;
            // 倒序删除,避免下标错位
            for (int i = data.Count - 1; i >= 0; --i)
                if (ShouldStrip(snippet, data[i]))
                    data.RemoveAt(i);
            s_Before += before;
            s_After += data.Count;
            int removed = before - data.Count;
            if (removed > 0)
            {
                s_RemovedPerShader.TryGetValue(shader.name, out int n);
                s_RemovedPerShader[shader.name] = n + removed;
            }
        }

        public void OnPreprocessBuild(BuildReport report)
        {
            s_Before = s_After = 0;
            s_RemovedPerShader.Clear();
        }

        public void OnPostprocessBuild(BuildReport report)
        {
            var sb = new StringBuilder();
            sb.AppendLine($"[CookbookVariantStripper] 变体(按 Pass×阶段计):{s_Before} → {s_After}");
            foreach (var kv in s_RemovedPerShader)
                sb.AppendLine($"  {kv.Key}: -{kv.Value}");
            Debug.Log(sb.ToString());
        }
    }
}