1.代码
ModelLoader.h
#ifndef MODELLOADER_H
#define MODELLOADER_H
#include <QObject>
#include <Qt3DRender>
#include <QVector3D>
#include <QGeometry>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
struct Triangle
{
int vertexIndex1 { 0 };
int vertexIndex2 { 0 };
int vertexIndex3 { 0 };
bool operator==(const Triangle &rhs) const {
return vertexIndex1 == rhs.vertexIndex1 &&
vertexIndex2 == rhs.vertexIndex2 &&
vertexIndex3 == rhs.vertexIndex3;
}
};
namespace RenderAttributes
{
Qt3DRender::QAttribute *create(const QVector<Triangle> &triangles, Qt3DRender::QGeometry *parent);
Qt3DRender::QAttribute *create(const QVector<QVector3D> &vertices, const QString &name, Qt3DRender::QGeometry *parent);
Qt3DRender::QAttribute *clone(Qt3DRender::QAttribute *from, Qt3DRender::QGeometry *parent);
}
class ModelLoader
{
public:
ModelLoader();
// 加载模型
static int loadModelFromFile(Qt3DCore::QEntity *rootEntity, QString filePath);
private:
static int processNode(aiNode *node, const aiScene *scene, Qt3DCore::QEntity *entity);
static int processMesh(aiMesh *mesh, const aiScene *scene, Qt3DCore::QEntity *entity);
};
class MyQPaintedTextureImage : public Qt3DRender::QPaintedTextureImage
{
public:
void setImage(QImage &i){
image = i;
setSize(i.size());
}
virtual void paint(QPainter *painter) override{
painter->drawImage(0, 0, image);
}
private:
QImage image;
};
#endif // MODELLOADER_H
ModelLoader.cpp
#include "modelloader.h"
#include <Qt3DExtras>
#include <Qt3DRender>
#include <QPaintedTextureImage>
Qt3DRender::QAttribute *RenderAttributes::create(const QVector<Triangle> &triangles, Qt3DRender::QGeometry *parent)
{
auto attribute = new Qt3DRender::QAttribute(parent);
QVector<uint> indices;
indices.reserve(triangles.size() * 3);
for (const Triangle &triangle : triangles) {
indices << static_cast<uint>(triangle.vertexIndex1)
<< static_cast<uint>(triangle.vertexIndex2)
<< static_cast<uint>(triangle.vertexIndex3);
}
Qt3DRender::QBuffer *dataBuffer = new Qt3DRender::QBuffer(attribute);
const int rawSize = indices.size() * static_cast<int>(sizeof(uint));
auto rawData = QByteArray::fromRawData(reinterpret_cast<const char*>(indices.constData()), rawSize);
rawData.detach();
dataBuffer->setData(rawData);
attribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
attribute->setBuffer(dataBuffer);
attribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt);
attribute->setVertexSize(1);
attribute->setByteOffset(0);
attribute->setByteStride(sizeof(uint));
attribute->setCount(static_cast<uint>(indices.size()));
return attribute;
}
Qt3DRender::QAttribute *RenderAttributes::create(const QVector<QVector3D> &vertices, const QString &name, Qt3DRender::QGeometry *parent)
{
auto attribute = new Qt3DRender::QAttribute(parent);
QVector<float> values;
values.reserve(vertices.size() * 3);
for (const QVector3D &v : vertices) {
values << v.x() << v.y() << v.z();
}
Qt3DRender::QBuffer *dataBuffer = new Qt3DRender::QBuffer(attribute);
const int rawSize = values.size() * static_cast<int>(sizeof(float));
auto rawData = QByteArray::fromRawData(reinterpret_cast<const char*>(values.constData()), rawSize);
rawData.detach();
dataBuffer->setData(rawData);
attribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
attribute->setBuffer(dataBuffer);
attribute->setVertexBaseType(Qt3DRender::QAttribute::Float);
attribute->setVertexSize(3);
attribute->setByteOffset(0);
attribute->setByteStride(3 * sizeof(float));
attribute->setName(name);
attribute->setCount(static_cast<uint>(vertices.size()));
return attribute;
}
uint qHash(const QVector3D &key, uint seed = 0)
{
if (key.isNull())
{
return seed;
}
else
{
float array[3] = {key.x(), key.y(), key.z()};
return qHashBits(array, 3 * sizeof(int), seed);
}
}
Qt3DRender::QAttribute *RenderAttributes::clone(Qt3DRender::QAttribute *from, Qt3DRender::QGeometry *parent)
{
auto attribute = new Qt3DRender::QAttribute(parent);
Qt3DRender::QBuffer *dataBuffer = new Qt3DRender::QBuffer(attribute);
auto dataCopy = from->buffer()->data();
dataCopy.detach();
dataBuffer->setData(dataCopy);
attribute->setAttributeType(from->attributeType());
attribute->setBuffer(dataBuffer);
attribute->setVertexBaseType(from->vertexBaseType());
attribute->setVertexSize(from->vertexSize());
attribute->setByteOffset(from->byteOffset());
attribute->setByteStride(from->byteStride());
attribute->setName(from->name());
attribute->setCount(from->count());
return attribute;
}
ModelLoader::ModelLoader() {}
int ModelLoader::loadModelFromFile(Qt3DCore::QEntity *rootEntity, QString filePath)
{
Assimp::Importer importer;
std::string modelPath = filePath.toStdString();
const aiScene* sceneObjPtr = importer.ReadFile(modelPath,
aiProcess_Triangulate | aiProcess_FlipUVs);
// const aiScene* sceneObjPtr = importer.ReadFile(filePath,
// aiProcess_FlipUVs);
if (!sceneObjPtr
|| sceneObjPtr->mFlags == AI_SCENE_FLAGS_INCOMPLETE
|| !sceneObjPtr->mRootNode)
{
// 加载模型失败
qDebug() << "Error:Model::loadModel, description: "
<< importer.GetErrorString();
return -1;
}
qDebug() << "load object:" << sceneObjPtr->mNumMeshes << sceneObjPtr->mNumAnimations;
// 是否存在动画
if(sceneObjPtr->HasAnimations())
{
aiAnimation* animation = sceneObjPtr->mAnimations[0];
qDebug() << animation->mDuration << animation->mTicksPerSecond << animation->mName.C_Str();
}
// 是否存在材质
if(sceneObjPtr->HasMaterials())
{
qDebug() << sceneObjPtr->mMaterials[0]->GetName().C_Str();
}
// 是否存在网格模型
if(sceneObjPtr->HasMeshes())
{
qDebug() << sceneObjPtr->mMeshes[0]->mName.C_Str();
}
Qt3DCore::QEntity *modelEntity = new Qt3DCore::QEntity(rootEntity);
// 修正一下坐标系
Qt3DCore::QTransform *mTransform = new Qt3DCore::QTransform();
mTransform->setRotationX(-90);
modelEntity->addComponent(mTransform);
return processNode(sceneObjPtr->mRootNode, sceneObjPtr, modelEntity);
}
int ModelLoader::processNode(aiNode *node, const aiScene *scene, Qt3DCore::QEntity *entity)
{
qDebug() << "node:" << node->mName.C_Str() << node->mNumMeshes << node->mNumChildren;
Qt3DCore::QEntity *partModel = new Qt3DCore::QEntity(entity);
Qt3DCore::QTransform *partTransform = new Qt3DCore::QTransform;
aiMatrix4x4 mat = node->mTransformation;
QMatrix4x4 qMat = QMatrix4x4(mat.a1, mat.a2, mat.a3, mat.a4,
mat.b1, mat.b2, mat.b3, mat.b4,
mat.c1, mat.c2, mat.c3, mat.c4,
mat.d1, mat.d2, mat.d3, mat.d4
);
partTransform->setMatrix(qMat);
partModel->addComponent(partTransform);
partModel->setObjectName(node->mName.C_Str()); // 这个很有用,可以用来后期对节点进行查找
// qDebug() << partTransform->rotationX()
// << partTransform->rotationY()
// << partTransform->rotationZ();
// process each mesh located at the current node
for (unsigned int i = 0; i < node->mNumMeshes; i++)
{
// the node object only contains indices to index the actual objects in the scene.
// the scene contains all the data, node is just to keep stuff organized (like relations between nodes).
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
processMesh(mesh, scene, partModel);
}
// after we've processed all of the meshes (if any) we then recursively process each of the children nodes
for (unsigned int i = 0; i < node->mNumChildren; i++)
{
processNode(node->mChildren[i], scene, partModel);
}
return 0;
}
int ModelLoader::processMesh(aiMesh *mesh, const aiScene *scene, Qt3DCore::QEntity *entity)
{
qDebug() << "---> mesh:" << mesh->mName.C_Str();;
Qt3DRender::QGeometry *geometry = new Qt3DRender::QGeometry(entity);
Qt3DRender::QGeometryRenderer *geoRen = new Qt3DRender::QGeometryRenderer(entity);
geoRen->setGeometry(geometry);
geoRen->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles);
// 如果网格包含法线,则添加法线节点
if(mesh->HasNormals())
{
// qDebug() << "has normal:" << mesh->mNumVertices;
// 法线数据
QVector<QVector3D> normals;
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
{
aiVector3D normal = mesh->mNormals[i];
normals << QVector3D(normal.x, normal.y, normal.z);
}
auto attr = RenderAttributes::create(normals, "vertexNormal", geometry);
geometry->addAttribute(attr);
}
// 如果网格包含纹理坐标,则添加纹理坐标节点
if(mesh->HasTextureCoords(0))
{
// qDebug() << "NumUVComponents :" << mesh->mNumUVComponents;
// if(mesh->mNumUVComponents > 0)
// {
// qDebug() << "has texture"
// << mesh->mTextureCoordsNames[0]->C_Str()
// ;
// }
QVector<QVector3D> textureCoordinates;
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
{
aiVector3D texCoord = mesh->mTextureCoords[0][i];
textureCoordinates << QVector3D(texCoord.x, texCoord.y, texCoord.z);
}
auto attrTC = RenderAttributes::create(textureCoordinates, Qt3DRender::QAttribute::defaultTextureCoordinateAttributeName(), geometry);
geometry->addAttribute(attrTC);
}
// 将Assimp网格的顶点坐标添加到坐标节点
// qDebug() << "vertices:" << mesh->mNumVertices;
QVector<QVector3D> vertices;
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
{
aiVector3D vertice = mesh->mVertices[i];
vertices << QVector3D(vertice.x, vertice.y, vertice.z);
}
auto attrVeti = RenderAttributes::create(vertices, Qt3DRender::QAttribute::defaultPositionAttributeName(), geometry);
geometry->addAttribute(attrVeti);
geometry->setBoundingVolumePositionAttribute(attrVeti);
// 将Assimp网格的面索引添加到面索引
// qDebug() << "faces:" << mesh->mNumFaces;
QVector<Triangle> faces;
for (unsigned int i = 0; i < mesh->mNumFaces; i++)
{
Triangle triFace;
aiFace face = mesh->mFaces[i];
triFace.vertexIndex1 = face.mIndices[0];
triFace.vertexIndex2 = face.mIndices[1];
triFace.vertexIndex3 = face.mIndices[2];
faces << triFace;
// for (unsigned int j = 0; j < face.mNumIndices; j++) // 填充面的索引
// {
// faceSet->coordIndex.set1Value(faceSet->coordIndex.getNum(), face.mIndices[j]);
// }
}
auto attrFace = RenderAttributes::create(faces, geometry);
geometry->addAttribute(attrFace);
// 设置材质
if(mesh->mMaterialIndex >= 0)
{
qDebug() << "set material";
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
// 创建材质节点
// // 使用QMetalRoughMaterial时,貌似亮度有点不对
// Qt3DExtras::QMetalRoughMaterial *qMaterial = new Qt3DExtras::QMetalRoughMaterial();
// qMaterial->setMetalness(0.4);
// qMaterial->setRoughness(0.55);
Qt3DExtras::QDiffuseMapMaterial *qMaterial = new Qt3DExtras::QDiffuseMapMaterial();
// 设置材质漫反射度
float shiness;
if (AI_SUCCESS == material->Get(AI_MATKEY_SHININESS, shiness)) {
qDebug() << "shiness:" << shiness;
// 使用 shiness 值
qMaterial->setShininess(shiness);
}
// 设置材质漫反射颜色
aiColor4D diffuseColor;
if(AI_SUCCESS == material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor))
{
qDebug() << "rgb:" << diffuseColor.r << diffuseColor.g << diffuseColor.b;
QColor tmpColor = QColor(diffuseColor.r * 255.0,
diffuseColor.g * 255.0,
diffuseColor.b * 255.0);
qDebug() << tmpColor << QColor(255, 255, 255);
// qMaterial->setBaseColor(tmpColor);
QImage img(100, 100, QImage::Format_ARGB32);
img.fill(tmpColor);
MyQPaintedTextureImage *txtImg = new MyQPaintedTextureImage();
txtImg->setImage(img);
Qt3DRender::QTexture2D *txt2d = new Qt3DRender::QTexture2D();
txt2d->addTextureImage(txtImg);
// qMaterial->setBaseColor(QVariant::fromValue(txt2d));
qMaterial->setDiffuse(txt2d);
}
// 设置环境颜色
aiColor4D ambientColor;
if(AI_SUCCESS == material->Get(AI_MATKEY_COLOR_AMBIENT, ambientColor))
{
QColor tmpColor = QColor(ambientColor.r, ambientColor.g, ambientColor.b, ambientColor.a);
qDebug() << "ambient color:" << tmpColor;
qMaterial->setAmbient(tmpColor);
}
else
{
qMaterial->setAmbient(QColor(128, 128, 128));
}
// 如果材质包含纹理,则创建纹理节点
aiString texturePath; //Assimp texture file path
if(AI_SUCCESS == material->GetTexture(aiTextureType_DIFFUSE, 0, &texturePath))
{
qDebug() << "texture file:" << texturePath.C_Str();
const aiTexture *embTexture = scene->GetEmbeddedTexture(texturePath.C_Str());
if(embTexture) // 假如内嵌的纹理
{
qDebug() << "has embed img:"
<< (char*)embTexture->achFormatHint
<< embTexture->mFilename.C_Str()
<< embTexture->mWidth << embTexture->mHeight;
// 利用QPaintedTextureImage可以实现加载内存中的图片
const unsigned char* data = reinterpret_cast<const unsigned char*>(embTexture->pcData);
const size_t size = embTexture->mWidth;
QByteArray imageData((char*)data, size);
QImage img;
if(img.loadFromData(imageData)){
qDebug() << img;
MyQPaintedTextureImage *txtImg = new MyQPaintedTextureImage();
txtImg->setImage(img);
Qt3DRender::QTexture2D *txt2d = new Qt3DRender::QTexture2D();
txt2d->addTextureImage(txtImg);
// qMaterial->setBaseColor(QVariant::fromValue(txt2d));
qMaterial->setDiffuse(txt2d);
}
}
else
{
qDebug() << "-----no embed img";
}
}
else
{
qDebug() << "get texture fail" << material->GetTextureCount(aiTextureType_DIFFUSE);
}
entity->addComponent(qMaterial);
}
entity->addComponent(geoRen);
return 0;
}
2.说明
2.1.调用
Qt3DExtras::Qt3DWindow *view = new Qt3DExtras::Qt3DWindow();
view->defaultFrameGraph()->setClearColor(QColor(QRgb(0x2b2b2b)));
QString filePath = "myModel.glb";
Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity();
ModelLoader::loadModelFromFile(rootEntity, filePath);
view->setRootEntity(rootEntity);
// 灯光及其他设置
2.2.关于贴图
假如使用QTextureImage
的话,只能加载本地图片,但是参考【How can I load a QPaintedTextureImage into a QTextureMaterial?】,可以使用QPaintedTextureImage
来加载内存中的图片。而glb/gltf文件本身包含了图片内容,读取glb文件时,图片内容已经加载到内存中了,因此QPaintedTextureImage
更加适合。
参考
【Qt3DExample】
【How can I load a QPaintedTextureImage into a QTextureMaterial?】