Files
JJBB/Assets/Editor/Scripts/GetTextureDpi.cs

386 lines
13 KiB
C#
Raw Normal View History

2024-08-23 15:49:34 +08:00
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;
/// <summary>
/// 用于获得材质像素密度的方法 - 返回材质在一个MeshRenderer或者SkinnedMeshRenderer上大致像素/单位长度
/// </summary>
public class GetTextureDpi
{
private readonly List<string> _prefabPaths = new List<string>();
private readonly List<string> _scenePaths = new List<string>();
private readonly List<TextureDpi> _textureDpi = new List<TextureDpi>();
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<GameObject>(_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<MeshFilter>();
for (var i = 0; i < meshFilters.Length; i++)
{
var mesh = meshFilters[i].sharedMesh;
if (mesh != null)
{
var meshRenderer = meshFilters[i].GetComponent<MeshRenderer>();
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<string> prefabRef = new List<string>();
private readonly List<string> sceneRef = new List<string>();
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]);
}
}
}
}