点的法向量计算

 

在OPENGL编程中,三维模型顶点法向量的计算很重要,它直接影响着显示的效果;本人接触OPENGL时间不长,很长一段时间一直困惑于法向量计算的问题,后经仔细研究并找了些资料才基本实现了法向量的计算算法,现总结如下,希望对初学者能有些帮助。
   大家知道,在OPENGL中顶点-三角形模型对光照的反射取决于顶点法向量的设置,如果法向量计算正确,显示出来的模型表面很光滑,有光泽,否则不是棱角分明就是模糊、看不清。下面假设模型数据来源于AUTOCAD 的DXF格式的文件,由于DXF文件保存的数据是顶点坐标和三角形顶点顺序,没有顶点法向量信息,所以要自己计算;由立体几何知识可知,一个点的法向量应该等于以这个点为顶点的所有三角形的法向量之和,所以只要计算出每个三角形的法向量,再把这个法向量加到此三角形三个顶点的法向量里即可。下面是程序实现的部分关键代码及注释:
  void CEntity::ComputeNormalVector()
  {
  int i,j;
   //取出每一个顶点结构
  VERTEXLIST * pvl = new VERTEXLIST[m_nVertexNum+1];
   VERTEXLIST * pvltemp = (VERTEXLIST *)m_vl.pNext;
   for(i=1;i<m_nVertexNum+1;i++)
   {
   *(pvl+i) = *(pvltemp);
   pvltemp =(VERTEXLIST *)pvltemp->pNext;
   }
   //取出每一个三角形顶点顺序的结构
   SEQUENCELIST * psl = new SEQUENCELIST[m_nSequenceNum];
   SEQUENCELIST * psltemp = (SEQUENCELIST *)m_sl.pNext;
   for(i=0;i<m_nSequenceNum;i++)
   {
   *(psl+i) = *(psltemp);
   psltemp =(SEQUENCELIST *)psltemp->pNext;
   }
   //计算每个三角形的法向量
   VERTEX v1,v2,v3;
   VERTEX temp_v1,temp_v2,v;
   for(i=0;i<m_nSequenceNum;i++)
   {
   v1 = (pvl+(psl+i)->sequence.a)->vertex;
   v2 = (pvl+(psl+i)->sequence.b)->vertex;
   v3 = (pvl+(psl+i)->sequence.c)->vertex; //取出三点坐标
  
   temp_v1=Vector2v(v1,v2); //两点坐标相减 求出两点组成的向量
   temp_v2=Vector2v(v2,v3); //求出三角形的两个向量
   v=NormalizeVertex(CrossVertex(temp_v2,temp_v1)); //两个向量叉乘,归一化
   (psl+i)->NormalVertex = v; //保存
   }
   VERTEX vNormal = VERTEX(0,0,0);
  //遍历每个三角形,把当前三角形法向量分别加到三个顶点法向量里去
   for(j=0;j<m_nSequenceNum;j++)
   {
   //取出当前三角形法向量
   vNormal = (psl+j)->NormalVertex;
   //将当前三角形法向量加到三个顶点法向量内
   (pvl+(psl+j)->sequence.a )->NormalVertex = ::AddVertex((pvl+(psl+j)->sequence.a )->NormalVertex,vNormal);
   (pvl+(psl+j)->sequence.b )->NormalVertex = ::AddVertex((pvl+(psl+j)->sequence.b )->NormalVertex,vNormal);
   (pvl+(psl+j)->sequence.c )->NormalVertex = ::AddVertex((pvl+(psl+j)->sequence.c )->NormalVertex,vNormal);
  
   }
  
   //遍历每一个顶点,将计算完的法向量存到VERTEXLIST链表内。
   pvltemp = (VERTEXLIST *)m_vl.pNext;
   for(i=1;i<m_nVertexNum+1;i++)
   {
   pvltemp->NormalVertex = (pvl+i)->NormalVertex;
   pvltemp =(VERTEXLIST *)pvltemp->pNext;
   }
   delete [] pvl;
   delete [] psl; //释放缓冲区
  }
  两个结构如下:
  typedef struct{
   void * pPre;
   VERTEX vertex;
   VERTEX NormalVertex; //顶点法向量
   void * pNext;
  }VERTEXLIST; //顶点 链表
  typedef struct{
   void * pPre;
   SEQUENCE sequence;
   VERTEX NormalVertex; //三角形法向量
   void * pNext;
  }SEQUENCELIST; //三角形顺序 链表
  至此,顶点法向量计算完毕,然后就是创建显示列表:
   id=glGenLists(1);
   glNewList(id,GL_COMPILE);
   glBegin(GL_TRIANGLES);
   for(i=0;i<nSnum;i++) //遍历每一个三角形
   {
   v1 = (pvertex+psl->sequence.a)->vertex; //顶点坐标
   v = (pvertex+psl->sequence.a)->NormalVertex; //顶点法向量
   glNormal3f(v.x,v.y,v.z); //设置法向量
  glVertex3f(v1.x,v1.y,v1.z); //画点
   v2=(pvertex+psl->sequence.b)->vertex;
   v = (pvertex+psl->sequence.b)->NormalVertex;
   glNormal3f(v.x,v.y,v.z);
  glVertex3f(v2.x,v2.y,v2.z);
   v3=(pvertex+psl->sequence.c)->vertex;
   v = (pvertex+psl->sequence.c)->NormalVertex;
   glNormal3f(v.x,v.y,v.z);
  glVertex3f(v3.x,v3.y,v3.z);
   psl=(SEQUENCELIST*)psl->pNext;
   }
   glEnd();
  另外,正确设置光照和材质是影响OPENGL显示效果的决定性因素,这里就不再赘述,只列出我设置的材质、光照模型:
   glShadeModel(GL_SMOOTH);
   //设置材质和光照
   GLfloat mat_ambient[]= { 0.6, 0.6, 0.6, 1.0 }; //环境光分量强度
   GLfloat mat_diffuse[]= { 0.2, 0.2, 0.2, 1.0 }; //漫反射光分量强度
   GLfloat mat_specular[] = { 0.0, 0.0, 0.0, 1.0 }; //镜面反射
   GLfloat mat_shininess[] = { 10.0 };
   GLfloat light1_ambient[]= { 0.6, 0.6, 0.6, 1.0 };
   GLfloat light1_diffuse[]= { 0.2, 0.2, 0.2, 1.0 };
   GLfloat light1_specular[] = { 0.0, 0.0, 0.0, 0.0 };
  GLfloat light1_position[] = { -400.0, -400.0, 200, 1.0 };

 

Wait... an article about how to calculate vertex normals for a mesh? This is very easy right? Yes, but let's say you want high quality normals at faster speed than usual implementatins. Let's say that you want the rutine to be small too. Ok, then this very short is the articles for you. I assume you know what a mesh is, what a vertex normal is, and that you know what a cross product is. Yes? Ok, let's start.


Getting rid of the divissions
Most mesh normalization implementations out there use the typical normal averaging of face normals to calculate the vertex normal. For that, one iterates all the faces on which the current vertex is contained, accumulate the normals and then divide by the amount of faces used in the accumulation. Now think what this division is doing to the normal. It does not change it's direction for sure, since a division by a scalar only affects the length of the normal. Actually we do not care about this length at all, since we will most probably normalize it to unit length. This means that rigth after the face normal accumulation, we are done, we don't need any divission. Thus we can skip not only this operation by also all the code to keep track of the amount of faces that affect each vertex.


Getting rid of the normalizations (square roots and divisions)
Now think again what we are really doing when accumulating the face normals into vertex normals. We are making a linear combination of normals, with equal importance or weight for all of them. Is this good? Well, one would think that polygons with bigger area should probably contribute more to the final result. This indeed gives better quality vertex normals. So, let's try to calculate the area of each polygon... Hey, but wait! Open your primary school math book. Have a look to the definition of cross product, specially to the length of the cross product. Cheeses, the length of a cross product is proportional to the area of the paralepiped created by the two vectors involved in the product. And is not our triangle normal calculated actually as the cross product of two of its edges? Basically the cross product of these two edges will then give as a face normal with length proportional to the area of the triangle. For free! Ok, this means we must not normalice the face normals, and just accumulate them on the vertices so that we do a high quallity vertex normal calculation. We just skiped one vector normalization per face (meaning, one square root and a division, or an inverse-square root)!


Looping only once
Some implementations make two passes on the mesh to normalize it, one on the faces to calc face normals and vertex-to-normal connectivity, and a second one where this information is used to do the actual normal accumulation for each vertex. This is unnecessary, we can make the code a lot smaller and faster, and use less memory by doing it all in one pass. For each face on the mesh, calc the face normal (without normalization, as just explained), and directly accumulate this normal in each vertex belonging to the face. After you are done with the faces, each vertex will have recieved all the face normals it was supposed to recieve. That simple.

The code
To finish, let's put all together in a small piece of code:
void Mesh_normalize( Mesh *myself ) { Vert *vert = myself->vert; Triangle *face = myself->face; for( int i=0; i < myself->mNumVerts; i++ ) vert[i].normal = vec3(0.0f); for( int i=0; i < myself->mNumFaces; i++ ) { const int ia = face[i].v[0]; const int ib = face[i].v[1]; const int ic = face[i].v[2]; const vec3 e1 = vert[ia].pos - vert[ib].pos; const vec3 e2 = vert[ic].pos - vert[ib].pos; const vec3 no = cross( e1, e2 ); vert[ia].normal += no; vert[ib].normal += no; vert[ic].normal += no; } for( i=0; i < myself->mNumVerts; i++ ) verts[i].normal = normalize( verts[i].normal ); }
This is quite fast, fast enough to do lot of mesh normalizations per frame. On top of it, if you are using vertex shaders you can be interested on skipping the last vertex normalization and do it on the shader (last line on the code above). Also in some cases, like 64 or even 4 kilobyte demos, it's usual to have all allocated buffers automatically initialized to zero. In that case, if this is the first and only normalization for a given mesh, you may skip the first loop on the function too of course.

转自:http://hi.baidu.com/%B0%EB%F5%FC%B9%E2%C4%EA/blog/item/7e96061b269fc1e2ae513378.html


 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值