1. 程式人生 > >MFC+OpenGL三維繪圖(二)——開啟一個STL檔案並顯示

MFC+OpenGL三維繪圖(二)——開啟一個STL檔案並顯示

    在上一節中,我們主要介紹瞭如何在VS2013平臺上利用OpenGL庫函式開發一個簡單的三維繪圖軟體。但那個軟體只是搭建一個簡單的三維繪圖軟體平臺,除了實現影象簡單的平移、旋轉、縮放功能外並沒有什麼實際的作用,但不用擔心,那只是三維圖形軟體繪製的基礎,為了實現一個完整的繪圖或影象處理軟體,我們就一步一步的實現這個功能。

    我們知道對於一個三維的處理軟體,首先應該有開啟指定檔案的功能,特別像一些三維繪圖軟體,這種功能是非常重要的。基於以上目的,我們本節內容介紹如何開啟一個STL檔案。

宣告:本節內容是在上節所完成的OpenGLDrawing軟體上執行的。

    在進行軟體編寫之前,我們首先必須對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檔案進行編寫。

在對STL檔案的格式清楚後,我們就可以進行程式的編寫了。

二、STL檔案開啟與顯示功能的實現

    在STL檔案程式設計之前還需要做的就是STL檔案的生成,本文章採用的是上圖所示的三維檔案,是在UG下生成的一個簡單檔案,然後匯出STL檔案,並儲存txt格式。stl的txt檔案必須有,其生成方法較多,可自行查詢。也參考下面網址:

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)就可以了。