1. 程式人生 > >opengl+openmesh重繪rabbit

opengl+openmesh重繪rabbit

本次實驗的物件是斯坦福大學著名的bunny模型(兔子模型)。本文主要介紹瞭如何如下兩點:

  • 使用openmesh讀取obj檔案
  • 使用opengl重繪讀取的檔案

本文所用的3d模型示意圖如下所示:

具體的實驗步驟講解如下所示: 

  1. 利用openMesh讀取obj檔案
    void readfile(string file) {
    	//請求頂點法線
    	mesh.request_vertex_normals();
    	//如果頂點法線不存在則報錯
    	if (!mesh.has_vertex_normals())
    	{
    		cout << "Error:Standard Vertex Property 'Normals' Not Available!" << endl;
    		return;
    	}
    	//讀取obj檔案
    	OpenMesh::IO::Options ort;
    	if (!OpenMesh::IO::read_mesh(mesh, file, ort))
    	{
    		cout << "Error:Cannot Read File:" << file << endl;
    		return;
    	}
    	else
    	{
    		cout << "Success Read File:" << file << endl;
    	}
    	//如果不存在頂點法線,則計算頂點法線
    	if (!ort.check(OpenMesh::IO::Options::VertexNormal))
    	{
    		//請求面法線
    		mesh.request_face_normals();
    		//利用面法線計算頂點法線
    		mesh.update_normals();
    		//釋放面法線
    		mesh.release_face_normals();
    	}
    }
  2. 初始化頂點與面的繪製
    //初始化頂點與面的繪製 
    void initGL()
    {
    	//設定背景顏色值為黑色
    	glClearColor(0.0, 0.0, 0.0, 0.0);
    	//設定清除深度快取時使用的深度值為2
    	glClearDepth(2.0);
    	//設定兩點間顏色變化為過渡模式
    	glShadeModel(GL_SMOOTH);
    	//開啟深度緩衝區的功能,從而跟蹤Z軸上的畫素
    	glEnable(GL_DEPTH_TEST);
        //啟用光源,利用當前的光照引數推導頂點顏色
    	glEnable(GL_LIGHTING);
    	//啟用1號光源
    	glEnable(GL_LIGHT0);
    	//申請一個面的顯示列表,從而實現加速顯示
    	showFaceList = glGenLists(1);
    	//申請一個線的顯示列表,從而實現加速顯示
    	showWireList = glGenLists(1);
    	//獲取網格中邊的數量
    	int temp = mesh.n_edges();
    
    	/*繪製圖像的線*/
    	//建立線的顯示列表,並指定模式為編譯模式
    	glNewList(showWireList, GL_COMPILE);
    	//關閉光源
    	glDisable(GL_LIGHTING);
    	//設定線的寬度
    	glLineWidth(1.0f);
    	//設定線的顏色為灰色
    	glColor3f(0.5f, 0.5f, 0.5f);
    	//將頂點作為線段進行處理,即雙定點線段
    	glBegin(GL_LINES);
    	for (MyMesh::HalfedgeIter he_it = mesh.halfedges_begin(); he_it != mesh.halfedges_end(); ++he_it) {
    		//連線有向邊的起點與終點
    		glVertex3fv(mesh.point(mesh.from_vertex_handle(*he_it)).data());
    		glVertex3fv(mesh.point(mesh.to_vertex_handle(*he_it)).data());
    	}
    	//與glBegin()配合
    	glEnd();
    	//啟用光源
    	glEnable(GL_LIGHTING);
    	//與glNewList配合
    	glEndList();
    
    	/*繪製圖像的面*/
    	//建立面的顯示列表,並指定模式為編譯模式
    	glNewList(showFaceList, GL_COMPILE);
    	for (MyMesh::FaceIter f_it = mesh.faces_begin(); f_it != mesh.faces_end(); ++f_it) {
    		//將頂點作為線段進行處理,即雙定點線段
    		glBegin(GL_TRIANGLES);
    		for (MyMesh::FaceVertexIter fv_it = mesh.fv_iter(*f_it); fv_it.is_valid(); ++fv_it) {
    			//連線法向量的起點與終點
    			glNormal3fv(mesh.normal(*fv_it).data());
    			//連線有向邊的起點與終點
    			glVertex3fv(mesh.point(*fv_it).data());
    		}
    		//與glBegin()配合
    		glEnd();
    	}
    	//與glNewList配合
    	glEndList();
    }
    
  3. 設定滑鼠的互動操作
    //滑鼠互動
    void myMouse(int button, int state, int x, int y)
    {
    	//滑鼠左鍵按下
    	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
    		mousetate = 1;
    		Oldx = x;
    		Oldy = y;
    	}
    	//滑鼠右鍵按下
    	if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    	{
    		mousetate = 0;
    	}
    	//滾輪事件:縮小操作
    	if (button == 3 && state == GLUT_UP) {
    			scale -= 0.1f;
    	}
    	//滾輪事件:放大操作
    	if (button == 4 && state == GLUT_UP) {
    			scale += 0.1f;
    	}
    	//標記重繪該介面
    	glutPostRedisplay();
    }
    
    // 滑鼠運動時
    void onMouseMove(int x, int y) {
    	if (mousetate) {
    		//計算y軸旋轉角度:x對應y是因為其對應的是法向量
    		yRotate += x - Oldx;
    		//標記當前視窗需要重繪
    		glutPostRedisplay();
    		//更新Oldx的數值
    		Oldx = x;
    		//計算x軸旋轉角度:y對應x是因為其對應的是法向量
    		xRotate += y - Oldy;
    		//標記當前視窗需要重繪
    		glutPostRedisplay();
    		//更新Oldy的數值
    		Oldy = y;
    	}
    }
  4. 設定鍵盤的互動操作
    //鍵盤操作顯示模式
    void myKeyboard(unsigned char key, int x, int y)
    {
    	switch (key) {
    	case '1':
    		showFaces = true;
    		showWires = false;
    		showFaceWires = false;
    		break;
    	case '2':
    		showFaces = false;
    		showWires = true;
    		showFaceWires = false;
    		break;
    	case '3':
    		showFaces = false;
    		showWires = false;
    		showFaceWires = true;
    		break;
    	default:
    		break;
    	}
    	glutPostRedisplay();
    }
  5. 編寫影象的繪製函式
    //影象顯示函式
    void myDisplay()
    {
    	//要清除之前的深度快取
    	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    	//將矩陣單位化
    	glLoadIdentity();
    	//讓物體在三維空間中進行縮放
    	glScalef(scale, scale, scale);
    	//讓物體繞x軸旋轉xRotate度
    	glRotatef(xRotate, 1.0f, 0.0f, 0.0f);
    	//讓物體繞y軸旋轉yRotate度
    	glRotatef(yRotate, 0.0f, 1.0f, 0.0f);
    	//讓物體沿著z軸平移
    	glTranslatef(0.0f, 0.0f, 0.0f);
    
    	/*傳遞想要顯示的列表*/
    	//顯示影象的面
    	if (showFaces)
    	{
    		glCallList(showFaceList);
    	}
    	//顯示影象的線
    	if (showWires)
    	{
    		glCallList(showWireList);
    	}
    	//顯示影象的面和線
    	if (showFaceWires) {
    		glCallList(showFaceList);
    		glCallList(showWireList);
    	}
    	//將前後緩衝區域進行交換
    	glutSwapBuffers(); 
    }
    
  6. 完整的程式碼如下所示
    #include <OpenMesh/Core/IO/MeshIO.hh>                 // 讀取檔案
    #include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> // 操作檔案
    #include <iostream>
    #include <math.h>
    #include <string.h>
    #include <sstream>
    #include <fstream>
    #include <vector>
    #include <windows.h>
    #include "GL\freeglut.h"
    
    using namespace std;
    typedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;
    
    //檔案讀取有關的定義
    MyMesh mesh;
    const string modelfile = "bunny.obj";
    
    //檔案的旋轉與縮放
    float scale = 1;
    float xRotate = 0.0f;
    float yRotate = 0.0f;
    
    //定義點、線、面的顯示列表
    GLuint showFaceList, showWireList;
    
    //設定顯示的模式
    bool showFaces = false;
    bool showWires = false;
    bool showFaceWires = true;
    
    //滑鼠互動有關的定義
    int mousetate = 0;  //滑鼠當前的狀態
    GLfloat Oldx = 0.0; //點選之前的x座標
    GLfloat Oldy = 0.0; //點選之前的y座標
    
    //利用OpenMesh讀取obj檔案
    void readfile(string file) {
    	//請求頂點法線
    	mesh.request_vertex_normals();
    	//如果頂點法線不存在則報錯
    	if (!mesh.has_vertex_normals())
    	{
    		cout << "Error:Standard Vertex Property 'Normals' Not Available!" << endl;
    		return;
    	}
    	//讀取obj檔案
    	OpenMesh::IO::Options ort;
    	if (!OpenMesh::IO::read_mesh(mesh, file, ort))
    	{
    		cout << "Error:Cannot Read File:" << file << endl;
    		return;
    	}
    	else
    	{
    		cout << "Success Read File:" << file << endl;
    	}
    	//如果不存在頂點法線,則計算頂點法線
    	if (!ort.check(OpenMesh::IO::Options::VertexNormal))
    	{
    		//請求面法線
    		mesh.request_face_normals();
    		//利用面法線計算頂點法線
    		mesh.update_normals();
    		//釋放面法線
    		mesh.release_face_normals();
    	}
    }
    
    //改變視窗大小時重繪函式
    void myReshape(GLint w, GLint h)
    {
    	//設定顯示視窗的大小
    	glViewport(0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h));
    	//將當前矩陣指定為投影矩陣
    	glMatrixMode(GL_PROJECTION);
    	//將矩陣設定為單位矩陣
    	glLoadIdentity();
    	/*設定修建空間的範圍,進行正射投影,從而建立一個平行視景圖*/
    	if (w > h)
    		glOrtho(-static_cast<GLdouble>(w) / h, static_cast<GLdouble>(w) / h, -1.0, 1.0, -100.0, 100.0);
    	else
    		glOrtho(-1.0, 1.0, -static_cast<GLdouble>(h) / w, static_cast<GLdouble>(h) / w, -100.0, 100.0);
    	//將當前矩陣設定為模型檢視矩陣
    	glMatrixMode(GL_MODELVIEW);
    	//將矩陣設定為單位矩陣
    	glLoadIdentity();
    }
    
    //閒置顯示操作函式
    void myIdle()
    {
    	//設定繞x軸的旋轉角度
    	xRotate += 0.5f;
    	//設定繞y軸的旋轉角度
    	yRotate += 0.5f;
    	//調整繞x軸超限後的旋轉角度
    	if (xRotate >= 360.0f)
    	{
    		xRotate -= 360.0f;
    	}
    	//調整繞y軸超限後的旋轉角度
    	if (yRotate >= 360.0f)
    	{
    		yRotate -= 360.0f;
    	}
    	//標記重繪此介面
    	glutPostRedisplay();
    }
    
    //初始化頂點與面的繪製 
    void initGL()
    {
    	//設定背景顏色值為黑色
    	glClearColor(0.0, 0.0, 0.0, 0.0);
    	//設定清除深度快取時使用的深度值為2
    	glClearDepth(2.0);
    	//設定兩點間顏色變化為過渡模式
    	glShadeModel(GL_SMOOTH);
    	//開啟深度緩衝區的功能,從而跟蹤Z軸上的畫素
    	glEnable(GL_DEPTH_TEST);
        //啟用光源,利用當前的光照引數推導頂點顏色
    	glEnable(GL_LIGHTING);
    	//啟用1號光源
    	glEnable(GL_LIGHT0);
    	//申請一個面的顯示列表,從而實現加速顯示
    	showFaceList = glGenLists(1);
    	//申請一個線的顯示列表,從而實現加速顯示
    	showWireList = glGenLists(1);
    	//獲取網格中邊的數量
    	int temp = mesh.n_edges();
    
    	/*繪製圖像的線*/
    	//建立線的顯示列表,並指定模式為編譯模式
    	glNewList(showWireList, GL_COMPILE);
    	//關閉光源
    	glDisable(GL_LIGHTING);
    	//設定線的寬度
    	glLineWidth(1.0f);
    	//設定線的顏色為灰色
    	glColor3f(0.5f, 0.5f, 0.5f);
    	//將頂點作為線段進行處理,即雙定點線段
    	glBegin(GL_LINES);
    	for (MyMesh::HalfedgeIter he_it = mesh.halfedges_begin(); he_it != mesh.halfedges_end(); ++he_it) {
    		//連線有向邊的起點與終點
    		glVertex3fv(mesh.point(mesh.from_vertex_handle(*he_it)).data());
    		glVertex3fv(mesh.point(mesh.to_vertex_handle(*he_it)).data());
    	}
    	//與glBegin()配合
    	glEnd();
    	//啟用光源
    	glEnable(GL_LIGHTING);
    	//與glNewList配合
    	glEndList();
    
    	/*繪製圖像的面*/
    	//建立面的顯示列表,並指定模式為編譯模式
    	glNewList(showFaceList, GL_COMPILE);
    	for (MyMesh::FaceIter f_it = mesh.faces_begin(); f_it != mesh.faces_end(); ++f_it) {
    		//將頂點作為線段進行處理,即雙定點線段
    		glBegin(GL_TRIANGLES);
    		for (MyMesh::FaceVertexIter fv_it = mesh.fv_iter(*f_it); fv_it.is_valid(); ++fv_it) {
    			//連線法向量的起點與終點
    			glNormal3fv(mesh.normal(*fv_it).data());
    			//連線有向邊的起點與終點
    			glVertex3fv(mesh.point(*fv_it).data());
    		}
    		//與glBegin()配合
    		glEnd();
    	}
    	//與glNewList配合
    	glEndList();
    }
    
    //滑鼠互動
    void myMouse(int button, int state, int x, int y)
    {
    	//滑鼠左鍵按下
    	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
    		mousetate = 1;
    		Oldx = x;
    		Oldy = y;
    	}
    	//滑鼠右鍵按下
    	if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    	{
    		mousetate = 0;
    	}
    	//滾輪事件:縮小操作
    	if (button == 3 && state == GLUT_UP) {
    			scale -= 0.1f;
    	}
    	//滾輪事件:放大操作
    	if (button == 4 && state == GLUT_UP) {
    			scale += 0.1f;
    	}
    	//標記重繪該介面
    	glutPostRedisplay();
    }
    
    // 滑鼠運動時
    void onMouseMove(int x, int y) {
    	if (mousetate) {
    		//計算y軸旋轉角度:x對應y是因為其對應的是法向量
    		yRotate += x - Oldx;
    		//標記當前視窗需要重繪
    		glutPostRedisplay();
    		//更新Oldx的數值
    		Oldx = x;
    		//計算x軸旋轉角度:y對應x是因為其對應的是法向量
    		xRotate += y - Oldy;
    		//標記當前視窗需要重繪
    		glutPostRedisplay();
    		//更新Oldy的數值
    		Oldy = y;
    	}
    }
    
    //鍵盤操作顯示模式
    void myKeyboard(unsigned char key, int x, int y)
    {
    	switch (key) {
    	case '1':
    		showFaces = true;
    		showWires = false;
    		showFaceWires = false;
    		break;
    	case '2':
    		showFaces = false;
    		showWires = true;
    		showFaceWires = false;
    		break;
    	case '3':
    		showFaces = false;
    		showWires = false;
    		showFaceWires = true;
    		break;
    	default:
    		break;
    	}
    	glutPostRedisplay();
    }
    
    //影象顯示函式
    void myDisplay()
    {
    	//要清除之前的深度快取
    	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    	//將矩陣單位化
    	glLoadIdentity();
    	//讓物體在三維空間中進行縮放
    	glScalef(scale, scale, scale);
    	//讓物體繞x軸旋轉xRotate度
    	glRotatef(xRotate, 1.0f, 0.0f, 0.0f);
    	//讓物體繞y軸旋轉yRotate度
    	glRotatef(yRotate, 0.0f, 1.0f, 0.0f);
    	//讓物體沿著z軸平移
    	glTranslatef(0.0f, 0.0f, 0.0f);
    
    	/*傳遞想要顯示的列表*/
    	//顯示影象的面
    	if (showFaces)
    	{
    		glCallList(showFaceList);
    	}
    	//顯示影象的線
    	if (showWires)
    	{
    		glCallList(showWireList);
    	}
    	//顯示影象的面和線
    	if (showFaceWires) {
    		glCallList(showFaceList);
    		glCallList(showWireList);
    	}
    	//將前後緩衝區域進行交換
    	glutSwapBuffers(); 
    }
    
    int main(int argc, char** argv)
    {
    	//初始化glut庫
    	glutInit(&argc, argv);
    	//設定glut的模式
    	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    	//設定顯示視窗的位置
    	glutInitWindowPosition(100, 100);
    	//設定顯示視窗的大小
    	glutInitWindowSize(800, 500);
    	//設定顯示視窗的名稱
    	glutCreateWindow("Mesh Viewer");
    	//讀取模型檔案
    	readfile(modelfile);
    	//初始化頂點與面的繪製
    	initGL();
    	//滑鼠互動功能
    	glutMouseFunc(myMouse);
    	//滑鼠移動功能
    	glutMotionFunc(onMouseMove);
    	//按鍵操作顯示模式
    	glutKeyboardFunc(&myKeyboard);
    	//調整介面大小後的重繪操作
    	glutReshapeFunc(&myReshape);
    	//影象的繪製
    	glutDisplayFunc(&myDisplay);
    	//空閒時呼叫的影象繪製函式
    	glutIdleFunc(&myIdle);
    	//glut迴圈更新介面操作
    	glutMainLoop();
    	return 0;
    }