读取STL模型 并用opengl显示
说起STL模型,相信使用过CAD三维软件的人都不陌生,
STL = STL文件,一种3D模型文件格式STL(STereo Lithography的缩写)
STL文件格式是由3D SYSTEMS 公司于1988 年制定的一个接口协议,是一种为快速原型制造技术服务的三维图形文件格式。STL 文件由多个三角形面片的定义组成,每个三角形面片的定义包括三角形各个定点的三维坐标及三角形面片的法矢量。三角形顶点的排列顺序遵循右手法则。 STL 文件有2 种类型:文本文件(ASCII格式)和二进制文件(BINARY)。
在此文中我们对ASCII格式的STL文件进行解析,并且用opengl对其进行显示。
STL的ASCII格式如下:
solid filenamestl //文件路径及文件名
facet normal x y z // 三角面片法向量的3个分量值
outer loop
vertex x y z ∥三角面片第一个顶点的坐标
vertex x y z // 三角面片第二个顶点的坐标
vertex x y z ∥三角面片第三个顶点的坐标
endloop
endfacet // 第一个三角面片定义完毕
……
……
endsolid filenamestl ∥整个文件结束
outer loop
vertex x y z ∥三角面片第一个顶点的坐标
vertex x y z // 三角面片第二个顶点的坐标
vertex x y z ∥三角面片第三个顶点的坐标
endloop
endfacet // 第一个三角面片定义完毕
……
……
endsolid filenamestl ∥整个文件结束
下面来说说处理这个解析的思路,首先我们可以按行一行行读来下,去掉第一行开始一直往下读,每读一行都检查是不是到了最后一行,但是这样显然效率不高,不如我们一开始把三角面片个个数求出来,循环中对每个三角面片进行解析,可以观察出每个三角面片所占的ASCII文件的行数为7行,那么把行数读出来除以七所得的商就是三角面片的个数,那么我们再一一对每个三角面片进行解析,实际上这个工作并不是很难,关键是要知道STL的格式与OPENGLBATCH的接口,OPENGL的接口实际上是一串float的数组,这里面保存了verts即顶点坐标信息,以及norms即法向量信息,那么刚好和我们的stl文件相一致。
以下是我们的核心代码;
#include "windows.h" #include <GLTools.h> // OpenGL toolkit #include <GLMatrixStack.h> #include <GLFrame.h> #include <GLFrustum.h> #include <GLGeometryTransform.h> #include <math.h> #ifdef __APPLE__ #include <glut/glut.h> #else #define FREEGLUT_STATIC #include <GL/glut.h> #endif GLFrame viewFrame; GLFrustum viewFrustum; GLBatch triangleBatch; GLMatrixStack modelViewMatix; GLMatrixStack projectionMatrix; GLGeometryTransform transformPipeline; GLShaderManager shaderManager; int num; float* verts; float* vnorms; void getstlmodel() { int max=0; bool isbegin=false; long size=0; int nlines=0; int count1=0; int count2=0; FILE* file=fopen("mystl.stl","r"); fseek(file,0L,SEEK_END); size=ftell(file); fclose(file); file=fopen("mystl.stl","r"); for (int i=0;i<size;i++) { if(getc(file)=='\n') { nlines++; } } num=nlines/7; rewind(file); while (getc(file) != '\n'); verts=new float[9*num]; vnorms=new float[9*num]; for (int i=0;i<num;i++) { char x[200]=""; char y[200]=""; char z[200]=""; if(3!=fscanf(file,"%*s %*s %80s %80s %80s\n",x,y,z)) { break; } vnorms[count1]=vnorms[count1+3]=vnorms[count1+6]=atof(x); count1++; vnorms[count1]=vnorms[count1+3]=vnorms[count1+6]=atof(y); count1++; vnorms[count1]=vnorms[count1+3]=vnorms[count1+6]=atof(z); count1+=7; fscanf(file,"%*s %*s"); if (3!=fscanf(file,"%*s %80s %80s %80s\n",x,y,z)) { break; } if (isbegin==false) { isbegin=true; max=atof(z); } verts[count2]=atof(x); count2++; verts[count2]=atof(y); count2++; verts[count2]=atof(z); count2++; if (3!=fscanf(file,"%*s %80s %80s %80s\n",x,y,z)) { break; } verts[count2]=atof(x); count2++; verts[count2]=atof(y); count2++; verts[count2]=atof(z); count2++; if (3!=fscanf(file,"%*s %80s %80s %80s\n",x,y,z)) { break; } verts[count2]=atof(x); count2++; verts[count2]=atof(y); count2++; verts[count2]=atof(z); count2++; fscanf(file,"%*s"); fscanf(file,"%*s"); } } void SetupRC() { // Black background glClearColor(0.3f, 0.3f, 0.3f, 1.0f ); shaderManager.InitializeStockShaders(); viewFrame.MoveForward(1000.0f); triangleBatch.Begin(GL_TRIANGLES, num*3); triangleBatch.CopyVertexData3f(verts); triangleBatch.CopyNormalDataf(vnorms); triangleBatch.End(); // Make the torus } void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f); if(key == GLUT_KEY_DOWN) viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f); if(key == GLUT_KEY_LEFT) viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f); if(key == GLUT_KEY_RIGHT) viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f); // Refresh the Window glutPostRedisplay(); } void ChangeSize(int w, int h) { // Prevent a divide by zero if(h == 0) h = 1; // Set Viewport to window dimensions glViewport(0, 0, w, h); viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 2000.0f); projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix()); transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix); } void RenderScene(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); modelViewMatix.PushMatrix(viewFrame); GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f }; //shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed); shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed); triangleBatch.Draw(); modelViewMatix.PopMatrix(); glutSwapBuffers(); } int main(int argc, char* argv[]) { getstlmodel(); gltSetWorkingDirectory(argv[0]); glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL); glutInitWindowSize(800, 600); glutCreateWindow("Geometry Test Program"); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); glutSpecialFunc(SpecialKeys); GLenum err = glewInit(); if (GLEW_OK != err) { fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err)); return 1; } SetupRC(); glutMainLoop(); return 0; }
以下是我们解析stl模型生成图形的实例:
其实,程序中还有一个地方还有缺陷,就是一开始我们设置的
viewFrame.MoveForward(1000.0f);
即是在里原点1000米的地方进行观测,然后
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 2000.0f);
这里是对投影的平截头体进行的设置,我们也设置的为角度35,深度为1到2000,但是stl模型不一定是落在这个区域内,那这个时候我们就不一定能观测得到图像,或者只能观测到部分图像。
关于stl解析并且显示的部分,在OPENCASCADE中也有功能非常强大的代码,其中还包括了对二进制STL模型的解析和显示,如果想深究的童鞋可以看看这个博客:http://www.cppblog.com/eryar/archive/2013/05/01/199882.aspx