碎碎念
? ? ? ? 由于课设和大创涉及到了模型的旋转,因此专门去学习了模型的导入,也是废了不少心思,现在总结一下两种格式的简单导入,以及对stl模型两种格式的简单介绍。网上有很多大佬都有详细的解答,结尾附上链接;在导入较大网格数的模型时发现qt不是运行不了就是加载时间很长,网上找了很多办法也没有解决,并且当面片数过高时,由于二进制文件的三角形面片数占4字节(最多65535个三角形),因此超出时便会报溢出异常。本人太菜不知道怎么解决,只好采取blender的精简修改器减少面片数,当然面片数过低模型精度会降低,希望有大佬能解答疑问。欢迎指正。
下面是效果图展示:
?(ps:模型来自Three D Scans)
1. stl格式介绍及导出
STL文件:STL文件是一种用许多空间小三角形面片逼近三维实体表面的数据模型,STL模型的数据通过给出组成三角形法向量的3个分量(用于确定三角面片的正反方向)及三角形的3个顶点坐标来实现,一个完整的STL文件记载了组成实体模型的所有三角形面片的法向量数据和顶点坐标数据信息。目前的STL文件格式包括二进制文件(BINARY)和文本文件(ASCII)两种。
1.1 ascll码格式
1.2 二进制格式
1.3 模型导出
模型可以自己在建模软件中制作或者去建模软件下载stl格式,这里以blender为例:
? ? ? ? 1. 首先打开blender,点击你想要导出的物体,注意在OpenGL中为右手坐标系,z轴朝外,如果发现方向不对需要将模型导入建模软件中调整一下模型方向再导出;
导出模型时最好以模型原点为中心,旋转时将以模型中心旋转;
?????????2. 点击文件,导出,选择stl格式;导出模型时注意点击仅导出选中物体,可根据需要选择是否导出ascll码格式,默认导出二进制格式。
这样就成功导出stl模型文件了。
1.4?举例解释两种格式文件
以上方导出的正方体为例解释:
????????显然正方体有六个面,每个面由两个等腰直角三角形拼接而成,故一共有2*6=12个三角形,即下图这样的三角形有12组?
下面使用二进制查看器查看机器码:(或者用qt的资源文件打开也可查看)
前八十字节记录了模型基本信息,即模型是从blender导出的;
紧接着四字节为三角形面片信息;
剩下对于每个三角形有3个四字节浮点数(面片法矢量),3个顶点坐标(每个坐标为4字节的三元组)最后两个字节描述三角形面片属性信息。即每个三角形占50字节(3*4 + 3*(3*4)=50)
?2 QOpenGLWidget类进行opengl绘图
基础准备
- 在qt中新建一个c++类,继承QWidget;
- 接着在该类导入相应的头文件
#include <QOpenGLWidget>
#include<QVector>
#include<QOpenGLExtraFunctions>
#include<QOpenGLShaderProgram>
#include<QOpenGLVertexArrayObject>
#include<QOpenGLBuffer>
#include<QMatrix3x3>
#include<QMatrix4x4>
#include<QMouseEvent>
#include<QWheelEvent> - 将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(); -
demoOpenglWidget.h #ifndef DEMOOPENGLWIDGET_H
#define DEMOOPENGLWIDGET_H
#include <QOpenGLWidget>
#include<QVector>
#include<QOpenGLExtraFunctions>
#include<QOpenGLShaderProgram>
#include<QOpenGLVertexArrayObject>
#include<QOpenGLBuffer>
#include<QMatrix3x3>
#include<QMatrix4x4>
#include<QMouseEvent>
#include<QWheelEvent>
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<float> loadAscllStl(QString filename,int ratio);//加载Ascll格式的stl文件
QVector<float> loadBinStl(std::string filename, int ratio);//加载二进制格式的stl文件
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);//图片缩放功能
private:
QVector<float> vertices;
QVector<float> Position;//顶点位置
QVector<float> 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
-
在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:"<<shaderprogram.log();
if(!shaderprogram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/stl.frag"))
qDebug()<<"ERROR:"<<shaderprogram.log(); //如果编译出错,打印报错信息
//将添加到此程序的着色器与addshader链接在一起
if(!shaderprogram.link())
qDebug()<<"ERROR:"<<shaderprogram.log(); //如果链接出错,打印报错信息
//创建VAO、VBO对象并绑定
VAO.create();
VAO.bind();
VBO.create();
VBO.bind();
VBO.allocate(vertices.data(),sizeof(float)*vertices.size());
//向着色器中传入顶点、法向量属性信息
shaderprogram.setAttributeBuffer("aPos",GL_FLOAT,0,3,sizeof(GLfloat)*6);
shaderprogram.enableAttributeArray("aPos");
shaderprogram.setAttributeBuffer("aNormal", GL_FLOAT,sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 6);
shaderprogram.enableAttributeArray("aNormal");
this->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码文件
?
QVector<float> demoOpenglWidget::loadAscllStl(QString filename, int ratio)
{//ratio为缩放系数
QVector<float> vertices_temp;
qDebug()<<"load text file:"<<filename;
//读取ascll码文件
QFile file(filename);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
qDebug() << "Open stl_file failed." << endl;
}
while(!file.atEnd()){
//将文件内各个信息通过空格分隔,保存在words数组内
QString line = file.readLine().trimmed();//trim去除首尾空白字符
QStringList words = line.split(' ',QString::SkipEmptyParts);
//法向量坐标开头为"facet"
if(!file.atEnd() && words[0]=="facet"){
Normal = {ratio*words[2].toFloat(),ratio*words[3].toFloat(),ratio*words[4].toFloat()};
}
//三角形各个顶点开头为"vertex"
else if (!file.atEnd() && words[0] == "vertex") {
Position = {ratio*words[1].toFloat(), ratio*words[2].toFloat(),ratio*words[3].toFloat()};
vertices_temp.append(Position);
vertices_temp.append(Normal);
}
else
continue;
}
file.close();
qDebug() << "write vertice_temp success!" << filename;
return vertices_temp;
}
2.2 读取二进制文件
QVector<float> demoOpenglWidget::loadBinStl(std::string filename, int ratio)
{//ratio为缩放系数
QVector<float> vertices_temp;
qDebug()<<"load text file:"<<filename.c_str();
FILE *file = fopen(filename.c_str(),"rb");
//80字节文件头
char header[80];
fread(header,80,1,file);
//4字节三角形面片数量
uint32_t triangleNum;
fread(&triangleNum,sizeof(uint32_t),1,file);
float n1,n2,n3;
float p1,p2,p3;
for(auto i=0;i<triangleNum;i++){//读取法向量信息
fread(&n1,sizeof(float),1,file);
fread(&n2,sizeof(float),1,file);
fread(&n3,sizeof(float),1,file);
qDebug()<<"n1="<<n1<<" n2="<<n2<<" n3="<<n3;
Normal={n1*ratio,n2*ratio,n3*ratio};
for(auto j=0;j<3;j++){//读取顶点信息
fread(&p1,sizeof(float),1,file);
fread(&p2,sizeof(float),1,file);
fread(&p3,sizeof(float),1,file);
qDebug()<<"p1="<<p1<<" p2="<<p2<<" p3="<<p3;
Position = {p1*ratio,p2*ratio,p3*ratio};
vertices_temp.append(Position);
vertices_temp.append(Normal);
}
//2字节三角形面片属性
char c[2];
fread(c,2,1,file);
}
fclose(file);
delete file;
qDebug() << "write vertice_temp success!" << filename.c_str();
return vertices_temp;
}
?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<QStringList>
#include <QFile>
#include<QtMath>
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:"<<shaderprogram.log();
if(!shaderprogram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/stl.frag"))
qDebug()<<"ERROR:"<<shaderprogram.log(); //如果编译出错,打印报错信息
//将添加到此程序的着色器与addshader链接在一起
if(!shaderprogram.link())
qDebug()<<"ERROR:"<<shaderprogram.log(); //如果链接出错,打印报错信息
VAO.create();
VAO.bind();
VBO.create();
VBO.bind();
VBO.allocate(vertices.data(),sizeof(float)*vertices.size());
shaderprogram.setAttributeBuffer("aPos",GL_FLOAT,0,3,sizeof(GLfloat)*6);
shaderprogram.enableAttributeArray("aPos");
shaderprogram.setAttributeBuffer("aNormal", GL_FLOAT,sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 6);
shaderprogram.enableAttributeArray("aNormal");
this->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<float> demoOpenglWidget::loadAscllStl(QString filename, int ratio)
{
QVector<float> vertices_temp;
qDebug()<<"load text file:"<<filename;
QFile file(filename);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
qDebug() << "Open stl_file failed." << endl;
}
while(!file.atEnd()){
QString line = file.readLine().trimmed();//trim去除首尾空白字符
QStringList words = line.split(' ',QString::SkipEmptyParts);
if(!file.atEnd() && words[0]=="facet"){
Normal = {ratio*words[2].toFloat(),ratio*words[3].toFloat(),ratio*words[4].toFloat()};
}
else if (!file.atEnd() && words[0] == "vertex") {
Position = {ratio*words[1].toFloat(), ratio*words[2].toFloat(),ratio*words[3].toFloat()};
vertices_temp.append(Position);
vertices_temp.append(Normal);
}
else
continue;
}
file.close();
qDebug() << "write vertice_temp success!" << filename;
return vertices_temp;
}
QVector<float> demoOpenglWidget::loadBinStl(std::string filename, int ratio)
{
QVector<float> vertices_temp;
qDebug()<<"load text file:"<<filename.c_str();
FILE *file = fopen(filename.c_str(),"rb");
//80字节文件头
char header[80];
fread(header,80,1,file);
//4字节三角形面片数量
uint32_t triangleNum;
fread(&triangleNum,sizeof(uint32_t),1,file);
float n1,n2,n3;
float p1,p2,p3;
for(auto i=0;i<triangleNum;i++){//读取法向量信息
fread(&n1,sizeof(float),1,file);
fread(&n2,sizeof(float),1,file);
fread(&n3,sizeof(float),1,file);
qDebug()<<"n1="<<n1<<" n2="<<n2<<" n3="<<n3;
Normal={n1*ratio,n2*ratio,n3*ratio};
for(auto j=0;j<3;j++){//读取顶点信息
fread(&p1,sizeof(float),1,file);
fread(&p2,sizeof(float),1,file);
fread(&p3,sizeof(float),1,file);
qDebug()<<"p1="<<p1<<" p2="<<p2<<" p3="<<p3;
Position = {p1*ratio,p2*ratio,p3*ratio};
vertices_temp.append(Position);
vertices_temp.append(Normal);
}
//2字节三角形面片属性
char c[2];
fread(c,2,1,file);
}
fclose(file);
delete file;
qDebug() << "write vertice_temp success!" << filename.c_str();
return vertices_temp;
}
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();
}
?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码打开文件,成功。
- 选择物体,打开修改器,选择精简
- 修改塌陷比率,越低三角形面片越少,当然模型越粗糙。
- 可以看出此时模型三角形清晰可见,面片变少,当然模型非常粗糙。
?
这样做的目的是:
?4.总结
模型处理时:
- 坐标中心位于模型中心,方便绕着模型中心旋转 - 面片过大时在blender中修改精简比例 - 输出文件注意是ascll码格式还是二进制文件
工程目录中:
- 内存溢出时加入配置文件:CONFIG+=resources_big - 目录注意更改(ascll可用相对路径,二进制用绝对(不要出现中文))?
?
?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文件读取。
|