386 lines
12 KiB
C#
386 lines
12 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEditor;
|
||
|
||
public class ShaderReplacer : MonoBehaviour
|
||
{
|
||
[Header("目标对象")]
|
||
public GameObject targetObject;
|
||
|
||
[Header("Shader设置")]
|
||
[SerializeField] private Shader opaqueShader = null; // 不透明shader
|
||
[SerializeField] private Shader transparentShader = null; // 透明shader
|
||
|
||
[Header("默认Shader名称")]
|
||
[SerializeField] private string defaultOpaqueShaderName = "Universal Render Pipeline/Lit";
|
||
[SerializeField] private string defaultTransparentShaderName = "Universal Render Pipeline/Lit";
|
||
|
||
// 存储贴图对应的材质球,确保相同贴图只创建一个材质球
|
||
private Dictionary<Texture2D, Material> textureMaterialMap = new Dictionary<Texture2D, Material>();
|
||
|
||
[ContextMenu("替换所有子对象Shader")]
|
||
public void ReplaceAllChildrenShaders()
|
||
{
|
||
if (targetObject == null)
|
||
{
|
||
Debug.LogError("目标对象未设置!");
|
||
return;
|
||
}
|
||
|
||
// 清空之前的映射
|
||
textureMaterialMap.Clear();
|
||
|
||
// 初始化默认shader
|
||
InitializeDefaultShaders();
|
||
|
||
// 获取所有包含MeshRenderer的子对象
|
||
MeshRenderer[] meshRenderers = targetObject.GetComponentsInChildren<MeshRenderer>();
|
||
|
||
Debug.Log($"找到 {meshRenderers.Length} 个MeshRenderer组件");
|
||
|
||
foreach (MeshRenderer meshRenderer in meshRenderers)
|
||
{
|
||
ProcessMeshRenderer(meshRenderer);
|
||
}
|
||
|
||
Debug.Log($"Shader替换完成!共创建了 {textureMaterialMap.Count} 个材质球");
|
||
|
||
#if UNITY_EDITOR
|
||
// 在编辑模式下标记场景为已修改
|
||
if (!Application.isPlaying)
|
||
{
|
||
UnityEditor.EditorUtility.SetDirty(targetObject);
|
||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(
|
||
UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene());
|
||
}
|
||
#endif
|
||
}
|
||
|
||
private void InitializeDefaultShaders()
|
||
{
|
||
// 如果没有手动设置shader,则使用默认的
|
||
if (opaqueShader == null)
|
||
{
|
||
opaqueShader = Shader.Find(defaultOpaqueShaderName);
|
||
}
|
||
|
||
if (transparentShader == null)
|
||
{
|
||
transparentShader = Shader.Find(defaultTransparentShaderName);
|
||
}
|
||
|
||
if (opaqueShader == null)
|
||
{
|
||
Debug.LogError($"找不到不透明Shader: {defaultOpaqueShaderName}");
|
||
}
|
||
|
||
if (transparentShader == null)
|
||
{
|
||
Debug.LogError($"找不到透明Shader: {defaultTransparentShaderName}");
|
||
}
|
||
}
|
||
|
||
private void ProcessMeshRenderer(MeshRenderer meshRenderer)
|
||
{
|
||
// 使用 sharedMaterials 避免在编辑模式下创建材质实例
|
||
Material[] originalMaterials = meshRenderer.sharedMaterials;
|
||
|
||
if (originalMaterials == null || originalMaterials.Length == 0)
|
||
{
|
||
Debug.LogWarning($"对象 {meshRenderer.gameObject.name} 没有材质球");
|
||
return;
|
||
}
|
||
|
||
Material[] newMaterials = new Material[originalMaterials.Length];
|
||
bool materialsChanged = false;
|
||
|
||
for (int i = 0; i < originalMaterials.Length; i++)
|
||
{
|
||
Material originalMaterial = originalMaterials[i];
|
||
if (originalMaterial == null)
|
||
{
|
||
Debug.LogWarning($"对象 {meshRenderer.gameObject.name} 的材质球 {i} 为空");
|
||
newMaterials[i] = null;
|
||
continue;
|
||
}
|
||
|
||
// 获取主贴图
|
||
Texture2D mainTexture = originalMaterial.mainTexture as Texture2D;
|
||
|
||
if (mainTexture == null)
|
||
{
|
||
Debug.LogWarning($"对象 {meshRenderer.gameObject.name} 的材质球没有主贴图");
|
||
newMaterials[i] = originalMaterial;
|
||
continue;
|
||
}
|
||
|
||
// 检查是否已经为这个贴图创建了材质球
|
||
if (textureMaterialMap.ContainsKey(mainTexture))
|
||
{
|
||
newMaterials[i] = textureMaterialMap[mainTexture];
|
||
materialsChanged = true;
|
||
Debug.Log($"复用材质球: {mainTexture.name}");
|
||
}
|
||
else
|
||
{
|
||
// 创建新材质球
|
||
Material newMaterial = CreateMaterialForTexture(mainTexture, originalMaterial);
|
||
if (newMaterial != null)
|
||
{
|
||
textureMaterialMap[mainTexture] = newMaterial;
|
||
newMaterials[i] = newMaterial;
|
||
materialsChanged = true;
|
||
Debug.Log($"创建新材质球: {mainTexture.name}, 透明通道: {HasAlphaChannel(mainTexture)}");
|
||
}
|
||
else
|
||
{
|
||
newMaterials[i] = originalMaterial;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 只有在材质发生变化时才应用新材质球数组
|
||
if (materialsChanged)
|
||
{
|
||
meshRenderer.sharedMaterials = newMaterials;
|
||
|
||
#if UNITY_EDITOR
|
||
// 在编辑模式下标记对象为已修改
|
||
if (!Application.isPlaying)
|
||
{
|
||
UnityEditor.EditorUtility.SetDirty(meshRenderer);
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
|
||
private Material CreateMaterialForTexture(Texture2D texture, Material originalMaterial)
|
||
{
|
||
bool hasAlpha = HasAlphaChannel(texture);
|
||
Shader targetShader = hasAlpha ? transparentShader : opaqueShader;
|
||
|
||
if (targetShader == null)
|
||
{
|
||
Debug.LogError($"目标Shader为空!透明: {hasAlpha}");
|
||
return null;
|
||
}
|
||
|
||
// 创建新材质球
|
||
Material newMaterial = new Material(targetShader);
|
||
newMaterial.name = $"{texture.name}_{(hasAlpha ? "Transparent" : "Opaque")}";
|
||
|
||
// 复制原材质的属性
|
||
CopyMaterialProperties(originalMaterial, newMaterial);
|
||
|
||
// 设置主贴图
|
||
newMaterial.mainTexture = texture;
|
||
|
||
// 如果是透明材质,设置渲染模式
|
||
if (hasAlpha)
|
||
{
|
||
SetTransparentRenderingMode(newMaterial);
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
// 在编辑模式下,将材质保存为Asset
|
||
if (!Application.isPlaying)
|
||
{
|
||
SaveMaterialAsAsset(newMaterial);
|
||
}
|
||
#endif
|
||
|
||
return newMaterial;
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
private void SaveMaterialAsAsset(Material material)
|
||
{
|
||
// 创建Materials文件夹(如果不存在)
|
||
string folderPath = "Assets/Materials/Generated";
|
||
if (!AssetDatabase.IsValidFolder(folderPath))
|
||
{
|
||
if (!AssetDatabase.IsValidFolder("Assets/Materials"))
|
||
{
|
||
AssetDatabase.CreateFolder("Assets", "Materials");
|
||
}
|
||
AssetDatabase.CreateFolder("Assets/Materials", "Generated");
|
||
}
|
||
|
||
// 生成唯一的文件名
|
||
string materialPath = $"{folderPath}/{material.name}.mat";
|
||
materialPath = AssetDatabase.GenerateUniqueAssetPath(materialPath);
|
||
|
||
// 保存材质为Asset
|
||
AssetDatabase.CreateAsset(material, materialPath);
|
||
AssetDatabase.SaveAssets();
|
||
|
||
Debug.Log($"材质已保存: {materialPath}");
|
||
}
|
||
#endif
|
||
|
||
private bool HasAlphaChannel(Texture2D texture)
|
||
{
|
||
if (texture == null) return false;
|
||
|
||
// 检查贴图格式是否支持透明通道
|
||
TextureFormat format = texture.format;
|
||
|
||
switch (format)
|
||
{
|
||
case TextureFormat.ARGB32:
|
||
case TextureFormat.RGBA32:
|
||
case TextureFormat.ARGB4444:
|
||
case TextureFormat.RGBA4444:
|
||
case TextureFormat.DXT5:
|
||
case TextureFormat.BC7:
|
||
case TextureFormat.ETC2_RGBA8:
|
||
case TextureFormat.ASTC_4x4:
|
||
case TextureFormat.ASTC_5x5:
|
||
case TextureFormat.ASTC_6x6:
|
||
case TextureFormat.ASTC_8x8:
|
||
case TextureFormat.ASTC_10x10:
|
||
case TextureFormat.ASTC_12x12:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private void CopyMaterialProperties(Material source, Material target)
|
||
{
|
||
// 复制通用属性
|
||
if (source.HasProperty("_Color") && target.HasProperty("_Color"))
|
||
{
|
||
target.color = source.color;
|
||
}
|
||
|
||
if (source.HasProperty("_BaseColor") && target.HasProperty("_BaseColor"))
|
||
{
|
||
target.SetColor("_BaseColor", source.GetColor("_BaseColor"));
|
||
}
|
||
|
||
// 复制其他常用属性
|
||
string[] commonFloatProperties = {
|
||
"_Metallic", "_Smoothness", "_BumpScale", "_OcclusionStrength",
|
||
"_Cutoff", "_Surface", "_Blend", "_AlphaClip"
|
||
};
|
||
|
||
foreach (string property in commonFloatProperties)
|
||
{
|
||
if (source.HasProperty(property) && target.HasProperty(property))
|
||
{
|
||
target.SetFloat(property, source.GetFloat(property));
|
||
}
|
||
}
|
||
|
||
// 复制颜色属性
|
||
string[] colorProperties = {
|
||
"_EmissionColor"
|
||
};
|
||
|
||
foreach (string property in colorProperties)
|
||
{
|
||
if (source.HasProperty(property) && target.HasProperty(property))
|
||
{
|
||
target.SetColor(property, source.GetColor(property));
|
||
}
|
||
}
|
||
|
||
// 复制贴图
|
||
string[] textureProperties = {
|
||
"_BumpMap", "_MetallicGlossMap", "_OcclusionMap", "_EmissionMap"
|
||
};
|
||
|
||
foreach (string texProperty in textureProperties)
|
||
{
|
||
if (source.HasProperty(texProperty) && target.HasProperty(texProperty))
|
||
{
|
||
Texture tex = source.GetTexture(texProperty);
|
||
if (tex != null)
|
||
{
|
||
target.SetTexture(texProperty, tex);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void SetTransparentRenderingMode(Material material)
|
||
{
|
||
// 设置URP透明渲染模式
|
||
if (material.HasProperty("_Surface"))
|
||
{
|
||
material.SetFloat("_Surface", 1); // Transparent
|
||
}
|
||
|
||
if (material.HasProperty("_Blend"))
|
||
{
|
||
material.SetFloat("_Blend", 0); // Alpha
|
||
}
|
||
|
||
// 设置渲染队列
|
||
material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
|
||
|
||
// 启用关键字
|
||
material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
|
||
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
|
||
}
|
||
|
||
[ContextMenu("清理材质球映射")]
|
||
public void ClearMaterialMapping()
|
||
{
|
||
textureMaterialMap.Clear();
|
||
Debug.Log("材质球映射已清理");
|
||
}
|
||
|
||
// 在Inspector中显示当前映射信息
|
||
void OnValidate()
|
||
{
|
||
if (targetObject == null)
|
||
{
|
||
targetObject = gameObject;
|
||
}
|
||
}
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
[CustomEditor(typeof(ShaderReplacer))]
|
||
public class ShaderReplacerEditor : Editor
|
||
{
|
||
public override void OnInspectorGUI()
|
||
{
|
||
DrawDefaultInspector();
|
||
|
||
ShaderReplacer replacer = (ShaderReplacer)target;
|
||
|
||
GUILayout.Space(10);
|
||
|
||
if (GUILayout.Button("替换所有子对象Shader", GUILayout.Height(30)))
|
||
{
|
||
replacer.ReplaceAllChildrenShaders();
|
||
}
|
||
|
||
if (GUILayout.Button("清理材质球映射"))
|
||
{
|
||
replacer.ClearMaterialMapping();
|
||
}
|
||
|
||
GUILayout.Space(10);
|
||
EditorGUILayout.HelpBox(
|
||
"使用说明:\n" +
|
||
"1. 设置目标对象(默认为当前对象)\n" +
|
||
"2. 可选:设置自定义的不透明和透明Shader\n" +
|
||
"3. 点击'替换所有子对象Shader'按钮执行替换\n" +
|
||
"4. 脚本会自动检测贴图透明通道并选择合适的Shader\n" +
|
||
"5. 相同贴图的对象会共享同一个材质球\n" +
|
||
"6. 生成的材质球会保存到Assets/Materials/Generated文件夹",
|
||
MessageType.Info
|
||
);
|
||
|
||
GUILayout.Space(5);
|
||
EditorGUILayout.HelpBox(
|
||
"注意:在编辑模式下运行时,新创建的材质球会自动保存为Asset文件,避免材质泄漏。",
|
||
MessageType.Warning
|
||
);
|
||
}
|
||
}
|
||
#endif |