将 .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
(
)
{
//Mesh mesh = ...
string
path
=
AssetDatabase
.
GetAssetPath
(
mesh
)
;
// 是否为 Assets 文件夹下的资源
bool
isAssetFolder
=
path
.
IndexOf
(
"Assets/"
)
>
-
1
;
if
(
isAssetFolder
)
{
return
true
;
// 在 Assets 文件夹下,启用 Flip Mesh Normals 菜单项
}
return
false
;
}
最后完整的代码如下(须放置在名为 Editor 的文件夹下):
#
if
UNITY_EDITOR
using
UnityEditor
;
using
UnityEngine
;
/// <summary>
/// 在 Hierarchy 视图对象的快捷菜单中增加 Flip Mesh Normals(反转网格法线)项
/// </summary>
public
class
EditorFlipMeshNormals
:
Editor
{
/// <summary> 翻转网格的法线 </summary>
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
;
}
/// <summary> 翻转多个游戏对象网格的法线 </summary>
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
)
;
// 是否为 Assets 文件夹下的资源(Assets 文件夹下的资源才能编辑,避免编辑到 Unity 的内置资源的网格)
bool
isAssetFolder
=
path
.
IndexOf
(
"Assets/"
)
>
-
1
;
if
(
isAssetFolder
)
{
FlipMeshNormals
(
mesh
)
;
}
}
}
}
/// <summary> 验证所选择的游戏对象有网格时菜单才可用(不计算子级) </summary>
[
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
)
;
// 是否为 Assets 文件夹下的资源(Assets 文件夹下的资源才能编辑,避免编辑到 Unity 的内置资源的网格)
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
效果如下图: