iOS OpenGl ES .obj 加载单位立方体后翻转计算面法线
我正在尝试将模型从 Maya 导出并加载到非常简单的 iOS OpenGL ES 设置中。为此,我编写了一个 ruby obj 解析器,它基本上获取顶点和法线并将它们计算到我简单包含的 C 标头中。简单三角剖分的单位立方体的输出结构如下:
Vertex3D cubeVertices[] = {
{{-0.500000f, -0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{0,1,2},{0,6,7},{0,7,1},{0,6,4},{0,4,2}},5},
{{0.500000f, -0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{1,0,2},{1,2,3},{1,0,7},{1,7,3}},4},
{{-0.500000f, 0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{2,0,1},{2,1,3},{2,3,4},{2,4,0}},4},
{{0.500000f, 0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{3,2,1},{3,2,4},{3,4,5},{3,1,7},{3,7,5}},5},
{{-0.500000f, 0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{4,2,3},{4,3,5},{4,5,6},{4,6,0},{4,0,2}},5},
{{0.500000f, 0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{5,4,3},{5,4,6},{5,6,7},{5,3,7}},4},
{{-0.500000f, -0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{6,4,5},{6,5,7},{6,7,0},{6,0,4}},4},
{{0.500000f, -0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{7,6,5},{7,6,0},{7,0,1},{7,1,3},{7,3,5}},5}
};
GLubyte cubeFaceIndices[] = {
0, 1, 2,
2, 1, 3,
2, 3, 4,
4, 3, 5,
4, 5, 6,
6, 5, 7,
6, 7, 0,
0, 7, 1,
1, 7, 3,
3, 7, 5,
6, 0, 4,
4, 0, 2
};
Vertex3D 的定义是
struct Vertex3D {
vec3 position;
vec3 normal;
vec4 color;
Index3D connected_faces[100];
int connectedFaceCount;
};
typedef struct Vertex3D Vertex3D;
现在我需要重新计算顶点法线,因为我想要为某些顶点的移动设置动画。为此,我只是将所有连接的顶点索引添加到每个顶点,这就是connected_faces 索引数组的用途。
对于计算,我简单地用叉积计算所有面法线。为此,我只需使用存储在 Index3D 中的 3 个顶点,加载位置,减去第一个顶点,这样我就可以获得向量并计算叉积。然后我将属于该顶点的所有叉积相加,并对向量进行归一化,这就是我的最终顶点法线。
我遇到的问题是,两个面有相同的法线,例如三角立方体总是有两个三角形分割四边形,该面的法线方向相反。如果我将它们相加,以便稍后计算,它们的总和当然是空向量。我知道发生这种情况是因为在某些情况下,2 个顶点的顺序不正确,这意味着 A 应该替换为 B,但我不知道我必须翻转哪一个。
有没有什么数学方法可以估计法线的方向,以及我是否计算 AxB 还是 BxA?我仔细检查了 Maya 中的法线,它们在那里非常完美。
编辑:我现在订购了连接的面,效果很好。我用以下方法计算法线:
// every vertex
GLsizei vertexCount = sizeof(cubeVertices) / sizeof(Vertex3D);
for (int i = 0; i < vertexCount; i++) {
vec3 newNormal = {0.0f, 0.0f, 0.0f};
// every vertex connected to the current vertex
GLsizei faceCount = cubeVertices[i].connectedFaceCount;
for(int j = 0; j < faceCount; j++){
Index3D index = cubeVertices[i].connected_faces[j];
vec3 vectorA = cubeVertices[index.a].position;
vec3 vectorB = cubeVertices[index.b].position;
vec3 vectorC = cubeVertices[index.c].position;
vec3 vectorNormal = {0.0f, 0.0f, 0.0f};
substractVectors(&vectorB, &vectorA);
substractVectors(&vectorC, &vectorA);
makeNormal(&vectorB, &vectorC, &vectorNormal);
addVectors(&newNormal, &vectorNormal);
}
// set normal for current vertex
normalize(&newNormal);
cubeVertices[i].normal = newNormal;
}
但现在我遇到的问题是,通过三角测量,有时我有 2 个法线指向完全相同的方向,这不会导致我预期的 { 0.33f, 0.33f, 0.33f }顶点法线。这是正确的行为,还是有其他方法可以计算?
非常感谢您的帮助!
I'm trying to export and load a model from Maya into a very simple iOS OpenGL ES setup. For that I wrote a ruby obj parser that basically takes the vertices and normals and computes them into a C header which I simply include. The output of a simply triangulated unit cube is the following structure:
Vertex3D cubeVertices[] = {
{{-0.500000f, -0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{0,1,2},{0,6,7},{0,7,1},{0,6,4},{0,4,2}},5},
{{0.500000f, -0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{1,0,2},{1,2,3},{1,0,7},{1,7,3}},4},
{{-0.500000f, 0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{2,0,1},{2,1,3},{2,3,4},{2,4,0}},4},
{{0.500000f, 0.500000f, 0.500000f},{0.000000f, 0.000000f, 1.000000f},{0.5f, 0.5f, 0.5f, 1},{{3,2,1},{3,2,4},{3,4,5},{3,1,7},{3,7,5}},5},
{{-0.500000f, 0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{4,2,3},{4,3,5},{4,5,6},{4,6,0},{4,0,2}},5},
{{0.500000f, 0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{5,4,3},{5,4,6},{5,6,7},{5,3,7}},4},
{{-0.500000f, -0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{6,4,5},{6,5,7},{6,7,0},{6,0,4}},4},
{{0.500000f, -0.500000f, -0.500000f},{0.000000f, 1.000000f, 0.000000f},{0.5f, 0.5f, 0.5f, 1},{{7,6,5},{7,6,0},{7,0,1},{7,1,3},{7,3,5}},5}
};
GLubyte cubeFaceIndices[] = {
0, 1, 2,
2, 1, 3,
2, 3, 4,
4, 3, 5,
4, 5, 6,
6, 5, 7,
6, 7, 0,
0, 7, 1,
1, 7, 3,
3, 7, 5,
6, 0, 4,
4, 0, 2
};
The definition for the Vertex3D is
struct Vertex3D {
vec3 position;
vec3 normal;
vec4 color;
Index3D connected_faces[100];
int connectedFaceCount;
};
typedef struct Vertex3D Vertex3D;
Now I need to recalculate my vertex normals because I want to animate the movement of some vertices. For this I simply added all connected vertex indices to every vertex, that's what the connected_faces index array is for.
For the computation I simply calculate all the face normals with the cross-product. For that I only have to use the 3 vertices stored in a Index3D, load the positions, subtract the first vertex so I have the vectors and calculate the cross-product. Then I add up all cross-products that belong to this vertex, and normalize the vector, that's my final vertex normal.
The problem I am running into is, that it happens that 2 faces that would have the same face normal, e.g. a triangulated cube always has 2 triangles that split the quad, have that face normal in the opposite direction. If I add them, to compute later, of course their sum is the Null-Vector. I know that this happens because in some cases the 2 vertices don't have the right order, this means A should be replaced with B, but I have no idea which one I have to flip.
Is there any mathematic way to estimate in which direction the normal goes, and whether I calculate AxB or BxA? I double checked the normals in Maya, they are just perfect there.
EDIT: I ordered the connected faces now, which works just fine. I'm computing the normals with the following:
// every vertex
GLsizei vertexCount = sizeof(cubeVertices) / sizeof(Vertex3D);
for (int i = 0; i < vertexCount; i++) {
vec3 newNormal = {0.0f, 0.0f, 0.0f};
// every vertex connected to the current vertex
GLsizei faceCount = cubeVertices[i].connectedFaceCount;
for(int j = 0; j < faceCount; j++){
Index3D index = cubeVertices[i].connected_faces[j];
vec3 vectorA = cubeVertices[index.a].position;
vec3 vectorB = cubeVertices[index.b].position;
vec3 vectorC = cubeVertices[index.c].position;
vec3 vectorNormal = {0.0f, 0.0f, 0.0f};
substractVectors(&vectorB, &vectorA);
substractVectors(&vectorC, &vectorA);
makeNormal(&vectorB, &vectorC, &vectorNormal);
addVectors(&newNormal, &vectorNormal);
}
// set normal for current vertex
normalize(&newNormal);
cubeVertices[i].normal = newNormal;
}
But now I have the problem that, through triangulation I sometimes have 2 normals pointing in the exact same direction, which doesn't lead to my expected { 0.33f, 0.33f, 0.33f } vertex normals. Is that a correct behavior, or is there any other way to calculate that?
Thank you very much for your help!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这取决于面的顶点顺序。如果它们按逆时针顺序排列(从外部看向脸部时),则可以将脸部法线计算为
AxB
,其中A = (v1-v0)
并且B = (v2-v0)
,如果按顺时针顺序排列,您必须交换 A 和 B。但只要您对每个面一致地执行此操作,它就应该有效。否则,立方体中的面的方向不一致,也许有人(出口商?)在将四边形转换为三角形或类似的东西时弄乱了方向。
顺便说一句,您可以在没有
connected_faces
数组的情况下计算它们,只需遍历一次面/索引数组,计算每个面的面法线并将其添加到相应三个顶点的法线中,然后通过单个顶点数组遍历来标准化法线。编辑:您可以尝试在 OpenGL 中渲染它们,并剔除正面或背面(使用
glCullFace(GL_BACK)
和glEnable(GL_CULL_FACE)
代码>)。如果内部(或外部,取决于剔除模式)的某些三角形被剔除,而另一些则未被剔除,则三角形的方向不一致,并且立方体数据被破坏。编辑:从你更新的问题来看,我认为至少你不再有 0-法线了。代码看起来也不错。事实上,并非每个法线都有三个相似的坐标(顺便说一句,它不是 0.33 而是 1/sqrt(3),但我知道你的意思)对于由三角形构建的立方体来说是很自然的。在极端情况下,您可能会得到连接到 6 个或仅 3 个三角形的顶点。在所有其他情况下(这应该会发生),您将不会获得完美的角法线。
这是由于立方体的三角测量造成的。您不再看到立方体(由 6 个面和 8 个顶点构成,每个面恰好连接到 3 个面),而是一个三角形网格,其顶点连接到 3 到 6 个之间的任意数量的三角形。您的程序逻辑不知道某些三角形属于在一起并形成四边形。这也是一般网格的常见问题。例如,在四边形网格(由四边形构建)上执行任何细分算法会产生与在该网格的三角剖分上执行相同细分(每个四边形有两个三角形)不同的几何形状。
如果您确实需要处理四边形,则需要一些更高级别的抽象来表示四边形层(可能是顶部的另一个数据结构,或者只是三角形的特殊排序,例如每对连续的三元组形成一个四边形)。否则,您也可以完全使用四边形,因为 OpenGL 可以直接渲染四边形(尽管我不确定未弃用的 GL 或至少 ES 是否已放弃对四边形的支持)。
编辑:在您的情况下,您可以直接订购它,因此每两个三角形形成一个四边形。然后,在法线计算中,您始终处理两个三角形,计算其中一个三角形的法线,然后仅更新四个不同顶点的顶点法线。
It depends on the vertex ordering of the faces. If they are ordered counter-clockwise (when looking from the outside onto the face), then you compute the face normal as
AxB
, withA = (v1-v0)
andB = (v2-v0)
, if ordered clockwise you have to switch A and B.But as long as you do this consistently for every face, it should work. Othwerwise the faces in the cube are not oriented consistently, maybe somebody (the exporter?) messed up the orientation when converting quads to triangles or something like this.
And by the way, you can compute them without the
connected_faces
array, by just traversing the face/index array once, computing the face normal for each face and adding this to the respective three vertices' normals, followed by a single vertex array traversal for normalizing the normals.EDIT: You can try to render them in OpenGL with culling either the front faces or the back faces (using
glCullFace(GL_BACK)
andglEnable(GL_CULL_FACE)
). If some triangles of the inside (or outside, depending on the culling mode) are culled away and some are not, then the triangles are not oriented consistently and your cube data is broken.EDIT: From your updated question I assume, that at least you don't have 0-normals anymore. The code also looks fine. The fact, that not every normal has three similar coordinates (btw it's not 0.33 but 1/sqrt(3), but I know what you mean) is natural for a cube built from triangles. In extreme cases you may get vertices connected to 6 or only 3 triangles. In all other cases (and this should happen) you won't get a perfect corner normal.
This is due to the trianglulation of the cube. You don't see a cube anymore (built from 6 faces and 8 vertices connected to exactly 3 faces each), but a triangular mesh with vertices connected to any number between 3 and 6 triangles. Your program logic does not know that some of the triangles belong together and form a quad. That is also a common problem with general meshes. For example doing any subdivision algorithm on a quadrangular mesh (built from quads) results in a different geometry than doing the same subdivision on a triangulation of that mesh (with two triangles for every quad).
If you really need to work on quads, you need some higher level of abstraction to represent the quad layer (maybe another datastructure on top or just a special ordering of the triangles e.g. every consecutive pair of tris forms a quad). Otherwise you can also work completely with quads, as OpenGL can render quads directly (although I'm not sure if non-deprecated GL or at least ES has dropped support for quads).
EDIT: In your case you can just order it, so every two triangles form a quad. Then in the normal computation you always process two triangles, compute the normal of one of them and then only update the vertex normals of the four distinct vertices.