Files
KopMap/Assets/Scripts/ShaderReplacer.cs
2025-09-02 18:55:19 +08:00

386 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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