网格颜色混乱可能是由于错误的顶点法线计算
Mesh color is messed up probably due to bad vertex normal computation
我有顶点 position 和 index 我想要顶点 normal:
// input
vector<Vec3f> points = ... // position
vector<Vec3i> facets = ... // index (triangles)
// output
vector<Vec3f> norms; // normal
方法一
我这样计算法线:
norms.resize(points.size()); // for each vertex there is a normal
for (Vec3i f : facets) {
int i0 = f.x();
int i1 = f.y(); // index
int i2 = f.z();
Vec3d pos0 = points.at(i0);
Vec3d pos1 = points.at(i1); // position
Vec3d pos2 = points.at(i2);
Vec3d N = triangleNormal(pos0, pos1, pos2); // face/triangle normal
norms[i0] = N;
norms[i1] = N; // Use the same normal for all 3 vertices
norms[i2] = N;
}
然后,使用 Phong material:
渲染输出网格
方法一反转法线
当我在方法1中反转法线方向时:
norms[i0] = -N;
norms[i1] = -N;
norms[i2] = -N;
暗区和亮区交换:
通过以下方式将位置 0 与位置 1 交换也会发生同样的情况:
// Vec3d N = triangleNormal(pos0, pos1, pos2);
Vec3d N = triangleNormal(pos1, pos0, pos2); // Swap pos0 with pos1
方法二
我通过 this method 计算法线:
// Count how many faces/triangles a vertex is shared by
vector<int> counters;
counters.resize(points.size());
norms.resize(points.size());
for (Vec3i f : facets) {
int i0 = f.x();
int i1 = f.y(); // index
int i2 = f.z();
Vec3d pos0 = points.at(i0);
Vec3d pos1 = points.at(i1); // position
Vec3d pos2 = points.at(i2);
Vec3d N = triangleNormal(pos0, pos1, pos2);
// Must be normalized
//
N.normalize();
norms[i0] += N;
norms[i1] += N; // add normal to all vertices used in face
norms[i2] += N;
counters[i0]++;
counters[i1]++; // increment count for all vertices used in face
counters[i2]++;
}
//
for (int i = 0; i < static_cast<int>(norms.size()); ++i) {
if (counters[i] > 0)
norms[i] /= counters[i];
else
norms[i].normalize();
}
此方法通过 Phong material:
产生完全黑暗的最终渲染
我也尝试了建议的方法 and ,这与方法 2 类似。它们都会导致最终渲染看起来像方法 2 的渲染,即没有任何亮区的所有黑暗区域。
方法二反转法线
我用的是方法二,但是最后我把法线方向反转了:
for (Vec3d & n : norms) {
n = -n;
}
令我惊讶的是,最终渲染图全黑了:
同样在方法 2 中,我尝试将位置 0 与位置 1 交换:
// Vec3d N = triangleNormal(pos0, pos1, pos2);
Vec3d N = triangleNormal(pos1, pos0, pos2); // swap pos0 with pos1
最终渲染图是全暗区,没有任何亮区。
如何?
知道如何让我的最终渲染全亮而没有任何暗区吗?
看来你的网格没有一致的缠绕规则。因此,一些 triangles/faces 是按顶点的 CCW 顺序定义的 CW 其他,导致您的某些法线朝向相反的方向。您可以采取一些措施来补救:
使用双面法线光照
这是最简单的...片段中的某处或您计算阴影的任何地方,如下所示:
out_color = face_color*(ambient_light+diffuse_light*max(0.0,dot(face_normal,light_direction)));
当法线方向错误时,dot
的结果为负,导致颜色变深,因此只需使用 abs
值代替:
out_color = face_color*(ambient_light+diffuse_light*abs(dot(face_normal,light_direction)));
在固定功能管道中甚至有此 IIRC 的开关:
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
修复网眼缠绕
必须有 3D 工具来执行此操作(Blender、3DS 等),或者如果您的网格是动态生成的,您可以更新代码以自行创建一致的缠绕。
正确的缠绕使您能够使用 GL_CULL_FACE
来显着加快渲染速度。它还支持更高级的功能,例如:
修复法线
在某些情况下,有一些方法可以检测法线是向外还是向内指向网格,例如:
所以只要在计算法线的过程中否定错误的就可以了。但是,如果您的网格太复杂(离凸面太远),这不是那么容易完成,因为您需要使用网格的局部“中心”,甚至是昂贵的内部多边形测试。
生成法线的平均方法为您提供法线两个方向的深色,这意味着您错误地计算了它们并且它们很可能为零。有关此类方法的更多信息,请参阅:
- How to achieve smooth tangent space normals?
无论如何调试这样的问题最好将法线渲染为从网格顶点出发的线(使用线框)。然后你会直接看到什么法线是好的和坏的。这里的例子:
我有顶点 position 和 index 我想要顶点 normal:
// input
vector<Vec3f> points = ... // position
vector<Vec3i> facets = ... // index (triangles)
// output
vector<Vec3f> norms; // normal
方法一
我这样计算法线:
norms.resize(points.size()); // for each vertex there is a normal
for (Vec3i f : facets) {
int i0 = f.x();
int i1 = f.y(); // index
int i2 = f.z();
Vec3d pos0 = points.at(i0);
Vec3d pos1 = points.at(i1); // position
Vec3d pos2 = points.at(i2);
Vec3d N = triangleNormal(pos0, pos1, pos2); // face/triangle normal
norms[i0] = N;
norms[i1] = N; // Use the same normal for all 3 vertices
norms[i2] = N;
}
然后,使用 Phong material:
渲染输出网格方法一反转法线
当我在方法1中反转法线方向时:
norms[i0] = -N;
norms[i1] = -N;
norms[i2] = -N;
暗区和亮区交换:
通过以下方式将位置 0 与位置 1 交换也会发生同样的情况:
// Vec3d N = triangleNormal(pos0, pos1, pos2);
Vec3d N = triangleNormal(pos1, pos0, pos2); // Swap pos0 with pos1
方法二
我通过 this method 计算法线:
// Count how many faces/triangles a vertex is shared by
vector<int> counters;
counters.resize(points.size());
norms.resize(points.size());
for (Vec3i f : facets) {
int i0 = f.x();
int i1 = f.y(); // index
int i2 = f.z();
Vec3d pos0 = points.at(i0);
Vec3d pos1 = points.at(i1); // position
Vec3d pos2 = points.at(i2);
Vec3d N = triangleNormal(pos0, pos1, pos2);
// Must be normalized
//
N.normalize();
norms[i0] += N;
norms[i1] += N; // add normal to all vertices used in face
norms[i2] += N;
counters[i0]++;
counters[i1]++; // increment count for all vertices used in face
counters[i2]++;
}
//
for (int i = 0; i < static_cast<int>(norms.size()); ++i) {
if (counters[i] > 0)
norms[i] /= counters[i];
else
norms[i].normalize();
}
此方法通过 Phong material:
产生完全黑暗的最终渲染我也尝试了建议的方法
方法二反转法线
我用的是方法二,但是最后我把法线方向反转了:
for (Vec3d & n : norms) {
n = -n;
}
令我惊讶的是,最终渲染图全黑了:
同样在方法 2 中,我尝试将位置 0 与位置 1 交换:
// Vec3d N = triangleNormal(pos0, pos1, pos2);
Vec3d N = triangleNormal(pos1, pos0, pos2); // swap pos0 with pos1
最终渲染图是全暗区,没有任何亮区。
如何?
知道如何让我的最终渲染全亮而没有任何暗区吗?
看来你的网格没有一致的缠绕规则。因此,一些 triangles/faces 是按顶点的 CCW 顺序定义的 CW 其他,导致您的某些法线朝向相反的方向。您可以采取一些措施来补救:
使用双面法线光照
这是最简单的...片段中的某处或您计算阴影的任何地方,如下所示:
out_color = face_color*(ambient_light+diffuse_light*max(0.0,dot(face_normal,light_direction)));
当法线方向错误时,
dot
的结果为负,导致颜色变深,因此只需使用abs
值代替:out_color = face_color*(ambient_light+diffuse_light*abs(dot(face_normal,light_direction)));
在固定功能管道中甚至有此 IIRC 的开关:
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
修复网眼缠绕
必须有 3D 工具来执行此操作(Blender、3DS 等),或者如果您的网格是动态生成的,您可以更新代码以自行创建一致的缠绕。
正确的缠绕使您能够使用
GL_CULL_FACE
来显着加快渲染速度。它还支持更高级的功能,例如:修复法线
在某些情况下,有一些方法可以检测法线是向外还是向内指向网格,例如:
所以只要在计算法线的过程中否定错误的就可以了。但是,如果您的网格太复杂(离凸面太远),这不是那么容易完成,因为您需要使用网格的局部“中心”,甚至是昂贵的内部多边形测试。
生成法线的平均方法为您提供法线两个方向的深色,这意味着您错误地计算了它们并且它们很可能为零。有关此类方法的更多信息,请参阅:
- How to achieve smooth tangent space normals?
无论如何调试这样的问题最好将法线渲染为从网格顶点出发的线(使用线框)。然后你会直接看到什么法线是好的和坏的。这里的例子: