This commit is contained in:
SoulliesOfficial
2026-07-17 17:46:16 -04:00
parent b00ac27e3a
commit 40fa80cd70
1178 changed files with 51090 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
using Unity.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using NBShader;
namespace NBShaderEditor
{
[CustomEditor(typeof(MeshUI))]
[CanEditMultipleObjects]
public class MeshUIInspector : Editor
{
private SerializedProperty _meshProp;
private SerializedProperty _subMeshMaterialsProp;
private SerializedProperty _colorProp;
private SerializedProperty _raycastTargetProp;
private SerializedProperty _maskableProp;
private void OnEnable()
{
_meshProp = serializedObject.FindProperty("mesh");
_subMeshMaterialsProp = serializedObject.FindProperty("subMeshMaterials");
_colorProp = serializedObject.FindProperty("m_Color");
_raycastTargetProp = serializedObject.FindProperty("m_RaycastTarget");
_maskableProp = serializedObject.FindProperty("m_Maskable");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawMeshSection();
EditorGUILayout.Space();
DrawSubMeshMaterials();
EditorGUILayout.Space();
DrawUGUISection();
EditorGUILayout.Space();
DrawMeshInfo();
serializedObject.ApplyModifiedProperties();
}
private void DrawMeshSection()
{
EditorGUILayout.LabelField("Mesh", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_meshProp, new GUIContent("模型"));
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
SyncSlotsToMesh();
serializedObject.Update();
}
DrawBakeMeshSection();
}
private void DrawSubMeshMaterials()
{
Mesh mesh = _meshProp.objectReferenceValue as Mesh;
int targetSize = mesh != null ? mesh.subMeshCount : _subMeshMaterialsProp.arraySize;
EditorGUILayout.LabelField("Mesh Materials", EditorStyles.boldLabel);
if (mesh != null && _subMeshMaterialsProp.arraySize != targetSize)
{
EditorGUILayout.HelpBox($"当前材质槽位数量为 {_subMeshMaterialsProp.arraySize}Mesh 的 SubMesh 数量为 {targetSize}。更换 Mesh 后会自动同步槽位。", MessageType.Warning);
}
for (int i = 0; i < _subMeshMaterialsProp.arraySize; i++)
{
SerializedProperty element = _subMeshMaterialsProp.GetArrayElementAtIndex(i);
string label = $"Sub Mesh {i}";
if (mesh != null && i < mesh.subMeshCount)
{
label += $" ({mesh.GetTopology(i)})";
}
EditorGUILayout.PropertyField(element, new GUIContent(label));
}
}
private void DrawUGUISection()
{
EditorGUILayout.LabelField("UGUI", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_colorProp, new GUIContent("颜色"));
EditorGUILayout.PropertyField(_raycastTargetProp, new GUIContent("射线检测"));
EditorGUILayout.PropertyField(_maskableProp, new GUIContent("支持 Mask"));
}
private void DrawMeshInfo()
{
Mesh mesh = _meshProp.objectReferenceValue as Mesh;
EditorGUILayout.LabelField("信息", EditorStyles.boldLabel);
if (mesh == null)
{
EditorGUILayout.HelpBox("未指定 Mesh。请先指定一个包含三角形拓扑的模型。", MessageType.Warning);
return;
}
int subMeshCount = mesh.subMeshCount;
int triangleSubMeshCount = GetTriangleSubMeshCount(mesh);
int vertexCount = mesh.vertexCount;
int indexCount = 0;
for (int i = 0; i < subMeshCount; i++)
{
if (mesh.GetTopology(i) == MeshTopology.Triangles)
{
indexCount += (int)mesh.GetIndexCount(i);
}
}
string renderMode = GetRenderModeDescription(mesh);
string info =
$"可读性: {(mesh.isReadable ? "" : "")}\n" +
$"顶点数: {vertexCount}\n" +
$"SubMesh 数量: {subMeshCount}\n" +
$"三角形 SubMesh 数量: {triangleSubMeshCount}\n" +
$"三角形索引数: {indexCount}\n" +
$"运行时模式: {renderMode}";
EditorGUILayout.HelpBox(info, MessageType.Info);
if (triangleSubMeshCount == 0)
{
EditorGUILayout.HelpBox("当前 Mesh 不包含可渲染的三角形 SubMesh。", MessageType.Error);
}
else if (triangleSubMeshCount != subMeshCount)
{
EditorGUILayout.HelpBox("当前 Mesh 中存在非三角形拓扑的 SubMesh运行时会生成 Mesh并且只渲染三角形 SubMesh。", MessageType.Warning);
}
}
private string GetRenderModeDescription(Mesh mesh)
{
if (!mesh.isReadable)
{
return "当前 Mesh 不可读,无法渲染";
}
if (GetTriangleSubMeshCount(mesh) != mesh.subMeshCount)
{
return "将生成运行时 Mesh原因存在非三角形 SubMesh";
}
Color color = _colorProp.colorValue;
if (color != Color.white)
{
return "将生成运行时 Mesh原因需要应用 UGUI 颜色)";
}
return "将直接使用原始 Mesh";
}
private void DrawBakeMeshSection()
{
Mesh mesh = _meshProp.objectReferenceValue as Mesh;
if (mesh == null || mesh.isReadable)
{
return;
}
EditorGUILayout.HelpBox("当前 Mesh 没有开启 Read/WriteMeshUI 无法直接通过 CanvasRenderer 渲染。请先烘焙出一个可读的 Mesh。", MessageType.Warning);
if (GUILayout.Button("烘焙只读Mesh"))
{
BakeMeshForTargets(mesh);
}
}
private int GetTriangleSubMeshCount(Mesh mesh)
{
int count = 0;
for (int i = 0; i < mesh.subMeshCount; i++)
{
if (mesh.GetTopology(i) == MeshTopology.Triangles)
{
count++;
}
}
return count;
}
private void BakeMeshForTargets(Mesh sourceMesh)
{
string defaultDirectory = GetDefaultSaveDirectory(sourceMesh);
string defaultName = $"{sourceMesh.name}_MeshUI";
string assetPath = EditorUtility.SaveFilePanelInProject(
"烘焙 MeshUI Mesh",
defaultName,
"asset",
"请选择烘焙后 Mesh 的保存位置。",
defaultDirectory);
if (string.IsNullOrEmpty(assetPath))
{
return;
}
Mesh bakedMesh = CreateReadableMeshCopy(sourceMesh, System.IO.Path.GetFileNameWithoutExtension(assetPath));
if (bakedMesh == null)
{
return;
}
string uniqueAssetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
AssetDatabase.CreateAsset(bakedMesh, uniqueAssetPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Mesh bakedAsset = AssetDatabase.LoadAssetAtPath<Mesh>(uniqueAssetPath);
if (bakedAsset == null)
{
Debug.LogError($"无法加载已烘焙的 Mesh 资源: '{uniqueAssetPath}'");
return;
}
Undo.RecordObjects(targets, "烘焙 MeshUI Mesh");
foreach (Object editorTarget in targets)
{
MeshUI meshUI = editorTarget as MeshUI;
if (meshUI == null)
{
continue;
}
meshUI.Mesh = bakedAsset;
EditorUtility.SetDirty(meshUI);
PrefabUtility.RecordPrefabInstancePropertyModifications(meshUI);
}
serializedObject.Update();
SyncSlotsToMesh();
}
private string GetDefaultSaveDirectory(Mesh sourceMesh)
{
string sourcePath = AssetDatabase.GetAssetPath(sourceMesh);
if (string.IsNullOrEmpty(sourcePath))
{
return "Assets";
}
string directory = System.IO.Path.GetDirectoryName(sourcePath);
if (string.IsNullOrEmpty(directory))
{
return "Assets";
}
return directory.Replace('\\', '/');
}
private static Mesh CreateReadableMeshCopy(Mesh sourceMesh, string newMeshName)
{
using (Mesh.MeshDataArray meshDataArray = MeshUtility.AcquireReadOnlyMeshData(sourceMesh))
{
if (meshDataArray.Length == 0)
{
Debug.LogError($"无法读取 Mesh 数据: {sourceMesh.name}");
return null;
}
Mesh.MeshData meshData = meshDataArray[0];
Mesh bakedMesh = new Mesh
{
name = newMeshName,
indexFormat = meshData.indexFormat
};
int vertexCount = meshData.vertexCount;
if (vertexCount <= 0)
{
Debug.LogError($"Mesh '{sourceMesh.name}' 不包含任何顶点。");
Object.DestroyImmediate(bakedMesh);
return null;
}
using (NativeArray<Vector3> vertices = new NativeArray<Vector3>(vertexCount, Allocator.Temp))
{
meshData.GetVertices(vertices);
bakedMesh.SetVertices(vertices);
}
if (meshData.HasVertexAttribute(VertexAttribute.Normal))
{
using (NativeArray<Vector3> normals = new NativeArray<Vector3>(vertexCount, Allocator.Temp))
{
meshData.GetNormals(normals);
bakedMesh.SetNormals(normals);
}
}
if (meshData.HasVertexAttribute(VertexAttribute.Tangent))
{
using (NativeArray<Vector4> tangents = new NativeArray<Vector4>(vertexCount, Allocator.Temp))
{
meshData.GetTangents(tangents);
bakedMesh.SetTangents(tangents);
}
}
if (meshData.HasVertexAttribute(VertexAttribute.TexCoord0))
{
using (NativeArray<Vector2> uv0 = new NativeArray<Vector2>(vertexCount, Allocator.Temp))
{
meshData.GetUVs(0, uv0);
bakedMesh.SetUVs(0, uv0);
}
}
int subMeshCount = sourceMesh.subMeshCount;
bakedMesh.subMeshCount = subMeshCount;
bool hasTriangleSubMesh = false;
for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++)
{
SubMeshDescriptor subMesh = meshData.GetSubMesh(subMeshIndex);
if (subMesh.topology == MeshTopology.Triangles && subMesh.indexCount > 0)
{
hasTriangleSubMesh = true;
if (meshData.indexFormat == IndexFormat.UInt16)
{
using (NativeArray<ushort> indices = new NativeArray<ushort>((int)subMesh.indexCount, Allocator.Temp))
{
meshData.GetIndices(indices, subMeshIndex, true);
bakedMesh.SetIndices(indices, MeshTopology.Triangles, subMeshIndex, false);
}
}
else
{
using (NativeArray<int> indices = new NativeArray<int>((int)subMesh.indexCount, Allocator.Temp))
{
meshData.GetIndices(indices, subMeshIndex, true);
bakedMesh.SetIndices(indices, MeshTopology.Triangles, subMeshIndex, false);
}
}
}
else
{
bakedMesh.SetIndices(System.Array.Empty<int>(), MeshTopology.Triangles, subMeshIndex, false);
}
}
if (!hasTriangleSubMesh)
{
Debug.LogError($"Mesh '{sourceMesh.name}' 不包含可烘焙的三角形 SubMesh。");
Object.DestroyImmediate(bakedMesh);
return null;
}
bakedMesh.RecalculateBounds();
return bakedMesh;
}
}
private void SyncSlotsToMesh()
{
foreach (Object editorTarget in targets)
{
MeshUI meshUI = editorTarget as MeshUI;
if (meshUI == null)
{
continue;
}
SerializedObject targetObject = new SerializedObject(meshUI);
SerializedProperty meshProp = targetObject.FindProperty("mesh");
SerializedProperty materialsProp = targetObject.FindProperty("subMeshMaterials");
Mesh mesh = meshProp.objectReferenceValue as Mesh;
int targetSize = mesh != null ? mesh.subMeshCount : 0;
materialsProp.arraySize = Mathf.Max(0, targetSize);
targetObject.ApplyModifiedProperties();
EditorUtility.SetDirty(meshUI);
PrefabUtility.RecordPrefabInstancePropertyModifications(meshUI);
}
}
}
}