参考网址:https://blog.csdn.net/chaojiwudixiaofeixia/article/details/50498629
https://blog.csdn.net/qq_28057541/article/details/51362945
本次实验的对象是斯坦福大学著名的bunny模型(兔子模型)。本文主要介绍了如何如下两点:
- 使用openmesh读取obj文件
- 使用opengl重绘读取的文件
本文所用的3d模型示意图如下所示:
具体的实验步骤讲解如下所示:
- 利用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 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(); }
- 完整的代码如下所示
#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; }
常用的模型库:https://blog.csdn.net/u013467442/article/details/46673331