using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using Object = UnityEngine.Object; /// /// 用于获得材质像素密度的方法 - 返回材质在一个MeshRenderer或者SkinnedMeshRenderer上大致像素/单位长度 /// public class GetTextureDpi { private readonly List _prefabPaths = new List(); private readonly List _scenePaths = new List(); private readonly List _textureDpi = new List(); private Scene? _currentScene; private int _index; [MenuItem("ResourceTool/Get All Texture Dpi")] public static void GetAllTextureDpi() { var instance = new GetTextureDpi(); instance.Start(); } public GetTextureDpi() { var allPaths = AssetDatabase.GetAllAssetPaths(); for (var i = 0; i < allPaths.Length; i++) { if (allPaths[i].StartsWith("Assets/")) { var assetType = AssetDatabase.GetMainAssetTypeAtPath(allPaths[i]); if (assetType == typeof(Texture2D)) _textureDpi.Add(new TextureDpi(allPaths[i])); else if (assetType == typeof(SceneAsset)) _scenePaths.Add(allPaths[i]); else if (assetType == typeof(GameObject)) _prefabPaths.Add(allPaths[i]); } } } public void Start() { _index = 0; EditorApplication.update = OnPrefabProcess; } private void OnPrefabProcess() { var finish = false; try { if (_index < _prefabPaths.Count) { if (!(AssetImporter.GetAtPath(_prefabPaths[_index]) is ModelImporter)) { var prefab = AssetDatabase.LoadAssetAtPath(_prefabPaths[_index]); if (prefab != null) ReadFromOneGameObject(prefab, _prefabPaths[_index], false); } } else { finish = true; } } catch (Exception e) { Debug.LogError(e.ToString()); } if (finish) { _index = 0; _currentScene = null; EditorApplication.update = OnSceneProcess; } else { Debug.Log(string.Format("完成预制物{0} / {1}", _index, _prefabPaths.Count)); _index++; } } private void OnSceneProcess() { var finish = false; try { if (_index < _scenePaths.Count) { if (_currentScene == null) { _currentScene = EditorSceneManager.OpenScene(_scenePaths[_index]); } else { var gameObjects = _currentScene.Value.GetRootGameObjects(); for (var i = 0; i < gameObjects.Length; i++) ReadFromOneGameObject(gameObjects[i], _scenePaths[_index], true); System.Diagnostics.Debug.Assert(_currentScene != null, "_currentScene != null"); EditorSceneManager.CloseScene(_currentScene.Value, true); _currentScene = null; } } else { finish = true; } } catch (Exception e) { Debug.LogError(e.ToString()); _currentScene = null; } if (finish) { _currentScene = null; EditorApplication.update = null; WriteToFile(); } // 如果是场景加载帧,则等候一帧再读取GameObject else if (_currentScene == null) { Debug.Log(string.Format("完成场景{0} / {1}", _index, _scenePaths.Count)); _index++; _currentScene = null; } } private void WriteToFile() { var builder = new StringBuilder(); builder.Append("Asset"); builder.Append('\t'); builder.Append("MaxDpi"); builder.Append('\t'); builder.Append("MinDpi"); builder.Append('\t'); builder.Append("Use In Prefab"); builder.Append('\t'); builder.Append("Use In Scene"); for (var i = 0; i < _textureDpi.Count; i++) _textureDpi[i].WriteToBuilder(builder); var filePath = Application.dataPath + "/_Test/TextureDpi.txt"; File.WriteAllText(filePath, builder.ToString()); Debug.LogWarning("结果输出到文件 " + filePath); } private void ReadFromOneGameObject(GameObject gameObject, string refName, bool isScene) { var meshFilters = gameObject.GetComponentsInChildren(); for (var i = 0; i < meshFilters.Length; i++) { var mesh = meshFilters[i].sharedMesh; if (mesh != null) { var meshRenderer = meshFilters[i].GetComponent(); if (meshRenderer != null) { var materials = meshRenderer.sharedMaterials; for (var j = 0; j < materials.Length; j++) { if (materials[j] == null) Debug.LogError("Material is Null " + refName); else ReadFromOneMaterial(materials[j], mesh, meshRenderer.transform.localToWorldMatrix, refName, isScene); } } } } } private void ReadFromOneMaterial(Material material, Mesh mesh, Matrix4x4 matrix, string refName, bool isScene) { if (material.shader != null) { var count = ShaderUtil.GetPropertyCount(material.shader); for (var i = 0; i < count; i++) if (ShaderUtil.GetPropertyType(material.shader, i) == ShaderUtil.ShaderPropertyType.TexEnv) { var name = ShaderUtil.GetPropertyName(material.shader, i); var texture = material.GetTexture(name) as Texture2D; if (texture != null) { var record = GetTextureDpiRecord(texture); if (record != null) { // 特殊处理无Scale的法线贴图 const string t4MNormalHeader = "_BumpSplat"; const string t4MColorHeader = "_Splat"; var scaleName = name.StartsWith(t4MNormalHeader) ? t4MColorHeader + name.Substring(t4MNormalHeader.Length) : name; var scale = material.GetTextureScale(scaleName); var dpi = GetTextureDpiByMesh(texture, scale, mesh, matrix); record.maxDpi = record.maxDpi == null ? dpi : Mathf.Max(record.maxDpi.Value, dpi); record.minDpi = record.minDpi == null ? dpi : Mathf.Min(record.minDpi.Value, dpi); record.AddRef(refName, isScene); } } } } } private TextureDpi GetTextureDpiRecord(Object texture) { TextureDpi result = null; var assetPath = AssetDatabase.GetAssetPath(texture); if (!string.IsNullOrEmpty(assetPath)) for (var i = 0; i < _textureDpi.Count; i++) if (_textureDpi[i].path.Equals(assetPath, StringComparison.OrdinalIgnoreCase)) { result = _textureDpi[i]; break; } return result; } public static float GetTextureDpiByMesh(Texture2D texture, Vector2 uvScale, Mesh mesh, Matrix4x4 meshMatrix) { var result = 0f; var importError = false; ModelImporter importer = null; if (!mesh.isReadable) { importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(mesh)) as ModelImporter; if (importer == null) { importError = true; } else { importer.isReadable = true; importer.SaveAndReimport(); } } if (importError) { Debug.LogError("模型无法读取,也无法正确获得Importer"); } else { var triangles = mesh.triangles; var dpiArray = new TriangleDpi[triangles.Length / 3]; if (dpiArray.Length > 0) { var verticles = mesh.vertices; var uv = mesh.uv; // 逐一扫描三角形,获得每个三角形的大致Dpi for (var i = 0; i < dpiArray.Length; i++) { var id0 = triangles[i * 3]; var id1 = triangles[i * 3 + 1]; var id2 = triangles[i * 3 + 2]; // 各边长值必然是最极端数值,因此最大Dpi必然是三边最大值 var point0 = meshMatrix.MultiplyPoint3x4(verticles[id0]); var point1 = meshMatrix.MultiplyPoint3x4(verticles[id1]); var point2 = meshMatrix.MultiplyPoint3x4(verticles[id2]); var a = Vector3.Distance(point0, point1); var b = Vector3.Distance(point1, point2); var c = Vector3.Distance(point2, point0); if (a > 0 && b > 0 && c > 0) { var p = (a + b + c) * 0.5f; var size = Mathf.Sqrt(p * (p - a) * (p - b) * (p - c)); var uv0 = uv[id0]; uv0.x *= uvScale.x * texture.width; uv0.y *= uvScale.y * texture.height; var uv1 = uv[id1]; uv1.x *= uvScale.x * texture.width; uv1.y *= uvScale.y * texture.height; var uv2 = uv[id2]; uv2.x *= uvScale.x * texture.width; uv2.y *= uvScale.y * texture.height; var dpi0 = Vector2.Distance(uv0, uv1) / a; var dpi1 = Vector2.Distance(uv1, uv2) / b; var dpi2 = Vector2.Distance(uv2, uv0) / c; dpiArray[i] = new TriangleDpi((double) (dpi0 + dpi1 + dpi2) / 3f, size); } else { dpiArray[i] = new TriangleDpi(0d, 0d); } } } var weight = 0d; for (var i = 0; i < dpiArray.Length; i++) weight += dpiArray[i].size; if (weight > 0f) { var dpi = 0d; for (var i = 0; i < dpiArray.Length; i++) dpi += dpiArray[i].dpi * dpiArray[i].size / weight; result = (float) dpi; } } if (importer != null) { importer.isReadable = false; importer.SaveAndReimport(); } return result; } private struct TriangleDpi { public readonly double dpi; public readonly double size; public TriangleDpi(double dpi, double size) { this.dpi = dpi; this.size = size; } } private class TextureDpi { public readonly string path; private readonly List prefabRef = new List(); private readonly List sceneRef = new List(); public float? maxDpi; public float? minDpi; public TextureDpi(string assetPath) { path = assetPath; } public void AddRef(string refName, bool isScene) { var list = isScene ? sceneRef : prefabRef; if (!list.Contains(refName)) list.Add(refName); } public void WriteToBuilder(StringBuilder builder) { if (builder.Length > 0) builder.Append('\n'); builder.Append(path); builder.Append('\t'); if (maxDpi == null) builder.Append("N/A"); else builder.Append(maxDpi.Value); builder.Append('\t'); if (minDpi == null) builder.Append("N/A"); else builder.Append(minDpi.Value); builder.Append('\t'); if (prefabRef.Count < 1) builder.Append("N/A"); else for (var i = 0; i < prefabRef.Count; i++) { if (i > 0) builder.Append("; "); builder.Append(prefabRef[i]); } builder.Append('\t'); if (sceneRef.Count < 1) builder.Append("N/A"); else for (var i = 0; i < sceneRef.Count; i++) { if (i > 0) builder.Append("; "); builder.Append(sceneRef[i]); } } } }