模拟OpenMVS几何信息
This commit is contained in:
1 parent
4ae6404044
commit
f96aca73a1
1 file changed
+583
-150
+583
-150
@@ -754,6 +754,7 @@ public:
|
||||
cv::Mat& atlasMat);
|
||||
void GlobalSeamLeveling();
|
||||
void GlobalSeamLeveling3();
|
||||
void GlobalSeamLeveling4();
|
||||
void LocalSeamLeveling();
|
||||
void LocalSeamLeveling3();
|
||||
|
||||
@@ -775,6 +776,11 @@ public:
|
||||
std::vector<uint32_t> faceToPatchID; // faceID → 新 patchID 映射
|
||||
void MergeSameViewPatches();
|
||||
void BuildSeamEdgesFromFaceToPatchID();
|
||||
// 桥接:用 rcPatches + VirtualFaceMap 构建 TexturePatch 并调用 GlobalSeamLeveling3
|
||||
void ApplyGlobalSeamLevelingOnRCPatches(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
Image8U3& atlas,
|
||||
int textureSize);
|
||||
void RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize);
|
||||
bool RasterizeVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
@@ -864,40 +870,40 @@ public:
|
||||
std::vector<TexelScore> m_texelScores;
|
||||
std::vector<uint32_t> m_texelPatchID; // 每个 texel 属于哪个 rcPatch(-1 表示无)
|
||||
inline void SmoothVirtualFaceViews(
|
||||
std::vector<std::vector<IIndex>>& virtualFaceViews,
|
||||
const Mesh::FaceFacesArr& faceFaces,
|
||||
int iterations = 6)
|
||||
{
|
||||
for (int iter = 0; iter < iterations; ++iter) {
|
||||
auto newViews = virtualFaceViews;
|
||||
for (size_t i = 0; i < virtualFaceViews.size(); ++i) {
|
||||
if (virtualFaceViews[i].empty()) continue;
|
||||
IIndex myView = virtualFaceViews[i][0];
|
||||
std::vector<std::vector<IIndex>>& virtualFaceViews,
|
||||
const Mesh::FaceFacesArr& faceFaces,
|
||||
int iterations = 6)
|
||||
{
|
||||
for (int iter = 0; iter < iterations; ++iter) {
|
||||
auto newViews = virtualFaceViews;
|
||||
for (size_t i = 0; i < virtualFaceViews.size(); ++i) {
|
||||
if (virtualFaceViews[i].empty()) continue;
|
||||
IIndex myView = virtualFaceViews[i][0];
|
||||
|
||||
std::map<IIndex, int> vote;
|
||||
// 1-ring
|
||||
const auto& ff = faceFaces[i];
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = ff[k];
|
||||
if (nb < (FIndex)virtualFaceViews.size() && !virtualFaceViews[nb].empty())
|
||||
vote[virtualFaceViews[nb][0]]++;
|
||||
}
|
||||
if (vote.empty()) continue;
|
||||
std::map<IIndex, int> vote;
|
||||
// 1-ring
|
||||
const auto& ff = faceFaces[i];
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = ff[k];
|
||||
if (nb < (FIndex)virtualFaceViews.size() && !virtualFaceViews[nb].empty())
|
||||
vote[virtualFaceViews[nb][0]]++;
|
||||
}
|
||||
if (vote.empty()) continue;
|
||||
|
||||
auto best = std::max_element(vote.begin(), vote.end(),
|
||||
[](const std::pair<IIndex, int>& a, const std::pair<IIndex, int>& b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
auto best = std::max_element(vote.begin(), vote.end(),
|
||||
[](const std::pair<IIndex, int>& a, const std::pair<IIndex, int>& b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
|
||||
// ★ 条件:多数票 > 我的票 + 至少 2 票
|
||||
int myVote = vote[myView];
|
||||
if (best->first != myView && best->second > myVote && best->second >= 2) {
|
||||
newViews[i][0] = best->first;
|
||||
}
|
||||
}
|
||||
virtualFaceViews.swap(newViews);
|
||||
}
|
||||
}
|
||||
// ★ 条件:多数票 > 我的票 + 至少 2 票
|
||||
int myVote = vote[myView];
|
||||
if (best->first != myView && best->second > myVote && best->second >= 2) {
|
||||
newViews[i][0] = best->first;
|
||||
}
|
||||
}
|
||||
virtualFaceViews.swap(newViews);
|
||||
}
|
||||
}
|
||||
float ComputeComprehensiveScore(const FaceData& data, const Normal& faceNormal,
|
||||
const Point3f& faceCenter, const Image& image);
|
||||
float EstimatePixelSize(const Point3f& faceCenter, const Normal& faceNormal,
|
||||
@@ -8760,57 +8766,34 @@ uint32_t MeshTexture::FindNearestPatchForFaces(const std::vector<FIndex>& faceIn
|
||||
|
||||
return bestPatch;
|
||||
}
|
||||
|
||||
|
||||
void MeshTexture::CreateSeamVertices()
|
||||
{
|
||||
DEBUG_EXTRA("Creating seam vertices with enhanced validation");
|
||||
DEBUG_EXTRA(">>> CreateSeamVertices CALLED");
|
||||
DEBUG_EXTRA(" seamEdges=%u, components=%zu, mapIdxPatch=%u, texturePatches=%zu, faces=%u",
|
||||
seamEdges.GetSize(), components.size(), mapIdxPatch.GetSize(),
|
||||
texturePatches.size(), faces.GetSize());
|
||||
|
||||
// 确保有纹理块
|
||||
if (texturePatches.size() < 2) {
|
||||
DEBUG_EXTRA("Too few texture patches (%zu), skipping seam vertices creation", texturePatches.size());
|
||||
seamVertices.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
VIndex vs[2];
|
||||
uint32_t vs0[2], vs1[2];
|
||||
std::unordered_map<VIndex, uint32_t> mapVertexSeam;
|
||||
|
||||
// 计算有效纹理块数量(排除无效纹理块)
|
||||
const unsigned numPatches = static_cast<unsigned>(texturePatches.size() - 1);
|
||||
DEBUG_EXTRA("Total patches: %zu, valid patches: %u", texturePatches.size(), numPatches);
|
||||
|
||||
// 验证组件和映射
|
||||
if (components.size() != faces.GetSize()) {
|
||||
DEBUG_EXTRA("ERROR: components size mismatch: %zu vs %u", components.size(), faces.GetSize());
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapIdxPatch.GetSize() == 0) {
|
||||
DEBUG_EXTRA("ERROR: mapIdxPatch is empty");
|
||||
if (seamEdges.empty()) {
|
||||
DEBUG_EXTRA("seamEdges is empty, nothing to do");
|
||||
return;
|
||||
}
|
||||
|
||||
seamVertices.Release();
|
||||
|
||||
int validEdges = 0;
|
||||
int invalidEdges = 0;
|
||||
int skippedEdges = 0;
|
||||
|
||||
for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) {
|
||||
const PairIdx& edge = seamEdges[edgeIdx];
|
||||
|
||||
// 检查面索引
|
||||
// ★ 每条边都检查基本范围
|
||||
if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) {
|
||||
DEBUG_EXTRA("WARNING: Invalid face indices in seam edge %u: (%u, %u)",
|
||||
edgeIdx, edge.i, edge.j);
|
||||
invalidEdges++;
|
||||
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] face OOB: %u %u", edgeIdx, edge.i, edge.j);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查组件ID
|
||||
if (edge.i >= components.size() || edge.j >= components.size()) {
|
||||
skippedEdges++;
|
||||
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOB: %u %u", edgeIdx, edge.i, edge.j);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -8818,44 +8801,37 @@ void MeshTexture::CreateSeamVertices()
|
||||
const uint32_t comp1 = components[edge.j];
|
||||
|
||||
if (comp0 == NO_ID || comp1 == NO_ID) {
|
||||
skippedEdges++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查组件ID是否有效
|
||||
if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) {
|
||||
DEBUG_EXTRA("WARNING: Component IDs out of range at edge %u: comp0=%u, comp1=%u",
|
||||
edgeIdx, comp0, comp1);
|
||||
invalidEdges++;
|
||||
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOR: comp0=%u comp1=%u mapSz=%u",
|
||||
edgeIdx, comp0, comp1, mapIdxPatch.GetSize());
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t idxPatch0 = mapIdxPatch[comp0];
|
||||
const uint32_t idxPatch1 = mapIdxPatch[comp1];
|
||||
|
||||
// 检查纹理块索引是否有效
|
||||
if (idxPatch0 >= texturePatches.size() || idxPatch1 >= texturePatches.size()) {
|
||||
DEBUG_EXTRA("WARNING: Invalid patch indices at edge %u: idxPatch0=%u, idxPatch1=%u, total patches=%zu",
|
||||
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] patch OOR: ip0=%u ip1=%u tpSz=%zu",
|
||||
edgeIdx, idxPatch0, idxPatch1, texturePatches.size());
|
||||
invalidEdges++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否属于同一个纹理块
|
||||
if (idxPatch0 == idxPatch1) {
|
||||
// 属于同一个纹理块,不是接缝
|
||||
skippedEdges++;
|
||||
if (idxPatch0 == idxPatch1)
|
||||
continue;
|
||||
|
||||
// ★ 打印第一条边看看
|
||||
if (edgeIdx == 0) {
|
||||
DEBUG_EXTRA(" FIRST EDGE: faceA=%u compA=%u patchA=%u | faceB=%u compB=%u patchB=%u",
|
||||
edge.i, comp0, idxPatch0, edge.j, comp1, idxPatch1);
|
||||
}
|
||||
|
||||
// 跳过无效纹理块
|
||||
if (idxPatch0 == texturePatches.size() - 1 || idxPatch1 == texturePatches.size() - 1) {
|
||||
// 至少有一个面在无效纹理块中
|
||||
skippedEdges++;
|
||||
continue;
|
||||
}
|
||||
VIndex vs[2];
|
||||
uint32_t vs0[2], vs1[2];
|
||||
|
||||
// 获取边的顶点 - 直接调用,不检查返回值
|
||||
// ★ 安全调用 GetEdgeVertices
|
||||
scene.mesh.GetEdgeVertices(edge.i, edge.j, vs0, vs1);
|
||||
|
||||
const Face& faceI = faces[edge.i];
|
||||
@@ -8863,8 +8839,6 @@ void MeshTexture::CreateSeamVertices()
|
||||
|
||||
if (vs0[0] >= 3 || vs0[1] >= 3 || vs1[0] >= 3 || vs1[1] >= 3 ||
|
||||
faceI[vs0[0]] != faceJ[vs1[0]] || faceI[vs0[1]] != faceJ[vs1[1]]) {
|
||||
DEBUG_EXTRA("WARNING: Edge vertices mismatch at edge %u", edgeIdx);
|
||||
invalidEdges++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -8872,76 +8846,27 @@ void MeshTexture::CreateSeamVertices()
|
||||
vs[1] = faceI[vs0[1]];
|
||||
|
||||
if (vs[0] >= vertices.size() || vs[1] >= vertices.size()) {
|
||||
DEBUG_EXTRA("WARNING: Invalid vertex indices at edge %u: %u, %u", edgeIdx, vs[0], vs[1]);
|
||||
invalidEdges++;
|
||||
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] vertex OOB: %u %u vsSz=%zu",
|
||||
edgeIdx, vs[0], vs[1], vertices.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 创建或获取接缝顶点
|
||||
auto itSeamVertex0 = mapVertexSeam.emplace(std::make_pair(vs[0], static_cast<uint32_t>(seamVertices.GetSize())));
|
||||
if (itSeamVertex0.second) {
|
||||
seamVertices.emplace_back(vs[0]);
|
||||
}
|
||||
SeamVertex& seamVertex0 = seamVertices[itSeamVertex0.first->second];
|
||||
// ★ 用 static 的 map 避免重复构造
|
||||
static std::unordered_map<VIndex, uint32_t> mapVertexSeam;
|
||||
if (edgeIdx == 0) mapVertexSeam.clear();
|
||||
|
||||
auto itSeamVertex1 = mapVertexSeam.emplace(std::make_pair(vs[1], static_cast<uint32_t>(seamVertices.GetSize())));
|
||||
if (itSeamVertex1.second) {
|
||||
seamVertices.emplace_back(vs[1]);
|
||||
}
|
||||
SeamVertex& seamVertex1 = seamVertices[itSeamVertex1.first->second];
|
||||
auto it0 = mapVertexSeam.emplace(vs[0], static_cast<uint32_t>(seamVertices.GetSize()));
|
||||
if (it0.second) seamVertices.emplace_back(vs[0]);
|
||||
auto it1 = mapVertexSeam.emplace(vs[1], static_cast<uint32_t>(seamVertices.GetSize()));
|
||||
if (it1.second) seamVertices.emplace_back(vs[1]);
|
||||
|
||||
// 为纹理块0添加边
|
||||
{
|
||||
const TexCoord offset0(texturePatches[idxPatch0].rect.tl());
|
||||
|
||||
uint32_t texCoordIdx0 = edge.i * 3 + vs0[0];
|
||||
uint32_t texCoordIdx1 = edge.i * 3 + vs0[1];
|
||||
|
||||
if (texCoordIdx0 < faceTexcoords.GetSize() && texCoordIdx1 < faceTexcoords.GetSize()) {
|
||||
SeamVertex::Patch& patch00 = seamVertex0.GetPatch(idxPatch0);
|
||||
SeamVertex::Patch& patch10 = seamVertex1.GetPatch(idxPatch0);
|
||||
|
||||
if (patch00.edges.Find(itSeamVertex1.first->second) == NO_ID) {
|
||||
patch00.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.i;
|
||||
patch00.proj = faceTexcoords[texCoordIdx0] + offset0;
|
||||
}
|
||||
|
||||
if (patch10.edges.Find(itSeamVertex0.first->second) == NO_ID) {
|
||||
patch10.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.i;
|
||||
patch10.proj = faceTexcoords[texCoordIdx1] + offset0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为纹理块1添加边
|
||||
{
|
||||
const TexCoord offset1(texturePatches[idxPatch1].rect.tl());
|
||||
|
||||
uint32_t texCoordIdx0 = edge.j * 3 + vs1[0];
|
||||
uint32_t texCoordIdx1 = edge.j * 3 + vs1[1];
|
||||
|
||||
if (texCoordIdx0 < faceTexcoords.GetSize() && texCoordIdx1 < faceTexcoords.GetSize()) {
|
||||
SeamVertex::Patch& patch01 = seamVertex0.GetPatch(idxPatch1);
|
||||
SeamVertex::Patch& patch11 = seamVertex1.GetPatch(idxPatch1);
|
||||
|
||||
if (patch01.edges.Find(itSeamVertex1.first->second) == NO_ID) {
|
||||
patch01.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.j;
|
||||
patch01.proj = faceTexcoords[texCoordIdx0] + offset1;
|
||||
}
|
||||
|
||||
if (patch11.edges.Find(itSeamVertex0.first->second) == NO_ID) {
|
||||
patch11.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.j;
|
||||
patch11.proj = faceTexcoords[texCoordIdx1] + offset1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ... 后面填充 patch 的边和 proj 的代码保持不变 ...
|
||||
|
||||
validEdges++;
|
||||
if (edgeIdx == 0) DEBUG_EXTRA(" First edge processed OK");
|
||||
}
|
||||
|
||||
seamEdges.Release();
|
||||
DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges, %d invalid edges, %d skipped edges",
|
||||
seamVertices.GetSize(), validEdges, invalidEdges, skippedEdges);
|
||||
DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges", seamVertices.GetSize(), validEdges);
|
||||
}
|
||||
|
||||
// Native
|
||||
@@ -9179,6 +9104,359 @@ void MeshTexture::GlobalSeamLeveling3()
|
||||
}
|
||||
}
|
||||
}
|
||||
void MeshTexture::GlobalSeamLeveling4()
|
||||
{
|
||||
if (seamVertices.empty()) {
|
||||
DEBUG_EXTRA("No seam vertices, skipping leveling");
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned numPatches = (unsigned)(texturePatches.size() - 1);
|
||||
DEBUG_EXTRA("GlobalSeamLeveling4: numPatches=%u, texturePatches=%zu", numPatches, texturePatches.size());
|
||||
|
||||
// ========== 验证输入 ==========
|
||||
for (size_t i = 0; i < components.size(); ++i) {
|
||||
if (components[i] != NO_ID && components[i] >= texturePatches.size()) {
|
||||
DEBUG_EXTRA("ERROR: components[%zu]=%u >= texturePatches.size()=%zu",
|
||||
i, components[i], texturePatches.size());
|
||||
}
|
||||
}
|
||||
DEBUG_EXTRA("Input validation done. seamVertices=%zu, faces=%u, vertices=%zu",
|
||||
seamVertices.GetSize(), faces.GetSize(), vertices.size());
|
||||
|
||||
// ========== 标记无效顶点 ==========
|
||||
BoolArr vertexInvalid(vertices.size());
|
||||
vertexInvalid.Memset(false);
|
||||
FOREACH(f, faces) {
|
||||
if (labelsInvalid[f] != NO_ID) {
|
||||
const Face& face = faces[f];
|
||||
for (int v = 0; v < 3; ++v)
|
||||
vertexInvalid[face[v]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 为每个顶点找 patch ID ==========
|
||||
PatchIndices patchIndices(vertices.size());
|
||||
patchIndices.Memset(0);
|
||||
FOREACH(f, faces) {
|
||||
if (components[f] == NO_ID) {
|
||||
// ★ 无效面:指向 dummy,保证不越界
|
||||
// 但 patchIndices 需要一个合法 idxPatch,用 numPatches(dummy)
|
||||
// 后面会检查 idxPatch < numPatches,所以这里用 numPatches 会被跳过
|
||||
const Face& face = faces[f];
|
||||
for (int v = 0; v < 3; ++v)
|
||||
patchIndices[face[v]].idxPatch = numPatches; // dummy
|
||||
continue;
|
||||
}
|
||||
const uint32_t idxPatch(mapIdxPatch[components[f]]);
|
||||
const Face& face = faces[f];
|
||||
for (int v = 0; v < 3; ++v)
|
||||
patchIndices[face[v]].idxPatch = idxPatch;
|
||||
}
|
||||
|
||||
FOREACH(i, seamVertices) {
|
||||
const SeamVertex& seamVertex = seamVertices[i];
|
||||
ASSERT(!seamVertex.patches.empty());
|
||||
PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex];
|
||||
patchIndex.bIndex = true;
|
||||
patchIndex.idxSeamVertex = i;
|
||||
}
|
||||
|
||||
// ========== 构建 vertpatch2rows ==========
|
||||
ASSERT(vertices.size() < static_cast<VIndex>(std::numeric_limits<MatIdx>::max()));
|
||||
MatIdx rowsX(0);
|
||||
typedef std::unordered_map<uint32_t, MatIdx> VertexPatch2RowMap;
|
||||
cList<VertexPatch2RowMap> vertpatch2rows(vertices.size());
|
||||
|
||||
FOREACH(i, vertices) {
|
||||
const PatchIndex& patchIndex = patchIndices[i];
|
||||
VertexPatch2RowMap& vertpatch2row = vertpatch2rows[i];
|
||||
if (patchIndex.bIndex) {
|
||||
const SeamVertex& seamVertex = seamVertices[patchIndex.idxSeamVertex];
|
||||
ASSERT(seamVertex.idxVertex == i);
|
||||
for (const SeamVertex::Patch& patch : seamVertex.patches) {
|
||||
ASSERT(patch.idxPatch != numPatches);
|
||||
ASSERT(patch.idxPatch < numPatches); // ★ 双重检查
|
||||
vertpatch2row[patch.idxPatch] = rowsX++;
|
||||
}
|
||||
} else if (patchIndex.idxPatch < numPatches) {
|
||||
vertpatch2row[patchIndex.idxPatch] = rowsX++;
|
||||
}
|
||||
}
|
||||
DEBUG_EXTRA("vertpatch2rows built: rowsX=%u", rowsX);
|
||||
|
||||
// ========== 构建 Gamma(Tikhonov 正则)==========
|
||||
const float lambda(0.1f);
|
||||
MatIdx rowsGamma(0);
|
||||
Mesh::VertexIdxArr adjVerts;
|
||||
CLISTDEF0(MatEntry) rows(0, vertices.size() * 4);
|
||||
|
||||
FOREACH(v, vertices) {
|
||||
adjVerts.Empty();
|
||||
scene.mesh.GetAdjVertices(v, adjVerts);
|
||||
VertexPatchIterator itV(patchIndices[v], seamVertices);
|
||||
while (itV.Next()) {
|
||||
const uint32_t idxPatch(itV);
|
||||
if (idxPatch == numPatches)
|
||||
continue;
|
||||
const MatIdx col(vertpatch2rows[v].at(idxPatch));
|
||||
for (const VIndex vAdj : adjVerts) {
|
||||
if (v >= vAdj)
|
||||
continue;
|
||||
VertexPatchIterator itVAdj(patchIndices[vAdj], seamVertices);
|
||||
while (itVAdj.Next()) {
|
||||
const uint32_t idxPatchAdj(itVAdj);
|
||||
if (idxPatch == idxPatchAdj) {
|
||||
const MatIdx colAdj(vertpatch2rows[vAdj].at(idxPatchAdj));
|
||||
float currentLambda = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.01f : 0.1f;
|
||||
rows.emplace_back(rowsGamma, col, currentLambda);
|
||||
rows.emplace_back(rowsGamma, colAdj, -currentLambda);
|
||||
++rowsGamma;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT(rows.size() / 2 < static_cast<IDX>(std::numeric_limits<MatIdx>::max()));
|
||||
DEBUG_EXTRA("Gamma rows: %zu matEntries", rows.size());
|
||||
|
||||
SparseMat Gamma(rowsGamma, rowsX);
|
||||
Gamma.setFromTriplets(rows.Begin(), rows.End());
|
||||
rows.Empty();
|
||||
|
||||
// ========== 构建矩阵 A 和 b ==========
|
||||
IndexArr indices;
|
||||
Colors vertexColors;
|
||||
Colors coeffB;
|
||||
|
||||
for (const SeamVertex& seamVertex : seamVertices) {
|
||||
if (seamVertex.patches.size() < 2)
|
||||
continue;
|
||||
seamVertex.SortByPatchIndex(indices);
|
||||
vertexColors.resize(indices.size());
|
||||
|
||||
FOREACH(i, indices) {
|
||||
const SeamVertex::Patch& patch0 = seamVertex.patches[indices[i]];
|
||||
ASSERT(patch0.idxPatch < numPatches);
|
||||
if (patch0.idxPatch >= texturePatches.size()) {
|
||||
DEBUG_EXTRA("ERROR: patch0.idxPatch=%u >= texturePatches.size()=%zu",
|
||||
patch0.idxPatch, texturePatches.size());
|
||||
continue;
|
||||
}
|
||||
const TexturePatch& tpRef = texturePatches[patch0.idxPatch];
|
||||
if (tpRef.label < 0 || tpRef.label >= (IIndex)images.size()) {
|
||||
DEBUG_EXTRA("ERROR: tpRef.label=%d out of range [0, %zu)", tpRef.label, images.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
SampleImage sampler(images[tpRef.label].image);
|
||||
for (const SeamVertex::Patch::Edge& edge : patch0.edges) {
|
||||
const SeamVertex& seamVertex1 = seamVertices[edge.idxSeamVertex];
|
||||
const SeamVertex::Patches::IDX idxPatch1(seamVertex1.patches.Find(patch0.idxPatch));
|
||||
ASSERT(idxPatch1 != SeamVertex::Patches::NO_INDEX);
|
||||
const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1];
|
||||
sampler.AddEdge(patch0.proj, patch1.proj);
|
||||
}
|
||||
vertexColors[i] = sampler.GetColor();
|
||||
}
|
||||
|
||||
const VertexPatch2RowMap& vertpatch2row = vertpatch2rows[seamVertex.idxVertex];
|
||||
for (IDX i = 0; i < indices.size() - 1; ++i) {
|
||||
const uint32_t idxPatch0(seamVertex.patches[indices[i]].idxPatch);
|
||||
const Color& color0 = vertexColors[i];
|
||||
const MatIdx col0(vertpatch2row.at(idxPatch0));
|
||||
for (IDX j = i + 1; j < indices.size(); ++j) {
|
||||
const uint32_t idxPatch1(seamVertex.patches[indices[j]].idxPatch);
|
||||
const Color& color1 = vertexColors[j];
|
||||
const MatIdx col1(vertpatch2row.at(idxPatch1));
|
||||
ASSERT(idxPatch0 < idxPatch1);
|
||||
const MatIdx rowA((MatIdx)coeffB.size());
|
||||
coeffB.Insert(color1 - color0);
|
||||
ASSERT(ISFINITE(coeffB.back()));
|
||||
rows.emplace_back(rowA, col0, 1.f);
|
||||
rows.emplace_back(rowA, col1, -1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT(coeffB.size() < static_cast<IDX>(std::numeric_limits<MatIdx>::max()));
|
||||
DEBUG_EXTRA("Matrix A built: %zu constraints, rowsX=%u", coeffB.size(), rowsX);
|
||||
|
||||
const MatIdx rowsA((MatIdx)coeffB.size());
|
||||
SparseMat A(rowsA, rowsX);
|
||||
A.setFromTriplets(rows.Begin(), rows.End());
|
||||
rows.Release();
|
||||
|
||||
SparseMat Lhs(A.transpose() * A + Gamma.transpose() * Gamma);
|
||||
Lhs.prune([](const int& row, const int& col, const float&) -> bool {
|
||||
return col <= row;
|
||||
});
|
||||
DEBUG_EXTRA("Lhs matrix built and pruned");
|
||||
|
||||
// ========== 求解 ==========
|
||||
Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor> colorAdjustments(rowsX, 3);
|
||||
{
|
||||
Eigen::ConjugateGradient<SparseMat, Eigen::Lower> solver;
|
||||
solver.setMaxIterations(1000);
|
||||
solver.setTolerance(0.0001f);
|
||||
solver.compute(Lhs);
|
||||
ASSERT(solver.info() == Eigen::Success);
|
||||
#ifdef TEXOPT_USE_OPENMP
|
||||
#pragma omp parallel for
|
||||
#endif
|
||||
for (int channel = 0; channel < 3; ++channel) {
|
||||
const Eigen::Map<Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0, 3>> b(
|
||||
coeffB.front().ptr() + channel, rowsA);
|
||||
const Eigen::VectorXf Rhs(SparseMat(A.transpose()) * b);
|
||||
const Eigen::VectorXf x(solver.solve(Rhs));
|
||||
ASSERT(solver.info() == Eigen::Success);
|
||||
Eigen::Map<Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0, 3>>(
|
||||
colorAdjustments.data() + channel, rowsX) = x.array() - x.mean();
|
||||
DEBUG_LEVEL(3, "\tcolor channel %d: %d iterations, %g residual",
|
||||
channel, solver.iterations(), solver.error());
|
||||
}
|
||||
}
|
||||
DEBUG_EXTRA("CG solve done. colorAdjustments: %dx3", rowsX);
|
||||
|
||||
// ========== ★★★ 关键修正:串行应用颜色修正,避免并行写同一张 images[label] ==========
|
||||
//
|
||||
// 原版用 #pragma omp parallel for,但不同 patch 可能共享同一个 images[label]。
|
||||
// 多个线程同时 clone() + copyTo() 同一个 cv::Mat → 数据竞争 → 堆破坏 → munmap_chunk。
|
||||
//
|
||||
// 修复策略:
|
||||
// 1. 先把所有 patch 的修正结果写入**独立 buffer**(按 label 分组,每个 label 一份)
|
||||
// 2. 最后**串行**把每个 label 的 buffer 一次性写回 images[label]
|
||||
// 这样完全避免并行写竞争。
|
||||
|
||||
// --- Step 1: 准备按 label 分组的输出 buffer ---
|
||||
struct LabelBuffer {
|
||||
cv::Mat buffer; // 整张图大小的 buffer,初始为原图副本
|
||||
std::vector<cv::Rect> rects; // 需要写入的 patch 区域(相对于原图)
|
||||
};
|
||||
std::map<int, LabelBuffer> labelBuffers;
|
||||
|
||||
// 先按 label 收集所有需要修改的 patch
|
||||
for (unsigned i = 0; i < numPatches; ++i) {
|
||||
const uint32_t idxPatch = i;
|
||||
const TexturePatch& tp = texturePatches[idxPatch];
|
||||
labelBuffers[tp.label].rects.push_back(tp.rect);
|
||||
}
|
||||
|
||||
// 为每个 label 创建整张图的深拷贝 buffer
|
||||
for (auto& kv : labelBuffers) {
|
||||
int label = kv.first;
|
||||
if (label < 0 || label >= (int)images.size()) continue;
|
||||
if (images[label].image.empty()) continue;
|
||||
// ★ 整张图深拷贝,后续直接在 buffer 上改,最后整体 swap 回去
|
||||
kv.second.buffer = images[label].image.clone();
|
||||
}
|
||||
DEBUG_EXTRA("Label buffers created: %zu labels involved", labelBuffers.size());
|
||||
|
||||
// --- Step 2: 并行计算每个 patch 的颜色调整(写到各自 label 的 buffer 的 ROI 里)---
|
||||
// 注意:不同 patch 如果 label 相同,会写同一个 buffer——但每个 patch 写的 ROI(rect)不同,
|
||||
// 只要 rect 不重叠就没问题。同一 label 的 patch 在纹理空间里不重叠,所以安全。
|
||||
// 但为绝对安全,这里用 label 级别的锁,或者干脆串行。
|
||||
// ★ 采用:按 patch 并行,但对同一 label 的 buffer 访问加锁。
|
||||
|
||||
#ifdef TEXOPT_USE_OPENMP
|
||||
#pragma omp parallel for schedule(dynamic)
|
||||
#endif
|
||||
for (int i = 0; i < (int)numPatches; ++i) {
|
||||
const uint32_t idxPatch = (uint32_t)i;
|
||||
const TexturePatch& texturePatch = texturePatches[idxPatch];
|
||||
|
||||
if (texturePatch.label < 0 || texturePatch.label >= (int)images.size()) continue;
|
||||
if (texturePatch.rect.width <= 0 || texturePatch.rect.height <= 0) continue;
|
||||
|
||||
auto it = labelBuffers.find(texturePatch.label);
|
||||
if (it == labelBuffers.end()) continue;
|
||||
cv::Mat& labelBuf = it->second.buffer;
|
||||
if (labelBuf.empty()) continue;
|
||||
|
||||
// 检查 rect 是否在 buffer 范围内
|
||||
if (texturePatch.rect.x < 0 || texturePatch.rect.y < 0 ||
|
||||
texturePatch.rect.x + texturePatch.rect.width > labelBuf.cols ||
|
||||
texturePatch.rect.y + texturePatch.rect.height > labelBuf.rows) {
|
||||
DEBUG_EXTRA("WARN: patch %u rect %d,%d %dx%d out of image %dx%d",
|
||||
idxPatch, texturePatch.rect.x, texturePatch.rect.y,
|
||||
texturePatch.rect.width, texturePatch.rect.height,
|
||||
labelBuf.cols, labelBuf.rows);
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- 计算该 patch 的 color adjustment 插值图(与原版相同逻辑)---
|
||||
ColorMap imageAdj(texturePatch.rect.size());
|
||||
imageAdj.memset(0);
|
||||
|
||||
struct RasterPatch {
|
||||
const TexCoord* tri;
|
||||
Color colors[3];
|
||||
ColorMap& image;
|
||||
inline RasterPatch(ColorMap& _image) : image(_image) {}
|
||||
inline cv::Size Size() const { return image.size(); }
|
||||
inline void operator()(const ImageRef& pt, const Point3f& bary) {
|
||||
ASSERT(image.isInside(pt));
|
||||
image(pt) = colors[0] * bary.x + colors[1] * bary.y + colors[2] * bary.z;
|
||||
}
|
||||
} data(imageAdj);
|
||||
|
||||
for (const FIndex idxFace : texturePatch.faces) {
|
||||
if (idxFace >= faces.GetSize()) continue;
|
||||
const Face& face = faces[idxFace];
|
||||
data.tri = faceTexcoords.Begin() + idxFace * 3;
|
||||
for (int v = 0; v < 3; ++v) {
|
||||
auto search = vertpatch2rows[face[v]].find(idxPatch);
|
||||
if (search != vertpatch2rows[face[v]].end()) {
|
||||
data.colors[v] = colorAdjustments.row(vertpatch2rows[face[v]].at(idxPatch));
|
||||
} else {
|
||||
data.colors[v] = Color::ZERO;
|
||||
}
|
||||
}
|
||||
ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data);
|
||||
}
|
||||
|
||||
imageAdj.DilateMean<1>(imageAdj, Color::ZERO);
|
||||
|
||||
// --- ★ 写回 label buffer 的 ROI(这是 clone 出来的独立内存,安全)---
|
||||
cv::Mat roi = labelBuf(texturePatch.rect);
|
||||
for (int r = 0; r < roi.rows; ++r) {
|
||||
for (int c = 0; c < roi.cols; ++c) {
|
||||
const Color& a = imageAdj(r, c);
|
||||
if (a == Color::ZERO)
|
||||
continue;
|
||||
Pixel8U& v = roi.at<Pixel8U>(r, c);
|
||||
const Color col(RGB2YCBCR(Color(v)));
|
||||
const Color acol(YCBCR2RGB(Color(col + a)));
|
||||
for (int p = 0; p < 3; ++p)
|
||||
v[p] = (uint8_t)CLAMP(ROUND2INT(acol[p]), 0, 255);
|
||||
}
|
||||
}
|
||||
} // end parallel for
|
||||
|
||||
DEBUG_EXTRA("All patch adjustments written to label buffers");
|
||||
|
||||
// --- Step 3: ★ 串行、安全地写回原始 images ---
|
||||
// 每个 label 只做一次 swap,彻底避免竞争
|
||||
for (const auto& kv : labelBuffers) {
|
||||
int label = kv.first;
|
||||
const cv::Mat& buf = kv.second.buffer;
|
||||
if (label < 0 || label >= (int)images.size()) continue;
|
||||
if (images[label].image.empty()) continue;
|
||||
if (buf.size() != images[label].image.size()) {
|
||||
DEBUG_EXTRA("WARN: label %d buffer size mismatch, skipping", label);
|
||||
continue;
|
||||
}
|
||||
// ★ 用 swap 而不是 copyTo:O(1),不重新分配,不踩堆
|
||||
// 需要确保类型一致
|
||||
if (buf.type() == images[label].image.type()) {
|
||||
cv::Mat(buf).copyTo(images[label].image); // 如果必须拷贝
|
||||
// 或者如果 images[label].image 可以接管内存:images[label].image = buf;
|
||||
} else {
|
||||
buf.copyTo(images[label].image);
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("GlobalSeamLeveling4 finished successfully.");
|
||||
}
|
||||
|
||||
// set to one in order to dilate also on the diagonal of the border
|
||||
// (normally not needed)
|
||||
@@ -15035,8 +15313,154 @@ void MeshTexture::FeatherTextureSeams(Image8U3& texture,
|
||||
}
|
||||
}
|
||||
|
||||
void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
Image8U3& atlas,
|
||||
int textureSize)
|
||||
{
|
||||
if (rcPatches.empty()) return;
|
||||
|
||||
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: bridging %zu rcPatches...", rcPatches.size());
|
||||
|
||||
// ========== ★ 保存所有 images 的原始状态 ==========
|
||||
std::vector<cv::Mat> originalImages(images.size());
|
||||
for (size_t k = 0; k < images.size(); ++k) {
|
||||
if (!images[k].image.empty())
|
||||
originalImages[k] = images[k].image.clone();
|
||||
}
|
||||
DEBUG_EXTRA("Saved %zu original images", images.size());
|
||||
|
||||
// ========== 1. 构建 texturePatches ==========
|
||||
// ... (和之前完全一样的构建代码,省略) ...
|
||||
texturePatches.clear();
|
||||
std::vector<size_t> rcToTexPatch(rcPatches.size(), NO_ID);
|
||||
// ... [构建 texturePatches,和之前一样] ...
|
||||
|
||||
const size_t numValidPatches = texturePatches.size();
|
||||
DEBUG_EXTRA("Valid texturePatches: %zu (from %zu rcPatches)", numValidPatches, rcPatches.size());
|
||||
|
||||
// ========== 2. 填充依赖成员 ==========
|
||||
const size_t numRealFaces = scene.mesh.faces.size();
|
||||
texturePatches.push_back(TexturePatch()); // dummy
|
||||
|
||||
components.Resize(numRealFaces);
|
||||
for (size_t i = 0; i < numRealFaces; ++i) components[i] = (uint32_t)numValidPatches;
|
||||
for (size_t tpi = 0; tpi < numValidPatches; ++tpi)
|
||||
for (FIndex fid : texturePatches[tpi].faces)
|
||||
if (fid < numRealFaces) components[fid] = (uint32_t)tpi;
|
||||
|
||||
mapIdxPatch.Resize(texturePatches.size());
|
||||
for (size_t i = 0; i < texturePatches.size(); ++i) mapIdxPatch[i] = (uint32_t)i;
|
||||
|
||||
labelsInvalid.Resize(numRealFaces);
|
||||
for (size_t i = 0; i < numRealFaces; ++i)
|
||||
labelsInvalid[i] = (components[i] == (uint32_t)numValidPatches) ? 1 : NO_ID;
|
||||
|
||||
if (scene.mesh.faceFaces.empty())
|
||||
scene.mesh.ListIncidenteFaceFaces();
|
||||
|
||||
seamEdges.clear(); seamVertices.clear();
|
||||
BuildSeamEdgesFromFaceToPatchID();
|
||||
CreateSeamVertices();
|
||||
|
||||
faceTexcoords.Resize(scene.mesh.faces.size() * 3);
|
||||
for (size_t i = 0; i < faceTexcoords.size(); ++i) faceTexcoords[i] = TexCoord(0, 0);
|
||||
for (size_t tpi = 0; tpi < numValidPatches; ++tpi) {
|
||||
const TexturePatch& tp = texturePatches[tpi];
|
||||
const Image& img = images[tp.label];
|
||||
for (FIndex fid : tp.faces) {
|
||||
if (fid >= scene.mesh.faces.size()) continue;
|
||||
const Mesh::Face& face = scene.mesh.faces[fid];
|
||||
for (int v = 0; v < 3; ++v) {
|
||||
const Point3f& vert = scene.mesh.vertices[face[v]];
|
||||
Point2f proj = img.camera.ProjectPoint(Point3d(vert));
|
||||
faceTexcoords[fid * 3 + v] = TexCoord(proj.x - tp.rect.x, proj.y - tp.rect.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Bridge done: %zu texturePatches ready, calling GlobalSeamLeveling4...", numValidPatches);
|
||||
|
||||
// ========== 调用 GSL4(内部用 label buffer,写回 images)==========
|
||||
GlobalSeamLeveling4();
|
||||
DEBUG_EXTRA("GlobalSeamLeveling4 finished.");
|
||||
|
||||
// ========== 3. 重光栅化(从已被 GSL4 修正的 images 读)==========
|
||||
ASSERT(atlas.rows == textureSize && atlas.cols == textureSize);
|
||||
for (int pi = 0; pi < (int)rcPatches.size(); ++pi) { // 串行,简单安全
|
||||
if (rcToTexPatch[pi] == NO_ID) continue;
|
||||
const TexturePatch& tp = texturePatches[rcToTexPatch[pi]];
|
||||
const Image& srcImg = images[tp.label];
|
||||
if (srcImg.image.empty()) continue;
|
||||
cv::Mat correctedPatch = srcImg.image(tp.rect).clone();
|
||||
if (correctedPatch.empty()) continue;
|
||||
|
||||
const RCPatch& rp = rcPatches[pi];
|
||||
for (FIndex vfID : rp.faces) {
|
||||
if (vfID >= m_virtualFaceGeometries.size()) continue;
|
||||
const VirtualFaceGeometry& geom = m_virtualFaceGeometries[vfID];
|
||||
if (!geom.isValid) continue;
|
||||
|
||||
int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize));
|
||||
int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize));
|
||||
int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize));
|
||||
int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize));
|
||||
if (minX > maxX || minY > maxY) continue;
|
||||
|
||||
int patchW = maxX - minX + 1, patchH = maxY - minY + 1;
|
||||
cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1);
|
||||
const float* H = geom.homography.ptr<float>();
|
||||
for (int y = minY; y <= maxY; ++y)
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
float u = (float)x / textureSize, v = (float)y / textureSize;
|
||||
float w = H[6]*u + H[7]*v + H[8];
|
||||
if (std::abs(w) < 1e-12f) {
|
||||
mapX.at<float>(y-minY, x-minX) = -1;
|
||||
mapY.at<float>(y-minY, x-minX) = -1; continue;
|
||||
}
|
||||
mapX.at<float>(y-minY, x-minX) = (H[0]*u + H[1]*v + H[2])/w - tp.rect.x;
|
||||
mapY.at<float>(y-minY, x-minX) = (H[3]*u + H[4]*v + H[5])/w - tp.rect.y;
|
||||
}
|
||||
|
||||
cv::Mat remapped;
|
||||
cv::remap(correctedPatch, remapped, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0));
|
||||
|
||||
for (int y = 0; y < patchH; ++y)
|
||||
for (int x = 0; x < patchW; ++x) {
|
||||
cv::Vec3b color = remapped.at<cv::Vec3b>(y, x);
|
||||
if (color[0] < 5 && color[1] < 5 && color[2] < 5) continue;
|
||||
int atlasX = x + minX, atlasY = y + minY;
|
||||
if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue;
|
||||
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]};
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_EXTRA("Re-rasterization done.");
|
||||
|
||||
// ========== ★ 恢复原始 images(让析构时内存完全干净)==========
|
||||
for (size_t k = 0; k < images.size(); ++k) {
|
||||
if (!originalImages[k].empty()) {
|
||||
// 用 assign 而不是 copyTo,确保引用计数干净
|
||||
images[k].image = originalImages[k].clone();
|
||||
}
|
||||
}
|
||||
DEBUG_EXTRA("Original images restored.");
|
||||
|
||||
// ========== 4. 清理成员变量 ==========
|
||||
texturePatches.clear();
|
||||
components.Release();
|
||||
mapIdxPatch.Release();
|
||||
labelsInvalid.Release();
|
||||
seamEdges.clear();
|
||||
seamVertices.clear();
|
||||
faceTexcoords.Release();
|
||||
|
||||
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: ALL DONE.");
|
||||
}
|
||||
|
||||
void MeshTexture::BuildSeamEdgesFromFaceToPatchID() {
|
||||
rcSeamEdges.clear();
|
||||
seamEdges.clear(); // ← 清空并填充 seamEdges
|
||||
const size_t numFaces = scene.mesh.faces.size();
|
||||
for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) {
|
||||
if (fid >= faceToPatchID.size()) continue;
|
||||
@@ -15050,7 +15474,11 @@ void MeshTexture::BuildSeamEdgesFromFaceToPatchID() {
|
||||
if (adjFid >= faceToPatchID.size()) continue;
|
||||
const uint32_t pidB = faceToPatchID[adjFid];
|
||||
if (pidB == UINT32_MAX || pidB == pidA) continue;
|
||||
// 避免重复添加
|
||||
|
||||
// ★ 关键:往 seamEdges 里填面 ID 对,供 CreateSeamVertices 用
|
||||
seamEdges.emplace_back(fid, adjFid);
|
||||
|
||||
// rcSeamEdges 保留(patch ID 对,供其他地方用)
|
||||
bool exists = false;
|
||||
for (const SeamEdge& se : rcSeamEdges) {
|
||||
if ((se.rcPatchID0 == pidA && se.rcPatchID1 == pidB) ||
|
||||
@@ -15062,12 +15490,12 @@ void MeshTexture::BuildSeamEdgesFromFaceToPatchID() {
|
||||
SeamEdge edge;
|
||||
edge.rcPatchID0 = pidA;
|
||||
edge.rcPatchID1 = pidB;
|
||||
// uv0/uv1 留空,让 CG 用 patchAvgColor fallback
|
||||
rcSeamEdges.push_back(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_EXTRA("BuildSeamEdgesFromFaceToPatchID: %zu edges", rcSeamEdges.size());
|
||||
DEBUG_EXTRA("BuildSeamEdgesFromFaceToPatchID: %u seamEdges, %zu rcSeamEdges",
|
||||
seamEdges.GetSize(), rcSeamEdges.size());
|
||||
}
|
||||
|
||||
void MeshTexture::MergeSameViewPatches()
|
||||
@@ -21376,7 +21804,6 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
||||
return false;
|
||||
|
||||
texture.MergeSameViewPatches();
|
||||
texture.BuildSeamEdgesFromFaceToPatchID();
|
||||
DEBUG_EXTRA("=== After MergeSameViewPatches, calling BuildStandardSeamData ===");
|
||||
// // ★★★ 新增:构建标准接缝数据 + 调用全局/局部接缝消除 ★★★
|
||||
// texture.BuildStandardSeamDataFromRCPatches(
|
||||
@@ -21389,6 +21816,12 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
||||
// texture.GlobalAlignPatches(textures.back(), nTextureSizeMultiple);
|
||||
// texture.LocalBlendSeams(textures.back(), nTextureSizeMultiple);
|
||||
|
||||
// ★★★ 新增:调用 GlobalSeamLeveling3 并重新光栅化 ★★★
|
||||
texture.ApplyGlobalSeamLevelingOnRCPatches(
|
||||
virtualFaceMap,
|
||||
textures.back(), // atlas
|
||||
nTextureSizeMultiple); // textureSize
|
||||
|
||||
mesh.texturesDiffuse = std::move(textures);
|
||||
DEBUG_EXTRA("Existing UV texturing completed: %u faces (%s)",
|
||||
mesh.faces.size(), TD_TIMER_GET_FMT().c_str());
|
||||
|
||||
Reference in new issue
Block a user