计算具有 100% 平滑度的平滑顶点法线的一般方法
General method for calculating Smooth vertex normals with 100% smoothness
我在网上看了很长时间,找不到这个问题的答案。
为简单起见,我们假设我需要完全平滑一组相连面的法线。 我想找到一组向量之间的实际几何平分线,忽略重复法线并保持三角形汤的准确性。基本上,它需要:
- 使用三角形汤 - 无论是三个、4 个还是 4000 个三角形,法线仍然需要以几何正确的方式一起工作,而不偏向任意区域
- 忽略重叠(平行)法线 - 如果我有一个立方体的边缘在 3 个三角形中聚集在一起,或者一个在两个边中的每一个都聚集在一个三角形中,最后一个边有 2 个(或更多)三角形,或者一个只有一侧有一百万个三角形,平分线不能改变
我似乎找到的最常见的法线平滑公式是,通过将法线向量相加并除以三来简单地对它们进行平均;示例:
normalize((A + B + C) / 3);
当然,除以三是没有用的,这已经表明人们提出的天真,不切实际的暴力平均法的氛围;问题还在于,即使是三角形汤和平行法线,它也会把时间搞得一团糟。
我似乎发现的另一个评论是保留初始 "facet" 法线,因为它们来自生成它们的常见交叉乘法运算,因为这样它们(有点)针对三角形的面积加权。在某些情况下这可能是您想做的事情,但是我需要纯平分线,因此面积不能影响公式,即使考虑到它仍然会被三角形汤弄乱。
我看到了一种提到的方法,它说要根据相邻面之间的角度进行加权,但我似乎无法正确地实施该公式 - 无论是那个还是没有按照我想要的方式进行。但这对我来说很难说,因为我找不到一个简明的解释,而且我的头脑也因所有这些浪费的头脑风暴而变得麻木。
有人知道通用公式吗?
如果有任何帮助,我正在使用 C++ 和 DirectX11。
编辑:这里有一些类似的问题描述了一些方法;
- How to achieve smooth tangent space normals?
- Calculating Vertex Normals of a mesh
- Calculating normals in a triangle mesh
- How does Blender calculate vertex normals?
- Most efficient algorithm to calculate vertex normals from set of triangles for Gouraud shading
还有这篇文章:
http://www.bytehazard.com/articles/vertnorm.html
不幸的是,我尝试的实施没有奏效,而且我找不到关于哪个公式实际上是我需要的公式的清晰、简明的陈述。经过反复试验,我终于发现按角度加权是正确的方法,只是我没能正确实施;因为它现在似乎可以正常工作,所以我将在下面添加我的实现作为答案。
正确的方法是将 "facet" 法线与 edge/corner 中共享的两个相邻顶点 之间的 角度进行加权。
(这里显示了相对于每个面的角度)
下面是示例实现的概要:
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);
}
这是一个 "naive" 法线平均值的示例(即没有角度加权);
你可以看到小平面组件都是一样的,但由于有些边有两个面,它们的插值部分加倍,使得平均值出现偏差。仅对表面积而不是角度进行加权会产生类似的结果。
这是同一个模型,但启用了角度加权;
现在插值法线在几何上都是正确的。
我在网上看了很长时间,找不到这个问题的答案。 为简单起见,我们假设我需要完全平滑一组相连面的法线。 我想找到一组向量之间的实际几何平分线,忽略重复法线并保持三角形汤的准确性。基本上,它需要:
- 使用三角形汤 - 无论是三个、4 个还是 4000 个三角形,法线仍然需要以几何正确的方式一起工作,而不偏向任意区域
- 忽略重叠(平行)法线 - 如果我有一个立方体的边缘在 3 个三角形中聚集在一起,或者一个在两个边中的每一个都聚集在一个三角形中,最后一个边有 2 个(或更多)三角形,或者一个只有一侧有一百万个三角形,平分线不能改变
我似乎找到的最常见的法线平滑公式是,通过将法线向量相加并除以三来简单地对它们进行平均;示例:
normalize((A + B + C) / 3);
当然,除以三是没有用的,这已经表明人们提出的天真,不切实际的暴力平均法的氛围;问题还在于,即使是三角形汤和平行法线,它也会把时间搞得一团糟。
我似乎发现的另一个评论是保留初始 "facet" 法线,因为它们来自生成它们的常见交叉乘法运算,因为这样它们(有点)针对三角形的面积加权。在某些情况下这可能是您想做的事情,但是我需要纯平分线,因此面积不能影响公式,即使考虑到它仍然会被三角形汤弄乱。
我看到了一种提到的方法,它说要根据相邻面之间的角度进行加权,但我似乎无法正确地实施该公式 - 无论是那个还是没有按照我想要的方式进行。但这对我来说很难说,因为我找不到一个简明的解释,而且我的头脑也因所有这些浪费的头脑风暴而变得麻木。
有人知道通用公式吗? 如果有任何帮助,我正在使用 C++ 和 DirectX11。
编辑:这里有一些类似的问题描述了一些方法;
- How to achieve smooth tangent space normals?
- Calculating Vertex Normals of a mesh
- Calculating normals in a triangle mesh
- How does Blender calculate vertex normals?
- Most efficient algorithm to calculate vertex normals from set of triangles for Gouraud shading
还有这篇文章: http://www.bytehazard.com/articles/vertnorm.html
不幸的是,我尝试的实施没有奏效,而且我找不到关于哪个公式实际上是我需要的公式的清晰、简明的陈述。经过反复试验,我终于发现按角度加权是正确的方法,只是我没能正确实施;因为它现在似乎可以正常工作,所以我将在下面添加我的实现作为答案。
正确的方法是将 "facet" 法线与 edge/corner 中共享的两个相邻顶点 之间的 角度进行加权。
(这里显示了相对于每个面的角度)
下面是示例实现的概要:
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);
}
这是一个 "naive" 法线平均值的示例(即没有角度加权);
你可以看到小平面组件都是一样的,但由于有些边有两个面,它们的插值部分加倍,使得平均值出现偏差。仅对表面积而不是角度进行加权会产生类似的结果。
这是同一个模型,但启用了角度加权;
现在插值法线在几何上都是正确的。