opengl | openmesh 读取显示3d模型文件

操作

鼠标控制物体旋转移动,滚轮缩放

F1,F2,F3 可以更换显示文件 (file1:cow.obj file2:cactus.ply file3 : Armadillo.off)

F4 更换显示模式 (wire,flat,flatlines)

截图

opengl | openmesh 读取显示3d模型文件_第1张图片

opengl | openmesh 读取显示3d模型文件_第2张图片

opengl | openmesh 读取显示3d模型文件_第3张图片

opengl | openmesh 读取显示3d模型文件_第4张图片

opengl | openmesh 读取显示3d模型文件_第5张图片

使用命令行显示当前状态

opengl | openmesh 读取显示3d模型文件_第6张图片

准备

openmesh的下载配置

  1. 下载最新的安装包
  2. 安装openmesh
  3. 配置vs

    • 工具-》选项-》项目和解决方案-》VC++目录 配置 包含文件和库文件,分别是openmesh\include和openmesh\lib两个路径(比如:加C:\Program Files (x86)\OpenMesh 2.3\include和C:\Program Files (x86)\OpenMesh 2.3\lib目录)
    • 在所建工程上右键-》属性-》预处理器-》预处理器定义添加_USE_MATH_DEFINE,同时在连接器-》输入-》附加依赖性中添加OpenMeshCored.libOpenMeshToolsd.lib
    • 在我的配置时,还出现了一个问题就是:报错 1>c:\program files (x86)\microsoft visual studio11.0\vc\include\xutility(2176): error C4996: ‘std::_Copy_impl’: Function callwith parameters that may be unsafe - this call relies on the caller to checkthat the passed values are correct. To disable this warning, use-D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ ‘CheckedIterators’ 这个的解决也是在预处理器重添加 _SCL_SECURE_NO_WARNINGS

openmesh使用和3d文件的原理

添加头文件即可:

#include <OpenMesh/Core/IO/MeshIO.hh> // 读取文件
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> // 操作文件 

mesh中3个关键元素:Face 面,Edge 边,Vertex 顶点
我们在绘图中就是遍历mesh文件中的这三种数据绘制的

具体可点击查看此链接

制作

第一步:读取文件

读取文件代码如下:

// 读取文件的函数
void readfile(string file) {
    // 请求顶点法线 vertex normals
    mesh.request_vertex_normals();
    //如果不存在顶点法线,则报错 
    if (!mesh.has_vertex_normals())
    {
        cout << "错误:标准定点属性 “法线”不存在" << endl;
        return;
    }
    // 如果有顶点发现则读取文件 
    OpenMesh::IO::Options opt;
    if (!OpenMesh::IO::read_mesh(mesh, file, opt))
    {
        cout << "无法读取文件:" << file << endl;
        return;
    }
    else cout << "成功读取文件:" << file << endl;
    cout << endl; // 为了ui显示好看一些
                  //如果不存在顶点法线,则计算出
    if (!opt.check(OpenMesh::IO::Options::VertexNormal))
    {
        // 通过面法线计算顶点法线
        mesh.request_face_normals();
        // mesh计算出顶点法线
        mesh.update_normals();
        // 释放面法线
        mesh.release_face_normals();
    }
}

这个文件读取看起来比较繁琐,但是对比网上其他实现读取的方法,我觉的这样写文件读取更加安全一些

第二步:显示

这里我使用了 显示列表 (Display list)进行显示, 关于显示列表是什么,怎么用,可以阅读以下的博客链接: link

其主要优势就是 可以优化程序的性能

//初始化顶点和面 
void initGL()
{
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearDepth(2.0);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST); //用来开启深度缓冲区的功能,启动后OPengl就可以跟踪Z轴上的像素,那么它只有在前面没有东西的情况下才会绘制这个像素,在绘制3d时,最好启用,视觉效果会比较真实
                             // ------------------- Lighting 
    glEnable(GL_LIGHTING); // 如果enbale那么就使用当前的光照参数去推导顶点的颜色
    glEnable(GL_LIGHT0); //第一个光源,而GL_LIGHT1表示第二个光源
                         // ------------------- Display List 
    showFaceList = glGenLists(1);
    showWireList = glGenLists(1);
    int temp = mesh.n_edges();

    // 绘制 wire 
    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());
    }
    glEnd();
    glEnable(GL_LIGHTING);
    glEndList();

    // 绘制flat
    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());
        }
        glEnd();
    }
    glEndList();
}

halfeage其实就是有向的边,吧所有的有向边的头尾链接起来就是网格了
face 更好画,使用绘制三角形的模式直接绘制每一个面就可以了

在以上文件中,我将绘制了面和网格的模式。在显示的时候,我会调用 glcalllist(list) 具体制定显示哪一个显示列表

第三步:良好的交互

在使用meshlab的过程中,可以可以用鼠标移动其中的物体角度,滚轮放缩

// 鼠标交互
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 (state == GLUT_UP && button == 3) {
        if (currentfile == 3)
            scale -= 0.002;
        else
            scale -= 0.1;
    }
    if (state == GLUT_UP && button == 4) {
        if (currentfile == 3)
            scale += 0.002;
        else
            scale += 0.1;
    }
    glutPostRedisplay();
}

// 鼠标运动时
void onMouseMove(int x, int y) {
    if (mousetate) {
        //x对应y是因为对应的是法向量
        yRotate += x - Oldx;
        glutPostRedisplay();
        Oldx = x;
        xRotate += y - Oldy;
        glutPostRedisplay();
        Oldy = y;
    }
}

以上函数在鼠标每次按住移动后,记录了当前位置相对于一开始按下点的移动位置,并转化为物体应该旋转的角度。滚轮事件也通过改变scale的大小来改变物体的缩放比

其他

以上就是要用到的主要技术:你还可以通过设定键盘事件来快速的更换显示文件,和显示模式等

还有一个略坑的东西就是最后一个文件极大,所以在更换文件为 file3 之后,要改变 scale 和 滑轮 滚动的缩放改变 到对应大小

全部代码

#include <iostream> 
#include <OpenMesh/Core/IO/MeshIO.hh> 
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> 
#include "GL\glut.h"
#include <math.h>
#include <Windows.h>
#include <string>

using namespace std;
typedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;

//鼠标交互有关的
int mousetate = 0; //鼠标当前的状态
GLfloat Oldx = 0.0; // 点击之前的位置
GLfloat Oldy = 0.0;
//与实现角度大小相关的参数,只需要两个就可以完成
float xRotate = 0.0f;
float yRotate = 0.0f;
float ty = 0.0f;
float scale = 1;

//文件读取有关的
MyMesh mesh;
const string file_1 = "cow.obj";
const string file_2 = "cactus.ply";
const string file_3 = "Armadillo.off";
int currentfile = 1;

GLuint showFaceList, showWireList;
int showstate = 1;
bool showFace = true;
bool showWire = false;
bool showFlatlines = false;

//初始化顶点和面 
void initGL()
{
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearDepth(2.0);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST); //用来开启深度缓冲区的功能,启动后OPengl就可以跟踪Z轴上的像素,那么它只有在前面没有东西的情况下才会绘制这个像素,在绘制3d时,最好启用,视觉效果会比较真实
                             // ------------------- Lighting 
    glEnable(GL_LIGHTING); // 如果enbale那么就使用当前的光照参数去推导顶点的颜色
    glEnable(GL_LIGHT0); //第一个光源,而GL_LIGHT1表示第二个光源
                         // ------------------- Display List 
    showFaceList = glGenLists(1);
    showWireList = glGenLists(1);
    int temp = mesh.n_edges();

    // 绘制 wire 
    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());
    }
    glEnd();
    glEnable(GL_LIGHTING);
    glEndList();

    // 绘制flat
    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());
        }
        glEnd();
    }
    glEndList();
}

// 当窗体改变大小的时候
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 readfile(string file) {
    // 请求顶点法线 vertex normals
    mesh.request_vertex_normals();
    //如果不存在顶点法线,则报错 
    if (!mesh.has_vertex_normals())
    {
        cout << "错误:标准定点属性 “法线”不存在" << endl;
        return;
    }
    // 如果有顶点发现则读取文件 
    OpenMesh::IO::Options opt;
    if (!OpenMesh::IO::read_mesh(mesh, file, opt))
    {
        cout << "无法读取文件:" << file << endl;
        return;
    }
    else cout << "成功读取文件:" << file << endl;
    cout << endl; // 为了ui显示好看一些
                  //如果不存在顶点法线,则计算出
    if (!opt.check(OpenMesh::IO::Options::VertexNormal))
    {
        // 通过面法线计算顶点法线
        mesh.request_face_normals();
        // mesh计算出顶点法线
        mesh.update_normals();
        // 释放面法线
        mesh.release_face_normals();
    }
}

// 键盘交互 1. 切换文件 2.切换显示
void mySpecial(int key, int x, int y) {
    switch (key) {
    case GLUT_KEY_F1:
        cout << "读取文件:" << file_1 << " 中......" << endl;
        readfile(file_1);
        scale = 1.0;
        currentfile = 1;
        initGL();
        break;
    case GLUT_KEY_F2:
        cout << "读取文件:" << file_2 << " 中......" << endl;
        readfile(file_2);
        scale = 1.2;
        currentfile = 2;
        initGL();
        break;
    case GLUT_KEY_F3:
        cout << "读取文件:" << file_3 << " 中......" << endl;
        readfile(file_3);
        scale = 0.01;
        currentfile = 3;
        initGL();
        break;
    case GLUT_KEY_F4:
        if (showFace == true) {
            showFace = false;
            showWire = true;
            cout << "切换显示模式为:WireFrame" << endl;
        }
        else if (showWire == true)
        {
            showWire = false;
            showFlatlines = true;
            cout << "切换显示模式为:Flatlines" << endl;
        }
        else if (showFlatlines == true) {
            showFlatlines = false;
            showFace = true;
            cout << "切换显示模式为:Flat" << endl;
        }
        break;
    case GLUT_KEY_UP:
        ty += 0.01;
        break;
    case GLUT_KEY_DOWN:
        ty -= 0.01;
        break;
    default:
        break;
    }
    glutPostRedisplay();
}

// 鼠标交互
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 (state == GLUT_UP && button == 3) {
        if (currentfile == 3)
            scale -= 0.002;
        else
            scale -= 0.1;
    }
    if (state == GLUT_UP && button == 4) {
        if (currentfile == 3)
            scale += 0.002;
        else
            scale += 0.1;
    }
    glutPostRedisplay();
}

// 鼠标运动时
void onMouseMove(int x, int y) {
    if (mousetate) {
        //x对应y是因为对应的是法向量
        yRotate += x - Oldx;
        glutPostRedisplay();
        Oldx = x;
        xRotate += y - Oldy;
        glutPostRedisplay();
        Oldy = y;
    }
}

void myDisplay()
{
    //要清除之前的深度缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    //与显示相关的函数
    glRotatef(xRotate, 1.0f, 0.0f, 0.0f); // 让物体旋转的函数 第一个参数是角度大小,后面的参数是旋转的法向量
    glRotatef(yRotate, 0.0f, 1.0f, 0.0f);
    glTranslatef(0.0f, 0.0f, ty);
    glScalef(scale, scale, scale); // 缩放

                                   //每次display都要使用glcalllist回调函数显示想显示的顶点列表
    if (showFace)
        glCallList(showFaceList);
    if (showFlatlines) {
        glCallList(showFaceList);
        glCallList(showWireList);
    }
    if (showWire)
        glCallList(showWireList);

    glutSwapBuffers(); //这是Opengl中用于实现双缓存技术的一个重要函数
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); // GLUT_Double 表示使用双缓存而非单缓存
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(800, 500);
    glutCreateWindow("Mesh Viewer");
    //一开始默认读取文件1
    readfile(file_1);
    initGL();
    glutMouseFunc(myMouse);
    glutMotionFunc(onMouseMove); // 鼠标移动的时候的函数
    glutSpecialFunc(&mySpecial);
    glutReshapeFunc(&myReshape);
    glutDisplayFunc(&myDisplay);

    glutMainLoop();
    return 0;
}

你可能感兴趣的:(OpenGL)