xatlas源码解析(五)

前一章讲到,为了组织计算facegroup的任务数据,调用了computeCharts函数,这个函数中将每一个mesh放到一个任务当中,并使用多线程分割mesh,并将分割后的mesh形成一个一个的facegroup,下面看看是如何分割的:

// 运行计算ComputeCharts的任务函数,计算每一个mesh分成多少个Charts的任务
static void runMeshComputeChartsTask(void *groupUserData, void *taskUserData)
{
	// 组数据
	auto groupArgs = (MeshComputeChartsTaskGroupArgs *)groupUserData;
	// 任务数据
	auto args = (MeshComputeChartsTaskArgs *)taskUserData;

	// 取消就返回
	if (groupArgs->progress->cancel)
		return;

	XA_PROFILE_START(computeChartsThread)
	// Create face groups.
	XA_PROFILE_START(createFaceGroups)

	// 依据mesh数据创建mesh-face-group
	MeshFaceGroups *meshFaceGroups = XA_NEW_ARGS(MemTag::Mesh, MeshFaceGroups, args->sourceMesh);
	// 找到有拓扑关系的面,并顺序组织自来【有连接关系的面、相同的材质、不被忽略】
	meshFaceGroups->compute();
	// 一共分了多少个组数(每一组中是有拓扑关系的面)
	const uint32_t chartGroupCount = meshFaceGroups->groupCount();
	XA_PROFILE_END(createFaceGroups)
	if (groupArgs->progress->cancel)
		goto cleanup;
#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
	{
		static std::mutex s_mutex;
		std::lock_guard<std::mutex> lock(s_mutex);
		char filename[256];
		XA_SPRINTF(filename, sizeof(filename), "debug_face_groups.obj");
		FILE *file;
		XA_FOPEN(file, filename, s_faceGroupsCurrentVertex == 0 ? "w" : "a");
		if (file) {
			const Mesh *mesh = args->sourceMesh;
			mesh->writeObjVertices(file);
			// groups
			uint32_t numGroups = 0;
			for (uint32_t i = 0; i < mesh->faceCount(); i++) {
				if (meshFaceGroups->groupAt(i) != MeshFaceGroups::kInvalid)
					numGroups = max(numGroups, meshFaceGroups->groupAt(i) + 1);
			}
			for (uint32_t i = 0; i < numGroups; i++) {
				fprintf(file, "o mesh_%03u_group_%04d\n", mesh->id(), i);
				fprintf(file, "s off\n");
				for (uint32_t f = 0; f < mesh->faceCount(); f++) {
					if (meshFaceGroups->groupAt(f) == i)
						mesh->writeObjFace(file, f, s_faceGroupsCurrentVertex);
				}
			}
			fprintf(file, "o mesh_%03u_group_ignored\n", mesh->id());
			fprintf(file, "s off\n");
			for (uint32_t f = 0; f < mesh->faceCount(); f++) {
				if (meshFaceGroups->groupAt(f) == MeshFaceGroups::kInvalid)
					mesh->writeObjFace(file, f, s_faceGroupsCurrentVertex);
			}
			mesh->writeObjBoundaryEges(file);
			s_faceGroupsCurrentVertex += mesh->vertexCount();
			fclose(file);
		}
	}
#endif
	// Create a chart group for each face group.
	// 每一个面分组创建一个chart group
	args->chartGroups->resize(chartGroupCount);
	for (uint32_t i = 0; i < chartGroupCount; i++) {
		// 每一个面分组都会放到chartGroups中
		(*args->chartGroups)[i] = XA_NEW_ARGS(MemTag::Default, ChartGroup, i, args->sourceMesh, meshFaceGroups, MeshFaceGroups::Handle(i));
	}
	// Extract invalid geometry via the invalid face group (MeshFaceGroups::kInvalid).
	// 通过“无效面”组提取无效几何体
	{
		XA_PROFILE_START(extractInvalidMeshGeometry)
		args->invalidMeshGeometry->extract(args->sourceMesh, meshFaceGroups);
		XA_PROFILE_END(extractInvalidMeshGeometry)
	}
	// One task for each chart group - compute charts.  计算charts
	{
		XA_PROFILE_START(chartGroupComputeChartsReal)
		// Sort chart groups by face count.  按照当前mesh分成多个拓扑面组,每一个组中有很多三角面,哪一个面数多就排在最前面
		Array<float> chartGroupSortData;
		chartGroupSortData.resize(chartGroupCount);
		for (uint32_t i = 0; i < chartGroupCount; i++)
			chartGroupSortData[i] = (float)(*args->chartGroups)[i]->faceCount();
		RadixSort chartGroupSort;
		chartGroupSort.sort(chartGroupSortData);

		// Larger chart groups are added first to reduce the chance of thread starvation.
		// 首先添加较大的图表组,以减少线程不足的可能性。
		ChartGroupComputeChartsTaskGroupArgs taskGroupArgs;
		taskGroupArgs.atlas = groupArgs->atlas;				// ctx->atlas
		taskGroupArgs.options = groupArgs->options;			// 输入的选项
		taskGroupArgs.progress = groupArgs->progress;		// 输出的进度
		taskGroupArgs.taskScheduler = groupArgs->taskScheduler;	// 调度
		taskGroupArgs.boundaryGrid = groupArgs->boundaryGrid;	// 边界的网格
		taskGroupArgs.chartBuffers = groupArgs->chartBuffers;	// chart的buffer
		taskGroupArgs.piecewiseParam = groupArgs->piecewiseParam;	//分段参数??
		// 获取一个任务组
		TaskGroupHandle taskGroup = groupArgs->taskScheduler->createTaskGroup(&taskGroupArgs, chartGroupCount);
		for (uint32_t i = 0; i < chartGroupCount; i++) {
			// 填充任务数据
			Task task;
			task.userData = (*args->chartGroups)[chartGroupCount - i - 1];    // 用户数据【按照有拓扑结构的面分组】
			task.func = runChartGroupComputeChartsTask;
			groupArgs->taskScheduler->run(taskGroup, task);                   // 将任务添加到任务队列中,在taskGroup所在的分组中
		}
		// 开启任务线程
		groupArgs->taskScheduler->wait(&taskGroup);
		XA_PROFILE_END(chartGroupComputeChartsReal)
	}
	XA_PROFILE_END(computeChartsThread)
cleanup:
	// 处理完成清空
	if (meshFaceGroups) {
		meshFaceGroups->~MeshFaceGroups();
		XA_FREE(meshFaceGroups);
	}
}

其中meshFaceGroups->compute();中就是计算facegroup的过程,这个过程当中循环查找共线的面,如果一个面与相邻的面使用了相同的材质就可以将它们组织起来:

// 找到有拓扑关系的面,并顺序组织自来【有连接关系的面、相同的材质、不被忽略】
	void compute()
	{
		// 初始化面组数组
		m_groups.resize(m_mesh->faceCount());
		m_groups.fillBytes(0xff); // Set all faces to kInvalid  设置所有的面无效

		uint32_t firstUnassignedFace = 0;
		Handle group = 0;
		Array<uint32_t> growFaces;
		// 面数量
		const uint32_t n = m_mesh->faceCount();
		m_nextFace.resize(n);

		for (;;) {
			// Find an unassigned face. 找一张未指定的面。
			uint32_t face = UINT32_MAX;
			for (uint32_t f = firstUnassignedFace; f < n; f++) {
				// 这个面还未被处理,并且这个面不能被忽略
				if (m_groups[f] == kInvalid && !m_mesh->isFaceIgnored(f)) {
					face = f;					   // 面索引
					firstUnassignedFace = f + 1;   // 下一次从第二个面开始
					break;						   // 没有处理的面,下面马上要处理
				}
			}

			if (face == UINT32_MAX)
				break; // All faces assigned to a group (except ignored faces). 指定给组的所有面

			// 当前面所在的组face---group,很多关联的面会在同一组
			m_groups[face] = group;
			m_nextFace[face] = UINT32_MAX;  // 下一个面??
			m_firstFace.push_back(face);    // 将这个面放到队列中【对所有有关联的面进行分组,firstface中存储每一个分组的第一个面索引】

			growFaces.clear();				
			growFaces.push_back(face);		// growFaces 是一个压栈,出栈的过程,每一次处理一个面的拓扑关系,处理完成后就弹出,参考3dtiles创建瓦片的过程,也是入栈、出栈

			uint32_t prevFace = face, groupFaceCount = 1;   // 【prevFace本次处理时的第一个面,与m_firstFace中元素对应】,【每一组有拓扑关系的面组中成员的数量groupFaceCount】

			// Find faces connected to the face and assign them to the same group as the face, unless they are already assigned to another group.
			// 查找连接到该面的面,并将其指定给与该面相同的组,除非已将其指定给其他组。
			for (;;) {
				if (growFaces.isEmpty())
					break;

				// 出栈
				const uint32_t f = growFaces.back();
				growFaces.pop_back();

				// 查找面使用的材质
				const uint32_t material = m_mesh->faceMaterial(f);

				// 遍历每一个面的所有边【只能是两个面共享一个边,或者单独的一个边】【遍历一个三角型的三个边,与这三个边面的三个三角形连接起来】
				for (Mesh::FaceEdgeIterator edgeIt(m_mesh, f); !edgeIt.isDone(); edgeIt.advance()) {
					
					// 1、0的边与0、1的边反方向
					const uint32_t oppositeEdge = m_mesh->findEdge(edgeIt.vertex1(), edgeIt.vertex0());

					if (oppositeEdge == UINT32_MAX)  // 没有共边的边
						continue; // Boundary edge. 这是边界边缘

					// 这个边所对的面
					const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
					if (m_mesh->isFaceIgnored(oppositeFace))
						continue; // Don't add ignored faces to group.   这个面是否被忽略了

					if (m_mesh->faceMaterial(oppositeFace) != material)// 共边的面使用的材质不相同
						continue; // Different material.  材质不相同

					if (m_groups[oppositeFace] != kInvalid)   // 这个面经被占用
						continue; // Connected face is already assigned to another group. 已将连接的面指定给另一个组。

					m_groups[oppositeFace] = group;         // 两个面有相同的边,材质也相同,就将这两个面放在同一组中,group相同
					m_nextFace[oppositeFace] = UINT32_MAX;  // 将这个面置为无效,

					if (prevFace != UINT32_MAX)
						m_nextFace[prevFace] = oppositeFace; // 前一个面指向这一个面,指向下一个面

					prevFace = oppositeFace;				 // 这一个面
					groupFaceCount++;
					growFaces.push_back(oppositeFace);       // 处理这一个
				}
			}
			// 每一相同材质并且三角面有拓扑际关系的面数是多少,并且最终分了多少组
			m_faceCount.push_back(groupFaceCount);
			group++;  // 组数
			XA_ASSERT(group < kInvalid);
		}
	}

可以看到面1~11面是一个组,12~15是一个组。组织完成拓扑面之后,后面又启用了子线程处理面分组后是否可以创建一个chart,以及如何创建一个chart,这个任务的处理过程在runChartGroupComputeChartsTask函数当中。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值