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);
}
}
如有错误,欢迎指正