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