From 9665d1226ad694e77c69ebe0211e635ea38d438b Mon Sep 17 00:00:00 2001 From: hesuicong Date: Sat, 5 Sep 2026 17:48:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 540 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 522 insertions(+), 18 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 7bcf50b..8a0935c 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -760,7 +760,9 @@ public: const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数 const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数 ); - + std::vector faceToPatchID; // faceID → 新 patchID 映射 + void MergeSameViewPatches(); + void RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize); bool RasterizeVirtualFaces( const VirtualFaceMap& virtualFaceMap, const std::vector>& virtualFaceViews, @@ -768,6 +770,12 @@ public: unsigned nTextureSizeMultiple, Pixel8U colEmpty, Mesh::Image8U3Arr& outTextures); + void BuildStandardSeamDataFromRCPatches( + const VirtualFaceMap& virtualFaceMap, + const std::vector>& virtualFaceViews, + int textureSize); + void GlobalAlignPatches(Image8U3& atlas, int textureSize); + void LocalBlendSeams(Image8U3& atlas, int textureSize); void FeatherTextureSeams(Image8U3& texture, const std::vector& texelPatchID, int textureSize, @@ -775,6 +783,7 @@ public: void AlignPatchColors(Image8U3& atlas, const std::vector& texelPatchID, int textureSize); + void SmoothAtlasPatchBoundaries(Image8U3& atlas, int textureSize); void GlobalPatchColorAlignment(Image8U3& atlas, int textureSize); void LocalSeamBlending(Image8U3& atlas, int textureSize); std::vector patchAvgColor; // ← 直接声明 vector,不要加括号! @@ -14479,6 +14488,100 @@ void MeshTexture::LocalSeamBlending(Image8U3& atlas, int textureSize) DEBUG_EXTRA("Local blending done (%s)", TD_TIMER_GET_FMT().c_str()); } +// ============================================================ +// 直接在 atlas 上做 patch 边界颜色扩散 +// 不依赖 seamEdges,只用 m_texelPatchID +// ============================================================ +void MeshTexture::SmoothAtlasPatchBoundaries(Image8U3& atlas, int textureSize) +{ + if (m_texelPatchID.empty()) return; + + const int bandWidth = 16; // 边界混合带宽度 + + // 1. 标记所有 patch 边界像素(4-邻域内 m_texelPatchID 不同的像素) + std::vector isBoundary(textureSize * textureSize, 0); + + for (int y = 1; y < textureSize - 1; ++y) { + for (int x = 1; x < textureSize - 1; ++x) { + size_t idx = y * textureSize + x; + int pid = m_texelPatchID[idx]; + if (pid == NO_ID) continue; + + // 检查 4-邻域 + int nb[4] = { + m_texelPatchID[(y-1)*textureSize + x], + m_texelPatchID[(y+1)*textureSize + x], + m_texelPatchID[y*textureSize + (x-1)], + m_texelPatchID[y*textureSize + (x+1)] + }; + + for (int n = 0; n < 4; ++n) { + if (nb[n] != NO_ID && nb[n] != pid) { + isBoundary[idx] = 1; + break; + } + } + } + } + + // 2. 对每个边界像素,向邻居 patch 的颜色做距离加权混合 + // 用多次迭代扩散,让颜色在边界两侧渐变 + Image8U3 atlasNew = atlas.clone(); + + for (int iter = 0; iter < 3; ++iter) { + #pragma omp parallel for + for (int y = 1; y < textureSize - 1; ++y) { + for (int x = 1; x < textureSize - 1; ++x) { + size_t idx = y * textureSize + x; + if (!isBoundary[idx]) continue; + + int pid = m_texelPatchID[idx]; + if (pid == NO_ID) continue; + + // 收集邻居中属于不同 patch 的像素颜色 + int count = 0; + cv::Vec3f sumColor(0, 0, 0); + float weightSum = 0; + + // 3x3 邻域 + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) continue; + int nx = x + dx, ny = y + dy; + size_t nidx = ny * textureSize + nx; + int npid = m_texelPatchID[nidx]; + if (npid == NO_ID || npid == pid) continue; + + const Pixel8U& npx = atlas(ny, nx); + if (npx[0] < 5 && npx[1] < 5 && npx[2] < 5) continue; + + float w = 1.0f / (dx*dx + dy*dy + 1); + sumColor[0] += npx[2] * w; + sumColor[1] += npx[1] * w; + sumColor[2] += npx[0] * w; + weightSum += w; + count++; + } + } + + if (count > 0 && weightSum > 0) { + // 混合比例:边界像素保留 70%,从邻居吸收 30% + const float blendFactor = 0.3f; + Pixel8U& px = atlasNew(y, x); + const Pixel8U& orig = atlas(y, x); + + px[2] = cv::saturate_cast(orig[2] * (1 - blendFactor) + (sumColor[0] / weightSum) * blendFactor); + px[1] = cv::saturate_cast(orig[1] * (1 - blendFactor) + (sumColor[1] / weightSum) * blendFactor); + px[0] = cv::saturate_cast(orig[0] * (1 - blendFactor) + (sumColor[2] / weightSum) * blendFactor); + } + } + } + atlas = atlasNew.clone(); + } + + DEBUG_EXTRA("SmoothAtlasPatchBoundaries: boundary blending done"); +} + // ===== 全局 Patch 颜色对齐 ===== // 在 seam edge 上采样两侧 patch 的颜色,求解每个 patch 的偏移量 void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize) @@ -14689,6 +14792,182 @@ void MeshTexture::FeatherTextureSeams(Image8U3& texture, } } } +void MeshTexture::MergeSameViewPatches() +{ + const size_t numPatches = rcPatches.size(); + if (numPatches == 0) return; + + const size_t numFaces = scene.mesh.faces.size(); // ★ 提前定义 + + if (faceFaces.empty()) { + scene.mesh.ListIncidenteFaceFaces(); + } + + // ---- Union-Find ---- + std::vector parent(numPatches); + std::iota(parent.begin(), parent.end(), 0); + + auto Find = [&](size_t x) -> size_t { + while (parent[x] != x) { + parent[x] = parent[parent[x]]; + x = parent[x]; + } + return x; + }; + + auto Union = [&](size_t a, size_t b) { + a = Find(a); + b = Find(b); + if (a != b) parent[b] = a; + }; + + // ---- 合并同 view 相邻 patch ---- + for (size_t i = 0; i < numPatches; ++i) { + const RCPatch& patch = rcPatches[i]; + if (patch.faces.empty()) continue; + + const FIndex faceID = patch.faces[0]; + if (faceID >= faceFaces.size()) continue; + + const Mesh::FaceFaces& adjFaces = faceFaces[faceID]; + for (int e = 0; e < 3; ++e) { + const FIndex adjFaceID = adjFaces[e]; + if (adjFaceID == NO_ID || adjFaceID >= (FIndex)numPatches) continue; + if (rcPatches[adjFaceID].viewID == patch.viewID) { + Union(i, adjFaceID); + } + } + } + + // ★ 构建旧 patch ID → 新 patch ID 的映射 + std::vector oldToNew(numPatches, UINT32_MAX); + uint32_t newIdx = 0; + for (size_t i = 0; i < numPatches; ++i) { + if (Find(i) == i) { + oldToNew[i] = newIdx++; + } + } + for (size_t i = 0; i < numPatches; ++i) { + oldToNew[i] = oldToNew[Find(i)]; + } + + // ---- 收集合并后的 groups,生成新 rcPatches ---- + std::map> groups; + for (size_t i = 0; i < numPatches; ++i) { + groups[Find(i)].push_back(i); + } + + std::vector newPatches; + newPatches.reserve(groups.size()); + for (const auto& [root, indices] : groups) { + RCPatch merged; + merged.viewID = rcPatches[root].viewID; + for (size_t idx : indices) { + merged.faces.insert(merged.faces.end(), + rcPatches[idx].faces.begin(), + rcPatches[idx].faces.end()); + } + int minX = INT_MAX, minY = INT_MAX, maxX = 0, maxY = 0; + for (size_t idx : indices) { + const cv::Rect& r = rcPatches[idx].rect; + minX = std::min(minX, r.x); + minY = std::min(minY, r.y); + maxX = std::max(maxX, r.x + r.width); + maxY = std::max(maxY, r.y + r.height); + } + if (minX < maxX && minY < maxY) { + merged.rect = cv::Rect(minX, minY, maxX - minX, maxY - minY); + } + newPatches.push_back(std::move(merged)); + } + + const size_t newPatchCount = newPatches.size(); // ★ 保存,move 后就拿不到了 + + DEBUG_EXTRA("MergeSameViewPatches: %zu → %zu patches (%.1f%% reduction)", + numPatches, newPatchCount, + 100.0 * (1.0 - (double)newPatchCount / numPatches)); + + // ★★★ 在 std::move 之前,用 newPatches 构建 faceToPatchID ★★★ + faceToPatchID.assign(numFaces, UINT32_MAX); + for (uint32_t newPatchID = 0; newPatchID < (uint32_t)newPatchCount; ++newPatchID) { + for (FIndex faceID : newPatches[newPatchID].faces) { + if (faceID < numFaces) + faceToPatchID[faceID] = newPatchID; + } + } + DEBUG_EXTRA("faceToPatchID built: %zu faces mapped", numFaces); + + // ★★★ m_texelPatchID 重映射(用 oldToNew,不依赖 newPatches)★★★ + if (!m_texelPatchID.empty()) { + for (uint32_t& pid : m_texelPatchID) { + if (pid != NO_ID && pid < (uint32_t)numPatches) { + pid = oldToNew[pid]; + } + } + } + + // ★★★ 现在才 move ★★★ + rcPatches = std::move(newPatches); + // 从此以后 newPatches 是空的,不要再用了 + + // ★ 更新 rcSeamEdges(用 oldToNew + rcPatches.size()) + if (!rcSeamEdges.empty()) { + for (SeamEdge& edge : rcSeamEdges) { + if (edge.rcPatchID0 != UINT32_MAX && edge.rcPatchID0 < (uint32_t)numPatches) + edge.rcPatchID0 = oldToNew[edge.rcPatchID0]; + if (edge.rcPatchID1 != UINT32_MAX && edge.rcPatchID1 < (uint32_t)numPatches) + edge.rcPatchID1 = oldToNew[edge.rcPatchID1]; + } + rcSeamEdges.erase( + std::remove_if(rcSeamEdges.begin(), rcSeamEdges.end(), + [](const SeamEdge& e) { return e.rcPatchID0 == e.rcPatchID1; }), + rcSeamEdges.end() + ); + DEBUG_EXTRA("rcSeamEdges remapped: %zu edges remaining", rcSeamEdges.size()); + } + + // ★ 调试断言 + #ifdef _DEBUG + const size_t patchCount = rcPatches.size(); + for (const SeamEdge& e : rcSeamEdges) { + ASSERT(e.rcPatchID0 < patchCount); + ASSERT(e.rcPatchID1 < patchCount); + } + for (uint32_t pid : m_texelPatchID) { + if (pid != UINT32_MAX) ASSERT(pid < patchCount); + } + #endif + + DEBUG_EXTRA("m_texelPatchID remapped, rcPatches now has %zu patches", rcPatches.size()); +} + +void MeshTexture::RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize) +{ + // 保存原始 label + std::vector savedLabels(texturePatches.size()); + for (size_t i = 0; i < texturePatches.size(); ++i) + savedLabels[i] = texturePatches[i].label; + + // 把 atlas 作为临时图像插入 images + const size_t atlasIdx = images.size(); + Image atlasImg; + atlasImg.image = atlas; // cv::Mat 引用,不拷贝 + images.push_back(atlasImg); + + // 所有 patch 指向 atlas + for (size_t i = 0; i < texturePatches.size() - 1; ++i) + texturePatches[i].label = (IIndex)atlasIdx; + + // 执行接缝消除(直接修改 atlas 像素) + GlobalSeamLeveling3(); + LocalSeamLeveling3(); + + // 恢复 + for (size_t i = 0; i < savedLabels.size(); ++i) + texturePatches[i].label = savedLabels[i]; + images.pop_back(); +} + // ============================================================ // 3. RC 风格光栅化主函数(含接缝优化) // ============================================================ @@ -14893,26 +15172,239 @@ bool MeshTexture::RasterizeVirtualFaces( AlignPatchColors(atlas, m_texelPatchID, textureSize); } - // 5/6/7. 接缝 - BuildSeamEdgesFromRCPatches(); - if (!rcSeamEdges.empty()) { - DEBUG_EXTRA("Before GlobalAlign: rcSeamEdges=%zu, patchAvgColor=%zu", - rcSeamEdges.size(), patchAvgColor.size()); - GlobalPatchColorAlignment(atlas, textureSize); - LocalSeamBlending(atlas, textureSize); - } - - if (!seamEdges.empty()) { - TD_TIMER_START(); - SeamBlendingFromOriginalImages(atlas); - DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", seamEdges.size(), TD_TIMER_GET_FMT().c_str()); - } + // // 5/6/7. 接缝 + // BuildSeamEdgesFromRCPatches(); + // if (!rcSeamEdges.empty()) { + // DEBUG_EXTRA("Before GlobalAlign: rcSeamEdges=%zu, patchAvgColor=%zu", + // rcSeamEdges.size(), patchAvgColor.size()); + // GlobalPatchColorAlignment(atlas, textureSize); + // LocalSeamBlending(atlas, textureSize); + // } + + // if (!seamEdges.empty()) { + // TD_TIMER_START(); + // SeamBlendingFromOriginalImages(atlas); + // DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", seamEdges.size(), TD_TIMER_GET_FMT().c_str()); + // } + + // 在 rasterization 循环结束后、SmoothAtlasPatchBoundaries 之前加: + int validPixels = 0; + for (size_t i = 0; i < m_texelPatchID.size(); ++i) + if (m_texelPatchID[i] != NO_ID) validPixels++; + DEBUG_EXTRA("m_texelPatchID: %d valid pixels out of %zu", validPixels, m_texelPatchID.size()); + + // SmoothAtlasPatchBoundaries(atlas, textureSize); // AlignPatchColors(atlas, m_texelPatchID, textureSize); // FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3); return true; } +void MeshTexture::GlobalAlignPatches(Image8U3& atlas, int textureSize) { + const size_t NP = rcPatches.size(); + if (NP == 0 || rcSeamEdges.empty()) return; + + // 每个 patch 有一个颜色增益 g_i,初始为 1.0 + std::vector gain(NP, 1.0f); + + // 用 patch 的平均颜色作为参考 + std::vector avgColor(NP, cv::Vec3f(0,0,0)); + for (size_t i = 0; i < NP; ++i) { + if (patchAvgColor.size() > i) { + avgColor[i] = patchAvgColor[i]; // 你之前算过的 patchAvgColor + } + } + + // 简单迭代求解:让相邻 patch 在接缝处颜色一致 + for (int iter = 0; iter < 10; ++iter) { + std::vector newGain(NP, 0.0f); + std::vector count(NP, 0); + + for (const SeamEdge& edge : rcSeamEdges) { + const uint32_t a = edge.rcPatchID0; + const uint32_t b = edge.rcPatchID1; + if (a >= NP || b >= NP) continue; + if (a == b) continue; + + // 目标:gain[a] * avgColor[a] ≈ gain[b] * avgColor[b] + // 用 avgColor 的比值更新 + const cv::Vec3f& ca = avgColor[a]; + const cv::Vec3f& cb = avgColor[b]; + for (int c = 0; c < 3; ++c) { + if (ca[c] > 1e-6f && cb[c] > 1e-6f) { + const float ratio = cb[c] / ca[c]; + newGain[a] += ratio * gain[b]; + count[a]++; + } + } + } + + for (size_t i = 0; i < NP; ++i) { + if (count[i] > 0) { + gain[i] = newGain[i] / count[i]; + } + } + } + + // 应用增益到 atlas:遍历每个 patch 的像素,乘上 gain + for (size_t i = 0; i < NP; ++i) { + const cv::Rect& r = rcPatches[i].rect; + if (r.x < 0 || r.y < 0 || r.x + r.width > atlas.cols || r.y + r.height > atlas.rows) + continue; + + cv::Mat patch = atlas(r); + const float g = std::clamp(gain[i], 0.5f, 2.0f); + patch.convertTo(patch, CV_32FC3); + patch *= g; + patch.convertTo(patch, CV_8UC3); + } + + DEBUG_EXTRA("GlobalAlignPatches: %zu patches aligned", NP); +} +void MeshTexture::LocalBlendSeams(Image8U3& atlas, int textureSize) { + // ★ 包装为 cv::Mat + cv::Mat atlasMat(atlas.cols, atlas.rows, CV_8UC3, atlas.data); + + const int blendWidth = 4; + + for (const SeamEdge& edge : rcSeamEdges) { + const uint32_t a = edge.rcPatchID0; + const uint32_t b = edge.rcPatchID1; + if (a >= rcPatches.size() || b >= rcPatches.size()) continue; + + const cv::Rect& ra = rcPatches[a].rect; + const cv::Rect& rb = rcPatches[b].rect; + cv::Rect overlap = ra & rb; + if (overlap.area() == 0) continue; + + // 在重叠区域做线性混合 + for (int y = 0; y < overlap.height; ++y) { + for (int x = 0; x < overlap.width; ++x) { + const float t = (float)x / overlap.width; + const cv::Vec3b& ca = atlasMat.at(ra.y + y, ra.x + x); + const cv::Vec3b& cb = atlasMat.at(rb.y + y, rb.x + x); + atlasMat.at(overlap.y + y, overlap.x + x) = cv::Vec3b( + (uint8_t)(ca[0] * (1 - t) + cb[0] * t), + (uint8_t)(ca[1] * (1 - t) + cb[1] * t), + (uint8_t)(ca[2] * (1 - t) + cb[2] * t) + ); + } + } + } + DEBUG_EXTRA("LocalBlendSeams: %zu edges blended", rcSeamEdges.size()); +} +// ============================================================ +// 把 rcPatches 体系转换为标准 texturePatches + seamVertices 体系 +// 必须在 RasterizeVirtualFaces() 完成后调用 +// ============================================================ +void MeshTexture::BuildStandardSeamDataFromRCPatches( + const VirtualFaceMap& virtualFaceMap, + const std::vector>& virtualFaceViews, + int textureSize) +{ + const size_t numFaces = scene.mesh.faces.size(); + const size_t NP = rcPatches.size(); + + // ---- 1. texturePatches(最后一个是哨兵)---- + texturePatches.clear(); + texturePatches.resize(NP + 1); + for (size_t i = 0; i < NP; ++i) { + TexturePatch& tp = texturePatches[i]; + tp.label = rcPatches[i].viewID; + tp.rect = rcPatches[i].rect; + // ★ 用 Insert 逐个拷贝,避免 cList 赋值问题 + tp.faces.clear(); + for (FIndex fid : rcPatches[i].faces) { + tp.faces.Insert(fid); + } + } + // 哨兵 patch(最后一个) + texturePatches[NP].label = NO_ID; + + // ---- 2. mapIdxPatch:每个 face → 所属 patch ---- + // ★ 用 faceToPatchID(Merge 时已构建好),不要重新扫描 + mapIdxPatch.resize(numFaces); + for (size_t fid = 0; fid < numFaces; ++fid) { + mapIdxPatch[fid] = (faceToPatchID[fid] != UINT32_MAX) + ? faceToPatchID[fid] + : (uint32_t)NP; // 未映射 → 指向哨兵 + } + + // ---- 3. components:与 mapIdxPatch 一致 ---- + components.resize(numFaces); + for (size_t fid = 0; fid < numFaces; ++fid) { + components[fid] = mapIdxPatch[fid]; // ★ 不用 NO_ID,直接用 patch ID + } + + // ---- 4. seamVertices:从 rcSeamEdges 构建(已有正确的 patch ID)---- + seamVertices.clear(); + if (!rcSeamEdges.empty()) { + // 按顶点收集相邻 patch(通过网格顶点 → face → patch) + std::vector> vertexPatches(scene.mesh.vertices.size()); + for (size_t pid = 0; pid < NP; ++pid) { + for (FIndex fid : rcPatches[pid].faces) { + if (fid >= scene.mesh.faces.size()) continue; + const Mesh::Face& face = scene.mesh.faces[fid]; + for (int v = 0; v < 3; ++v) { + vertexPatches[face[v]].push_back((uint32_t)pid); + } + } + } + // 去重 + for (auto& vp : vertexPatches) { + std::sort(vp.begin(), vp.end()); + vp.erase(std::unique(vp.begin(), vp.end()), vp.end()); + } + + for (size_t vid = 0; vid < vertexPatches.size(); ++vid) { + if (vertexPatches[vid].size() < 2) continue; + + SeamVertex sv; + sv.idxVertex = (VIndex)vid; + + for (uint32_t pid : vertexPatches[vid]) { + if (pid >= texturePatches.size()) continue; + const TexturePatch& tp = texturePatches[pid]; + if (tp.faces.empty()) continue; + + // 找该顶点在 patch 中某个 face 上的 UV + TexCoord uv(0, 0); + bool found = false; + 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) { + if (face[v] == (VIndex)vid) { + uv = scene.mesh.faceTexcoords[fid * 3 + v]; + found = true; + break; + } + } + if (found) break; + } + if (!found) continue; + + // ★ 防御:检查 UV 有效性 + if (!(uv.x >= 0 && uv.x <= 1 && uv.y >= 0 && uv.y <= 1)) { + DEBUG_EXTRA("Invalid UV at vid=%zu fid=%u: (%f, %f)", vid, + tp.faces[0], uv.x, uv.y); + continue; + } + + SeamVertex::Patch sp; + sp.idxPatch = pid; + sp.proj = Point2f(uv.x * (float)textureSize, uv.y * (float)textureSize); + sv.patches.Insert(sp); + } + + if (sv.patches.size() >= 2) + seamVertices.push_back(sv); + } + } + + DEBUG_EXTRA("BuildStandardSeamData: %zu patches, %zu seamVertices, %zu edges", + NP, seamVertices.size(), rcSeamEdges.size()); +} // ============================================================ // 全局光度校正:估计每视图相对 gain (R/G/B 独立) // 用点云中被多张图共同可见的点做灰度加权最小二乘, @@ -15849,7 +16341,7 @@ if (!g_avgColorsComputed) { // ========== 颜色一致性代价 ========== { - const float lambda = 2.35f; + const float lambda = 0.0f; // ========== 收集邻居颜色(修复版:1-ring + faceToView)========== std::vector neighborColors; if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { @@ -15990,7 +16482,7 @@ if (!g_avgColorsComputed) { // ★ 简化:去掉颜色阻断,直接让评分说话 // 只要多数view的平均分不低于我太多(差 < 0.3),就切换 - if (majorityAvgScore > currentScore - 0.3f) { + if (majorityAvgScore > currentScore - 0.5f) { newFaceToView[fid] = majorityView; newFaceScores[fid] = majorityAvgScore; } @@ -20358,8 +20850,8 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi fRatioDataSmoothness, nIgnoreMaskLabel, views)) return false; - texture.SmoothVirtualFaceViews(texture.faceViews, mesh.faceFaces, 6); + // ✅ 4. RC 风格光栅化(Affine + Homography 混合) Mesh::Image8U3Arr textures; if (!texture.RasterizeVirtualFaces( @@ -20368,6 +20860,18 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi nTextureSizeMultiple, colEmpty, textures)) return false; + texture.MergeSameViewPatches(); + DEBUG_EXTRA("=== After MergeSameViewPatches, calling BuildStandardSeamData ==="); + // // ★★★ 新增:构建标准接缝数据 + 调用全局/局部接缝消除 ★★★ + // texture.BuildStandardSeamDataFromRCPatches( + // virtualFaceMap, texture.faceViews, nTextureSizeMultiple); + + // // 直接操作 atlas(不污染 images) + // texture.RunSeamLevelingOnAtlas(textures.back(), nTextureSizeMultiple); + + texture.GlobalAlignPatches(textures.back(), nTextureSizeMultiple); + texture.LocalBlendSeams(textures.back(), nTextureSizeMultiple); + mesh.texturesDiffuse = std::move(textures); DEBUG_EXTRA("Existing UV texturing completed: %u faces (%s)", mesh.faces.size(), TD_TIMER_GET_FMT().c_str());