Files
KopMap/Assets/Scripts/ShaderReplacer.cs

386 lines
12 KiB
C#
Raw Normal View History

2025-09-02 18:55:19 +08:00
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