将 .fbx 文件导入 Unity 后,偶尔会发现模型正面是透明的,背面却能看到,这是由于模型网格中的三角形顶点的排列顺序相反造成的,解决此问题可能到 Maya、3DMax 等 3D 建模软件中将模型中有问题的部分进行翻转法线操作,也可以在 Unity 中将三角形顶点的排列顺序进行反转,避免使用 3D 建模软件重新导出又导入到 Unity 的麻烦,如下代码:
SkinnedMeshRenderer skinnedMeshRenderer = gameObject.GetComponent<SkinnedMeshRenderer>();
Mesh mesh = skinnedMeshRenderer.sharedMesh;
int[] triangles = mesh.triangles;
for (int i = 0, len = triangles.Length; i < len; i += 3) {
int t = triangles[i];
triangles[i] = triangles[i + 2];
triangles[i + 2] = t;
}
mesh.triangles = triangles;
为了方便可以在 Hierarchy 面板中给 GameObject 的快捷菜单添加 Flip Mesh Normals 选项,快速对拥有网格的对象进行法线翻转操作。以下代码放置在名称 Editor 的文件夹下
public class EditorFlipMeshNormals : Editor {
[MenuItem("GameObject/Flip Mesh Normals", false, 11)]
private static void FlipMeshNormalsOnGameObject () {
}
}
为了不修改 Unity 内置对象的共享网格(如 Cube、Sphere等),还可以设置是否启动 Flip Mesh Normals 快捷菜单项,此处我们只想处理我们自己的资源,只需要判断共享网格的资源路径是否在 Assets文件下就行了(内置对象的资源路径都在项目根目录的 Library 文件夹下)。
[MenuItem("GameObject/Flip Mesh Normals", true)]
private static bool ValidateFlipMeshNormalsOnGameObject () {
string path = AssetDatabase.GetAssetPath(mesh);
bool isAssetFolder = path.IndexOf("Assets/") > -1;
if (isAssetFolder) {
return true;
}
return false;
}
最后完整的代码如下(须放置在名为 Editor 的文件夹下):
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
public class EditorFlipMeshNormals : Editor {
private static void FlipMeshNormals (Mesh mesh) {
int[] triangles = mesh.triangles;
for (int i = 0, len = triangles.Length; i < len; i += 3) {
int t = triangles[i];
triangles[i] = triangles[i + 2];
triangles[i + 2] = t;
}
mesh.triangles = triangles;
}
private static void FlipMeshNormals (GameObject[] gameObjects) {
for (int i = 0, len = gameObjects.Length; i < len; i++) {
GameObject go = gameObjects[i];
Mesh mesh = null;
SkinnedMeshRenderer skinnedMeshRenderer = go.GetComponent<SkinnedMeshRenderer>();
if (skinnedMeshRenderer) {
mesh = skinnedMeshRenderer.sharedMesh;
} else {
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
if (meshFilter) {
mesh = meshFilter.sharedMesh;
}
}
if (mesh) {
string path = AssetDatabase.GetAssetPath(mesh);
bool isAssetFolder = path.IndexOf("Assets/") > -1;
if (isAssetFolder) {
FlipMeshNormals(mesh);
}
}
}
}
[MenuItem("GameObject/Flip Mesh Normals", true)]
private static bool ValidateFlipMeshNormalsOnGameObject () {
bool isEnableMenuItem = false;
GameObject[] gameObjects = Selection.gameObjects;
for (int i = 0, len = gameObjects.Length; i < len; i++) {
GameObject go = gameObjects[i];
Mesh mesh = null;
SkinnedMeshRenderer skinnedMeshRenderer = go.GetComponent<SkinnedMeshRenderer>();
if (skinnedMeshRenderer) {
mesh = skinnedMeshRenderer.sharedMesh;
} else {
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
if (meshFilter) {
mesh = meshFilter.sharedMesh;
}
}
if (mesh) {
string path = AssetDatabase.GetAssetPath(mesh);
bool isAssetFolder = path.IndexOf("Assets/") > -1;
if (isAssetFolder) {
isEnableMenuItem = true;
break;
}
}
}
return isEnableMenuItem;
}
[MenuItem("GameObject/Flip Mesh Normals", false, 11)]
private static void FlipMeshNormalsOnGameObject () {
FlipMeshNormals(Selection.gameObjects);
}
}
#endif