导入到 Unity 的模型在未设置其 Read/Write
属性时无法在脚本中获取模型的相关信息:
此时若想在脚本中获取网格的顶点相关的信息则会报错:
public class MeshTest : MonoBehaviour
{
void Start()
{
MeshFilter filter = GetComponent<MeshFilter>();
if (filter != null)
{
Mesh mesh = filter.sharedMesh;
Vector3[] verticies = mesh.vertices;
Debug.Log(verticies.Length);
}
}
}
首先,回到代码中,Mesh 类的 vertices
属性具体实现如下:
namespace UnityEngine
{
public sealed partial class Mesh : Object
{
[FreeFunction(Name = "AllocExtractMeshComponentFromScript", HasExplicitThis = true)]
extern private System.Array GetAllocArrayFromChannelImpl(VertexAttribute channel, VertexAttributeFormat format, int dim);
private T[] GetAllocArrayFromChannel<T>(VertexAttribute channel, VertexAttributeFormat format, int dim)
{
if (canAccess)
{
if (HasVertexAttribute(channel))
return (T[])GetAllocArrayFromChannelImpl(channel, format, dim);
}
else
{
PrintErrorCantAccessChannel(channel);
}
return new T[0];
}
private T[] GetAllocArrayFromChannel<T>(VertexAttribute channel)
{
return GetAllocArrayFromChannel<T>(channel, VertexAttributeFormat.Float32, DefaultDimensionForChannel(channel));
}
public Vector3[] vertices
{
get { return GetAllocArrayFromChannel<Vector3>(VertexAttribute.Position); }
set { SetArrayForChannel(VertexAttribute.Position, value, UnityEngine.Rendering.MeshUpdateFlags.Default); }
}
}
}
一层层查看,在 GetAllocArrayFromChannel
方法中,首先检查是否可访问,这里的 canAccess
属性获取的就是上面 Read/Write
属性设置的值,最后通过 GetAllocArrayFromChannelImpl
方法获取。
但是,由于权限问题无法直接调用 GetAllocArrayFromChannelImpl
方法来跳过检查,这里可以使用反射来访问 private
成员,以下是为 Mesh
类添加的扩展方法:
// MeshExtensions.cs
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Rendering;
public static class MeshExtensions
{
// 使用反射方式调用 GetAllocArrayFromChannel 方法
public static Array GetAllocArrayFromChannelImpl(this Mesh mesh, VertexAttribute channel, VertexAttributeFormat format, int dim)
{
MethodInfo method = typeof(Mesh).GetMethod("GetAllocArrayFromChannelImpl", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = { channel, format, dim };
return (Array)method.Invoke(mesh, parameters);
}
//
static int DefaultDimensionForChannel(VertexAttribute channel)
{
if (channel == VertexAttribute.Position || channel == VertexAttribute.Normal)
return 3;
else if (channel >= VertexAttribute.TexCoord0 && channel <= VertexAttribute.TexCoord7)
return 2;
else if (channel == VertexAttribute.Tangent || channel == VertexAttribute.Color)
return 4;
throw new ArgumentException("DefaultDimensionForChannel called for bad channel", "channel");
}
public static T[] GetAllocArrayFromChannel<T>(this Mesh mesh, VertexAttribute channel, VertexAttributeFormat format, int dim)
{
// 禁用原有代码中的访问检查
// if (canAccess)
{
if (mesh.HasVertexAttribute(channel))
return (T[])GetAllocArrayFromChannelImpl(mesh, channel, format, dim);
}
// else
// {
// PrintErrorCantAccessChannel(channel);
// }
return new T[0];
}
public static T[] GetAllocArrayFromChannel<T>(this Mesh mesh, VertexAttribute channel)
{
return GetAllocArrayFromChannel<T>(mesh, channel, VertexAttributeFormat.Float32, DefaultDimensionForChannel(channel));
}
public static Vector3[] get_vertices(this Mesh mesh)
{
return GetAllocArrayFromChannel<Vector3>(mesh, VertexAttribute.Position);
}
public static Vector3[] get_normals(this Mesh mesh)
{
return GetAllocArrayFromChannel<Vector3>(mesh, VertexAttribute.Normal);
}
public static Vector2[] get_uv(this Mesh mesh)
{
return GetAllocArrayFromChannel<Vector2>(mesh, VertexAttribute.TexCoord0);
}
}
在脚本中使用扩展方法:
public class MeshTest : MonoBehaviour
{
void Start()
{
MeshFilter filter = GetComponent<MeshFilter>();
if (filter != null)
{
// 内置方法
Mesh mesh = filter.sharedMesh;
Vector3[] verticies = mesh.vertices;
Debug.Log(verticies.Length);
// 使用扩展方法获取顶点数据
verticies = mesh.get_vertices();
Debug.Log(verticies.Length);
Debug.Log(verticies);
Debug.Log(verticies[0]);
}
}
}
控制台输出如下,可以看到能够正常获取到顶点信息: