From 986ead3ecf2b1c1899d78bff4ee6afcba3540be9 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 17 Oct 2026 13:35:39 +0200 Subject: [PATCH] urp: Ship the shader variants the material resolver needs. The resolver builds URP/Lit and triplanar materials at runtime and switches shader_feature keywords that no material in a build uses, so builds stripped those variants. Since mask maps carry all of the metallic and plastics use the triplanar shader, metals and plastics rendered white in player builds, while the editor compiled the variants on demand. A shader variant collection in Resources now lists every keyword set the resolver produces (VPE > Shaders > Generate URP Resolver Variants), and URP player builds fail when it's missing or out of date. --- .../VpeUrpResolverVariants.shadervariants | 334 ++++++++++++++++++ ...VpeUrpResolverVariants.shadervariants.meta | 8 + Editor/UrpShaderVariants.cs | 197 +++++++++++ Editor/UrpShaderVariants.cs.meta | 2 + 4 files changed, 541 insertions(+) create mode 100644 Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants create mode 100644 Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants.meta create mode 100644 Editor/UrpShaderVariants.cs create mode 100644 Editor/UrpShaderVariants.cs.meta diff --git a/Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants b/Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants new file mode 100644 index 0000000..cfaa112 --- /dev/null +++ b/Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants @@ -0,0 +1,334 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!200 &20000000 +ShaderVariantCollection: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: VpeUrpResolverVariants + m_Shaders: + - first: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + second: + variants: + - keywords: + passType: 8 + - keywords: _ALPHATEST_ON + passType: 8 + - keywords: + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP + _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHATEST_ON + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _NORMALMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _NORMALMAP + passType: 13 + - keywords: _EMISSION + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _NORMALMAP + passType: 13 + - keywords: _EMISSION _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _NORMALMAP + passType: 13 + - keywords: _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _SURFACE_TYPE_TRANSPARENT + passType: 13 + - first: {fileID: 4800000, guid: 5c8e21bce15211d45b491dce08046aae, type: 3} + second: + variants: + - keywords: + passType: 8 + - keywords: _ALPHATEST_ON + passType: 8 + - keywords: + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP + _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP + _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _NORMALMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _EMISSION _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _NORMALMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _ALPHAPREMULTIPLY_ON _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _METALLICSPECGLOSSMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _NORMALMAP + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _EMISSION _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _OCCLUSIONMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _METALLICSPECGLOSSMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _NORMALMAP + passType: 13 + - keywords: _ALPHATEST_ON _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _ALPHATEST_ON _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _OCCLUSIONMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _METALLICSPECGLOSSMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _NORMALMAP + passType: 13 + - keywords: _EMISSION _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _NORMALMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _EMISSION _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _EMISSION _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _OCCLUSIONMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _OCCLUSIONMAP + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _OCCLUSIONMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _OCCLUSIONMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _METALLICSPECGLOSSMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _NORMALMAP + passType: 13 + - keywords: _NORMALMAP _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _NORMALMAP _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _NORMALMAP _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _SURFACE_TYPE_TRANSPARENT + passType: 13 + - keywords: _SURFACE_TYPE_TRANSPARENT _VPE_MAPPING_PLANAR + passType: 13 + - keywords: _VPE_MAPPING_PLANAR + passType: 13 diff --git a/Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants.meta b/Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants.meta new file mode 100644 index 0000000..5d8ca31 --- /dev/null +++ b/Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5e2e8d964077c764194d8a1a8fb7b117 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 20000000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/UrpShaderVariants.cs b/Editor/UrpShaderVariants.cs new file mode 100644 index 0000000..085e285 --- /dev/null +++ b/Editor/UrpShaderVariants.cs @@ -0,0 +1,197 @@ +// Visual Pinball Engine +// Copyright (C) 2026 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +namespace VisualPinball.Unity.Urp +{ + /// + /// The shader variants needs in a player build. + /// + /// + /// + /// The resolver builds URP/Lit and triplanar materials at runtime and switches shader_feature keywords + /// (surface type, mask map, normal map, ...). A build only compiles the shader_feature variants that + /// materials or variant collections in the build use, and no material in the build uses most of these. + /// Without them, the runtime materials fall back to stripped variants: mask-driven metals turn into white + /// dielectrics, transparent triplanar plastics lose their look. This collection lists every keyword set + /// the resolver can produce, and lives in Resources so every URP build includes it. + /// + public static class UrpShaderVariants + { + public const string CollectionPath = "Packages/org.visualpinball.engine.unity.urp/Assets/Resources/Shaders/VpeUrpResolverVariants.shadervariants"; + + private const string LitShaderName = "Universal Render Pipeline/Lit"; + private const string TriplanarShaderPath = "Packages/org.visualpinball.engine.unity.urp/Assets/Resources/Shaders/VpeUrpLitTriplanar.shader"; + + // Surface states the resolver sets (ApplySurface, ApplyBlend, BuildDecal, BuildInsert). + private static readonly string[][] Surfaces = { + new string[0], + new[] { "_ALPHATEST_ON" }, + new[] { "_SURFACE_TYPE_TRANSPARENT" }, + new[] { "_SURFACE_TYPE_TRANSPARENT", "_ALPHAPREMULTIPLY_ON" }, + }; + + // Mask map states (ApplyMaskMap): none, mask, mask with occlusion. + private static readonly string[][] Masks = { + new string[0], + new[] { "_METALLICSPECGLOSSMAP" }, + new[] { "_METALLICSPECGLOSSMAP", "_OCCLUSIONMAP" }, + }; + + private static readonly string[] Toggles = { "_NORMALMAP", "_EMISSION" }; + + // The shadow caster pass only has the alpha test. + private static readonly string[][] ShadowKeywordSets = { new string[0], new[] { "_ALPHATEST_ON" } }; + + // UrpProjectedMapping: triplanar without, planar with this keyword. + private const string PlanarKeyword = "_VPE_MAPPING_PLANAR"; + + [MenuItem("VPE/Shaders/Generate URP Resolver Variants")] + public static void Generate() + { + var lit = Shader.Find(LitShaderName); + var triplanar = AssetDatabase.LoadAssetAtPath(TriplanarShaderPath); + if (!lit || !triplanar) { + Debug.LogError($"UrpShaderVariants: missing shader ({LitShaderName}: {(bool)lit}, triplanar: {(bool)triplanar})."); + return; + } + + var collection = new ShaderVariantCollection { name = System.IO.Path.GetFileNameWithoutExtension(CollectionPath) }; + var added = Add(collection, lit, PassType.ScriptableRenderPipeline, KeywordSets(false)) + + Add(collection, triplanar, PassType.ScriptableRenderPipeline, KeywordSets(true)) + + Add(collection, lit, PassType.ShadowCaster, ShadowKeywordSets) + + Add(collection, triplanar, PassType.ShadowCaster, ShadowKeywordSets); + + var existing = AssetDatabase.LoadAssetAtPath(CollectionPath); + if (existing) { + EditorUtility.CopySerialized(collection, existing); + EditorUtility.SetDirty(existing); + Object.DestroyImmediate(collection); + } else { + AssetDatabase.CreateAsset(collection, CollectionPath); + } + AssetDatabase.SaveAssets(); + Debug.Log($"UrpShaderVariants: {added} variants written to {CollectionPath}."); + } + + /// + /// Every shader_feature keyword set the resolver can give a material of the given shader. + /// + public static IEnumerable KeywordSets(bool triplanar) + { + var mappings = triplanar ? new[] { new string[0], new[] { PlanarKeyword } } : new[] { new string[0] }; + foreach (var mapping in mappings) { + foreach (var surface in Surfaces) { + foreach (var mask in Masks) { + for (var toggles = 0; toggles < 1 << Toggles.Length; toggles++) { + var keywords = new List(mapping); + keywords.AddRange(surface); + keywords.AddRange(mask); + keywords.AddRange(Toggles.Where((_, i) => (toggles & (1 << i)) != 0)); + yield return keywords.ToArray(); + } + } + } + } + } + + /// + /// The keyword sets the collection at doesn't have, or why it can't be checked. + /// + public static IEnumerable Missing() + { + var collection = AssetDatabase.LoadAssetAtPath(CollectionPath); + var lit = Shader.Find(LitShaderName); + var triplanar = AssetDatabase.LoadAssetAtPath(TriplanarShaderPath); + if (!collection || !lit || !triplanar) { + yield return $"{CollectionPath} or its shaders are missing"; + yield break; + } + foreach (var (shader, isTriplanar) in new[] { (lit, false), (triplanar, true) }) { + var sets = KeywordSets(isTriplanar).Select(k => (PassType.ScriptableRenderPipeline, k)) + .Concat(ShadowKeywordSets.Select(k => (PassType.ShadowCaster, k))); + foreach (var (passType, keywords) in sets) { + ShaderVariantCollection.ShaderVariant variant; + try { + variant = new ShaderVariantCollection.ShaderVariant(shader, passType, keywords); + } catch (System.ArgumentException) { + continue; // not a valid set for this shader, Generate skips it as well + } + if (!collection.Contains(variant)) { + yield return $"{shader.name} [{string.Join(" ", keywords)}]"; + } + } + } + } + + private static int Add(ShaderVariantCollection collection, Shader shader, PassType passType, IEnumerable keywordSets) + { + var added = 0; + foreach (var keywords in keywordSets) { + // all URP passes but the shadow caster (and motion vectors) are of the SRP type + try { + if (collection.Add(new ShaderVariantCollection.ShaderVariant(shader, passType, keywords))) { + added++; + } + } catch (System.ArgumentException e) { + Debug.LogWarning($"UrpShaderVariants: {shader.name} [{string.Join(" ", keywords)}]: {e.Message}"); + } + } + return added; + } + } + + /// + /// Fails URP player builds whose resolver variant collection is missing or out of date, since the build would + /// render runtime-built materials with stripped variants. + /// + public class UrpShaderVariantsBuildGuard : UnityEditor.Build.IPreprocessBuildWithReport + { + public int callbackOrder => 0; + + public void OnPreprocessBuild(UnityEditor.Build.Reporting.BuildReport report) + { + if (!UsesUrp()) { + return; + } + var missing = UrpShaderVariants.Missing().ToList(); + if (missing.Count > 0) { + throw new UnityEditor.Build.BuildFailedException( + $"VPE: the URP resolver's shader variant collection lacks {missing.Count} variant(s), e.g. {missing[0]}. " + + "Run VPE > Shaders > Generate URP Resolver Variants and commit the result."); + } + } + + // the default pipeline, or the one a quality level overrides it with + private static bool UsesUrp() + { + if (GraphicsSettings.defaultRenderPipeline is UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset) { + return true; + } + for (var i = 0; i < QualitySettings.names.Length; i++) { + if (QualitySettings.GetRenderPipelineAssetAt(i) is UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset) { + return true; + } + } + return false; + } + } +} diff --git a/Editor/UrpShaderVariants.cs.meta b/Editor/UrpShaderVariants.cs.meta new file mode 100644 index 0000000..6d027a3 --- /dev/null +++ b/Editor/UrpShaderVariants.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ff6930a2473298540a04a871666398f0 \ No newline at end of file