文章

翻转法线遇到的SubMesh的坑

翻转法线遇到的SubMesh的坑

翻转法线遇到的SubMesh的坑

00 前言

翻转法线时导致SubMesh丢失.

01 过程

首先, 通常3D美术认为的翻转法线, 即连法线和渲染面一并翻转. 但实际上在Unity中是两个操作. 不仅要翻转法线, 同时也要”翻转”模型”triangles”的构建方式, 才能达到DCC软件中的翻转法线的效果.

翻转”triangles”的方式很简单, 即把三角面的构建方式从”顺时针变为逆时针/逆时针变为顺时针”. 假设”1->2->3”这样渲染, 那么如果要翻转, 只需要将第一个和第三个进行对调即可, 按照”3->2->1”这样渲染, 即完成了”triangles”的翻转.

于是第一版函数为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//翻转法线, 这一步没有问题, 正常操作.
private void FlipNormals(Mesh mesh)
{
    var normalsHash = mesh.normals;
    for (int i = 0; i < normalsHash.Length; i++)
    {
        normalsHash[i] = -normalsHash[i];
    }
    mesh.normals = normalsHash;
}
//翻转Triangles的构建方式
private void FlipTriangles(Mesh mesh)
{
    var trianglesHash = mesh.triangles;
    for (int i = 0; i < trianglesHash.Length; i++)
    {
        (trianglesHash[i], trianglesHash[i + 2]) = (trianglesHash[i + 2], trianglesHash[i]);
    }
    mesh.triangles = trianglesHash;
}

但这样翻转之后, 如果模型并没有SubMesh的情况, 那么一切正常, 而有SubMesh的模型, 会导致SubMesh失效, 整个模型只能接受一个材质球. 显然目前的方法有问题.

查阅关于Mesh.trianglesAPI的信息, 会发现, 如果直接对其赋值, 则会导致SubMesh被统一设置为1. 同时建议使用SetTriangles这个API.

Unity - Scripting API: Mesh.triangles (unity3d.com)

image-20240321104325057

02 处理方法

按照SubMesh分割triangles数组, 将每个数组翻转后, 再使用SetTriangles赋值回去. 修改FlipTriangles函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 private void FlipTriangles(Mesh mesh)
{
    var trianglesHash = mesh.triangles;
    int offset = 0;
    for (int i = 0; i < mesh.subMeshCount; i++)
    {
        //数组按照offset的值切片
        var trianglesSubHash = new ArraySegment<int>(trianglesHash,offset,mesh.GetSubMesh(i).indexCount).ToArray();
        //offset的值会根据SubMesh占用, 进行增加.
        offset += mesh.GetSubMesh(i).indexCount;
        //正常的翻转triangles的操作
        for (int j = 0; j < mesh.GetSubMesh(i).indexCount; j += 3)
        {
            (trianglesSubHash[j], trianglesSubHash[j + 2]) = (trianglesSubHash[j + 2], trianglesSubHash[j]);
        }
        //分别设置每个SubMesh的triangles
        mesh.SetTriangles(trianglesSubHash, i);
    }
}

此时, 目前解决了翻墙模型的需求.

03 附录

默认情况下, OpenGL会认为逆时针的构建是正面, 顺时针的构建是背面.

Image of viewer seeing front or back facing triangles

当然, 也可以通过命令来让顺时针的构建变为正面, 比如

1
2
3
glEnable(GL_CULL_FACE);//激活面剔除
glCullFace(GL_BACK);//指定剔除背面
glFrontFace(GL_CW);//指定"顺时针"的是正面, 如下图CW为顺时针, CCW为逆时针

Winding order of OpenGL triangles

结果就和”翻转法线”效果相同.

Image of faceculling with clockwise ordering, only culling the front faces

参考网页

Unity 法线翻转_unity法线翻转 - 分享有价值的内容

Unity描边法线平滑工具 x踩坑记录 - 知乎 (zhihu.com)

如何在Unity里翻转网格法线 - 技术专栏 - Unity官方开发者社区

Unity问题(1)——mesh法线反转_unity 翻转mesh-CSDN博客

本文由作者按照 CC BY 4.0 进行授权