GLKit/OpenGL-ES中自动计算法线

4
我正在OpenGL-ES中制作一些基本图形,参考了Apple的示例代码。他们使用了一个点数组,并在第一个数组中用一个索引数组和每组三个索引创建多边形。这很不错,我可以做出我想要的形状。为了正确着色这些图形,我相信需要为每个多边形上的每个顶点计算法向量。最初这些形状是长方体,所以非常容易,但现在我正在制作(稍微)更高级的形状,我想自动创建这些法向量。如果我得到一个多边形两个边的向量(这里所有多边形都是三角形),并使用它们的叉积来计算该多边形上每个顶点的法向量,这似乎很容易。之后我使用下面的代码来绘制形状。
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, triangleVertices);

glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, 0, triangleColours);

glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 0, triangleNormals);

glDrawArrays(GL_TRIANGLES, 0, 48);

glDisableVertexAttribArray(GLKVertexAttribPosition);
glDisableVertexAttribArray(GLKVertexAttribColor);
glDisableVertexAttribArray(GLKVertexAttribNormal);

我不太理解的是为什么我必须手动执行此操作。我确信除了一个垂直于表面的向量以外,还有其他情况需要考虑,但我也确信这是最常见的用例,所以难道没有更简单的方法吗?我是否错过了一些显而易见的东西? glCalculateNormals() 将非常实用。
//以下是答案 传入一个 GLKVector3[] 数组来存储您的法线向量,另一个数组存储顶点(每个三角形都由三个顶点组成),然后再传入顶点的数量。
- (void) calculateSurfaceNormals: (GLKVector3 *) normals forVertices: (GLKVector3 *)incomingVertices count:(int) numOfVertices
{

    for(int i = 0; i < numOfVertices; i+=3)
    {
        GLKVector3 vector1 = GLKVector3Subtract(incomingVertices[i+1],incomingVertices[i]);
        GLKVector3 vector2 = GLKVector3Subtract(incomingVertices[i+2],incomingVertices[i]);        
        GLKVector3 normal = GLKVector3Normalize(GLKVector3CrossProduct(vector1, vector2));
        normals[i] = normal;
        normals[i+1] = normal;
        normals[i+2] = normal;
    }
}
1个回答

3
再次回答:OpenGL既不是场景管理库,也不是几何库,而只是绘图API,可以将美丽的图片绘制到屏幕上。对于光照,它需要法线向量,用户提供这些向量即可。这就是全部。如果这可以由用户完成,并与实际绘图无关,为何还要计算法线?
通常情况下,你根本不需要在运行时计算法线,而是从文件中加载。而且有许多方法来计算法线。你需要每个面的法线还是每个顶点的法线?你需要任何特定的硬边缘或任何特定的平滑区域?如果要平均面法线以获得顶点法线,如何进行平均处理?
随着着色器的出现和新版本OpenGL中已删除内置的法线属性和光照计算,这整个问题变得过时了,因为你可以按照自己的方式进行光照,并且不一定需要传统的法线。
顺便说一句,听起来你目前正在使用每个面的法线,这意味着面的每个顶点都有相同的法线。这会创建一个非常分段的模型,具有硬边缘,并且与索引不兼容。如果你想要平滑的模型(我不知道,也许你真的想要分段的外观),你应该对每个顶点的相邻面的面法线进行平均处理,以计算每个顶点的法线。这实际上是更常见的用例,而不是每个面的法线。
所以,你可以像这样使用伪代码:
for each vertex normal:
    intialize to zero vector

for each face:
    compute face normal using cross product
    add face normal to each vertex normal of this face

for each vertex normal:
    normalize

生成平滑的每个顶点法线。

即使在实际代码中,这应该只需要10至20行代码,这并不算复杂。


我曾经认为我的情况是大多数情况,但如果这不是通常的做法,那么我可以理解它是一个手动工作。我主要想要块,所以我可能会坚持使用我的系统。在您描述的平均顶点法线方法中,我如何告诉OpenGL,尽管我有12个多边形和36个顶点,但我只计算了8个法线?我需要一个包含36个法线的数组来匹配顶点,并按相同顺序放置法线吗?这意味着每当两个多边形共享一个顶点时,一些顶点会重复出现,还是有一种将法线和顶点链接起来的方法? - Craig
@Craig,每个顶点都有一个普通向量。如果按照您的方式计算法线(面积法线),则每个面积法线都必须为面的每个顶点重复。如果计算每个顶点的法线,则每个顶点的法线将为其所属的每个面重复,就像实际上的顶点一样。在概念上,顶点是其所有属性的整体,而不仅仅是OpenGL中不幸被称为顶点的位置。 - Christian Rau
@Craig 但是每个顶点法线的优势(关于存储效率)在于,您可以使用索引数组(和glDrawElements)并完全减少重复,因为每个唯一的顶点具有相同的法线。对于每个面的法线,索引不会带来任何好处,因为您无论如何都需要复制顶点,因为每个具有唯一位置的顶点可能具有多个不同的法线。 - Christian Rau
@Craig 所以你说的基本上就是做法,因为如果你有36个顶点,那么你也有36个顶点法线而不仅仅是8个。怎么可能只有8个,你甚至事先都不知道你只有8个唯一的法线,因为你还没有计算任何东西。所以是的,法线数组(以及每个其他属性数组)始终与顶点数组匹配,无论它包含哪些值。 - Christian Rau
8个法线是针对一个立方体的,其中有8个顶点和一个索引数组。在该索引数组中,类似于{0,1,2,0,2,3},表示每个多边形要使用哪些顶点,每次三个。然后我会使用相同的索引数组来排序(并重复)八个法线,每个多边形上每个顶点都有一个。如果我想要平均值,那就这么做。但是,叉积非常适合我的块。谢谢。 - Craig
@Craig 是的,如果你不想让每个顶点法线平均化,那么你不是有8个顶点而是24个,因为你必须为立方体的每个顶点重复面法线。但在这种情况下,你也可以完全放弃索引数组,使用glDrawArrays。在这种情况下,你当然需要36个顶点。关键是,要时刻记住一个顶点不仅仅是一个位置,而是所有顶点属性的集合。因此,如果你想让两个顶点具有相同的位置但不同的法线,这在使用每个面法线时是必需的,所以重复是自然的。 - Christian Rau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接