计算平滑度为 100% 的平滑顶点法线的一般方法

Ban*_*eri 9 algorithm 3d vertices normals

我在网上看了很长时间,找不到这个问题的答案。为简单起见,让我们假设我需要完全平滑一组连接面的法线。我想找到一组向量之间的实际几何平分线,忽略重复的法线并保持三角形汤的准确性。基本上,它需要:

  • 使用三角形汤 - 无论是三个、4 个还是 4000 个三角形,法线仍然需要以几何正确的方式协同工作,而不会偏向于任意区域
  • 忽略重叠(平行)法线 - 如果我有一个立方体的边,它聚集在 3 个三角形中,或者一个三角形的两个边中的每一边都聚集在一个三角形中,最后一侧有 2 个(或更多)个三角形,或者一个有 100 万个三角形只有一侧的三角形,平分线不能改变

我似乎找到的最常见的法线平滑公式是,通过将法线向量相加并除以三来简单地平均它们;例子:

normalize((A + B + C) / 3);
Run Code Online (Sandbox Code Playgroud)

当然除以三是没有用的,这已经代表了人们提出的天真、自以为是的蛮力平均法的氛围;与此有关的问题还在于,即使是三角形汤和平行法线,它也会把时间弄得一团糟。

我似乎发现的另一个评论是保留初始“面”法线,因为它们来自生成它们的常见交叉乘法运算,因为这样它们(有点)相对于三角形的面积加权。在某些情况下,这可能是您想要做的事情,但是我需要纯平分线,因此面积不能影响公式,即使考虑到它,它仍然会被三角汤弄乱。

我看到了一个提到的方法,它说对相邻面之间的角度进行加权,但我似乎无法正确实施该公式 - 要么是那样,要么不是我想要的。然而,这对我来说很难说,因为我找不到一个简洁的解释,而且我的头脑因所有这些浪费的头脑风暴而变得麻木。

有人知道通用公式吗?如果有任何帮助,我正在使用 C++ 和 DirectX11。


编辑:这里有一些类似的问题,描述了一些方法;

还有这篇文章:http : //www.bytehazard.com/articles/vertnorm.html

不幸的是,我尝试的实现不起作用,我找不到关于哪个公式实际上是我需要的一个清晰、简洁的声明。经过反复试验,我终于发现按角度加权是正确的方法,只是我无法正确实现它;因为它现在似乎正在工作,所以我将在下面添加我的实现作为答案。

Ban*_*eri 7

正确的方法是根据边/角中共享的两个相邻顶点之间的角度对“面”法线进行加权。

(此处显示了相对于每个面的角度) 公共点的每个面角

下面是一个示例实现的概要:

for (int f = 0; f < tricount; f++)
{
    // ...
    // p1, p2 and p3 are the points in the face (f)

    // calculate facet normal of the triangle using cross product;
    // both components are "normalized" against a common point chosen as the base
    float3 n = (p2 - p1).Cross(p3 - p1);    // p1 is the 'base' here

    // get the angle between the two other points for each point;
    // the starting point will be the 'base' and the two adjacent points will be normalized against it
    a1 = (p2 - p1).Angle(p3 - p1);    // p1 is the 'base' here
    a2 = (p3 - p2).Angle(p1 - p2);    // p2 is the 'base' here
    a3 = (p1 - p3).Angle(p2 - p3);    // p3 is the 'base' here

    // normalize the initial facet normals if you want to ignore surface area
    if (!area_weighting)
    {
        normalize(n);
    }

    // store the weighted normal in an structured array
    v1.wnormals.push_back(n * a1);
    v2.wnormals.push_back(n * a2);
    v3.wnormals.push_back(n * a3);
}
for (int v = 0; v < vertcount; v++)
{
    float3 N;

    // run through the normals in each vertex's array and interpolate them
    // vertex(v) here fetches the data of the vertex at index 'v'
    for (int n = 0; n < vertex(v).wnormals.size(); v++)
    {
        N += vertex(v).wnormals.at(n);
    }

    // normalize the final normal
    normalize(N);
}
Run Code Online (Sandbox Code Playgroud)

这是法线的“天真”平均值示例(即没有角度加权);

在此处输入图片说明

您可以看到小平面组件完全相同,但由于某些侧面有两个面,因此它们的插值部分加倍,从而使平均值偏斜。仅针对表面积而不是角度进行加权会产生类似的结果。

这是同一个模型,但启用了角度加权;

在此处输入图片说明

现在插值法线在几何上都是正确的。