using System.Collections.Generic; using System.Text; using UnityEditor; using UnityEngine; /// /// VRM 런타임 로딩에 필요한 셰이더를 빌드에 강제로 포함시킨다. /// /// 왜 필요한가: Unity 는 어떤 머티리얼도 참조하지 않는 셰이더를 빌드에서 제거한다. /// 에디터에서는 프로젝트의 모든 셰이더를 찾을 수 있어 문제가 드러나지 않지만, /// 빌드된 플레이어에서는 Shader.Find 가 null 을 돌려주고 /// "ArgumentNullException: Parameter name: Shader" 로 로드가 실패한다. /// /// 일반적인 게임이라면 씬에 쓰인 머티리얼을 보고 Unity 가 알아서 판단하지만, /// 우리는 실행 중에 임의의 VRM 을 불러오므로 어떤 셰이더가 필요할지 빌드 시점에 /// 알 수 없다. 그래서 Always Included Shaders 에 명시적으로 등록한다. /// public static class VrmShaderBuildSetup { // UrpVrm10MaterialDescriptorGenerator / BuiltInVrm10MaterialDescriptorGenerator 가 // 참조하는 셰이더들. 현재 URP 를 쓰지만 파이프라인을 바꿔도 되도록 둘 다 넣는다. static readonly string[] RequiredShaders = { "VRM10/Universal Render Pipeline/MToon10", // URP 용 MToon (지금 쓰는 것) "VRM10/MToon10", // Built-in 용 MToon "Universal Render Pipeline/Lit", // MToon 이 아닌 PBR 머티리얼 "Universal Render Pipeline/Unlit", "UniGLTF/UniUnlit", }; [MenuItem("Tools/Desktop Overlay/6. Include VRM Shaders In Build")] public static void IncludeShaders() { var graphicsSettings = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/GraphicsSettings.asset"); if (graphicsSettings == null || graphicsSettings.Length == 0) { Debug.LogError("[VrmShaderBuildSetup] GraphicsSettings.asset 을 열지 못했습니다."); return; } var so = new SerializedObject(graphicsSettings[0]); var list = so.FindProperty("m_AlwaysIncludedShaders"); if (list == null) { Debug.LogError("[VrmShaderBuildSetup] m_AlwaysIncludedShaders 프로퍼티를 찾지 못했습니다."); return; } // 이미 들어있는 것 수집 var existing = new HashSet(); for (int i = 0; i < list.arraySize; i++) { var s = list.GetArrayElementAtIndex(i).objectReferenceValue as Shader; if (s != null) existing.Add(s); } var added = new List(); var already = new List(); var notFound = new List(); foreach (var name in RequiredShaders) { var shader = Shader.Find(name); if (shader == null) { notFound.Add(name); continue; } if (existing.Contains(shader)) { already.Add(name); continue; } list.InsertArrayElementAtIndex(list.arraySize); list.GetArrayElementAtIndex(list.arraySize - 1).objectReferenceValue = shader; existing.Add(shader); added.Add(name); } so.ApplyModifiedProperties(); AssetDatabase.SaveAssets(); var sb = new StringBuilder("[VrmShaderBuildSetup] Always Included Shaders 갱신\n"); if (added.Count > 0) { sb.AppendLine($"추가됨 ({added.Count})"); foreach (var s in added) sb.AppendLine(" + " + s); } if (already.Count > 0) { sb.AppendLine($"이미 있음 ({already.Count})"); foreach (var s in already) sb.AppendLine(" = " + s); } if (notFound.Count > 0) { sb.AppendLine($"찾지 못함 ({notFound.Count}) — 해당 파이프라인을 안 쓰면 정상입니다"); foreach (var s in notFound) sb.AppendLine(" ? " + s); } sb.AppendLine(); sb.AppendLine("이제 다시 빌드하세요."); Debug.Log(sb.ToString()); } [MenuItem("Tools/Desktop Overlay/7. Log Always Included Shaders")] public static void LogIncluded() { var graphicsSettings = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/GraphicsSettings.asset"); var so = new SerializedObject(graphicsSettings[0]); var list = so.FindProperty("m_AlwaysIncludedShaders"); var sb = new StringBuilder($"[VrmShaderBuildSetup] Always Included Shaders ({list.arraySize}개)\n"); for (int i = 0; i < list.arraySize; i++) { var s = list.GetArrayElementAtIndex(i).objectReferenceValue as Shader; sb.AppendLine($" [{i}] {(s != null ? s.name : "")}"); } Debug.Log(sb.ToString()); } }