在 GLKit/OpenGL-ES 中自动计算法线
我正在基于 Apple 的示例代码在 OpenGL-ES 中制作一些相当基本的形状。他们使用了一个点数组,第一个数组中有一个索引数组,每组三个索引创建一个多边形。太好了,我可以做出我想要的形状。为了正确地对形状进行着色,我相信我需要计算每个多边形上每个顶点的法线。起初,形状是立方体,所以非常容易,但现在我正在制作(稍微)更高级的形状,我想自动创建这些法线。如果我获取多边形两条边的向量(这里所有多边形都是三角形)并对该多边形上的每个顶点使用它们的叉积,这似乎很容易。之后,我使用如下代码来绘制形状。
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;
}
}
I'm making some fairly basic shapes in OpenGL-ES based on sample code from Apple. They've used an array of points, with an array of indices into the first array and each set of three indices creates a polygon. That's all great, I can make the shapes I want. To shade the shapes correctly I believe I need to calculate normals for each vertex on each polygon. At first the shapes were cuboidal so it was very easy, but now I'm making (slightly) more advanced shapes I want to create those normals automatically. It seems easy enough if I get vectors for two edges of a polygon (all polys are triangles here) and use their cross product for every vertex on that polygon. After that I use code like below to draw the shape.
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);
What I'm having trouble understanding is why I have to do this manually. I'm sure there are cases when you'd want something other than just a vector perpendicular to the surface, but I'm also sure that this is the most popular use case by far, so shouldn't there be an easier way? Have I missed something obvious? glCalculateNormals() would be great.
//And here is an answer
Pass in a GLKVector3[] that you wish to be filled with your normals, another with the vertices (each three are grouped into polygons) and then the count of the vertices.
- (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;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
答案是:OpenGL 既不是场景管理库,也不是几何库,而只是一个绘图 API,可以在屏幕上绘制漂亮的图片。对于照明,它需要法线,并且您给它法线。就这样。如果这只能由用户完成并且与实际绘图无关,为什么要计算法线呢?
通常您不会在运行时计算它们,而是从文件加载它们。计算法线的方法有很多种。您想要每个面法线还是每个顶点法线?您需要任何特定的硬边或任何特定的平滑补丁吗?如果你想平均面法线以获得顶点法线,你想如何平均这些?
随着着色器的出现以及较新 OpenGL 版本中内置法线属性和光照计算的删除,整个问题无论如何都变得过时了,因为您可以以任何您想要的方式进行光照,并且不再需要传统的法线。
顺便说一句,听起来现在您正在使用每个面的法线,这意味着面的每个顶点都具有相同的法线。这会创建一个具有硬边缘的非常多面的模型,并且与索引一起也不能很好地工作。如果您想要一个平滑的模型(我不知道,也许您真的想要一个多面的外观),您应该对每个顶点的相邻面的面法线进行平均,以计算每个顶点的法线。这实际上是更常见的用例,而不是每张脸的法线。
因此,您可以执行类似伪代码的操作:
生成平滑的每顶点法线。即使在实际代码中,这也应该会产生 10 到 20 行代码,这并不是很复杂。
And again the answer is: OpenGL is neither a scene managment library nor a geometry library, but just a drawing API that draws nice pictures to the screen. For lighting it needs normals and you give it the normals. That's all. Why should it compute normals if this can just be done by the user and has nothing to do with the actual drawing?
Often you don't compute them at runtime anyway, but load them from a file. And there are many many ways to compute normals. Do you want per-face normals or per-vertex normals? Do you need any specific hard edges or any specific smooth patches? If you want to average face normals to get vertex normals, how do you want to average these?
And with the advent of shaders and the removing of the builtin normal attribute and lighting computations in newer OpenGL versions, this whole question becomes obsolete anyway as you can do lighting any way you want and don't neccessarily need traditional normals anymore.
By the way, it sounds like at the moment you are using per-face normals, which means every vertex of a face has the same normal. This creates a very faceted model with hard edges and also doesn't work very well together with indices. If you want a smooth model (I don't know, maybe you really want a faceted look), you should average the face normals of the adjacent faces for each vertex to compute per-vertex normals. That would actually be the more usual use-case and not per-face normals.
So you can do something like this pseudo-code:
to generate smooth per-vertex normals. Even in actual code this should result in something between 10 and 20 lines of code, which isn't really complex.