在上一节中,我们主要介绍了如何在VS2013平台上利用OpenGL库函数开发一个简单的三维绘图软件。但那个软件只是搭建一个简单的三维绘图软件平台,除了实现图像简单的平移、旋转、缩放功能外并没有什么实际的作用,但不用担心,那只是三维图形软件绘制的基础,为了实现一个完整的绘图或图像处理软件,我们就一步一步的实现这个功能。
我们知道对于一个三维的处理软件,首先应该有打开指定文件的功能,特别像一些三维绘图软件,这种功能是非常重要的。基于以上目的,我们本节内容介绍如何打开一个STL文件。
声明:本节内容是在上节所完成的OpenGLDrawing软件上执行的。
OpenGLDrawing源代码下载地址:https://download.csdn.net/download/belence_zhao/10408772
OpenGLDrawing文章地址:https://blog.csdn.net/Belence_zhao/article/details/80279976
在进行软件编写之前,我们首先必须对STL文件有所了解,只有知道STL文件的保存形式,我们才能够进行程序编写》
一、STL文件
总的来说,STL文件其实就是一种用三角面片来组合三维实体模型的一种文件保存格式。也就是所,用三角面片的形式保存数据的一种方法,如下图是实体模型的三角化。
1.STL文件的格式
STL文件的文件保存形式有两种,一种是ASCII形式的文件,另一种是二进制形式的保存文件。
a.ASCII文件格式
一个ASCII的STL文件形式如下:
solid name
facet normal ni nj nk outer loop vertex v1xv1yv1z vertex v2xv2yv2z vertex v3xv3yv3z endloop endfacet
endsolid name
一个STL文件有大三部分组成,以solid +文件名开始,以endsolid+文件名结束。中间部分每7行组成一个部分,在每个部分中解释如下:
facet normal //此三角面片的法向量3个分量
outer loop
vertex //第一个顶点坐标
vertex //第二个顶点坐标
vertex //第三个顶点坐标
endloop
endfacet //当前三角面片结束标志
b.二进制STL形式
只要明白了STL的ASCII格式,二进制文件的理解就简单多了。
UINT8[80] – Header //文件头 UINT32 – Number of triangles //三角面片的个数
foreach triangle REAL32[3] – Normal vector //当前三角面片的法矢 REAL32[3] – Vertex 1 //三角面片第一个顶点坐标 REAL32[3] – Vertex 2 //三角面片第二个顶点坐标 REAL32[3] – Vertex 3 //三角面片第三个顶点坐标 UINT16 – Attribute byte count //文件属性统计 end
以上就是STL两种文件的简单介绍,由于两种文件的相似性,我们本节主要对STL的ASCII文件进行编写,感兴趣的可以自行对二进制STL文件进行编写。
文献参考:https://en.wikipedia.org/wiki/STL_%28file_format%29
https://baike.baidu.com/item/stl格式
在对STL文件的格式清楚后,我们就可以进行程序的编写了。
二、STL文件打开与显示功能的实现
在STL文件编程之前还需要做的就是STL文件的生成,本文章采用的是上图所示的三维文件,是在UG下生成的一个简单文件,然后导出STL文件,并保存txt格式。stl的txt文件必须有,其生成方法较多,可自行查找。也参考下面网址:
https://jingyan.baidu.com/article/86f4a73e85344c37d65269bb.html
1、STL的打开
在C/C++语言中,对文件的打开方式有多种,在MFC中为了简单一些,我们采用MFC自带的FileDlg函数打开一个文件对话框的形式来打开文件。
首先,打开或建立一个单文档的MFC文件,找到资源视图中的菜单,对菜单中的打开添加事件处理程序到类COpenGLDrawingView中,函数为OnFileOpen。
添加CString变量 Path用来保存打开文件的路径。
由于会用到向量,因此在头文件中添加#include<vector>并添加using std::vector。然后添加两个向量verts和vnorms分别存放三角面片的法矢和点坐标。代码如下:
#include<vector> using std::vector;
CString Path; vector<float>verts; //存放点坐标 vector<float>vnorms;//存放法矢
并在构造函数中进行初始化:
COpenGLDrawingView::COpenGLDrawingView()
: Path(_T(""))
{
// TODO: 在此处添加构造代码
m_xPos = 0.0f;
m_yPos = 0.0f;
m_zPos = 0.0f;
m_xAngle = 0.0f;
m_yAngle = 0.0f;
m_zAngle = 0.0f;
m_Scale = 1.0f;
verts.clear();
vnorms.clear();
}
然后添加OnFileOpen函数:
void COpenGLDrawingView::OnFileOpen() { // TODO: 在此添加命令处理程序代码 CFileDialog fDlg(TRUE, _TEXT("txt"), NULL, 4 | 2, _TEXT("全部文件(*.*)|*.*|(*.rbs_App*)|*.rbs_App*|文本文件(*.txt,*.ini,*.log)|*.txt;*.ini;*.log||")); if (fDlg.DoModal() == IDOK) { Path = fDlg.GetPathName(); CString str; CStdioFile fFile; int section = 0, point = 0; fFile.Open(Path, CStdioFile::modeReadWrite/*|CStdioFile::modeCreate|CStdioFile::modeWrite*/); while (fFile.ReadString(str)) { if ((str.Find("solid") == -1) && (str.Find("outer loop") == -1) && (str.Find("endloop") == -1) && (str.Find("endfacet") == -1) && (str.Find("endsolid") == -1)) { if (str.Find("facet normal") != -1) { float a, b, c; sscanf(str, "%*s %*s %f %f %f", &a ,&b,&c); vnorms.push_back(a); vnorms.push_back(b); vnorms.push_back(c); vnorms.push_back(a); vnorms.push_back(b); vnorms.push_back(c); vnorms.push_back(a); vnorms.push_back(b); vnorms.push_back(c); } else { float a, b, c; sscanf(str, "%*s %f %f %f", &a, &b, &c); verts.push_back(a); verts.push_back(b); verts.push_back(c); } } } fFile.Close(); } else { AfxMessageBox("打开失败!"); return; }
//Invalidate(NULL,FALSE);}
这时,我们完成了对一个STL的txt文件的读取,并将所需要的数据分别保存在向量verts和vnorms中。也许大家已经发现在对三角面片的三个定点保存时候我按顺序保存了三遍,这样对内存浪费严重。确实如此,这样做的目的只是为了后边绘图方便,其实只保存一遍也是可以的。在这里就看各位看官的习惯了,但是不建议这样,哈哈!好了让我们去实现绘图功能吧!
2.图形的绘制
图形的绘制是在OnDraw函数中实现的,为了onDraw函数中代码的清晰,我们在类COpenGLDrawingView中建立新的函数STLDraw来实现绘图功能,再在OnDraw函数中对此函数调用,以此来实现绘图功能。
OnDraw函数中的调用:
void COpenGLDrawingView::OnDraw(CDC* /*pDC*/) { COpenGLDrawingDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 if (m_hglrc) wglMakeCurrent(m_pDC->GetSafeHdc(), m_hglrc); else return; //glRotatef(m_zAngle, 0.0f, 0.0f, 1.0f); glScalef(m_Scale, m_Scale, m_Scale); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(m_xPos, m_yPos, m_zPos); glRotatef(m_xAngle, 1.0f, 0.0f, 0.0f); glRotatef(m_yAngle, 0.0f, 1.0f, 0.0f); glScalef(m_Scale, m_Scale, m_Scale); glColor3f(1.0,1.0,0.0); STLDraw(); //glutWireTeapot(2); ::SwapBuffers(m_pDC->GetSafeHdc()); }
接下来,STLDraw函数的代码如下:
void COpenGLDrawingView::STLDraw() { glPushMatrix(); int m_div = 1; for (int i = 0; i < vnorms.size(); i++) { glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glNormal3f(vnorms[i] / m_div, vnorms[i + 1] / m_div, vnorms[i + 2] / m_div); glVertex3f((-verts[i] + verts[1]) / m_div, (-verts[i + 1] + verts[2]) / m_div, (-verts[i + 2] + verts[3]) / m_div); i += 3; glColor3f(0.0f, 1.0f, 0.0f); glNormal3f(vnorms[i] / m_div, vnorms[i + 1] / m_div, vnorms[i + 2] / m_div); glVertex3f((-verts[i] + verts[1]) / m_div, (-verts[i + 1] + verts[2]) / m_div, (-verts[i + 2] + verts[3]) / m_div); i += 3; glColor3f(0.0f, 0.0f, 1.0f); glNormal3f(vnorms[i] / m_div, vnorms[i + 1] / m_div, vnorms[i + 2] / m_div); glVertex3f((-verts[i] + verts[1]) / m_div, (-verts[i + 1] + verts[2]) / m_div, (-verts[i + 2] + verts[3]) / m_div); i += 2; glEnd(); } glPopMatrix(); //Invalidate(1); }
在此处,其实代码已近写完了,然后运行一下,咦?怎么没出现图形呢?难道我们写错了?其实对着,我们动一下鼠标然后惊奇的发现得到了我们想要的结果,如下图:相比较第一幅图我们发现一样,颜色不同时由于每个顶点颜色不同而已。
三、总结:
此时,我们还有个问题没解决,为什么动一下鼠标才会显示图形呢?这是一个简单的问题,由于先前绘图的向量初始化为空,当打开文件后向量不为空,但绘图的缓存没有刷新,依旧显示没有图形。
解决方法:只需要在获取向量数据后直接刷新一下缓存就OK了,即OnFileOpen函数后添加一个InvalidateRect(NULL, FALSE)就可以了。
文章代码:https://download.csdn.net/download/belence_zhao/10416758