2021SC@SDUSC

从本周开始,我们团队正式展开了Dust3D源代码的分析工作。经过小组讨论后,我们粗略地按照开源项目的文件名将文件划分进了模型构造、骨骼与动画、渲染与材质三大模块中。其中我们推测mesh-开头的文件与Dust3D的核心功能模型构造有关,故本周我将首先从meshgenerator这一部分开始学习。

MeshGenerator类

MeshGenerator是QObject的公有派生类。QObject 是Qt模块的核心,它的最主要特征是关于对象间无缝通信的机制:信号与槽,该机制使 Qt 对象之间可以进行无缝的交互。

class MeshGenerator : public QObject
{
    Q_OBJECT
public:
    MeshGenerator(Snapshot *snapshot);
    ~MeshGenerator();
    bool isSuccessful();
    Model *takeResultMesh();
    Model *takePartPreviewMesh(const QUuid &partId);
    QImage *takePartPreviewImage(const QUuid &partId);
    const std::set<QUuid> &generatedPreviewPartIds();
    const std::set<QUuid> &generatedPreviewImagePartIds();
    Object *takeObject();
    std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *takeCutFaceTransforms();
    std::map<QUuid, std::map<QString, QVector2D>> *takeNodesCutFaces();
    void generate();
    void setGeneratedCacheContext(GeneratedCacheContext *cacheContext);
    void setSmoothShadingThresholdAngleDegrees(float degrees);
    void setInterpolationEnabled(bool interpolationEnabled);
    void setDefaultPartColor(const QColor &color);
    void setId(quint64 id);
    void setWeldEnabled(bool enabled);
    quint64 id();
signals:
    void finished();
public slots:
    void process();
    
private:
    QColor m_defaultPartColor = Qt::white;
    Snapshot *m_snapshot = nullptr;
    GeneratedCacheContext *m_cacheContext = nullptr;
    std::set<QString> m_dirtyComponentIds;
    std::set<QString> m_dirtyPartIds;
    float m_mainProfileMiddleX = 0;
    float m_sideProfileMiddleX = 0;
    float m_mainProfileMiddleY = 0;
    Object *m_object = nullptr;
    std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> m_nodeVertices;
    std::map<QString, std::set<QString>> m_partNodeIds;
    std::map<QString, std::set<QString>> m_partEdgeIds;
    std::set<QUuid> m_generatedPreviewPartIds;
    std::set<QUuid> m_generatedPreviewImagePartIds;
    Model *m_resultMesh = nullptr;
    std::map<QUuid, Model *> m_partPreviewMeshes;
    std::map<QUuid, QImage *> m_partPreviewImages;
    bool m_isSuccessful = false;
    bool m_cacheEnabled = false;
    float m_smoothShadingThresholdAngleDegrees = 60;
    std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *m_cutFaceTransforms = nullptr;
    std::map<QUuid, std::map<QString, QVector2D>> *m_nodesCutFaces = nullptr;
    quint64 m_id = 0;
    std::vector<QVector3D> m_clothCollisionVertices;
    std::vector<std::vector<size_t>> m_clothCollisionTriangles;
    bool m_weldEnabled = true;
    bool m_interpolationEnabled = true;
    
    void collectParts();
    void collectIncombinableComponentMeshes(const QString &componentIdString);
    void collectIncombinableMesh(const MeshCombiner::Mesh *mesh, const GeneratedComponent &componentCache);
    bool checkIsComponentDirty(const QString &componentIdString);
    bool checkIsPartDirty(const QString &partIdString);
    bool checkIsPartDependencyDirty(const QString &partIdString);
    void checkDirtyFlags();
    bool fillPartWithMesh(GeneratedPart &partCache, 
        const QUuid &fillMeshFileId,
        float deformThickness,
        float deformWidth,
        float cutRotation,
        const StrokeMeshBuilder *strokeMeshBuilder);
    MeshCombiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool *retryable, bool addIntermediateNodes=true);
    MeshCombiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
    void makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces,
        std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces);
    void collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces,
        std::set<std::pair<PositionKey, PositionKey>> *sharedQuadEdges);
    MeshCombiner::Mesh *combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second,
        MeshCombiner::Method method,
        bool recombine=true);
    void generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
        const std::vector<QVector3D> &triangleNormals,
        std::vector<std::vector<QVector3D>> *triangleVertexNormals);
    const std::map<QString, QString> *findComponent(const QString &componentIdString);
    CombineMode componentCombineMode(const std::map<QString, QString> *component);
    MeshCombiner::Mesh *combineComponentChildGroupMesh(const std::vector<QString> &componentIdStrings,
        GeneratedComponent &componentCache);
    MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true);
    QString componentColorName(const std::map<QString, QString> *component);
    void collectUncombinedComponent(const QString &componentIdString);
    void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate);
    void postprocessObject(Object *object);
    void collectErroredParts();
    void preprocessMirror();
    QString reverseUuid(const QString &uuidString);
};

网格表面的演化是由节点球定义的标量场决定的。为了允许对形状进行局部操作,我们可以添加一些辅助的节点球球来影响标量场,从而控制网格表面的演化。
MeshGenerator类的构造函数获取了当前输入的snapshot的画布、顶点、边、part、component等信息,并声明了一系列网格填充、网格组合、顶点法线平滑处理等的函数用于模型网格的控制。

MeshGenerator类中包含了这样几个从名称来看比较重要的函数:

函数名/
fillPartWithMesh网格填充
combinePartMesh组合网格
combineComponentMesh组合组件网格
makeXmirror创建沿X轴的镜像
combineTwoMeshes组合两个网格
generateSmoothTriangleVertexNormals生成平滑的三角形顶点法线
combineComponentChildGroupMesh组合组件子组网格
combineMultipleMeshes组合多个网格

fillPartWithMesh()函数

Dust3D在建模过程中实现了对多个子模型的网格进行无缝缝合,fillPartWithMesh()函数就是实现这一功能所需的函数之一。
fillPartWithMesh()函数返回一个bool值,用于判断,若fillMesh的区域为空返回false,若fillMesh区域存在,则对输入的partCache进行冲程,获取节点的位置和半径,将获取的object的点和边插入到partCache中,最后根据partCache中所有的点和边信息创建面片重新生成fillMesh区域的网格,并返回true值,表示填充成功。

bool MeshGenerator::fillPartWithMesh(GeneratedPart &partCache, 
    const QUuid &fillMeshFileId,
    float deformThickness,
    float deformWidth,
    float cutRotation,
    const StrokeMeshBuilder *strokeMeshBuilder)
{
    bool fillIsSucessful = false;
    const QByteArray *fillMeshByteArray = FileForever::getContent(fillMeshFileId);
    //若fillMesh的区域为空返回false
    if (nullptr == fillMeshByteArray)
        return false;
    
    QXmlStreamReader fillMeshStream(*fillMeshByteArray);  
    Snapshot *fillMeshSnapshot = new Snapshot;
    loadSkeletonFromXmlStream(fillMeshSnapshot, fillMeshStream);
    
    GeneratedCacheContext *fillMeshCacheContext = new GeneratedCacheContext();
    MeshGenerator *meshGenerator = new MeshGenerator(fillMeshSnapshot);
    meshGenerator->setWeldEnabled(false);
    meshGenerator->setGeneratedCacheContext(fillMeshCacheContext);
    meshGenerator->generate();
    fillIsSucessful = meshGenerator->isSuccessful();
    Object *object = meshGenerator->takeObject();
    //若fillMesh区域存在,则对输入的partCache进行冲程,获取节点的位置和半径
    if (nullptr != object) {
        MeshStroketifier stroketifier;
        std::vector<MeshStroketifier::Node> strokeNodes;
        for (const auto &nodeIndex: strokeMeshBuilder->nodeIndices()) {
            const auto &node = strokeMeshBuilder->nodes()[nodeIndex];
            MeshStroketifier::Node strokeNode;
            strokeNode.position = node.position;
            strokeNode.radius = node.radius;
            strokeNodes.push_back(strokeNode);
        }
        stroketifier.setCutRotation(cutRotation);
        stroketifier.setDeformWidth(deformWidth);
        stroketifier.setDeformThickness(deformThickness);
        if (stroketifier.prepare(strokeNodes, object->vertices)) {
            stroketifier.stroketify(&object->vertices);
            std::vector<MeshStroketifier::Node> agentNodes(object->nodes.size());
            for (size_t i = 0; i < object->nodes.size(); ++i) {
                auto &dest = agentNodes[i];
                const auto &src = object->nodes[i];
                dest.position = src.origin;
                dest.radius = src.radius;
            }
            stroketifier.stroketify(&agentNodes);
            for (size_t i = 0; i < object->nodes.size(); ++i) {
                const auto &src = agentNodes[i];
                auto &dest = object->nodes[i];
                dest.origin = src.position;
                dest.radius = src.radius;
            }
        }
        //将获取的object的点和边插入到partCache中
        partCache.objectNodes.insert(partCache.objectNodes.end(), object->nodes.begin(), object->nodes.end());
        partCache.objectEdges.insert(partCache.objectEdges.end(), object->edges.begin(), object->edges.end());
        partCache.vertices.insert(partCache.vertices.end(), object->vertices.begin(), object->vertices.end());
        if (!strokeNodes.empty()) {
            for (auto &it: partCache.vertices)
                it += strokeNodes.front().position;
        }
        //根据partCache中所有的点和边信息创建面片重新生成fillMesh区域的网格
        for (size_t i = 0; i < object->vertexSourceNodes.size(); ++i)
            partCache.objectNodeVertices.push_back({partCache.vertices[i], object->vertexSourceNodes[i]});
        partCache.faces.insert(partCache.faces.end(), object->triangleAndQuads.begin(), object->triangleAndQuads.end());
        fillIsSucessful = true;
    }
    delete object;
    delete meshGenerator;
    delete fillMeshCacheContext;

    return fillIsSucessful;
}

fillPartMesh()函数会在之后的combinePartMesh()函数中被调用,用于strokeMeshBuilder仅包含了初始化法线情况下的网格填充。由于combinePartMesh()函数定义有500+行,所以我们放到之后再进行讨论。

makeXmirror()函数

Dust3D有个重要的功能是,对于类似手臂、双腿等成对出现的组件可以在建好一个之后选择关于X轴镜像自动生成与之对称的模型。其实现方法是先将源模型的顶点坐标(x0,y0,z0)的X值关于原点对称、Y值、Z值不变,即(-x0,y0,z0)作为目标模型的顶点坐标,再将源模型每个面片的起点和终点翻转后作为目标模型的面片存储。一般面片顶点存储顺序统一为顺时针/逆时针,而反方向存储的是摄像机不可见的面片,故在镜面后需要将顶点顺序进行倒序。
在这里插入图片描述

void MeshGenerator::makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces,
        std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces)
{
    for (const auto &mirrorFrom: sourceVertices) {
        destVertices->push_back(QVector3D(-mirrorFrom.x(), mirrorFrom.y(), mirrorFrom.z()));
    }
    std::vector<std::vector<size_t>> newFaces;
    for (const auto &mirrorFrom: sourceFaces) {
        auto newFace = mirrorFrom;
        std::reverse(newFace.begin(), newFace.end());  //翻转newFace的顶点序列
        destFaces->push_back(newFace);
    }
}

如有错误,欢迎指正

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐