使用Assimp加载glb/gltf文件,然后使用Qt3D来渲染

发布于:2024-09-19 ⋅ 阅读:(17) ⋅ 点赞:(0)

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?】


网站公告

今日签到

点亮在社区的每一天
去签到