开头先放GIF效果,最底下是我的项目Github仓
首先创建一个名为: AssimpQuick3d项目
我选择的是mingw64位平台,如果用的是msvc的话,对应库就要改成windows类型,.lib和.dll
首先创建相应文件
mesh.h
#ifndef MESH_H
#define MESH_H
#include "QObject"
#include <QVector3D>
#include <QList>
struct Vertex
{
inline float x() const { return position.x(); }
inline float y() const { return position.y(); }
inline float z() const { return position.z(); }
QVector3D position;
QVector3D normal;
};
Q_DECLARE_METATYPE(Vertex)
struct Mesh
{
public:
inline Mesh() {}
Mesh(const QList<Vertex> &vertices, const QList<quint32> &indices, QVector3D min, QVector3D max)
: vertices(vertices), indices(indices), boundsMin(min), boundsMax(max)
{ }
QList<Vertex> vertices;
QList<quint32> indices;
QVector3D boundsMin;
QVector3D boundsMax;
};
Q_DECLARE_METATYPE(Mesh)
#endif // MESH_H
mygeometry.h
#ifndef MYGEOMETRY_H
#define MYGEOMETRY_H
#include "mesh.h"
#include <QQuick3DGeometry>
class MyGeometry : public QQuick3DGeometry
{
Q_OBJECT
Q_PROPERTY(Mesh mesh READ mesh WRITE setMesh NOTIFY meshChanged)
QML_NAMED_ELEMENT(MyGeometry)
public:
explicit MyGeometry();
inline const Mesh &mesh() const { return _mesh; }
void setMesh(const Mesh &mesh);
signals:
void meshChanged();
private:
void updateData();
void updatePro();
private:
Mesh _mesh;
};
#endif // MYGEOMETRY_H
viewmanager.h
#ifndef VIEWMANAGER_H
#define VIEWMANAGER_H
#include <QQmlEngine>
#include <QObject>
#include <QList>
#include "mesh.h"
#include "mygeometry.h"
class aiNode;
class aiScene;
class aiMesh;
class ViewManager : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<Mesh> meshes READ meshes CONSTANT)
Q_PROPERTY(QList<MyGeometry*> geometrys READ geometrys CONSTANT)
QML_NAMED_ELEMENT(ViewManager)
public:
explicit ViewManager(QObject *parent = nullptr);
Q_INVOKABLE void loadFile();
QList<Mesh> meshes() const { return _meshs; }
QList<MyGeometry*> geometrys() const { return _geometrys; }
private:
void processNode(aiNode *node, const aiScene *scene);
Mesh processMesh(aiMesh *mesh);
private:
QList<Mesh> _meshs;
QList<MyGeometry*> _geometrys;
};
#endif // VIEWMANAGER_H
mygeometry.cpp
#include "mygeometry.h"
MyGeometry::MyGeometry()
{
// updatePro();
}
void MyGeometry::setMesh(const Mesh &mesh)
{
_mesh = mesh;
updateData();
update();
emit meshChanged();
}
void MyGeometry::updateData()
{
clear();
int stride = sizeof(Vertex);
QByteArray vertexData(_mesh.vertices.count() * stride, Qt::Initialization::Uninitialized);
memcpy(vertexData.data(), _mesh.vertices.constData(), vertexData.size());
setVertexData(vertexData);
QByteArray indexData(_mesh.indices.count()*sizeof(quint32), Qt::Initialization::Uninitialized);
memcpy(indexData.data(), _mesh.indices.constData(), indexData.size());
setIndexData(indexData);
setStride(stride);
setBounds(_mesh.boundsMin, _mesh.boundsMax);
setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
addAttribute(Attribute::PositionSemantic, 0, Attribute::ComponentType::F32Type);
addAttribute(Attribute::NormalSemantic, sizeof(QVector3D), Attribute::ComponentType::F32Type);
addAttribute(Attribute::IndexSemantic, 0, Attribute::ComponentType::U32Type);
}
void MyGeometry::updatePro()
{
clear();
int stride = 3 * sizeof(float);
QByteArray vertexData(3 * stride, Qt::Initialization::Uninitialized);
float *p = reinterpret_cast<float *>(vertexData.data());
*p++ = -1.0f; *p++ = -1.0f; *p++ = 0.0f;
*p++ = 1.0f; *p++ = -1.0f; *p++ = 0.0f;
*p++ = 0.0f; *p++ = 1.0f; *p++ = 0.0f;
setVertexData(vertexData);
setStride(stride);
setBounds(QVector3D(-1.0f, -1.0f, 0.0f), QVector3D(+1.0f, +1.0f, 0.0f));
setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
}
viewmanager.cpp
#include "viewmanager.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <QFileDialog>
#include <iostream>
ViewManager::ViewManager(QObject *parent)
: QObject{parent}
{
}
void ViewManager::loadFile()
{
Assimp::Importer import;
std::string szOut;
import.GetExtensionList(szOut);
QString t_assimp=tr("ASSIMP (") +QString::fromStdString(szOut) +tr(")");
QString all_filter;
all_filter+=t_assimp;
QString filename = QFileDialog::getOpenFileName(nullptr,tr("open file"),"./",all_filter);
if(filename.isEmpty()) {
return;
}
_meshs.clear();
const aiScene *scene = import.ReadFile(filename.toStdString(), aiProcess_Triangulate | aiProcess_FlipUVs);
if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
std::cout << "ERROR::ASSIMP::" << import.GetErrorString()<<std::endl;
return;
}
processNode(scene->mRootNode, scene);///开始遍历scene
}
void ViewManager::processNode(aiNode *node, const aiScene *scene)
{
// 处理节点所有的网格(如果有的话)
for(unsigned int i = 0; i < node->mNumMeshes; i++){
aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; ///一个节点有多个mesh
auto nodeMesh = processMesh(mesh);
_meshs.push_back(nodeMesh);
MyGeometry *geometry = new MyGeometry;
geometry->setMesh(nodeMesh);
_geometrys.append(geometry);
}
// 接下来对它的子节点重复这一过程
for(unsigned int i = 0; i < node->mNumChildren; i++) { ///一个节点有多个子节点
processNode(node->mChildren[i], scene);
}
}
Mesh ViewManager::processMesh(aiMesh *mesh)
{
QList<Vertex> vertices;
QList<quint32> triangles;
//遍历顶点,只存位置
auto p = mesh->mVertices[0];
QVector3D min, max;
min = max = QVector3D(p.x, p.y, p.z);
for(unsigned int i = 0; i < mesh->mNumVertices; i++) {
Vertex ver;
ver.position = QVector3D(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z);
ver.normal = QVector3D(1, 1, 0);
vertices.push_back(ver); ///把顶点存放的vector
min.setX(std::min(min.x(), ver.position.x()));
min.setY(std::min(min.y(), ver.position.y()));
min.setZ(std::min(min.z(), ver.position.z()));
max.setX(std::max(max.x(), ver.position.x()));
max.setY(std::max(max.y(), ver.position.y()));
max.setZ(std::max(max.z(), ver.position.z()));
}
// 处理索引
for(unsigned int i = 0; i < mesh->mNumFaces; i++) { ///索引数量mNumFaces
aiFace face = mesh->mFaces[i];
for(unsigned int j = 0; j < face.mNumIndices; j++) {
triangles.push_back(face.mIndices[j]);
}
}
//构建mesh并作为返回值
return Mesh(vertices, triangles, min, max);
}
Shape.qml
import QtQuick
import QtQuick3D
Node {
id: shape
property var geometryData
property real xRotation: Math.random() * (360 - (-360)) + -360
property real yRotation: Math.random() * (360 - (-360)) + -360
property real zRotation: Math.random() * (360 - (-360)) + -360
property real hue: Math.random()
property bool isPicked: false
Model {
id: model
property bool isPicked: shape.isPicked
scale: Qt.vector3d(100, 100, 100)
eulerRotation.x: 90
pickable: true
geometry: geometryData
onIsPickedChanged: hue = Math.random()
SequentialAnimation on eulerRotation {
loops: Animation.Infinite
running: model.isPicked
PropertyAnimation {
duration: Math.random() * (10000 - 1000) + 1000
to: Qt.vector3d(xRotation - 360, yRotation - 360, zRotation - 360)
from: Qt.vector3d(xRotation, yRotation, zRotation)
}
}
materials: [ DefaultMaterial { diffuseColor: model.isPicked ? Qt.hsva(hue, 1.0, 1.0, 1.0) : "red" } ]
}
}
最后的是main.qml
import QtQuick
import QtQuick.Controls
import QtQuick3D
import QtQuick3D.Helpers
import MyGeometryExample
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
RoundButton {
z:1
anchors.top: parent.top
anchors.topMargin: 5
anchors.horizontalCenter: parent.horizontalCenter
text: "Import"
font.bold: true
palette.button: "slategrey"
onClicked: shapeSpawner.addShape()
}
ViewManager {
id: manager
}
color: "black"
View3D {
id: v3d
anchors.fill: parent
camera: camera
Node {
id: originNode
PerspectiveCamera {
id: camera
position: Qt.vector3d(0, 0, 600)
}
}
OrbitCameraController {
anchors.fill: parent
origin: originNode
camera: camera
}
DirectionalLight {
position: Qt.vector3d(-500, 500, -100)
color: Qt.rgba(0.4, 0.2, 0.6, 1.0)
ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
}
PointLight {
position: Qt.vector3d(0, 0, 100)
color: Qt.rgba(0.1, 1.0, 0.1, 1.0)
ambientColor: Qt.rgba(0.2, 0.2, 0.2, 1.0)
}
Node {
id: shapeSpawner
property var instances: []
property int count
function addShape() {
manager.loadFile()
for(var i = 0; i < manager.geometrys.length; i++) {
var geo = manager.geometrys[i]
var shapeComponent = Qt.createComponent("Shape.qml")
if(shapeComponent.status === Component.Ready) {
var props = {}
props["geometryData"] = geo
let instance = shapeComponent.createObject(shapeSpawner, props)
instances.push(instance)
count = instances.length
}
}
}
}
}
MouseArea {
anchors.fill: v3d
onClicked: function(mouse) {
var result = v3d.pick(mouse.x, mouse.y)
if (result.objectHit) {
var pickedObject = result.objectHit
pickedObject.isPicked = !pickedObject.isPicked
}
}
}
}
源码解析:
mesh.h文件中定义定义了模型所需要的结构体,Mesh和Vertex,用来存储模型的基础数据的,3D模型都是会转成顶点列表和索引列表来进行显示,Qt Quick3D、OpenGL、D3D等等都是如此。
然后是mygeometry.h文件,其中定义了MyGeometry类,它是继承了Qt的QQuick3DGeometry类,这个类主要作用是把这些顶点列表和索引列表、材质等等包裹起来变成一个item,前端的Model引用了它,就可以显示模型了,而Model就是相当于一个父级容器,它具有矩阵变换等等功能,这样移动旋转缩放就不用每个顶点去变换了。
最后是viewmanager.h文件,其中包含ViewManager类,这个类做的事情是把外部的文件加载进来,这里就用到了assimp加载文件数据,然后把数据封装到一个个的mesh中,然后根据这些mesh也创建了多个MyGeometry对象,并保存起来。
后端所要做的就这三件事,加载文件,读到mesh,用mesh创建MyGeometry对象。
接下来是前端源码解析:
首先看下Shape.qml,这个其实就是官方dymaiccreation的WeirdShape.qml文件,我在它的基础上进行了稍微的改动而来,主要它是读取本地的mesh文件,我改成了动态加载的MyGeometry对象,并在里面加上了官方picking示例的一个核心功能,模型拾取,如果大家想看详细示例可以到qt源码文件夹搜这两个名字的示例打开看。
最后前端就是main.qml文件,里面也很简单。
一个RoundButton按钮,用来控制导入模型
一个ViewManager对象,用于控制后端的文件
还有View3D对象,这是quick3D里面必须有的,还有几个其他必须有的对象,相机(PerspectiveCamera)和灯光(DirectionalLight、PointLight),这里我用到了OrbitCameraController,它主要作用是鼠标可以控制相机方向,主要围绕相机的父节点旋转缩放我们的相机,还有动态创建model的Node,id定义为shapeSpawner,它主要负责动态创建我们的模型,并添加到它这里。
最后的最后,加了一个MouseArea,anchors.fill了view3d这个对象,主要作用是获取鼠标左键,然后拾取模型
好了,最后我把源码都放到我的github仓库上了。
原创贴,需要请引用
一起学习可以加微信
微信: xgxhxtaly
仓库链接:原创 大家一起学习