【合并工具】移到项目内

# Conflicts:
#	ProjectNLD/Assets/PhxhSDK
main
马远征 2026-01-16 18:37:50 +08:00
parent 0da9456e91
commit fe7c213a77
19 changed files with 1409 additions and 1 deletions

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2cc6cd0fb49146789026812735f60505
timeCreated: 1704259084

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ea835a762fa042a883b89cafbaf5929a
timeCreated: 1716894113

View File

@ -0,0 +1,27 @@
using PhxhSDK.Phxh.Others;
using UnityEditor;
using UnityEngine;
namespace PhxhSDK.Phxh.AutoCombine.Inspector
{
[CustomEditor(typeof(CmpSkinCombine))]
public class CmpSkinCombineInspector : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// base.OnInspectorGUI();
var cmp = target as CmpSkinCombine;
if (cmp == null) return;
if (GUILayout.Button("转换所有Mesh为SkinMesh", GUILayout.Height(40)))
{
AutoCombine.ConvertMeshToSkinMesh(cmp.gameObject);
}
if (GUILayout.Button("合并所有SkinMesh", GUILayout.Height(40)))
{
AutoCombine.CombineSkinMesh(cmp.gameObject, new string[] { "_MainTex", "_MaskTex" });
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d4cd9c040ace42bfa152950483bb1c0d
timeCreated: 1716894123

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 17313f90a5414192b618e320758a754a
timeCreated: 1703670894

View File

@ -0,0 +1,106 @@
using System.Collections.Generic;
using Performance.Data;
using UnityEditor;
using UnityEngine;
namespace Performance
{
public class AutoObjCombine
{
private GameObject _rootObj;
private Dictionary<Shader, ShaderGroupCombiner> _shaderCombinerDic = new Dictionary<Shader, ShaderGroupCombiner>();
private string _savePath;
private string _objName;
public AutoObjCombine(GameObject rootObj)
{
_rootObj = rootObj;
_objName = _rootObj.name;
}
public void DoCombine(string savePath)
{
_savePath = savePath;
_ProtectPath();
_CollectAllRenderers();
_CombineToNewObj();
}
private void _ProtectPath()
{
if (!_savePath.EndsWith("/"))
{
_savePath += "/";
}
_savePath += _rootObj.name + "_Combined";
AssetDatabase.DeleteAsset(_savePath);
}
private void _CombineToNewObj()
{
var newObj = Object.Instantiate(_rootObj);
// 删除所有meshrenderderers和meshfilters
var allMeshRenderers = newObj.GetComponentsInChildren<MeshRenderer>();
var allMeshFilters = newObj.GetComponentsInChildren<MeshFilter>();
for (int i = 0; i < allMeshRenderers.Length; i++)
{
Object.DestroyImmediate(allMeshRenderers[i]);
}
for (int i = 0; i < allMeshFilters.Length; i++)
{
Object.DestroyImmediate(allMeshFilters[i]);
}
foreach (var kvp in _shaderCombinerDic)
{
var combineObj = kvp.Value.CombineToSingleObject(_savePath, new List<Material>());
if (combineObj == null)
{
DebugUtil.LogError("合并对象失败, 可能是没有找到 MeshFilter 或 MeshRenderer");
continue;
}
combineObj.transform.SetParent(newObj.transform);
}
// 确保_savePath存在, 是从Assets目录开始的
if (!AssetDatabase.IsValidFolder(_savePath))
{
var realPath = Application.dataPath + "/" + _savePath.Replace("Assets/", "");
System.IO.Directory.CreateDirectory(realPath);
}
// 把newObj保存到指定路径
var prefabPath = _savePath + "/" + _objName + ".prefab";
PrefabUtility.SaveAsPrefabAsset(newObj, prefabPath);
// 清理新创建的 GameObject
Object.DestroyImmediate(newObj);
AssetDatabase.Refresh();
}
private void _CollectAllRenderers()
{
var allMeshRenderers = _rootObj.GetComponentsInChildren<MeshRenderer>();
for (int i = 0; i < allMeshRenderers.Length; i++)
{
var meshRenderer = allMeshRenderers[i];
if (meshRenderer == null || meshRenderer.sharedMaterial == null)
{
continue;
}
var shader = meshRenderer.sharedMaterial.shader;
if (!_shaderCombinerDic.TryGetValue(shader, out var combiner))
{
combiner = new ShaderGroupCombiner(shader);
_shaderCombinerDic[shader] = combiner;
}
combiner.AddRenderer(meshRenderer);
}
DebugUtil.Log($"共收集到 {allMeshRenderers.Length} 个 MeshRenderer, 分别属于 {_shaderCombinerDic.Count} 个 Shader");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d22e9d4f920e4e5191e4f0781cd52dc6
timeCreated: 1752576177

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7c21427ed9e049f49be82588f2c2ef72
timeCreated: 1703671650

View File

@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.IO;
using PhxhSDK;
using UnityEditor;
using UnityEngine;
using Directory = System.IO.Directory;
using File = System.IO.File;
namespace Performance.Data
{
/// <summary>
/// 负责保存合并后的资源
/// </summary>
public class AssetSaver
{
/// <summary>
/// 贴图类型定义
/// </summary>
public enum TextureType
{
Main,
Mask
}
/// <summary>
/// 贴图信息结构体
/// </summary>
public struct TextureInfo
{
public TextureType Type;
public string PropertyName;
public string FileName;
public Texture2D[] SourceTextures;
public Texture2D CombinedTexture;
}
/// <summary>
/// 保存单个贴图到文件
/// </summary>
public static Texture2D SaveTexture(Texture2D texture, string filePath)
{
try
{
// 确保路径以.png结尾
if (!filePath.EndsWith(".png"))
{
filePath += ".png";
}
string directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 将纹理编码为PNG并保存
byte[] bytes = texture.EncodeToPNG();
File.WriteAllBytes(filePath, bytes);
// 刷新资源数据库以识别新文件
AssetDatabase.Refresh();
// 加载保存后的纹理资源
Texture2D savedTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(filePath);
if (savedTexture == null)
{
Debug.LogError($"Failed to load saved texture from: {filePath}");
return null;
}
Debug.Log($"Texture saved and loaded successfully from: {filePath}");
return savedTexture;
}
catch (System.Exception ex)
{
Debug.LogError($"Error saving texture to {filePath}: {ex.Message}");
throw;
}
}
/// <summary>
/// 处理并保存多个类型的贴图
/// </summary>
public static Dictionary<TextureType, Texture2D> ProcessAndSaveTextures(
Texture2D[] mainTextures,
Texture2D[] maskTextures,
string outputPath, string suffix = "")
{
var textureInfos = new[]
{
new TextureInfo
{
Type = TextureType.Main,
PropertyName = "_MainTex",
FileName = $"CombinedMain{suffix}.png",
SourceTextures = mainTextures
},
new TextureInfo
{
Type = TextureType.Mask,
PropertyName = "_MaskTex",
FileName = $"CombinedMask{suffix}.png",
SourceTextures = maskTextures
}
};
var results = new Dictionary<TextureType, Texture2D>();
try
{
foreach (var info in textureInfos)
{
if (info.SourceTextures == null || info.SourceTextures.Length == 0)
{
Debug.LogWarning($"No {info.Type} textures to process");
continue;
}
Texture2D combinedTexture = TextureUtils.CombineTextures(info.FileName, info.SourceTextures);
if (combinedTexture != null)
{
Texture2D savedTexture = SaveTexture(combinedTexture, $"{outputPath}{info.FileName}");
if (savedTexture != null)
{
results[info.Type] = savedTexture;
}
}
}
if (results.Count > 0)
{
ReloadAssets();
}
return results;
}
catch (System.Exception ex)
{
Debug.LogError($"Error processing textures: {ex.Message}");
return results;
}
}
/// <summary>
/// 创建资源输出路径
/// </summary>
public static string GetGroupOutputPath(string directory, string groupName, string shaderName)
{
var outputPath = $"{directory}/{groupName}_{ConvertShaderName(shaderName)}";
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
return outputPath;
}
/// <summary>
/// 保存材质和网格资源
/// </summary>
public static void SaveAssets(Material material, Mesh mesh, string outputPath, string suffix = "")
{
var materialPath = $"{outputPath}/Combined{suffix}.mat";
var meshPath = $"{outputPath}/Combined{suffix}.asset";
AssetDatabase.CreateAsset(material, materialPath);
AssetDatabase.CreateAsset(mesh, meshPath);
}
/// <summary>
/// 刷新资源数据库
/// </summary>
public static void ReloadAssets()
{
AssetDatabase.Refresh();
}
/// <summary>
/// 转换着色器名称为有效的路径名
/// </summary>
public static string ConvertShaderName(string shaderName)
{
return shaderName.Replace("/", "_");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ecbef4ed2ec344443b5212107a789743
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
using UnityEngine;
namespace Performance.Data
{
public class AutoCombieMeshData
{
public Mesh mesh;
public List<GameObject> objs = new List<GameObject>();
public AutoCombieMeshData(Mesh mesh)
{
this.mesh = mesh;
}
public void AddObj(GameObject obj)
{
objs.Add(obj);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 11d7a1f0d53d4c45b0e32129671d2242
timeCreated: 1703671666

View File

@ -0,0 +1,478 @@
using System.Collections.Generic;
using System.IO;
using PhxhSDK;
using UnityEditor;
using UnityEngine;
using Directory = System.IO.Directory;
using File = System.IO.File;
namespace Performance.Data
{
public class ShaderGroupCombiner
{
private const string MainTextureProperty = "_MainTex";
private const string MaskTextureProperty = "_MaskTex";
private const string DefaultGroupName = "Default";
private const int DefaultTextureUnitSize = 256;
private const int InitialRendererCapacity = 1000;
public readonly Shader shader;
public readonly string groupName;
private readonly int _textureUnitSize;
public readonly Renderer[] renderers;
private int _rendererCount;
public int textureGridWidth;
public Vector2Int TextureSize { get; private set; }
// 缓存字段
private readonly HashSet<Material> _cachedMaterials;
private Texture2D[] _cachedMainTextures;
private Texture2D[] _cachedMaskTextures;
private bool _isPreCalculated;
private readonly Dictionary<Material, int> _materialTextureIndices;
private readonly Dictionary<Renderer, Vector2[]> _rendererUVs;
public ShaderGroupCombiner(
Shader shader,
int textureUnitSize = DefaultTextureUnitSize,
string groupName = DefaultGroupName)
{
this.shader = shader ?? throw new System.ArgumentNullException(nameof(shader));
_textureUnitSize = textureUnitSize;
this.groupName = groupName ?? DefaultGroupName;
_materialTextureIndices = new Dictionary<Material, int>();
_rendererUVs = new Dictionary<Renderer, Vector2[]>();
_cachedMaterials = new HashSet<Material>();
renderers = new Renderer[InitialRendererCapacity];
_rendererCount = 0;
}
public void AddRenderer(Renderer renderer)
{
if (renderer == null)
{
Debug.LogError("Renderer cannot be null");
return;
}
if (_rendererCount >= renderers.Length)
{
Debug.LogError($"Renderer array is full. Consider increasing the initial size from {InitialRendererCapacity}.");
return;
}
renderers[_rendererCount++] = renderer;
_isPreCalculated = false;
}
public void PreCalculate()
{
if (_rendererCount == 0)
{
Debug.LogWarning("No renderers to process");
return;
}
if (_isPreCalculated) return;
try
{
CollectUniqueMaterials();
if (_cachedMaterials.Count == 0)
{
Debug.LogWarning("No valid materials found");
return;
}
var (mainTextures, maskTextures) = CollectTexturePairs(_cachedMaterials);
_cachedMainTextures = mainTextures;
_cachedMaskTextures = maskTextures;
TextureSize = TextureUtils.CalculateTextureSize(_cachedMainTextures, _textureUnitSize);
textureGridWidth = Mathf.CeilToInt(Mathf.Sqrt(_cachedMainTextures.Length));
PreCalculateMaterialIndices();
PreCalculateUVCoordinates();
_isPreCalculated = true;
}
catch (System.Exception ex)
{
Debug.LogError($"Error during pre-calculation: {ex.Message}");
_isPreCalculated = false;
throw;
}
}
public GameObject CombineToSingleObject(string outputPath, List<Material> sampleMaterials, string prefix = "")
{
if (string.IsNullOrEmpty(outputPath))
{
Debug.LogError("Output path cannot be null or empty");
return null;
}
if (!IsShaderSupported())
{
Debug.LogError($"Unsupported shader: {shader.name}");
return null;
}
ValidatePreCalculation();
return ProcessNLDShaders(outputPath, sampleMaterials, prefix);
}
private void PreCalculateMaterialIndices()
{
if (_materialTextureIndices.Count == 0)
{
Debug.LogWarning("Material texture indices not properly initialized in CollectTexturePairs");
}
}
private bool TryGetMainTexture(Material material, out Texture2D mainTexture)
{
mainTexture = material.GetTexture(MainTextureProperty) as Texture2D;
return mainTexture != null;
}
private void PreCalculateUVCoordinates()
{
_rendererUVs.Clear();
for (int i = 0; i < _rendererCount; i++)
{
var renderer = renderers[i];
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null) continue;
var material = renderer.sharedMaterial;
if (material == null) continue;
var uvs = CalculateUVCoordinates(meshFilter.sharedMesh, material);
_rendererUVs[renderer] = uvs;
}
}
private void ValidatePreCalculation()
{
if (!_isPreCalculated)
{
Debug.LogWarning("Pre-calculation not performed. Running PreCalculate now...");
PreCalculate();
}
}
private void CollectUniqueMaterials()
{
_cachedMaterials.Clear();
for (int i = 0; i < _rendererCount; i++)
{
var renderer = renderers[i];
if (renderer?.sharedMaterial != null)
{
_cachedMaterials.Add(renderer.sharedMaterial);
}
}
}
private (Texture2D[] mainTextures, Texture2D[] maskTextures) CollectTexturePairs(IEnumerable<Material> materials)
{
if (materials == null)
{
Debug.LogWarning("No materials to process textures from");
return (System.Array.Empty<Texture2D>(), System.Array.Empty<Texture2D>());
}
var materialTexturePairs = new List<(Material material, Texture2D mainTexture, Texture2D maskTexture)>();
var invalidMaterials = new List<Material>();
// 先收集所有有效的材质和其对应的贴图对
foreach (var material in materials)
{
if (material == null) continue;
if (TryGetMainTexture(material, out var mainTexture))
{
TryGetMaskTexture(material, out var maskTexture);
// 即使maskTexture为null也加入列表保持索引一致性
materialTexturePairs.Add((material, mainTexture, maskTexture));
}
else
{
invalidMaterials.Add(material);
}
}
// 创建结果数组确保主贴图和mask贴图的索引一一对应
var mainTextures = new Texture2D[materialTexturePairs.Count];
var maskTextures = new Texture2D[materialTexturePairs.Count];
for (int i = 0; i < materialTexturePairs.Count; i++)
{
mainTextures[i] = materialTexturePairs[i].mainTexture;
maskTextures[i] = materialTexturePairs[i].maskTexture;
// 记录材质的贴图索引用于后续UV计算
_materialTextureIndices[materialTexturePairs[i].material] = i;
}
if (invalidMaterials.Count > 0)
{
Debug.LogError($"Found {invalidMaterials.Count} materials with invalid textures");
foreach (var material in invalidMaterials)
{
Debug.LogError($"Material: {material.name}", material);
}
}
return (mainTextures, maskTextures);
}
private bool TryGetMaskTexture(Material material, out Texture2D maskTexture)
{
maskTexture = material.GetTexture(MaskTextureProperty) as Texture2D;
return maskTexture != null;
}
private bool IsShaderSupported()
{
return shader.name switch
{
"NLD_URP/NLD_Charactor_Lambert" => true,
"NLD_URP/NLD_Charactor" => true,
"NLD_URP/NLD_Scene_Lambert" => true,
"NLD_URP/NLD_Scene_Lambert_DoubleSide" => true,
"NLD_URP/NLD_Scene_Lightmap" => true,
"NLD_URP/NLD_Scene_Opaque" => true,
_ => false
};
}
private void ValidateMaterialTextures(Material material)
{
if (material == null)
{
Debug.LogError("Material cannot be null");
return;
}
var missingTextures = new List<string>();
if (material.GetTexture(MainTextureProperty) == null)
{
missingTextures.Add(MainTextureProperty);
}
if (material.GetTexture(MaskTextureProperty) == null)
{
missingTextures.Add(MaskTextureProperty);
}
if (missingTextures.Count > 0)
{
Debug.LogError($"Material {material.name} is missing textures: {string.Join(", ", missingTextures)}", material);
}
}
private GameObject ProcessNLDShaders(string outputDirectory, List<Material> sampleMaterials, string prefix = "")
{
if (string.IsNullOrEmpty(outputDirectory))
{
Debug.LogError("Output directory cannot be null or empty");
return null;
}
if (_rendererCount == 0)
{
Debug.LogError("No renderers to process");
return null;
}
try
{
string sceneName = Path.GetFileNameWithoutExtension(outputDirectory);
//string groupOutputPath = AssetSaver.GetGroupOutputPath(outputDirectory, groupName, shader.name);
string groupOutputPath = $"{outputDirectory}/"; // 简化保存的名字
if (!PostProcessInstructor.DicNumber.TryGetValue(shader.name, out int number))
PostProcessInstructor.DicNumber[shader.name] = PostProcessInstructor.DicNumber.Count;
string suffix = $"_{sceneName}_{PostProcessInstructor.DicNumber[shader.name]}{groupName.Replace("Group", string.Empty).Replace("Default", string.Empty)}";
var textureResults = AssetSaver.ProcessAndSaveTextures(
_cachedMainTextures,
_cachedMaskTextures,
groupOutputPath,
suffix
);
if (!textureResults.TryGetValue(AssetSaver.TextureType.Main, out var combinedMainTexture))
{
Debug.LogError("Failed to process main texture");
return null;
}
if (!textureResults.TryGetValue(AssetSaver.TextureType.Mask, out var combinedMaskTexture))
{
Debug.LogWarning("No mask texture found, proceeding with only main texture");
}
var firstMaterial = new List<Material>(_cachedMaterials)[0];
var resultMaterial = CreateCombinedMaterial(
firstMaterial,
combinedMainTexture,
combinedMaskTexture,
sampleMaterials
);
if (resultMaterial == null)
{
Debug.LogError("Failed to create combined material");
return null;
}
var resultMesh = CreateCombinedMesh();
if (resultMesh == null)
{
Debug.LogError("Failed to create combined mesh");
return null;
}
var resultObject = CreateResultObject(resultMaterial, resultMesh);
if (resultObject == null)
{
Debug.LogError("Failed to create result object");
return null;
}
AssetSaver.SaveAssets(resultMaterial, resultMesh, groupOutputPath, suffix);
return resultObject;
}
catch (System.Exception ex)
{
Debug.LogError($"Error processing NLD shaders: {ex.Message}\n{ex.StackTrace}");
return null;
}
}
private Material CreateCombinedMaterial(Material firstMaterial, Texture2D mainTexture, Texture2D maskTexture, List<Material> sampleMaterials)
{
var sampleMaterial = FindSampleMaterial(firstMaterial, sampleMaterials);
var resultMaterial = new Material(sampleMaterial);
if (mainTexture != null)
{
resultMaterial.SetTexture(MainTextureProperty, mainTexture);
Debug.Log($"Setting main texture on combined material: {mainTexture.name}, size: {mainTexture.width}x{mainTexture.height}");
}
else
{
Debug.LogError("Main texture is null when creating combined material");
}
if (maskTexture != null)
{
resultMaterial.SetTexture(MaskTextureProperty, maskTexture);
Debug.Log($"Setting mask texture on combined material: {maskTexture.name}, size: {maskTexture.width}x{maskTexture.height}");
}
else
{
Debug.LogWarning("No mask texture available for combined material, shader effects may not work properly");
}
return resultMaterial;
}
private Mesh CreateCombinedMesh()
{
var combineInstances = new List<CombineInstance>();
var combinedUVs = new List<Vector2>();
for (int i = 0; i < _rendererCount; i++)
{
var renderer = renderers[i];
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null) continue;
var combineInstance = CreateCombineInstance(renderer);
if (_rendererUVs.TryGetValue(renderer, out var uvs))
{
combineInstances.Add(combineInstance);
combinedUVs.AddRange(uvs);
}
}
return CreateFinalMesh(combineInstances, combinedUVs);
}
private CombineInstance CreateCombineInstance(Renderer renderer)
{
return new CombineInstance
{
mesh = renderer.GetComponent<MeshFilter>().sharedMesh,
transform = renderer.transform.localToWorldMatrix
};
}
private Vector2[] CalculateUVCoordinates(Mesh mesh, Material material)
{
if (_materialTextureIndices.TryGetValue(material, out var textureIndex))
{
var uvs = mesh.uv;
var xIndex = textureIndex % textureGridWidth;
var yIndex = textureIndex / textureGridWidth;
var uvScale = new Vector2(1f / textureGridWidth, 1f / textureGridWidth);
var result = new Vector2[uvs.Length];
for (int i = 0; i < uvs.Length; i++)
{
result[i] = new Vector2(
uvs[i].x * uvScale.x + xIndex * uvScale.x,
uvs[i].y * uvScale.y + yIndex * uvScale.y
);
}
return result;
}
Debug.LogWarning($"No texture index found for material: {material.name}");
return mesh.uv;
}
private Mesh CreateFinalMesh(List<CombineInstance> combineInstances, List<Vector2> combinedUVs)
{
var resultMesh = new Mesh
{
indexFormat = UnityEngine.Rendering.IndexFormat.UInt32
};
resultMesh.CombineMeshes(combineInstances.ToArray(), true, true);
resultMesh.uv = combinedUVs.ToArray();
resultMesh.uv2 = null;
resultMesh.uv3 = null;
Unwrapping.GenerateSecondaryUVSet(resultMesh);
return resultMesh;
}
private GameObject CreateResultObject(Material material, Mesh mesh)
{
var objectName = $"{groupName}_{AssetSaver.ConvertShaderName(shader.name)}";
var resultObject = new GameObject(objectName);
var resultRenderer = resultObject.AddComponent<MeshRenderer>();
var resultFilter = resultObject.AddComponent<MeshFilter>();
resultFilter.sharedMesh = mesh;
resultRenderer.sharedMaterial = material;
return resultObject;
}
private Material FindSampleMaterial(Material firstMaterial, List<Material> sampleMaterials)
{
if (sampleMaterials == null) return firstMaterial;
return sampleMaterials.Find(sample => sample.shader.name == firstMaterial.shader.name)
?? firstMaterial;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d493dc01f7824c28b0970ee59143400
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,323 @@
using System.Collections.Generic;
using Performance.Data;
using PhxhSDK;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Performance
{
public class PerformanceWindow : EditorWindow
{
// [MenuItem("Tools/性能优化/打开窗口")]
public static void ShowWindow()
{
var window = GetWindow<PerformanceWindow>();
window.titleContent = new GUIContent("性能优化");
window.Show();
}
private GameObject _selectObj;
private bool _isAnalysised;
[SerializeField]
private List<string> _skipChilds = new List<string>()
{
"Trees", // 树
"Stuffs", // 树
"Ground", // 地面,走大贴图
"Lights", // 灯光
"Fog", // 雾,是一个特殊的材质
"Global Volume", // 全局后处理
};
private List<AutoCombieMeshData> _cacheMeshes = new List<AutoCombieMeshData>();
private List<ShaderGroupCombiner> _cacheShaders = new List<ShaderGroupCombiner>();
private Vector2 _scrollPos1;
private Vector2 _scrollPos2;
private SerializedObject _serializedObject;
private SerializedProperty _serializedProperty;
private int _forceUnitTextureSize = 256;
private int _totalCount;
private GameObject _cacheRootObj;
private void _InitFiled()
{
_serializedObject = new SerializedObject(this);
_serializedProperty = _serializedObject.FindProperty("_skipChilds");
_cacheRootObj = GameObject.Find("CombieRoot");
_selectObj = GameObject.Find("Art");
}
private void OnGUI()
{
// 自动勾选所有材质的GPU Instancing
if (GUILayout.Button("自动勾选所有材质的GPU Instancing"))
{
AutoSetAllGpuInst.AutoSet();
}
if (_serializedObject == null)
{
_InitFiled();
}
_serializedObject.Update();
EditorGUILayout.PropertyField(_serializedProperty, true);
_serializedObject.ApplyModifiedProperties();
var selectObj = EditorGUILayout.ObjectField("场景根节点", _selectObj, typeof(GameObject), true) as GameObject;
if (selectObj != _selectObj)
{
_selectObj = selectObj;
_cacheMeshes.Clear();
_cacheShaders.Clear();
_isAnalysised = false;
_totalCount = 0;
_cacheRootObj = GameObject.Find("CombieRoot");
}
_forceUnitTextureSize = EditorGUILayout.IntField("强制单张贴图大小", _forceUnitTextureSize);
if (GUILayout.Button("分析"))
{
_DoAnalysis();
}
if (_isAnalysised)
{
GUILayout.Label($"共有不同的Mesh: {_cacheMeshes.Count}个");
GUILayout.Label($"预计合并后的顶点数量: {_totalCount}个");
_scrollPos1 = EditorGUILayout.BeginScrollView(_scrollPos1, GUILayout.Height(100));
GUILayout.BeginVertical();
foreach (var mesh in _cacheMeshes)
{
GUILayout.Label($" {mesh.objs.Count}个 {mesh.mesh.name}");
}
GUILayout.EndVertical();
EditorGUILayout.EndScrollView();
GUILayout.Label($"共有不同的Shader: {_cacheShaders.Count}个");
_scrollPos2 = EditorGUILayout.BeginScrollView(_scrollPos2, GUILayout.Height(100));
GUILayout.BeginVertical();
foreach (var shader in _cacheShaders)
{
GUILayout.Label($" {shader.renderers.Length}个 {shader.shader.name} 预计贴图大小: {shader.textureGridWidth}");
}
GUILayout.EndVertical();
EditorGUILayout.EndScrollView();
if (GUILayout.Button("自动合并"))
{
_DoCombie();
}
}
if (_cacheRootObj != null)
{
GUILayout.BeginHorizontal();
// 还原和切换
if (GUILayout.Button("还原"))
{
_ShowCombie(false);
}
if (GUILayout.Button("切换"))
{
_ShowCombie(true);
}
GUILayout.EndHorizontal();
}
}
private void _ShowCombie(bool show)
{
if (show)
{
// 隐藏原始节点
_SetOldObjActive(false);
// 显示合并节点
_cacheRootObj.SetActive(true);
}
else
{
// 隐藏合并节点
_cacheRootObj.SetActive(false);
// 显示原始节点
_SetOldObjActive(true);
}
}
private void _SetOldObjActive(bool active)
{
if (_selectObj == null)
{
return;
}
var childCount = _selectObj.transform.childCount;
for (var i = 0; i < childCount; ++i)
{
var child = _selectObj.transform.GetChild(i);
if (_skipChilds.Contains(child.name))
{
continue;
}
child.gameObject.SetActive(active);
}
}
private void _DoCombie()
{
// 强制替换
if (_cacheRootObj == null)
{
var findObj = GameObject.Find("CombieRoot");
if (findObj != null)
{
DestroyImmediate(findObj);
}
}
else
{
DestroyImmediate(_cacheRootObj);
}
_cacheRootObj = new GameObject("CombieRoot");
var sceneDir = SceneManager.GetActiveScene();
for (int i = 0; i < _cacheShaders.Count; i++)
{
var shaderData = _cacheShaders[i];
var combieObj = shaderData.CombineToSingleObject($"{sceneDir}/Combined", new List<Material>());
if (combieObj == null)
{
continue;
}
combieObj.transform.SetParent(_cacheRootObj.transform);
}
// 隐藏原始节点
_SetOldObjActive(false);
AssetDatabase.Refresh();
}
private List<Renderer> _CollectRenderers()
{
var result = new List<Renderer>();
var childCount = _selectObj.transform.childCount;
for (var i = 0; i < childCount; ++i)
{
var child = _selectObj.transform.GetChild(i);
if (_skipChilds.Contains(child.name))
{
continue;
}
var renderers = child.GetComponentsInChildren<Renderer>();
result.AddRange(renderers);
}
return result;
}
private void _DoAnalysis()
{
if (_selectObj == null)
{
Debug.LogError("请先选择场景根节点");
return;
}
var renderers = _CollectRenderers();
_CollectMeshs(renderers);
_CollectShaders(renderers);
_isAnalysised = true;
}
private void _CollectShaders(List<Renderer> renderers)
{
_cacheShaders.Clear();
foreach (var renderer in renderers)
{
var materials = renderer.sharedMaterials;
if (materials.Length > 1)
{
Debug.LogError(renderer.name + "材质数量大于1", renderer.gameObject);
continue;
}
var shareMaterial = renderer.sharedMaterial;
if (shareMaterial == null)
{
Debug.LogError(renderer.name + "没有材质", renderer.gameObject);
continue;
}
var shader = shareMaterial.shader;
if (shader == null)
{
Debug.LogError(renderer.name + "没有Shader", renderer.gameObject);
continue;
}
var shaderData = _GetShaderData(shader);
shaderData.AddRenderer(renderer);
}
// 预计算贴图大小
foreach (var shaderData in _cacheShaders)
{
shaderData.PreCalculate();
}
}
private void _CollectMeshs(List<Renderer> renderers)
{
_totalCount = 0;
_cacheMeshes.Clear();
foreach (var renderer in renderers)
{
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter == null)
{
Debug.LogError(renderer.name + "没有MeshFilter", renderer.gameObject);
continue;
}
var mesh = meshFilter.sharedMesh;
if (mesh == null)
{
Debug.LogError(renderer.name + "没有Mesh", renderer.gameObject);
continue;
}
var meshData = _GetMeshData(mesh);
_totalCount += mesh.vertexCount;
meshData.AddObj(renderer.gameObject);
}
}
private ShaderGroupCombiner _GetShaderData(Shader shader)
{
foreach (var shaderData in _cacheShaders)
{
if (shaderData.shader == shader)
{
return shaderData;
}
}
var newShaderData = new ShaderGroupCombiner(shader, _forceUnitTextureSize);
_cacheShaders.Add(newShaderData);
return newShaderData;
}
private AutoCombieMeshData _GetMeshData(Mesh mesh)
{
foreach (var meshData in _cacheMeshes)
{
if (meshData.mesh == mesh)
{
return meshData;
}
}
var newMeshData = new AutoCombieMeshData(mesh);
_cacheMeshes.Add(newMeshData);
return newMeshData;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 90dcfacca7654dee9b981e0bd4238416
timeCreated: 1703670909

View File

@ -0,0 +1,209 @@
using System.Collections;
using System.Collections.Generic;
using Performance.Data;
using PhxhSDK;
using UnityEngine;
public class SceneCombineUtil : Singlenton<SceneCombineUtil>
{
private int _totalCount;
private List<AutoCombieMeshData> _cacheMeshes = new List<AutoCombieMeshData>();
private List<ShaderGroupCombiner> _cacheShaders = new List<ShaderGroupCombiner>();
private List<Material> _sampleMaterials = new List<Material>();
private int _forceUnitTextureSize = 256;
public List<GameObject> CombineGameObjectsByGroups(Dictionary<string, List<Renderer>> rendererGroups,
Transform root,
string outputPath,
int postProcessTextureUnitSize = 256,
List<Material> sampleMaterials = null,string prefix = "")
{
if (sampleMaterials != null)
{
_sampleMaterials.AddRange(sampleMaterials);
}
_forceUnitTextureSize = postProcessTextureUnitSize;
List<GameObject> result = new List<GameObject>();
foreach (var group in rendererGroups)
{
string groupName = group.Key;
List<Renderer> groupRenderers = group.Value;
// 为每个组创建单独的输出路径
string groupOutputPath = $"{outputPath}";
// 收集该组的网格和着色器
_CollectMeshs(groupRenderers);
_CollectShaders(groupRenderers, groupName);
// 合并该组
var groupResult = _DoCombine(root, groupOutputPath, prefix);
result.AddRange(groupResult);
// 清理缓存,准备下一组
_cacheMeshes.Clear();
_cacheShaders.Clear();
}
return result;
}
[System.Obsolete("Use CombineGameObjectsByGroups instead")]
public List<GameObject> CombineGameObjects(List<Renderer> renderers,
Transform root,
string outputPath,
int postProcessTextureUnitSize = 256,
List<Material> sampleMaterials = null, string prefix = "")
{
var rendererGroups = new Dictionary<string, List<Renderer>>
{
{ "Default", renderers }
};
return CombineGameObjectsByGroups(rendererGroups, root, outputPath, postProcessTextureUnitSize, sampleMaterials, prefix);
}
private List<GameObject> _DoCombine(Transform root, string outputPath, string prefix = "")
{
// 强制替换
// if (_cacheRootObj == null)
// {
// var findObj = GameObject.Find("CombieRoot");
// if (findObj != null)
// {
// DestroyImmediate(findObj);
// }
// }
// else
// {
// DestroyImmediate(_cacheRootObj);
// }
//_cacheRootObj = new GameObject("CombieRoot");
List<GameObject> result = new();
for (int i = 0; i < _cacheShaders.Count; i++)
{
var shaderData = _cacheShaders[i];
var combineObj = shaderData.CombineToSingleObject(outputPath, _sampleMaterials, prefix);
if (combineObj != null)
{
result.Add(combineObj);
combineObj.transform.SetParent(root);
}
//combieObj.transform.SetParent(_cacheRootObj.transform);
}
return result;
// 隐藏原始节点
//_SetOldObjActive(false);
//AssetDatabase.Refresh();
}
private void _CollectShaders(List<Renderer> renderers, string groupName = "Default")
{
_cacheShaders.Clear();
foreach (var renderer in renderers)
{
var materials = renderer.sharedMaterials;
if (materials.Length > 1)
{
Debug.LogError(renderer.name + "材质数量大于1", renderer.gameObject);
continue;
}
var shareMaterial = renderer.sharedMaterial;
if (shareMaterial == null)
{
Debug.LogError(renderer.name + "没有材质", renderer.gameObject);
continue;
}
var shader = shareMaterial.shader;
if (shader == null)
{
Debug.LogError(renderer.name + "没有Shader", renderer.gameObject);
continue;
}
var shaderData = _GetShaderData(shader, groupName);
shaderData.AddRenderer(renderer);
}
// 预计算贴图大小
foreach (var shaderData in _cacheShaders)
{
shaderData.PreCalculate();
}
}
private List<Renderer> _CollectRenderers(List<GameObject> gameObjects)
{
var result = new List<Renderer>();
foreach (var go in gameObjects)
{
var renderer = go.GetComponent<Renderer>();
if (renderer != null)
result.Add(renderer);
}
return result;
}
private void _CollectMeshs(List<Renderer> renderers)
{
_totalCount = 0;
_cacheMeshes.Clear();
foreach (var renderer in renderers)
{
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter == null)
{
Debug.LogError(renderer.name + "没有MeshFilter", renderer.gameObject);
continue;
}
var mesh = meshFilter.sharedMesh;
if (mesh == null)
{
Debug.LogError(renderer.name + "没有Mesh", renderer.gameObject);
continue;
}
var meshData = _GetMeshData(mesh);
_totalCount += mesh.vertexCount;
meshData.AddObj(renderer.gameObject);
}
}
private ShaderGroupCombiner _GetShaderData(Shader shader, string groupName = "Default")
{
foreach (var shaderData in _cacheShaders)
{
if (shaderData.shader == shader && shaderData.groupName == groupName)
{
return shaderData;
}
}
var newShaderData = new ShaderGroupCombiner(shader, _forceUnitTextureSize, groupName);
_cacheShaders.Add(newShaderData);
return newShaderData;
}
private AutoCombieMeshData _GetMeshData(Mesh mesh)
{
foreach (var meshData in _cacheMeshes)
{
if (meshData.mesh == mesh)
{
return meshData;
}
}
var newMeshData = new AutoCombieMeshData(mesh);
_cacheMeshes.Add(newMeshData);
return newMeshData;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4923e8f03afee074d96c8e493cd939c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -1 +1 @@
Subproject commit 77f685ff4f492e605f782aab45c6265e01c0f283
Subproject commit 72d5ff680bfab96d675a4d42a9bd032a0fa3bbed