Files
MyCharacterAgent/Assets/02_Scripts/Editor/VrmShaderBuildSetup.cs
2026-08-26 03:39:36 +09:00

123 lines
4.8 KiB
C#

using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;
/// <summary>
/// VRM 런타임 로딩에 필요한 셰이더를 빌드에 강제로 포함시킨다.
///
/// 왜 필요한가: Unity 는 어떤 머티리얼도 참조하지 않는 셰이더를 빌드에서 제거한다.
/// 에디터에서는 프로젝트의 모든 셰이더를 찾을 수 있어 문제가 드러나지 않지만,
/// 빌드된 플레이어에서는 Shader.Find 가 null 을 돌려주고
/// "ArgumentNullException: Parameter name: Shader" 로 로드가 실패한다.
///
/// 일반적인 게임이라면 씬에 쓰인 머티리얼을 보고 Unity 가 알아서 판단하지만,
/// 우리는 실행 중에 임의의 VRM 을 불러오므로 어떤 셰이더가 필요할지 빌드 시점에
/// 알 수 없다. 그래서 Always Included Shaders 에 명시적으로 등록한다.
/// </summary>
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<Shader>();
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<string>();
var already = new List<string>();
var notFound = new List<string>();
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 : "<null>")}");
}
Debug.Log(sb.ToString());
}
}