计算三angular形网格中的法线

我绘制了一个有10000个顶点(100×100)的三angular形网格,这将是一个草地。 我使用gldrawelements()。 我看了整天,仍然不明白如何计算这个法线。 每个顶点都有自己的法线,还是每个三angular形都有自己的法线? 有人可以指导我如何编辑我的代码,以正确的方向?

struct vertices { GLfloat x; GLfloat y; GLfloat z; }vertices[10000]; GLuint indices[60000]; /* 99..9999 98..9998 ........ 01..9901 00..9900 */ void CreateEnvironment() { int count=0; for (float x=0;x<10.0;x+=.1) { for (float z=0;z<10.0;z+=.1) { vertices[count].x=x; vertices[count].y=0; vertices[count].z=z; count++; } } count=0; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ GLuint v1=(a*100)+b;indices[count]=v1;count++; GLuint v2=(a*100)+b+1;indices[count]=v2;count++; GLuint v3=(a*100)+b+100;indices[count]=v3;count++; } } count=30000; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ indices[count]=(a*100)+b+100;count++;//9998 indices[count]=(a*100)+b+1;count++;//9899 indices[count]=(a*100)+b+101;count++;//9999 } } } void ShowEnvironment(){ //ground glPushMatrix(); GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0}; glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient); glEnableClientState(GL_VERTEX_ARRAY); glIndexPointer( GL_UNSIGNED_BYTE, 0, indices ); glVertexPointer(3,GL_FLOAT,0,vertices); glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices); glDisableClientState(GL_VERTEX_ARRAY); glPopMatrix(); } 

编辑1这是我写出来的代码。 我只是使用数组而不是vector,并将所有法线存储在称为法线的结构中。 但它仍然不起作用。 我在*索引处得到未处理的exception。

 struct Normals { GLfloat x; GLfloat y; GLfloat z; }normals[20000]; Normals* normal = normals; //***************************************ENVIRONMENT************************************************************************* struct vertices { GLfloat x; GLfloat y; GLfloat z; }vertices[10000]; GLuint indices[59403]; /* 99..9999 98..9998 ........ 01..9901 00..9900 */ void CreateEnvironment() { int count=0; for (float x=0;x<10.0;x+=.1) { for (float z=0;z<10.0;z+=.1) { vertices[count].x=x; vertices[count].y=rand()%2-2;; vertices[count].z=z; count++; } } //calculate normals GLfloat vector1[3];//XYZ GLfloat vector2[3];//XYZ count=0; for (int x=0;x<9900;x+=100){ for (int z=0;z<99;z++){ vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z vector2[0]= vertices[x+z+1].x-vertices[x+z+100].x;//vector2x vector2[1]= vertices[x+z+1].y-vertices[x+z+100].y;//vector2y vector2[2]= vertices[x+z+1].z-vertices[x+z+100].z;//vector2z normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1]; normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2]; normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++; } } count=10000; for (int x=100;x<10000;x+=100){ for (int z=0;z<99;z++){ vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x -- JUST ARRAYS vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z vector2[0]= vertices[x+z+1].x-vertices[x+z-100].x;//vector2x vector2[1]= vertices[x+z+1].y-vertices[x+z-100].y;//vector2y vector2[2]= vertices[x+z+1].z-vertices[x+z-100].z;//vector2z normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1]; normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2]; normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++; } } count=0; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ GLuint v1=(a*100)+b;indices[count]=v1;count++; GLuint v2=(a*100)+b+1;indices[count]=v2;count++; GLuint v3=(a*100)+b+100;indices[count]=v3;count++; } } count=30000; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ indices[count]=(a*100)+b+100;count++;//9998 indices[count]=(a*100)+b+1;count++;//9899 indices[count]=(a*100)+b+101;count++;//9999 } } } void ShowEnvironment(){ //ground glPushMatrix(); GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0}; GLfloat GroundDiffuse[]={1.0,0.0,0.0,1.0}; glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient); glMaterialfv(GL_FRONT,GL_DIFFUSE,GroundDiffuse); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer( GL_FLOAT, 0, normal); glVertexPointer(3,GL_FLOAT,0,vertices); glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glPopMatrix(); } //*************************************************************************************************************************** 

每个顶点都有自己的法线,还是每个三angular形都有自己的法线?

像往常一样,答案是:“这取决于”。 由于法线被定义为与给定平面内的所有vector垂直的vector(在N维中),因此需要一个平面来计algorithm线。 顶点位置只是一个点,因此单数,所以你实际上需要一个面来计算正常。 因此,天真地,可以假定法线是每个人脸 ,正常计算的第一步是通过评估人脸边缘的叉积来确定人脸法线。

假设你有一个三angular形的点ABC ,那么这些点具有位置向量↑A↑B↑C ,边的向量是↑B – ↑A↑C – ↑A,所以面法向量是↑ N f =(↑B – ↑A)×(↑C – ↑A)

请注意,如上所述, ↑N f的大小与人脸的面积成正比。

在光滑的曲面中,顶点在面之间共享(或者你可以说这些面共享一个顶点)。 在这种情况下,顶点的法线不是它所包含的面的法线之一,而是它们的线性组合:

↑N v =Σp↑N f ; 其中p是每张脸的权重。

可以假设参与的脸部法线之间的权重相等。 但假设面部越大,对正常的贡献就越大,这就更有意义了。

现在回想一下,你用一个向量↑v进行归一化处理,通过按比例缩放它来实现: ↑v i =↑v / |↑v | 。 但正如已经告诉过的,脸部的长度已经取决于脸部的面积。 所以上面给出的权重因子p已经包含在vector本身中:它的长度,也就是幅度。 所以我们可以通过简单地总结所有的脸部法线来得到顶点法向量。

在照明计算中,法向vector必须是单位长度,即标准化为可用。 所以在总结之后,我们对新find的顶点法线进行标准化并使用它。

carefull读者可能已经注意到我具体说光滑的表面共享顶点。 事实上,如果几何中有一些折痕/硬边,那么两边的面不共享顶点。 在OpenGL中,一个顶点是整个的组合

  • 位置
  • 正常
  • (颜色)
  • N个纹理坐标
  • M更多的属性

你改变其中的一个,你得到了一个完全不同的顶点。 现在一些3Dbuild模人员只将顶点看作点的位置,并将其余的那些属性存储在每个面上(Blender就是这样一个build模器)。 这节省了一些内存(或相当多的内存,取决于属性的数量)。 但是OpenGL需要所有的东西,所以如果使用这样一个混合的范例文件,你将不得不首先将它分解成OpenGL兼容的数据。 看看Blender的导出脚本之一,就像PLY导出器一样,看看它是如何完成的。


现在来掩饰一些其他的事情。 在你的代码中你有这样的:

  glIndexPointer( GL_UNSIGNED_BYTE, 0, indices ); 

索引指针与顶点数组索引无关 ! 从今天起,这是一个不合时宜的过程,当时graphics仍然使用调色板而不是真正的颜色。 一个像素的颜色不是通过给它的RGB值设置的,而是由一个数字偏移到一个有限的颜色调色板中。 调色板的颜色仍然可以在几种graphics文件格式中find,但没有像样的硬件使用它们了。

请擦除你的内存和你的代码中的glIndexPointer (和glIndex),他们不会做你认为他们做的事情整个索引颜色模式是神秘的使用,坦率地说,我不知道1998年后,仍然支持它。

每个顶点。

使用交叉积来计算给定顶点周围的三angular形的面法线,将它们相加,并归一化。

竖起大拇指为datenwolf! 我完全同意他的做法。 为每个顶点添加相邻三angular形的法向vector,然后进行标准化即可。 我只是想稍微推敲一下这个答案,仔细看一看矩形平滑网格的具体常见情况,这个网格具有恒定的x / y步长 。 换句话说,在每个点上都有一个可变高度的矩形x / y网格。

这样的网格是通过在x和y上循环并为z设置一个值来创build的,并且可以表示诸如山的表面之类的东西。 所以网格的每一个点都由一个向量表示

 P = (x, y, f(x,y)) 

其中f(x,y)是给出网格上每个点的z的函数。

通常绘制这样一个网格,我们使用TriangleStrip或TriangleFan,但任何技术应该给结果三angular形相似的地形。

  |/ |/ |/ |/ ...--+----U----UR---+--... /| /| 2 /| /| Y / | / | / | / | ^ | / | / | / | / | |/ 1 |/ 3 |/ |/ | ...--L----P----R----+--... +-----> X /| 6 /| 4 /| /| / | / | / | / | | /5 | / | / | / |/ |/ |/ |/ ...--DL---D----+----+--... /| /| /| /| 

对于三angular形条带,每个顶点P =(x0,y0,z0)有6个相邻的顶点

 up = (x0 , y0 + ay, Zup) upright = (x0 + ax, y0 + ay, Zupright) right = (x0 + ax, y0 , Zright) down = (x0 , y0 - ay, Zdown) downleft = (x0 - ax, y0 - ay, Zdownleft) left = (x0 - ax, y0 , Zleft) 

其中ax / ay分别是x / y轴上的恒定网格步长。 在一个方形网格ax = ay。

 ax = width / (nColumns - 1) ay = height / (nRows - 1) 

因此,每个顶点具有6个相邻的三angular形,每个三angular形具有其自己的法向量(表示为N1到N6)。 这些可以使用定义三angular形边的两个向量的叉积来计算,并且要小心我们交叉积的顺序。 如果法线向量指向Z方向:

 N1 = up x left = = (Yup*Zleft - Yleft*Zup, Xleft*Zup - Xup*ZLeft, Xleft*Yup - Yleft*Xup) =( (y0 + ay)*Zleft - y0*Zup, (x0 - ax)*Zup - x0*Zleft, x0*y0 - (y0 + ay)*(x0 - ax) ) N2 = upright x up N3 = right x upright N4 = down x right N5 = downleft x down N6 = left x downleft 

所得到的每个点P的法向vector是N1到N6的和。 我们在求和之后归一化。 创build一个循环,计算每个法向量的值,添加它们然后归一化是非常容易的。 但是,正如Shickadance先生所指出的那样,这可能需要相当长的一段时间,特别是对于大网格和/或embedded式设备。

如果我们仔细观察并手工计算,我们会发现大多数项相互抵消,给我们留下一个非常优雅和容易计算最终解N的结果。这里的要点是通过避免计算N1到N6的坐标加速计算,对每个点进行6次交叉乘积和6次加法运算。 代数可以帮助我们直接跳到解决scheme,使用更less的内存和更less的CPU时间。

我不会显示计算的详细信息,因为它很长,但很直接,并会跳转到网格上任何点的法向量的最终expression式。 为了清楚起见,只有N1被分解,其他向量看起来相似。 求和后,我们得到尚未归一化的N:

 N = N1 + N2 + ... + N6 = .... (long but easy algebra) ... = ( (2*(Zleft - Zright) - Zupright + Zdownleft + Zup - Zdown) / ax, (2*(Zdown - Zup) + Zupright + Zdownleft - Zup - Zleft) / ay, 6 ) 

你走了! 只要将这个向量归一化,并且你知道网格上任意一点的法线向量,只要知道它周围点的Z值和网格的水平/垂直步长。

请注意,这是周围三angular形法向量的加权平均值。 重量是三angular形的面积,已经包含在叉积中。

您甚至可以通过仅考虑四个周围点(上,下,左和右)的Z值来简化它。 在这种情况下,你会得到:

  | \|/ | N = N1 + N2 + N3 + N4 ..--+----U----+--.. = ( (Zleft - Zright) / ax, | /|\ | (Zdown - Zup ) / ay, | / | \ | 2 ) \ | / 1|2 \ | / \|/ | \|/ ..--L----P----R--... /|\ | /|\ / | \ 4|3 / | \ | \ | / | | \|/ | ..--+----D----+--.. | /|\ | 

这更加优雅,甚至更快计算。

希望这会使一些网格更快。 干杯

Interesting Posts