You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
12 KiB
295 lines
12 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Text; |
|
using UnityEditor.SceneManagement; |
|
using UnityEngine; |
|
using UnityEngine.SceneManagement; |
|
using Object = UnityEngine.Object; |
|
|
|
namespace UnityEditor.Rendering.Universal.Converters |
|
{ |
|
enum IdentifierType { kNullIdentifier = 0, kImportedAsset = 1, kSceneObject = 2, kSourceAsset = 3, kBuiltInAsset = 4 }; |
|
|
|
internal static class ReadonlyMaterialMap |
|
{ |
|
public static readonly Dictionary<string, string> Map = new Dictionary<string, string> |
|
{ |
|
{"Default-Diffuse", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Lit.mat"}, |
|
{"Default-Material", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Lit.mat"}, |
|
{"Default-ParticleSystem", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/ParticlesUnlit.mat"}, |
|
{"Default-Particle", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/ParticlesUnlit.mat"}, |
|
{"Default-Terrain-Diffuse", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/TerrainLit.mat"}, |
|
{"Default-Terrain-Specular", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/TerrainLit.mat"}, |
|
{"Default-Terrain-Standard", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/TerrainLit.mat"}, |
|
{"Sprites-Default", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Sprite-Unlit-Default.mat"}, |
|
{"Sprites-Mask", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Sprite-Unlit-Default.mat"}, |
|
{"SpatialMappingOcclusion", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/SpatialMappingOcclusion.mat"}, |
|
{"SpatialMappingWireframe", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/SpatialMappingWireframe.mat"}, |
|
|
|
// TODO: These currently render in URP, but they are using BIRP shaders. Create a task to convert these. |
|
// {"Default UI Material", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Lit.mat"}, |
|
// {"ETC1 Supported UI Material", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Lit.mat"}, |
|
// {"Default-Line", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Lit.mat"}, |
|
// {"Default-Skybox", "Packages/com.unity.render-pipelines.universal/Runtime/Materials/Lit.mat"}, |
|
}; |
|
} |
|
|
|
internal class ReadonlyMaterialConverter : RenderPipelineConverter |
|
{ |
|
public override string name => "Readonly Material Converter"; |
|
public override string info => "Converts references to Built-In readonly materials to URP readonly materials. This will create temporarily a .index file and that can take a long time."; |
|
public override Type container => typeof(BuiltInToURPConverterContainer); |
|
public override bool needsIndexing => true; |
|
|
|
List<string> guids = new List<string>(); |
|
|
|
public override void OnInitialize(InitializeConverterContext ctx, Action callback) |
|
{ |
|
var context = Search.SearchService.CreateContext("asset", "urp:convert-readonly"); |
|
|
|
Search.SearchService.Request(context, (c, items) => |
|
{ |
|
// we're going to do this step twice in order to get them ordered, but it should be fast |
|
var orderedRequest = items.OrderBy(req => |
|
{ |
|
GlobalObjectId.TryParse(req.id, out var gid); |
|
return gid.assetGUID; |
|
}); |
|
|
|
foreach (var r in orderedRequest) |
|
{ |
|
if (r == null || !GlobalObjectId.TryParse(r.id, out var gid)) |
|
{ |
|
continue; |
|
} |
|
|
|
var label = r.provider.fetchLabel(r, r.context); |
|
var description = r.provider.fetchDescription(r, r.context); |
|
|
|
var item = new ConverterItemDescriptor() |
|
{ |
|
name = description.Split('/').Last().Split('.').First(), |
|
info = $"{label}", |
|
}; |
|
guids.Add(gid.ToString()); |
|
|
|
ctx.AddAssetToConvert(item); |
|
} |
|
|
|
callback.Invoke(); |
|
}); |
|
context?.Dispose(); |
|
} |
|
|
|
public override void OnRun(ref RunItemContext ctx) |
|
{ |
|
var obj = LoadObject(ref ctx); |
|
var result = true; |
|
var errorString = new StringBuilder(); |
|
|
|
if (obj != null) |
|
{ |
|
var materials = MaterialReferenceBuilder.GetMaterialsFromObject(obj); |
|
|
|
foreach (var material in materials) |
|
{ |
|
if (material == null) |
|
{ |
|
continue; |
|
} |
|
// there might be multiple materials on this object, we only care about the ones we explicitly try to remap that fail |
|
if (!MaterialReferenceBuilder.GetIsReadonlyMaterial(material)) continue; |
|
if (!ReadonlyMaterialMap.Map.ContainsKey(material.name)) continue; |
|
if (!ReassignMaterial(obj, material.name, ReadonlyMaterialMap.Map[material.name])) |
|
{ |
|
result = false; |
|
errorString.AppendLine($"Material {material.name} failed to be reassigned"); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
result = false; |
|
errorString.AppendLine($"Object {ctx.item.descriptor.name} could not be loaded"); |
|
} |
|
|
|
if (!result) |
|
{ |
|
ctx.didFail = true; |
|
ctx.info = errorString.ToString(); |
|
} |
|
else |
|
{ |
|
// make sure the changes get saved |
|
EditorUtility.SetDirty(obj); |
|
var currentScene = SceneManager.GetActiveScene(); |
|
EditorSceneManager.SaveScene(currentScene); |
|
} |
|
} |
|
|
|
public override void OnClicked(int index) |
|
{ |
|
if (GlobalObjectId.TryParse(guids[index], out var gid)) |
|
{ |
|
var containerPath = AssetDatabase.GUIDToAssetPath(gid.assetGUID); |
|
EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<Object>(containerPath)); |
|
} |
|
} |
|
|
|
private static bool ReassignMaterial(Object obj, string oldMaterialName, string newMaterialPath) |
|
{ |
|
var result = true; |
|
|
|
// do the reflection to make sure we get the right material reference |
|
if (obj is GameObject go) |
|
{ |
|
foreach (var key in MaterialReferenceBuilder.GetComponentTypes()) |
|
{ |
|
var components = go.GetComponentsInChildren(key); |
|
foreach (var component in components) |
|
{ |
|
result &= ReassignMaterialOnComponentOrObject(component, |
|
oldMaterialName, |
|
newMaterialPath); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
result &= ReassignMaterialOnComponentOrObject(obj, |
|
oldMaterialName, |
|
newMaterialPath); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private static bool ReassignMaterialOnComponentOrObject(Object obj, |
|
string oldMaterialName, |
|
string newMaterialPath) |
|
{ |
|
var result = true; |
|
|
|
var materialProperties = obj.GetType().GetMaterialPropertiesWithoutLeaking(); |
|
|
|
foreach (var property in materialProperties) |
|
{ |
|
var materialValue = property.GetGetMethod().GetMaterialFromMethod(obj, (methodName, objectName) => |
|
$"The method {methodName} was not found on {objectName}. Ignoring this property."); |
|
|
|
if (materialValue is Material material) |
|
{ |
|
if (material.name.Equals(oldMaterialName, StringComparison.OrdinalIgnoreCase)) |
|
{ |
|
var newMaterial = AssetDatabase.LoadAssetAtPath<Material>(newMaterialPath); |
|
|
|
if (newMaterial != null) |
|
{ |
|
var setMethod = property.GetSetMethod(); |
|
if (setMethod != null) |
|
{ |
|
setMethod.Invoke(obj, new object[] { newMaterial }); |
|
} |
|
else |
|
{ |
|
// failed to set the material from the SetMethod |
|
result = false; |
|
} |
|
} |
|
else |
|
{ |
|
// a material we expected to exist does not |
|
result = false; |
|
} |
|
} |
|
} |
|
else if (materialValue is Material[] materialList) |
|
{ |
|
for (int i = 0; i < materialList.Length; i++) |
|
{ |
|
var mat = materialList[i]; |
|
if (mat == null) |
|
{ |
|
continue; |
|
} |
|
if (mat.name.Equals(oldMaterialName, StringComparison.OrdinalIgnoreCase)) |
|
{ |
|
var newMaterial = AssetDatabase.LoadAssetAtPath<Material>(newMaterialPath); |
|
|
|
if (newMaterial != null) |
|
{ |
|
materialList[i] = newMaterial; |
|
} |
|
else |
|
{ |
|
// a material we expected to exist does not |
|
result = false; |
|
} |
|
} |
|
} |
|
|
|
var setMethod = property.GetSetMethod(); |
|
if (setMethod != null) |
|
{ |
|
setMethod.Invoke(obj, new object[] { materialList }); |
|
} |
|
else |
|
{ |
|
// failed to set the material from the SetMethod |
|
result = false; |
|
} |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private Object LoadObject(ref RunItemContext ctx) |
|
{ |
|
var item = ctx.item; |
|
var guid = guids[item.index]; |
|
|
|
if (GlobalObjectId.TryParse(guid, out var gid)) |
|
{ |
|
var obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(gid); |
|
if (!obj) |
|
{ |
|
// Open container scene |
|
if (gid.identifierType == (int)IdentifierType.kSceneObject) |
|
{ |
|
var containerPath = AssetDatabase.GUIDToAssetPath(gid.assetGUID); |
|
|
|
var mainInstanceID = AssetDatabase.LoadAssetAtPath<Object>(containerPath); |
|
AssetDatabase.OpenAsset(mainInstanceID); |
|
|
|
// if we have a prefab open, then we already have the object we need to update |
|
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); |
|
if (prefabStage != null) |
|
{ |
|
obj = mainInstanceID; |
|
} |
|
|
|
// Reload object if it is still null |
|
if (obj == null) |
|
{ |
|
obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(gid); |
|
if (!obj) |
|
{ |
|
ctx.didFail = true; |
|
ctx.info = $"Object {gid.assetGUID} failed to load..."; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return obj; |
|
} |
|
|
|
ctx.didFail = true; |
|
ctx.info = $"Failed to parse Global ID {item.descriptor.info}..."; |
|
|
|
return null; |
|
} |
|
} |
|
}
|
|
|