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
|