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

操作

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

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

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

截图

使用命令行显示当前状态


准备

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;
}
时间: 2024-10-09 17:20:18

opengl | openmesh 读取显示3d模型文件的相关文章

Qt 3D研究(三):显示3D模型

Qt 3D研究(三):显示3D模型 上一篇文章介绍了如何使用最少的代码创建一个Qt 3D的应用.和大家最初接触的glut一样,对于3D应用来说,需要做的准备工作还真不少,不过呢,Qt 3D把一些窗口相关的琐碎事情解决了,剩下的,该由我们完成重要的渲染部分了,可以说,带来了某种程度的方便. 蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/43964499.欢迎同行前来探讨. 我们接下来要使用Qt 3D将一个模型显示出来.Qt 3

将.stl文件转化为.dae并动态加载到SceneKit显示(ios中显示3d模型)

ios8之后苹果推出了一个3D模型渲染框架.SceneKit.但是国内针对这方面的教程并不是很多.前两天搞了一下也是一头雾水,终于把最基础的内容搞明白了之后,写下这篇随笔作为cnblogs的开篇,希望能一直写下去. SceneKit现在可以支持有限的几种模型,截止到我写这篇文章为止似乎只有.dae和.abc后一种模型我没有使用过.这篇文章只针对.dae模型写. 首先如果是希望加载一个已有的,不需要程序在运行的时候动态添加的dae模型.那么我们可以直接新建一个game类型的工程.在选项中选择Sce

3D模型文件读写.Net SDK

AnyCAD .Net/C++ SDK支持多种3D/2D文件格式,比如BREP.STEP.IGES.STL.DXF.3DS.OBJ.FBX.SKP.IFC.DAE……等,根据使用场景提供不同的API. 1.   几何数据I/O 支持BREP.IGES.STEP.STL格式,其中BREP支持读取和保存字符串流. 1.1.  读取文件 支持读取BREP.IGES.STEP和STL,结果保存在TopoShape中. STEP文件读取示例: TopoShape shape = GlobalInstanc

Flash Stage3D 在2D UI 界面上显示3D模型问题完美解决

一直以来很多Stage3D开发者都在为3D模型在2DUI上显示的问题头疼.Stage3D一直是在 Stage2D下面.为了做到3D模型在2DUI上显示通常大家有几种实现方式,下面来说说这几种实现方式吧. 实现方式1: 在2DUI上挖个洞透过去显示3D层.这种做法的缺陷在于如果两个UI界面同时打开就会UI错层显示错乱.为了解决这问题很多程序员选择了当挖洞显示3D的UI打时把其他界面隐藏掉,用户体验超差. 实现方式2: 利用Context3D 的 drawToBitmapData API 实时将3D

3D模型文件——OBJ 文件格式

OBJ文件是一种标准的3D模型文件格式,以纯文本的形式存储了模型的顶点.法线和纹理坐标和材质使用信息,因此可以使用记事本打开直接阅读. 在OBJ文件中,每一行为一条信息,每行的格式如下:前缀  参数1 参数2 参数3 ... 常用前缀 #表示注释的前缀 v 表示本行指定一个坐标.后跟着3个文本表示的float,分别表示该定点的X.Y.Z坐标值(参数之间使用空格分开) vt 表示本行指定一个纹理坐标.后跟着2个文本表示的float.分别表示此纹理坐标的U.V值(参数之间使用空格分开) vn 表示本

3D模型文件 OBJ格式模型详细介绍

obj格式有4种数据,分别以一下字母开头: v顶点 vt纹理坐标 vn顶点法向量 f 面 一.顶点 v 0.232323 0.8978, 0.34312 格式:v x y z意义:每个顶点的坐标 二.纹理坐标 vt 0.032 0.005 0 格式:vt u v w 意义:绘制模型的三角面片时,每个顶点取像素点时对应的纹理图片上的坐标.纹理图片的坐标指的是,纹理图片如果被放在屏幕上显示时,以屏幕左下角为原点的坐标.注意:w一般用于形容三维纹理,大部分是用不到的,基本都为0. 三.顶点法向量 vn

C# winform用sharpGL(OpenGl)解析读取3D模型obj

原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11783026.html 自己写了个简单的类读取解析obj模型,使用导入类,然后new个对象,在读取obj模型,然后调用显示列表显示就可以了.至于其他什么旋转移动的你们自己加起来应该很容易的,因为我有看过c#下别人写的obj模型解析的代码项目,加了很多东西,我都找不到自己要用的代码在哪里,而我只需要读取解析obj模型这块代码而已,气的我自己写了个类自己解析,所以我怕我代码写多了, 你们反而看起

自制C#版3DS文件的解析器并用SharpGL显示3DS模型

+BIT祝威+悄悄在此留下版了个权的信息说: 据说*.3ds格式的3D模型文件是很古老和过时的格式.本文参考了(http://www.spacesimulator.net/wiki/index.php?title=Tutorials:3ds_Loader)和(http://www.cnblogs.com/lookof/archive/2009/03/27/1423695.html),在此表示感谢.本文讲解如何从零开始用C#写一个3ds文件的解析器,然后用SharpGL(C#对opengl的封装)

3d模型一般怎么导入到到Threejs中使用

这是我之前做的一个demo,导入的3d模型文件是obj格式的,需要使用OBJLoader和MTLLoader, mtl文件用于描述多边形可视面貌的材质如果你可以导出obj.mtl文件的话,那么就可以使用下面的代码把3d模型添加到three.js构建的场景里了 function loadBuild() { var loader = new THREE.OBJLoader(); var mtlLoader = new THREE.MTLLoader(); mtlLoader.setPath( "ci