Files
JJBB/Assets/Editor/Scripts/GetTextureDpi.cs
2024-08-23 15:49:34 +08:00

386 lines
13 KiB
C#
Raw Permalink 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;
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]);
}
}
}
}