using System.Collections.Generic; using UnityEngine; namespace NBShader { /// /// Runtime API for applying NBShader feature tiers to materials. /// public static class NBShaderFeatureRuntime { /// /// Applies an NBShader feature tier to one material in place. Only materials whose shader name is /// "Effects/NBShader" are processed. Managed Catalog keywords and shader passes are derived from /// serialized material intent, then filtered by the target tier. /// /// Material to process. Null materials are ignored. /// /// Target tier. When null, Ultra is used. This overload does not load runtime settings, so it keeps all /// managed Catalog keywords and pass features available. /// /// /// If lower-tier shader variants have been stripped from the build, call this API before the material is /// used for rendering. Otherwise Unity can request a missing variant and select a similar available variant. /// public static void ApplyTier(Material material, NBShaderFeatureTier? tier = null) { ApplyTierInternal(material, null, tier); } /// /// Applies an NBShader feature tier using a user-loaded runtime settings asset. /// Pass null for to resolve the tier from the settings quality mapping. /// public static void ApplyTier(Material material, NBShaderFeatureRuntimeSettings settings, NBShaderFeatureTier? tier) { ApplyTierInternal(material, settings, tier); } /// /// Applies an NBShader feature tier to multiple materials in place. Only materials whose shader name is /// "Effects/NBShader" are processed. Catalog-external keywords are left unchanged unless they are /// explicitly tied to a managed NBShader feature. /// /// Materials to process. Null collections and null entries are ignored. /// /// Target tier. When null, Ultra is used. This overload does not load runtime settings, so it keeps all /// managed Catalog keywords and pass features available. /// /// /// If lower-tier shader variants have been stripped from the build, call this API before the materials are /// used for rendering. Otherwise Unity can request a missing variant and select a similar available variant. /// public static void ApplyTier(IEnumerable materials, NBShaderFeatureTier? tier = null) { ApplyTierInternal(materials, null, tier); } /// /// Applies an NBShader feature tier to multiple materials using a user-loaded runtime settings asset. /// Pass null for to resolve the tier from the settings quality mapping. /// public static void ApplyTier(IEnumerable materials, NBShaderFeatureRuntimeSettings settings, NBShaderFeatureTier? tier) { ApplyTierInternal(materials, settings, tier); } private static void ApplyTierInternal(IEnumerable materials, NBShaderFeatureRuntimeSettings settings, NBShaderFeatureTier? tier) { if (materials == null) { return; } foreach (Material material in materials) { ApplyTierInternal(material, settings, tier); } } private static void ApplyTierInternal(Material material, NBShaderFeatureRuntimeSettings settings, NBShaderFeatureTier? tier) { if (!IsNBShaderMaterial(material)) { return; } NBShaderFeatureTier resolvedTier = tier.HasValue ? tier.Value : ResolveTierFromQuality(settings); HashSet allowed = settings != null ? settings.BuildAllowedSet(resolvedTier) : BuildAllowAllCatalogKeywordSet(); HashSet allowedPassFeatures = settings != null ? settings.BuildAllowedPassFeatureSet(resolvedTier) : null; NBShaderMaterialIntentResult result = NBShaderMaterialIntentResolver.Resolve(material, resolvedTier, allowed, allowedPassFeatures); ApplyResolvedIntent(material, result); } private static bool IsNBShaderMaterial(Material material) { return material != null && material.shader != null && material.shader.name == NBShaderFeatureCatalog.ShaderName; } private static void ApplyResolvedIntent(Material material, NBShaderMaterialIntentResult result) { if (material == null || result == null) { return; } var effectiveKeywords = new HashSet(result.effectiveKeywords); for (int i = 0; i < NBShaderFeatureCatalog.RawKeywords.Length; i++) { string keyword = NBShaderFeatureCatalog.RawKeywords[i]; bool shouldEnable = effectiveKeywords.Contains(keyword); if (material.IsKeywordEnabled(keyword) != shouldEnable) { if (shouldEnable) { material.EnableKeyword(keyword); } else { material.DisableKeyword(keyword); } } } bool evaluateShVertex = effectiveKeywords.Contains("_FX_LIGHT_MODE_SIX_WAY"); if (material.IsKeywordEnabled("EVALUATE_SH_VERTEX") != evaluateShVertex) { if (evaluateShVertex) { material.EnableKeyword("EVALUATE_SH_VERTEX"); } else { material.DisableKeyword("EVALUATE_SH_VERTEX"); } } for (int i = 0; i < result.passes.Length; i++) { NBShaderPassIntent pass = result.passes[i]; if (!string.IsNullOrEmpty(pass.passName) && material.GetShaderPassEnabled(pass.passName) != pass.included) { material.SetShaderPassEnabled(pass.passName, pass.included); } } } private static NBShaderFeatureTier ResolveTierFromQuality(NBShaderFeatureRuntimeSettings settings) { if (settings == null) { return NBShaderFeatureTier.Ultra; } string qualityName = GetCurrentQualityName(); NBShaderFeatureTier tier; return settings.TryGetTierForQualityName(qualityName, out tier) ? tier : NBShaderFeatureTier.Ultra; } private static string GetCurrentQualityName() { string[] names = QualitySettings.names; int index = QualitySettings.GetQualityLevel(); if (names != null && index >= 0 && index < names.Length) { return names[index]; } return string.Empty; } private static HashSet BuildAllowAllCatalogKeywordSet() { return new HashSet(NBShaderFeatureCatalog.RawKeywords); } } }