模型文件有很多种格式,有一种方法是利用Assimp库来加载各类格式的模型,也就是众生平等!
但是本人太菜,网上搜索的各种方法我都没能成功在QT中实现,而且本人项目需求不高,所以我选择了简单的方法来加载模型文件——STL模型文件加载,也就是说我这个只能加载一个格式的模型文件,但对我来说够用了。
文件格式如上图:STL格式是将模型保存为一个又一个的小三角形,精度越高,三角形数量越多。
其中fact-endfact之间是一个三角形的信息,包括facet normal后面的是该面的法向量,用于光照。
三个vertex是三角形三个顶点。通过如下函数可将该模型文件的信息读入数组之中。
vertices = loadAscllStl("C:\\Users\\86132\\Desktop\\STL_model_ascll\\test1.STL",1);
ratio是放大系数,用于模型文件尺寸数据过大或者过小时,可用该系数调整。
QVector<float> Widget::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(); // trimmed去除了开头和结尾的空白字符串
QStringList words = line.split(' ', QString::SkipEmptyParts);
if (words[0] == "facet") {
Normal = {ratio*words[2].toFloat(), ratio*words[3].toFloat(),ratio*words[4].toFloat()};//获取法向量
}
else if (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;
}
}
qDebug() << "write vertice_temp success!" << filename;
return vertices_temp;
// file.close();
}
这里填写模型文件地址时,我始终加载不成功,最后找到个说法,得用双斜杠在地址中才能加载成功!
首先,最好以原点为模型的中心,这样旋转的时候才会以模型的中心旋转,而不是以某一顶点旋转。
其次,保存文件时,在选项中可选择单位,精度,以及勾选不将坐标转为正空间。如果不勾选,软件回默认将你的模型坐标全部转为正坐标,这样,即使你画模型时,将模型中心放在原点,也会全部被转为正坐标,到时候出来的模型旋转会以一个顶点而非中心旋转。
精度选取可自行选取,看上面的GIF,中间圆柱很明显像是很多长方形连起来的,这是因为我选的精度不高。
这部分原理与坐标变换那部分相同。主要是获取鼠标的操作信息,来获取xtrans,ytrans,ztrans。然后改变model矩阵,通过该矩阵与模型顶点数据相乘,即可得到旋转放缩的模型。之前的旋转的立方体的model是随时间变化,这里只不过改成了随鼠标的输入变化而已。
void Widget::mousePressEvent(QMouseEvent *event)//鼠标事件获取
{
mousePos = QVector2D(event->pos());
event->accept();
}
void Widget::mouseMoveEvent(QMouseEvent *event)//鼠标移动
{
if(event->buttons() == Qt::LeftButton)
{
QVector2D newPos = (QVector2D)event->pos();
QVector2D diff = newPos - mousePos;
qreal angle = (diff.length())/3.6;
// Rotation axis is perpendicular to the mouse position difference
// vector
QVector3D rotationAxis = QVector3D(diff.y(), diff.x(), 0.0).normalized();
rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angle) * rotation;
mousePos = newPos;
this->update();
}
event->accept();
}
void Widget::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();
}
model.setToIdentity();
model.translate(xtrans, ytrans, ztrans);
model.rotate(rotation);
shaderprogram.setUniformValue("model", model);
gl_Position = projection * view * model * vec4(aPos, 1.0);
QVector3D lightColor(1.0f,1.0f,1.0f);
QVector3D objectColor(1.0f,0.5f,0.31f);
QVector3D lightPos(0.0f,0.0f,50.0f);//光源位置
shaderprogram.setUniformValue("objectColor",objectColor);
shaderprogram.setUniformValue("lightColor",lightColor);
shaderprogram.setUniformValue("lightPos", lightPos);
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);
main.cpp
#include "widget.h"
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
w.resize(800, 600);
return a.exec();
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class Widget : public QOpenGLWidget,public QOpenGLExtraFunctions
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
virtual void initializeGL() override;
virtual void paintGL() override;
virtual void resizeGL(int w,int h) override;
QVector<float> loadAscllStl(QString filename,int ratio);//文件名和放大系数
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;//声明VAO顶点数组对象
QOpenGLBuffer VBO;//声明VBO数组缓冲对象
QMatrix4x4 model;
QMatrix4x4 view;
QMatrix4x4 projection;
GLfloat xtrans, ytrans, ztrans; // translation on x,y,z-axis
QVector2D mousePos;
QQuaternion rotation;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
,VBO(QOpenGLBuffer::VertexBuffer)
,xtrans(0),ytrans(0),ztrans(0.0)
{
QSurfaceFormat format;
format.setAlphaBufferSize(24); //设置alpha缓冲大小
format.setVersion(3,3); //设置版本号
format.setSamples(10); //设置重采样次数,用于反走样
this->setFormat(format);
vertices = loadAscllStl("C:\\Users\\86132\\Desktop\\STL_model_ascll\\test1.STL",1);
}
Widget::~Widget()
{
makeCurrent();
}
QVector<float> Widget::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(); // trimmed去除了开头和结尾的空白字符串
QStringList words = line.split(' ', QString::SkipEmptyParts);
if (words[0] == "facet") {
Normal = {ratio*words[2].toFloat(), ratio*words[3].toFloat(),ratio*words[4].toFloat()};
}
else if (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;
}
}
qDebug() << "write vertice_temp success!" << filename;
return vertices_temp;
// file.close();
}
void Widget::initializeGL()
{
this->initializeOpenGLFunctions();//初始化opengl函数
shaderprogram.create();//生成着色器程序
if(!shaderprogram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/stl.vert")){
qDebug()<<"ERROR:"<<shaderprogram.log(); //如果编译出错,打印报错信息
}
if(!shaderprogram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/stl.frag")){
qDebug()<<"ERROR:"<<shaderprogram.log(); //如果编译出错,打印报错信息
}
//将添加到此程序的着色器与addshader链接在一起
if(!shaderprogram.link()){
qDebug()<<"ERROR:"<<shaderprogram.log(); //如果链接出错,打印报错信息
}
// QOpenGLVertexArrayObject::Binder{&VAO};
VAO.create();// 创建一个VAO对象,OpenGL会给它(顶点数组缓存对象)分配一个id
VAO.bind();//将RC中的当前顶点数组缓存对象Id设置为VAO的id
VBO.create();
VBO.bind();
VBO.allocate(vertices.data(),sizeof(float)*vertices.size());//将顶点数据分配到VBO中,第一个参数为数据指针,第二个参数为数据的字节长度
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);
// VAO.release();//释放
// VBO.release();
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));
//shaderprogram.setUniformValue("view", view);
}
void Widget::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);
//shaderprogram.setUniformValue("projection", projection);
}
void Widget::paintGL()
{
this->glClearColor(0.0f,0.0f,0.0f,1.0f);//设置清屏颜色
this->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清空颜色缓冲区
shaderprogram.bind();
//将此着色器程序绑定到活动的qopenglcontext,并使其成为当前着色器程序。任何先前绑定的着色器程序都将被释放
//成功绑定返回ture,反之,返回false.
{
QVector3D lightColor(1.0f,1.0f,1.0f);
QVector3D objectColor(1.0f,0.5f,0.31f);
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;
QOpenGLVertexArrayObject::Binder bind(&VAO);//绑定VAO
this->glDrawArrays(GL_TRIANGLES,0,n);
}
}
void Widget::mousePressEvent(QMouseEvent *event)
{
mousePos = QVector2D(event->pos());
event->accept();
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() == Qt::LeftButton)
{
QVector2D newPos = (QVector2D)event->pos();
QVector2D diff = newPos - mousePos;
qreal angle = (diff.length())/3.6;
// Rotation axis is perpendicular to the mouse position difference
// vector
QVector3D rotationAxis = QVector3D(diff.y(), diff.x(), 0.0).normalized();
rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angle) * rotation;
mousePos = newPos;
this->update();
}
event->accept();
}
void Widget::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();
}
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);
}