FantasyAsset/Assets/Editor/URPMaterialConverterWindow.cs

399 lines
14 KiB
C#
Raw Normal View History

2025-09-03 19:48:48 +05:00
// Assets/Editor/URPMaterialConverterWindow.cs
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class URPMaterialConverterWindow : EditorWindow
{
private DefaultAsset _folder; // project folder reference
private bool _dryRun = false;
private bool _includeSubfolders = true;
[MenuItem("Tools/URP Material Converter")]
public static void Open() => GetWindow<URPMaterialConverterWindow>("URP Material Converter");
private void OnGUI()
{
GUILayout.Label("Convert Built-in/BiRP materials to URP", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Select a project folder that contains .mat assets. This tool will scan and convert them to URP-compatible shaders.", MessageType.Info);
_folder = (DefaultAsset)EditorGUILayout.ObjectField("Folder", _folder, typeof(DefaultAsset), false);
_includeSubfolders = EditorGUILayout.Toggle("Include Subfolders", _includeSubfolders);
_dryRun = EditorGUILayout.Toggle(new GUIContent("Dry Run (report only)"), _dryRun);
using (new EditorGUI.DisabledScope(_folder == null))
{
if (GUILayout.Button(_dryRun ? "Analyze" : "Convert to URP"))
{
var path = AssetDatabase.GetAssetPath(_folder);
if (!AssetDatabase.IsValidFolder(path))
{
EditorUtility.DisplayDialog("Invalid Folder", "Please select a valid project folder.", "OK");
return;
}
ConvertAllInFolder(path, _includeSubfolders, _dryRun);
}
}
GUILayout.Space(8);
if (GUILayout.Button("Select URP Sample Shaders (Ping)"))
{
PingURPShaders();
}
}
private static void ConvertAllInFolder(string folderPath, bool recursive, bool dryRun)
{
string[] searchIn = new[] { folderPath };
string filter = "t:Material";
var guids = AssetDatabase.FindAssets(filter, searchIn);
int converted = 0;
int analyzed = 0;
try
{
for (int i = 0; i < guids.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
if (!assetPath.EndsWith(".mat")) continue;
var mat = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
if (mat == null) continue;
if (EditorUtility.DisplayCancelableProgressBar(
dryRun ? "Analyzing Materials…" : "Converting Materials…",
$"{mat.name} ({i+1}/{guids.Length})",
(float)(i + 1) / guids.Length))
{
break;
}
bool willConvert = WillConvert(mat);
analyzed++;
if (willConvert && !dryRun)
{
Undo.RecordObject(mat, "Convert Material to URP");
ConvertMaterialToURP(mat);
EditorUtility.SetDirty(mat);
converted++;
}
}
}
finally
{
EditorUtility.ClearProgressBar();
if (!dryRun)
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
EditorUtility.DisplayDialog(
dryRun ? "URP Converter — Analysis" : "URP Converter — Complete",
dryRun
? $"Analyzed {analyzed} materials.\n{CountConvertible(guids)} can be converted."
: $"Processed {analyzed} materials.\nConverted: {converted}",
"OK");
// Local helper to count convertible without loading twice
int CountConvertible(string[] g)
{
int c = 0;
foreach (var guid in g)
{
var p = AssetDatabase.GUIDToAssetPath(guid);
var m = AssetDatabase.LoadAssetAtPath<Material>(p);
if (m != null && WillConvert(m)) c++;
}
return c;
}
}
private static bool WillConvert(Material mat)
{
if (mat == null || mat.shader == null) return false;
string s = mat.shader.name;
// Skip if already URP
if (s.StartsWith("Universal Render Pipeline/")) return false;
// Common convertible buckets
if (s == "Standard" || s == "Standard (Specular setup)") return true;
if (s.StartsWith("Unlit/")) return true;
if (s.StartsWith("Particles/Standard")) return true;
// You can add more mappings here as needed.
return false;
}
private static void ConvertMaterialToURP(Material mat)
{
if (mat == null || mat.shader == null) return;
string src = mat.shader.name;
if (src == "Standard" || src == "Standard (Specular setup)")
{
ConvertStandardToURPLit(mat, specularWorkflow: src.Contains("Specular"));
return;
}
if (src.StartsWith("Unlit/"))
{
var urpUnlit = Shader.Find("Universal Render Pipeline/Unlit");
if (urpUnlit != null)
{
var data = CaptureCommon(mat);
mat.shader = urpUnlit;
ApplyCommon(mat, data);
// Unlit ignores metallic/smoothness, but we still keep base/albedo/alpha/emission
ApplySurfaceSettingsFromStandard(mat);
ApplyEmission(mat, data);
}
return;
}
if (src.StartsWith("Particles/Standard Unlit"))
{
var urpParticlesUnlit = Shader.Find("Universal Render Pipeline/Particles/Unlit");
if (urpParticlesUnlit != null)
{
var data = CaptureCommon(mat);
mat.shader = urpParticlesUnlit;
ApplyCommon(mat, data);
ApplySurfaceSettingsFromStandard(mat);
ApplyEmission(mat, data);
}
return;
}
if (src.StartsWith("Particles/Standard Surface"))
{
var urpParticlesLit = Shader.Find("Universal Render Pipeline/Particles/Lit");
if (urpParticlesLit != null)
{
var data = CapturePBR(mat);
mat.shader = urpParticlesLit;
ApplyCommon(mat, data.common);
ApplyPBR(mat, data);
ApplySurfaceSettingsFromStandard(mat);
ApplyEmission(mat, data.common);
}
return;
}
// Fallback: try to place it on URP/Lit retaining as much as possible
var fallback = Shader.Find("Universal Render Pipeline/Lit");
if (fallback != null)
{
var data = CapturePBR(mat);
mat.shader = fallback;
ApplyCommon(mat, data.common);
ApplyPBR(mat, data);
ApplySurfaceSettingsFromStandard(mat);
ApplyEmission(mat, data.common);
}
}
// ----- Data capture structs -----
private struct CommonData
{
public Texture mainTex; public Color baseColor;
public Texture baseMap; // alias for mainTex
public float cutoff; public bool alphaClip;
public int renderQueue;
public Texture emissionMap; public Color emissionColor; public bool emissionEnabled;
}
private struct PBRData
{
public CommonData common;
// Metallic workflow
public Texture metallicGlossMap; public float metallic; public float glossiness;
// Specular workflow
public Texture specGlossMap; public Color specColor;
// Normals
public Texture bumpMap; public float bumpScale;
// Occlusion
public Texture occlusionMap; public float occlusionStrength;
}
// ----- Capture/Apply helpers -----
private static CommonData CaptureCommon(Material m)
{
var d = new CommonData();
d.renderQueue = m.renderQueue;
if (m.HasProperty("_MainTex")) d.mainTex = m.GetTexture("_MainTex");
if (m.HasProperty("_BaseMap")) d.baseMap = m.GetTexture("_BaseMap");
if (m.HasProperty("_Color")) d.baseColor = m.GetColor("_Color");
else if (m.HasProperty("_BaseColor")) d.baseColor = m.GetColor("_BaseColor");
else d.baseColor = Color.white;
if (m.HasProperty("_Cutoff")) d.cutoff = m.GetFloat("_Cutoff");
if (m.HasProperty("_AlphaClip")) d.alphaClip = m.GetFloat("_AlphaClip") > 0.5f;
// Emission capture
d.emissionEnabled = m.IsKeywordEnabled("_EMISSION");
if (m.HasProperty("_EmissionMap")) d.emissionMap = m.GetTexture("_EmissionMap");
if (m.HasProperty("_EmissionColor")) d.emissionColor = m.GetColor("_EmissionColor");
return d;
}
private static PBRData CapturePBR(Material m)
{
var d = new PBRData { common = CaptureCommon(m) };
if (m.HasProperty("_MetallicGlossMap")) d.metallicGlossMap = m.GetTexture("_MetallicGlossMap");
if (m.HasProperty("_Metallic")) d.metallic = m.GetFloat("_Metallic");
if (m.HasProperty("_Glossiness")) d.glossiness = m.GetFloat("_Glossiness"); // Standard
if (m.HasProperty("_Smoothness")) d.glossiness = m.GetFloat("_Smoothness"); // URP Lit also uses _Smoothness
if (m.HasProperty("_SpecGlossMap")) d.specGlossMap = m.GetTexture("_SpecGlossMap");
if (m.HasProperty("_SpecColor")) d.specColor = m.GetColor("_SpecColor");
else if (m.HasProperty("_SpecularColor")) d.specColor = m.GetColor("_SpecularColor");
if (m.HasProperty("_BumpMap")) d.bumpMap = m.GetTexture("_BumpMap");
if (m.HasProperty("_BumpScale")) d.bumpScale = m.GetFloat("_BumpScale");
if (m.HasProperty("_OcclusionMap")) d.occlusionMap = m.GetTexture("_OcclusionMap");
if (m.HasProperty("_OcclusionStrength")) d.occlusionStrength = m.GetFloat("_OcclusionStrength");
return d;
}
private static void ApplyCommon(Material m, CommonData d)
{
// Base map/color
if (m.HasProperty("_BaseMap"))
{
var tex = d.baseMap != null ? d.baseMap : d.mainTex;
m.SetTexture("_BaseMap", tex);
}
if (m.HasProperty("_BaseColor"))
m.SetColor("_BaseColor", d.baseColor == default ? Color.white : d.baseColor);
// Alpha clip and cutoff
if (m.HasProperty("_AlphaClip")) m.SetFloat("_AlphaClip", d.alphaClip ? 1f : 0f);
if (m.HasProperty("_Cutoff")) m.SetFloat("_Cutoff", d.cutoff);
// Keep render queue if custom
if (d.renderQueue != -1) m.renderQueue = d.renderQueue;
}
private static void ApplyPBR(Material m, PBRData d)
{
// Metallic workflow
if (m.HasProperty("_MetallicGlossMap")) m.SetTexture("_MetallicGlossMap", d.metallicGlossMap);
if (m.HasProperty("_Metallic")) m.SetFloat("_Metallic", d.metallic);
if (m.HasProperty("_Smoothness")) m.SetFloat("_Smoothness", d.glossiness);
// Specular workflow (URP Lit supports both via keyword)
if (m.HasProperty("_SpecGlossMap")) m.SetTexture("_SpecGlossMap", d.specGlossMap);
if (m.HasProperty("_SpecColor")) m.SetColor("_SpecColor", d.specColor == default ? Color.white : d.specColor);
// Normals
if (m.HasProperty("_BumpMap")) m.SetTexture("_BumpMap", d.bumpMap);
if (m.HasProperty("_BumpScale")) m.SetFloat("_BumpScale", d.bumpScale);
// Occlusion
if (m.HasProperty("_OcclusionMap")) m.SetTexture("_OcclusionMap", d.occlusionMap);
if (m.HasProperty("_OcclusionStrength")) m.SetFloat("_OcclusionStrength", Mathf.Approximately(d.occlusionStrength, 0f) ? 1f : d.occlusionStrength);
}
private static void ApplyEmission(Material m, CommonData d)
{
bool hasMap = d.emissionMap != null;
bool hasColor = d.emissionColor.maxColorComponent > 0.0001f;
if (m.HasProperty("_EmissionMap")) m.SetTexture("_EmissionMap", d.emissionMap);
if (m.HasProperty("_EmissionColor")) m.SetColor("_EmissionColor", hasColor ? d.emissionColor : Color.black);
if (hasMap || hasColor || d.emissionEnabled)
m.EnableKeyword("_EMISSION");
else
m.DisableKeyword("_EMISSION");
}
private static void ConvertStandardToURPLit(Material mat, bool specularWorkflow)
{
var urpLit = Shader.Find("Universal Render Pipeline/Lit");
if (urpLit == null) return;
var data = CapturePBR(mat);
mat.shader = urpLit;
ApplyCommon(mat, data.common);
ApplyPBR(mat, data);
ApplySurfaceSettingsFromStandard(mat);
ApplyEmission(mat, data.common);
}
/// <summary>
/// Maps Standard's _Mode to URP's _Surface/_AlphaClip. Also propagates cutoff.
/// </summary>
private static void ApplySurfaceSettingsFromStandard(Material m)
{
// Standard _Mode: 0 Opaque, 1 Cutout, 2 Fade, 3 Transparent
int mode = m.HasProperty("_Mode") ? Mathf.RoundToInt(m.GetFloat("_Mode")) : 0;
if (m.HasProperty("_Surface"))
{
if (mode == 0) // Opaque
{
m.SetFloat("_Surface", 0f);
if (m.HasProperty("_AlphaClip")) m.SetFloat("_AlphaClip", 0f);
}
else if (mode == 1) // Cutout
{
m.SetFloat("_Surface", 0f);
if (m.HasProperty("_AlphaClip")) m.SetFloat("_AlphaClip", 1f);
}
else // Fade or Transparent
{
m.SetFloat("_Surface", 1f); // Transparent
if (m.HasProperty("_AlphaClip")) m.SetFloat("_AlphaClip", 0f);
}
}
// Keep cutoff if present
if (m.HasProperty("_Cutoff") && !m.HasProperty("_AlphaClip"))
{
// Some shaders only rely on cutoff without explicit _AlphaClip.
// Nothing special to set here; URP Lit uses _AlphaClip.
}
// Optional: set blending for Fade (2) vs Transparent (3)
if (m.HasProperty("_Blend"))
{
// URP Lit doesn't use _Blend the same way; leaving it alone.
}
}
private static void PingURPShaders()
{
var candidates = new[]
{
"Universal Render Pipeline/Lit",
"Universal Render Pipeline/Unlit",
"Universal Render Pipeline/Particles/Unlit",
"Universal Render Pipeline/Particles/Lit"
};
foreach (var n in candidates)
{
var s = Shader.Find(n);
if (s != null) EditorGUIUtility.PingObject(s);
}
}
}