You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编辑高度图后局部重计算法线的问题及解决(C#/MonoGame)

地形编辑器法线局部更新问题与解决

我使用C#和MonoGame开发了一款地形编辑器。编辑地形后,希望仅重新计算与位置发生变化的三角形相关联的顶点的法线,但遇到了问题:编辑区域周围总会出现一圈法线不匹配的“环”。而重新计算网格的所有法线时,显示就完全正常。

效果对比

  • 仅计算受影响三角形法线的效果:
    仅更新受影响顶点法线的效果

  • 正确效果(通过计算所有顶点法线实现,计算逻辑完全一致):
    正确效果

初始问题代码

最初用于仅重新计算受影响顶点法线的代码如下:

private void RecalculateNormals(Terrain terrain, List<int> uniqueVertexIndex)
{
    List<int[]> totalTriangles = new List<int[]>();
    List<int> verticesToNormalize = new List<int>();
    foreach (int vertexIndex in uniqueVertexIndex)
    {
        Console.WriteLine("Vertex Position Changed: " + vertexIndex);
        foreach (int[] triangle in terrain.Collider.lookupTrianglesAttachedToVertex(vertexIndex))
        {
            bool skip = false;
            foreach (int[] testTriangle in totalTriangles)
                if (triangle[0] == testTriangle[0])
                {
                    skip = true;
                }
            if (!skip)
            {
                totalTriangles.Add(triangle);
                foreach (int index in triangle)
                {
                    if (!verticesToNormalize.Contains(terrain.Mesh.Indices[index]))
                    {
                        verticesToNormalize.Add(terrain.Mesh.Indices[index]);
                    }
                }
            }
        }
    }

    foreach (int vertex in verticesToNormalize)
    {
        terrain.Mesh.Vertices[vertex].Normal = new Vector3(0, 0, 0);
        Console.WriteLine("Vertex Updating: " + vertex);
    }

    foreach (int[] triangle in totalTriangles)
    {
        int index1 = terrain.Mesh.Indices[triangle[0]];
        int index2 = terrain.Mesh.Indices[triangle[1]];
        int index3 = terrain.Mesh.Indices[triangle[2]];
        Vector3 side1 = terrain.Mesh.Vertices[index3].Position - terrain.Mesh.Vertices[index2].Position;
        Vector3 side2 = terrain.Mesh.Vertices[index2].Position - terrain.Mesh.Vertices[index1].Position;
        Vector3 normal = Vector3.Cross(side1, side2);
    
        terrain.Mesh.Vertices[index1].Normal += normal;
        terrain.Mesh.Vertices[index2].Normal += normal;
        terrain.Mesh.Vertices[index3].Normal += normal;

        Console.WriteLine("Recalculated Normals on Triangle: " + triangle[0] + " " + index1 + " | " + triangle[1] + " " + index2 + " | " + triangle[2] + " " + index3);

    }

    foreach (int vertex in verticesToNormalize)
       terrain.Mesh.Vertices[vertex].Normal.Normalize();

}

问题解决与修正代码

经过进一步调试,发现问题核心是未考虑位置未变化但关联三角形受影响的顶点,同时移除对未变化顶点的法线累加操作后,问题已完美解决!修正后的代码如下:

private void RecalculateNormals(Terrain terrain, List<int> uniqueVertexIndex)
{
    List<int[]> totalTriangles = new List<int[]>();
    List<int> verticesToNormalize = new List<int>();
    foreach (int vertexIndex in uniqueVertexIndex)
    {
        //Console.WriteLine("Selected Vertex: {0}", vertexIndex);
        foreach (int[] triangle in terrain.Collider.lookupTrianglesAttachedToVertex(vertexIndex))
        {
            bool skip = false;
            foreach (int[] testTriangle in totalTriangles)
                {
                    if (testTriangle[0] == triangle[0])
                    {
                        skip = true;
                    }
                }
                if (!skip)
                {
                    totalTriangles.Add(triangle);
                }
            foreach (int index in triangle)
            {
                if (!verticesToNormalize.Contains(terrain.Mesh.Indices[index]))
                {
                    //Console.WriteLine("Adding Vertex: " + terrain.Mesh.Indices[index]);
                    verticesToNormalize.Add(terrain.Mesh.Indices[index]);
                } 
            }
        }
    }

    foreach (int vertex in verticesToNormalize)
    {
        //Console.WriteLine("Vertex To Update: {0}", vertex);
        foreach (int[] triangle in terrain.Collider.lookupTrianglesAttachedToVertex(vertex))
        {
            bool skip = false;
            foreach (int[] testTriangle in totalTriangles)
            {
                if (testTriangle[0] == triangle[0])
                    skip = true; //Console.WriteLine("Skipping Triangle: {0} | Compared to Existing Triangle: {1}", triangle[0], testTriangle[0]);
            }

            if (!skip)
            {
                //Console.WriteLine("Not Skipping Triangle {0}", triangle[0]);
                totalTriangles.Add(triangle);
            }
        }
    }

    Dictionary<int,int> NormalUpdateCount = new Dictionary<int, int>();

    foreach (int vertex in verticesToNormalize)
    {
        terrain.Mesh.Vertices[vertex].Normal = new Vector3(0, 0, 0);
        //Console.WriteLine("Vertex Updating: " + vertex);
        NormalUpdateCount.Add(vertex, 0);
    }

    foreach (int[] triangle in totalTriangles)
    {
        
        int index1 = terrain.Mesh.Indices[triangle[0]];
        int index2 = terrain.Mesh.Indices[triangle[1]];
        int index3 = terrain.Mesh.Indices[triangle[2]];
        Vector3 side1 = terrain.Mesh.Vertices[index3].Position - terrain.Mesh.Vertices[index2].Position;
        Vector3 side2 = terrain.Mesh.Vertices[index2].Position - terrain.Mesh.Vertices[index1].Position;
        Vector3 normal = Vector3.Cross(side1, side2);

        // terrain.Mesh.Vertices[index1].Normal += normal;
        // terrain.Mesh.Vertices[index2].Normal += normal;
        // terrain.Mesh.Vertices[index3].Normal += normal;

        if (verticesToNormalize.Contains(index1))
        {
            NormalUpdateCount[index1] += 1;
            terrain.Mesh.Vertices[index1].Normal += normal;
        } 
        if (verticesToNormalize.Contains(index2))
        {
            NormalUpdateCount[index2] += 1;
            terrain.Mesh.Vertices[index2].Normal += normal;
        }
        if (verticesToNormalize.Contains(index3))
        {
            NormalUpdateCount[index3] += 1;
            terrain.Mesh.Vertices[index3].Normal += normal;
        }
    }

    // foreach (KeyValuePair<int, int> kvp in NormalUpdateCount)
    // {
    //     Console.WriteLine("Vertex {0} updated with {1} normal additions", kvp.Key, kvp.Value);
    // }

    foreach (int vertex in verticesToNormalize)
    {
       terrain.Mesh.Vertices[vertex].Normal.Normalize();
    }
}

内容的提问来源于stack exchange,提问作者servvs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 09:31:02