QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转

碎碎念

        由于课设和大创涉及到了模型的旋转,因此专门去学习了模型的导入,也是废了不少心思,现在总结一下两种格式的简单导入,以及对stl模型两种格式的简单介绍。网上有很多大佬都有详细的解答,结尾附上链接;在导入较大网格数的模型时发现qt不是运行不了就是加载时间很长,网上找了很多办法也没有解决,并且当面片数过高时,由于二进制文件的三角形面片数占4字节(最多65535个三角形),因此超出时便会报溢出异常。本人太菜不知道怎么解决,只好采取blender的精简修改器减少面片数,当然面片数过低模型精度会降低,希望有大佬能解答疑问。欢迎指正。

下面是效果图展示:

 (ps:模型来自Three D Scans)

1. stl格式介绍及导出

STL文件:STL文件是一种用许多空间小三角形面片逼近三维实体表面的数据模型,STL模型的数据通过给出组成三角形法向量的3个分量(用于确定三角面片的正反方向)及三角形的3个顶点坐标来实现,一个完整的STL文件记载了组成实体模型的所有三角形面片的法向量数据和顶点坐标数据信息。目前的STL文件格式包括二进制文件(BINARY)和文本文件(ASCII)两种。

1.1 ascll码格式

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第1张图片

1.2 二进制格式

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第2张图片

1.3 模型导出

模型可以自己在建模软件中制作或者去建模软件下载stl格式,这里以blender为例:

        1. 首先打开blender,点击你想要导出的物体,注意在OpenGL中为右手坐标系,z轴朝外,如果发现方向不对需要将模型导入建模软件中调整一下模型方向再导出;

导出模型时最好以模型原点为中心,旋转时将以模型中心旋转;QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第3张图片

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第4张图片

         2. 点击文件,导出,选择stl格式;导出模型时注意点击仅导出选中物体,可根据需要选择是否导出ascll码格式,默认导出二进制格式。

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第5张图片

这样就成功导出stl模型文件了。

1.4 举例解释两种格式文件

以上方导出的正方体为例解释:

        显然正方体有六个面,每个面由两个等腰直角三角形拼接而成,故一共有2*6=12个三角形,即下图这样的三角形有12组 

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第6张图片

下面使用二进制查看器查看机器码:(或者用qt的资源文件打开也可查看)

前八十字节记录了模型基本信息,即模型是从blender导出的;

紧接着四字节为三角形面片信息;

剩下对于每个三角形有3个四字节浮点数(面片法矢量),3个顶点坐标(每个坐标为4字节的三元组)最后两个字节描述三角形面片属性信息。即每个三角形占50字节(3*4 + 3*(3*4)=50)

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第7张图片

 2 QOpenGLWidget类进行opengl绘图

基础准备

  1. 在qt中新建一个c++类,继承QWidget;QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第8张图片
  2. 接着在该类导入相应的头文件
    #include 
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
  3. 将demoOpenglWidget的继承类改为
    class demoOpenglWidget : public QOpenGLWidget,public QOpenGLExtraFunctions

    点击QOpenGLWidget头文件,找到下面三个函数进行重载:

    protected:
    //建立OpenGL的资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次
        void initializeGL();
    //设置OpenGL视口,投影等。每当调整Widget的大小时(第一次显示窗口Widget时会调用它,因为所有新创建Widget都会自动获得调整大小的事件)
        void resizeGL(int width, int height);
    //渲染OpenGL场景,需要更新Widget时就会调用
        void paintGL();
  4. demoOpenglWidget.h

    #ifndef DEMOOPENGLWIDGET_H
    #define DEMOOPENGLWIDGET_H
    
    #include 
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    
    class demoOpenglWidget : public QOpenGLWidget,public QOpenGLExtraFunctions
    {
        Q_OBJECT
    public:
        explicit demoOpenglWidget(QWidget *parent = nullptr);
        ~demoOpenglWidget();
    public:
        double rotate_y=0;
        double rotate_x=0;
        double rotate_z=0;
    protected:
        void initializeGL();
        void resizeGL(int width, int height);
        void paintGL();
        QVector loadAscllStl(QString filename,int ratio);//加载Ascll格式的stl文件
        QVector loadBinStl(std::string filename, int ratio);//加载二进制格式的stl文件
        void mousePressEvent(QMouseEvent *event);
        void mouseMoveEvent(QMouseEvent *event);
        void wheelEvent(QWheelEvent *event);//图片缩放功能
    
    private:
        QVector vertices;
        QVector Position;//顶点位置
        QVector Normal;//法向量
        QOpenGLShaderProgram shaderprogram;
        QOpenGLVertexArrayObject VAO;
        QOpenGLBuffer VBO;
    
        //MVP
        QMatrix4x4 model;
        QMatrix4x4 view;
        QMatrix4x4 projection;
    
    //    int verticesCnt;
        GLfloat xtrans,ytrans,ztrans;
        QVector2D mousePos;
        QQuaternion rotation;
    };
    
    
    
    
    #endif // DEMOOPENGLWIDGET_H
    
  5. 在qt资源中导入顶点着色器、片段着色器,并重载initializeGL、resizeGL和paintGL。
    //stl.vert
    #version 330 core
    layout (location = 0) in vec3 aPos;   // 位置变量的属性位置值为 0
    layout (location = 1) in vec3 aNormal; // 颜色变量的属性位置值为 1
    
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;
    
    out vec3 FragPos;
    out vec3 Normal;
    
    void main()
    {
        gl_Position = projection * view * model * vec4(aPos, 1.0);
        Normal = mat3(model) * aNormal;//用于旋转时,使得法向量一起改变
        FragPos = vec3(model * vec4(aPos, 1.0));
    }
    
    
    //stl.frag
    #version 330 core
    //layout( location = 0 ) out vec4 FragColor;
    
    out vec4 FragColor;
    uniform vec3 objectColor;
    uniform vec3 lightColor;
    in vec3 FragPos;
    in vec3 Normal;
    uniform vec3 lightPos;
    
    void main()
    {
         float ambientStrength = 0.1;
           vec3 ambient = ambientStrength * lightColor;
    
           vec3 norm = normalize(Normal);
           vec3 lightDir = normalize(lightPos - FragPos);
           float diff = max(dot(norm, lightDir), 0.0);
           vec3 diffuse = diff * lightColor;
    
           vec3 result = (ambient + diffuse) * objectColor;
           FragColor = vec4(result, 1.0);
    
    }
    
    
    //渲染OpenGL场景,widget需要更新时调用
    void demoOpenglWidget::initializeGL()
    {
        this->initializeOpenGLFunctions();
        shaderprogram.create();
    
    //将着色器加载到shaderprogram小程序中(注意路径根据自己的路径名称修改)    
    if(!shaderprogram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/stl.vert"))
            qDebug()<<"ERROR:"<glEnable(GL_DEPTH_TEST);
    
        view.setToIdentity();
        view.lookAt(QVector3D(0.0f,0.0f,3.0f),QVector3D(0.0f,0.0f,0.0f),QVector3D(0.0f,1.0f,0.0f));
    
    }
    
    //设置OpenGL视口、投影等。
    void demoOpenglWidget::resizeGL(int w, int h)
    {
        this->glViewport(0,0,w,h);
        projection.setToIdentity();
        projection.perspective(60.0f,(GLfloat)w/(GLfloat)h, 0.001f, 100.0f);//视角-宽高比例-zNear-zFar
    
    }
    
    //设置OenGL资源和状态;第一次调用resizeGL/paintGL调用
    void demoOpenglWidget::paintGL()
    {
        this->glClearColor(0.0f,0.0f,0.0f,1.0f);
        this->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        shaderprogram.bind();
    
    
        //定义参数
        QVector3D lightColor(1.0f,1.0f,1.0f);
        QVector3D objectColor(1.0f,1.0f,0.88f);//模型颜色
        QVector3D lightPos(0.0f,0.0f,50.0f);
        //传值到小程序
        shaderprogram.setUniformValue("objectColor",objectColor);
        shaderprogram.setUniformValue("lightColor",lightColor);
        shaderprogram.setUniformValue("lightPos", lightPos);
    
        model.setToIdentity();
        model.translate(xtrans,ytrans,ztrans);
        model.rotate(rotation);
        shaderprogram.setUniformValue("view",view);
        shaderprogram.setUniformValue("projection", projection);
        shaderprogram.setUniformValue("model", model);
    
        int n = vertices.capacity()/sizeof(float);
        qDebug() << n;//打印stl中三角形的数量(stl是多个三角形组成的文件)
        QOpenGLVertexArrayObject::Binder bind(&VAO);//绑定VAO
        this->glDrawArrays(GL_TRIANGLES,0,n);
    
    }

2.1 读取ascll码文件

 

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第9张图片

QVector demoOpenglWidget::loadAscllStl(QString filename, int ratio)
{//ratio为缩放系数
    QVector vertices_temp;
    qDebug()<<"load text file:"<

2.2 读取二进制文件

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第10张图片

QVector demoOpenglWidget::loadBinStl(std::string filename, int ratio)
{//ratio为缩放系数
    QVector vertices_temp;
    qDebug()<<"load text file:"<

 2.3 鼠标交互


void demoOpenglWidget::mousePressEvent(QMouseEvent *event)
{
    mousePos = QVector2D(event->pos());
    event->accept();
}

void demoOpenglWidget::mouseMoveEvent(QMouseEvent *event)
{
    if(event->buttons() == Qt::LeftButton){
        QVector2D newPos = (QVector2D)event->pos();
        QVector2D diff = newPos - mousePos;
        qreal angle = (diff.length())/3.6;

        QVector3D rotationAxis = QVector3D(diff.y(), diff.x(), 0.0).normalized();
        rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angle) * rotation;
        mousePos = newPos;
        this->update();
    }
    event->accept();
}

//鼠标滚轮缩放模型

void demoOpenglWidget::wheelEvent(QWheelEvent *event)
{
    QPoint numDegrees = event->angleDelta()/8;

    if(numDegrees.y() > 0){
        ztrans += 0.25f;
    }
    else if(numDegrees.y() < 0){
        ztrans -= 0.25f;
    }
    this->update();
    event->accept();
}

 2.4 cpp源代码

#include "demoopenglwidget.h"
#include"QDebug"
#include
#include 
#include


demoOpenglWidget::demoOpenglWidget(QWidget *parent)
    : QOpenGLWidget(parent),
      VBO(QOpenGLBuffer::VertexBuffer),
      xtrans(0),ytrans(0),ztrans(0.0)
//      verticesCnt(0)//顶点坐标计数
{
    QSurfaceFormat format;
    format.setAlphaBufferSize(24);
    format.setVersion(3,3);
    format.setSamples(10);//设置重采样次数,用于反走样

    this->setFormat(format);


//导入模型文件(路径根据自己情况更改)
//    vertices=loadAscllStl(":/res/stl/demo_ascll.txt",1);
    vertices=loadBinStl(":/res/stl/demo_bin.stl",1);
}

demoOpenglWidget::~demoOpenglWidget()
{
    makeCurrent();
}

//渲染OpenGL场景,widget需要更新时调用
void demoOpenglWidget::initializeGL()
{
    this->initializeOpenGLFunctions();
    shaderprogram.create();
    if(!shaderprogram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/stl.vert"))
        qDebug()<<"ERROR:"<glEnable(GL_DEPTH_TEST);

    view.setToIdentity();
    view.lookAt(QVector3D(0.0f,0.0f,3.0f),QVector3D(0.0f,0.0f,0.0f),QVector3D(0.0f,1.0f,0.0f));

}

//设置OpenGL视口、投影等。
void demoOpenglWidget::resizeGL(int w, int h)
{
    this->glViewport(0,0,w,h);
    projection.setToIdentity();
    projection.perspective(60.0f,(GLfloat)w/(GLfloat)h, 0.001f, 100.0f);//视角-宽高比例-zNear-zFar

}

//设置OenGL资源和状态;第一次调用resizeGL/paintGL调用
void demoOpenglWidget::paintGL()
{
    this->glClearColor(0.0f,0.0f,0.0f,1.0f);
    this->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    shaderprogram.bind();


    //定义参数
    QVector3D lightColor(1.0f,1.0f,1.0f);
    QVector3D objectColor(1.0f,1.0f,0.88f);//模型颜色
    QVector3D lightPos(0.0f,0.0f,50.0f);
    //传值到小程序
    shaderprogram.setUniformValue("objectColor",objectColor);
    shaderprogram.setUniformValue("lightColor",lightColor);
    shaderprogram.setUniformValue("lightPos", lightPos);

    model.setToIdentity();
    model.translate(xtrans,ytrans,ztrans);
    model.rotate(rotation);
    shaderprogram.setUniformValue("view",view);
    shaderprogram.setUniformValue("projection", projection);
    shaderprogram.setUniformValue("model", model);

    int n = vertices.capacity()/sizeof(float);
    qDebug() << n;//打印stl中三角形的数量(stl是多个三角形组成的文件)
    QOpenGLVertexArrayObject::Binder bind(&VAO);//绑定VAO
    this->glDrawArrays(GL_TRIANGLES,0,n);

}

QVector demoOpenglWidget::loadAscllStl(QString filename, int ratio)
{
    QVector vertices_temp;
    qDebug()<<"load text file:"< demoOpenglWidget::loadBinStl(std::string filename, int ratio)
{
    QVector vertices_temp;
    qDebug()<<"load text file:"<pos());
    event->accept();
}

void demoOpenglWidget::mouseMoveEvent(QMouseEvent *event)
{
    if(event->buttons() == Qt::LeftButton){
        QVector2D newPos = (QVector2D)event->pos();
        QVector2D diff = newPos - mousePos;
        qreal angle = (diff.length())/3.6;

        QVector3D rotationAxis = QVector3D(diff.y(), diff.x(), 0.0).normalized();
        rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angle) * rotation;
        mousePos = newPos;
        this->update();
    }
    event->accept();
}

void demoOpenglWidget::wheelEvent(QWheelEvent *event)//鼠标滚轮缩放模型
{
    QPoint numDegrees = event->angleDelta()/8;

    if(numDegrees.y() > 0){
        ztrans += 0.25f;
    }
    else if(numDegrees.y() < 0){
        ztrans -= 0.25f;
    }
    this->update();
    event->accept();
}


 3.问题

3.1 内存溢出

out of memory 内存溢出 在pro配置文件加入CONFIG+=resources_big

3.2 三角形面片过多

可以发现在解析bin的文件时,默认四字节的三角形面片数,也即超过0xFFFF(65535)个面片将会内存溢出。当在解析函数中更改为64位读取时发现也不成功,仍然出现out of memory内存溢出错误;

解决方案:

配置文件加CONFIG+=resources_big

采取精简面片,此时面片为0xfe0e刚好在32位内,但此时发现文件读取不了,fopen一直打开是空指针。

查了很多博客发现还是无法解决,突然想到之前打开ascll码文件时出现文件内存溢出,再试一下ascll码打开文件,成功。

  1. 选择物体,打开修改器,选择精简QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第11张图片
  2. 修改塌陷比率,越低三角形面片越少,当然模型越粗糙。QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第12张图片
  3. 可以看出此时模型三角形清晰可见,面片变少,当然模型非常粗糙。QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第13张图片

 

这样做的目的是:

  • ascll码&&二进制文件过大,加载较慢甚至加载不出来,故此步骤相当于压缩

  • 当面片数过多时,在读取ascll码文件时,在提取完80个字节的头文件信息后,发现三角形面片数量保存在32位4字节中,当面片数量超出0xffff时,猜测面片数量将会以64位存储(甚至更大),但是具体怎么处理我还没有实现。to be continue....

 4.总结

模型处理时:

- 坐标中心位于模型中心,方便绕着模型中心旋转
- 面片过大时在blender中修改精简比例
- 输出文件注意是ascll码格式还是二进制文件

工程目录中:

- 内存溢出时加入配置文件:CONFIG+=resources_big
- 目录注意更改(ascll可用相对路径,二进制用绝对(不要出现中文)) 

QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转_第14张图片

 

 5.参考博客

QT OpenGL加载STL模型文件并旋转放缩_GT_L_0813的博客-CSDN博客

Qt/C++ + opengl 解析stl文件(二进制和Ascii两种格式)_shenmingyi.blog.csdn.net-CSDN博客_qt stl文件

6.工程文件

OpenGL_Qt: 实现加载ascll码&&二进制格式stl文件加载,并在OpenGL控件中展示。实现了鼠标拖动旋转,滚轮缩放。注意ascll可用相对路径加载,二进制仅可用绝对路径加载。值得注意的是二进制文件无法打开过大文件(打开文件指针为空),此项目只适用于较小stl文件读取。

你可能感兴趣的:(c++,windows,3d,经验分享,qt)