|
|
|
@ -545,6 +545,21 @@ struct MeshTexture { |
|
|
|
AABB2f uvBounds; // UV 包围盒
|
|
|
|
AABB2f uvBounds; // UV 包围盒
|
|
|
|
cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图)
|
|
|
|
cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图)
|
|
|
|
bool isValid = false; |
|
|
|
bool isValid = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<FIndex> neighborFaces; // 邻接面ID
|
|
|
|
|
|
|
|
std::vector<int> sharedEdgeIdx; // 共享边索引 (0,1,2)
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct SeamBlendParams { |
|
|
|
|
|
|
|
int blendWidth = 4; // 融合宽度(像素)
|
|
|
|
|
|
|
|
float sigma = 1.0f; // 高斯权重参数
|
|
|
|
|
|
|
|
bool enableBlending = true; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct SeamInfo { |
|
|
|
|
|
|
|
FIndex faceA, faceB; |
|
|
|
|
|
|
|
int edgeIdxA, edgeIdxB; // 在各自面中的边索引
|
|
|
|
|
|
|
|
std::vector<Point2f> seamPixels; // 接缝上的像素坐标
|
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// used to interpolate adjustments color over the whole texture patch
|
|
|
|
// used to interpolate adjustments color over the whole texture patch
|
|
|
|
@ -657,8 +672,36 @@ public: |
|
|
|
return x + 1; |
|
|
|
return x + 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void FeatherSeams( |
|
|
|
|
|
|
|
const std::vector<SeamInfo>& seams, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
Image8U3& atlas); |
|
|
|
|
|
|
|
void GenerateSeamMask( |
|
|
|
|
|
|
|
const std::vector<SeamInfo>& seams, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
cv::Mat& seamMask); // 输出:CV_32FC1,接缝处值 > 0
|
|
|
|
bool ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMap); |
|
|
|
bool ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMap); |
|
|
|
|
|
|
|
bool DetectSeamsForBlending( |
|
|
|
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
|
|
|
const std::vector<std::vector<IIndex>>& virtualFaceViews, |
|
|
|
|
|
|
|
std::vector<SeamInfo>& seams); |
|
|
|
|
|
|
|
void ExtractSeamPixels( |
|
|
|
|
|
|
|
const SeamInfo& seam, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomA, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomB, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
std::vector<Point2i>& pixelsA, |
|
|
|
|
|
|
|
std::vector<Point2i>& pixelsB); |
|
|
|
|
|
|
|
void BlendSeamPixels( |
|
|
|
|
|
|
|
const SeamInfo& seam, |
|
|
|
|
|
|
|
const std::vector<Point2i>& pixelsA, |
|
|
|
|
|
|
|
const std::vector<Point2i>& pixelsB, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomA, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomB, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
Image8U3& atlas); |
|
|
|
|
|
|
|
float PointToLineDistance(const Point2f& p, const Point2f& a, const Point2f& b); |
|
|
|
|
|
|
|
|
|
|
|
bool ComputeHomographyForVirtualFace( |
|
|
|
bool ComputeHomographyForVirtualFace( |
|
|
|
const VirtualFace& vf, |
|
|
|
const VirtualFace& vf, |
|
|
|
IIndex viewID, |
|
|
|
IIndex viewID, |
|
|
|
@ -14334,6 +14377,9 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
{ |
|
|
|
{ |
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
|
|
|
|
// 在 RasterizeVirtualFaces 的像素写入循环中
|
|
|
|
|
|
|
|
// ✅ 只保留最核心的逻辑,不做任何衰减
|
|
|
|
|
|
|
|
|
|
|
|
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); |
|
|
|
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); |
|
|
|
if (color[0] == 0 && color[1] == 0 && color[2] == 0) |
|
|
|
if (color[0] == 0 && color[1] == 0 && color[2] == 0) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
@ -14347,12 +14393,11 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
size_t idx = atlasY * textureSize + atlasX; |
|
|
|
size_t idx = atlasY * textureSize + atlasX; |
|
|
|
TexelScore& ts = m_texelScores[idx]; |
|
|
|
TexelScore& ts = m_texelScores[idx]; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 获取当前虚拟面的评分
|
|
|
|
|
|
|
|
float currentScore = virtualFaceViewWeights[i].empty() |
|
|
|
float currentScore = virtualFaceViewWeights[i].empty() |
|
|
|
? -1.0f |
|
|
|
? -1.0f |
|
|
|
: virtualFaceViewWeights[i][0]; |
|
|
|
: virtualFaceViewWeights[i][0]; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 只接受更高评分的写入
|
|
|
|
// ✅ 正常写入,不衰减,不修改
|
|
|
|
if (currentScore > ts.score) { |
|
|
|
if (currentScore > ts.score) { |
|
|
|
atlas(atlasY, atlasX) = |
|
|
|
atlas(atlasY, atlasX) = |
|
|
|
Pixel8U{color[2], color[1], color[0]}; |
|
|
|
Pixel8U{color[2], color[1], color[0]}; |
|
|
|
@ -14365,6 +14410,41 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
m_texelScores.clear(); |
|
|
|
m_texelScores.clear(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:接缝融合后处理
|
|
|
|
|
|
|
|
if (true) { // 可配置开关
|
|
|
|
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 检测需要融合的接缝
|
|
|
|
|
|
|
|
std::vector<SeamInfo> seams; |
|
|
|
|
|
|
|
if (!DetectSeamsForBlending(virtualFaceMap, virtualFaceViews, seams)) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("Seam detection failed"); |
|
|
|
|
|
|
|
return true; // 不影响主流程
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// // 2. 方向性高斯羽化
|
|
|
|
|
|
|
|
// FeatherSeams(seams, textureSize, atlas);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 对每个接缝进行融合
|
|
|
|
|
|
|
|
for (const SeamInfo& seam : seams) { |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomA = m_virtualFaceGeometries[seam.faceA]; |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomB = m_virtualFaceGeometries[seam.faceB]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 提取接缝两侧像素
|
|
|
|
|
|
|
|
std::vector<Point2i> pixelsA, pixelsB; |
|
|
|
|
|
|
|
ExtractSeamPixels(seam, geomA, geomB, textureSize, pixelsA, pixelsB); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!pixelsA.empty() || !pixelsB.empty()) { |
|
|
|
|
|
|
|
// 执行融合
|
|
|
|
|
|
|
|
BlendSeamPixels(seam, pixelsA, pixelsB, geomA, geomB, textureSize, atlas); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Seam blending completed: %zu seams processed (%s)", |
|
|
|
|
|
|
|
seams.size(), TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -15094,6 +15174,11 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa |
|
|
|
// ✅ std::vector 用 resize
|
|
|
|
// ✅ std::vector 用 resize
|
|
|
|
m_virtualFaceGeometries.resize(virtualFaceMap.size()); |
|
|
|
m_virtualFaceGeometries.resize(virtualFaceMap.size()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 确保面邻接关系已计算
|
|
|
|
|
|
|
|
if (scene.mesh.faceFaces.empty()) { |
|
|
|
|
|
|
|
scene.mesh.ListIncidenteFaceFaces(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
@ -15106,6 +15191,21 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FIndex faceID = vf.faces[0]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 存储邻接面信息
|
|
|
|
|
|
|
|
geom.neighborFaces.clear(); |
|
|
|
|
|
|
|
geom.sharedEdgeIdx.clear(); |
|
|
|
|
|
|
|
const Mesh::Face& neighbors = scene.mesh.faceFaces[faceID]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int edgeIdx = 0; edgeIdx < 3; ++edgeIdx) { |
|
|
|
|
|
|
|
FIndex neighborID = neighbors[edgeIdx]; |
|
|
|
|
|
|
|
if (neighborID != NO_ID) { |
|
|
|
|
|
|
|
geom.neighborFaces.push_back(neighborID); |
|
|
|
|
|
|
|
geom.sharedEdgeIdx.push_back(edgeIdx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
IIndex viewID = faceViews[i][0]; |
|
|
|
IIndex viewID = faceViews[i][0]; |
|
|
|
geom.isValid = ComputeHomographyForVirtualFace(vf, viewID, geom); |
|
|
|
geom.isValid = ComputeHomographyForVirtualFace(vf, viewID, geom); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -15113,6 +15213,401 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MeshTexture::FeatherSeams( |
|
|
|
|
|
|
|
const std::vector<SeamInfo>& seams, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
Image8U3& atlas) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// 1. 生成接缝掩码
|
|
|
|
|
|
|
|
cv::Mat seamMask; |
|
|
|
|
|
|
|
GenerateSeamMask(seams, textureSize, seamMask); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建输出缓冲区
|
|
|
|
|
|
|
|
cv::Mat result(atlas.rows, atlas.cols, CV_8UC3); |
|
|
|
|
|
|
|
atlas.copyTo(result); // 先复制原始数据
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const int featherRadius = 8; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 对每条接缝做方向性高斯模糊
|
|
|
|
|
|
|
|
for (const SeamInfo& seam : seams) { |
|
|
|
|
|
|
|
int eA = seam.edgeIdxA; |
|
|
|
|
|
|
|
const TexCoord& uvA0 = scene.mesh.faceTexcoords[seam.faceA * 3 + eA]; |
|
|
|
|
|
|
|
const TexCoord& uvA1 = scene.mesh.faceTexcoords[seam.faceA * 3 + ((eA + 1) % 3)]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 接缝方向向量(像素空间)
|
|
|
|
|
|
|
|
float dx = (uvA1.x - uvA0.x) * textureSize; |
|
|
|
|
|
|
|
float dy = (uvA1.y - uvA0.y) * textureSize; |
|
|
|
|
|
|
|
float len = std::sqrt(dx * dx + dy * dy); |
|
|
|
|
|
|
|
if (len < 1e-6f) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 单位方向向量
|
|
|
|
|
|
|
|
float dirX = dx / len; |
|
|
|
|
|
|
|
float dirY = dy / len; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 沿接缝采样中心点
|
|
|
|
|
|
|
|
const int samples = 30; |
|
|
|
|
|
|
|
for (int s = 0; s <= samples; ++s) { |
|
|
|
|
|
|
|
float t = (float)s / samples; |
|
|
|
|
|
|
|
int cx = (int)((uvA0.x + t * (uvA1.x - uvA0.x)) * textureSize + 0.5f); |
|
|
|
|
|
|
|
int cy = (int)((uvA0.y + t * (uvA1.y - uvA0.y)) * textureSize + 0.5f); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 沿接缝方向做一维高斯模糊
|
|
|
|
|
|
|
|
for (int side = -1; side <= 1; side += 2) { |
|
|
|
|
|
|
|
for (int offset = 1; offset <= featherRadius; ++offset) { |
|
|
|
|
|
|
|
int px = cx + (int)(side * (-dirY) * offset); // 法线方向
|
|
|
|
|
|
|
|
int py = cy + (int)(side * (dirX) * offset); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (px < 0 || px >= textureSize || py < 0 || py >= textureSize) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 高斯权重
|
|
|
|
|
|
|
|
float sigma = featherRadius / 2.0f; |
|
|
|
|
|
|
|
float weight = std::exp(-(offset * offset) / (2 * sigma * sigma)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 沿接缝方向采样(一维卷积)
|
|
|
|
|
|
|
|
cv::Vec3f sum(0, 0, 0); |
|
|
|
|
|
|
|
float totalW = 0.0f; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int k = -featherRadius; k <= featherRadius; ++k) { |
|
|
|
|
|
|
|
int sx = px + (int)(dirX * k); |
|
|
|
|
|
|
|
int sy = py + (int)(dirY * k); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (sx < 0 || sx >= textureSize || sy < 0 || sy >= textureSize) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float kw = std::exp(-(k * k) / (2 * sigma * sigma)); |
|
|
|
|
|
|
|
Pixel8U p = atlas(sy, sx); |
|
|
|
|
|
|
|
sum[0] += p.b * kw; |
|
|
|
|
|
|
|
sum[1] += p.g * kw; |
|
|
|
|
|
|
|
sum[2] += p.r * kw; |
|
|
|
|
|
|
|
totalW += kw; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (totalW > 0.0f) { |
|
|
|
|
|
|
|
// 混合原始颜色和模糊颜色
|
|
|
|
|
|
|
|
Pixel8U orig = atlas(py, px); |
|
|
|
|
|
|
|
float maskVal = seamMask.at<float>(py, px); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cv::Vec3f blended( |
|
|
|
|
|
|
|
orig.b * (1.0f - maskVal) + (sum[0] / totalW) * maskVal, |
|
|
|
|
|
|
|
orig.g * (1.0f - maskVal) + (sum[1] / totalW) * maskVal, |
|
|
|
|
|
|
|
orig.r * (1.0f - maskVal) + (sum[2] / totalW) * maskVal |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result.at<cv::Vec3b>(py, px) = cv::Vec3b( |
|
|
|
|
|
|
|
(uchar)std::min(255.0f, blended[0]), |
|
|
|
|
|
|
|
(uchar)std::min(255.0f, blended[1]), |
|
|
|
|
|
|
|
(uchar)std::min(255.0f, blended[2]) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 写回
|
|
|
|
|
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
|
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
|
|
|
|
cv::Vec3b c = result.at<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
atlas(y, x) = Pixel8U{c[2], c[1], c[0]}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MeshTexture::GenerateSeamMask( |
|
|
|
|
|
|
|
const std::vector<SeamInfo>& seams, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
cv::Mat& seamMask) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
seamMask = cv::Mat(textureSize, textureSize, CV_32FC1, cv::Scalar(0.0f)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const int featherRadius = 8; // 羽化半径(像素),可调 4~16
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const SeamInfo& seam : seams) { |
|
|
|
|
|
|
|
// 获取接缝的两个端点(UV 空间)
|
|
|
|
|
|
|
|
int eA = seam.edgeIdxA; |
|
|
|
|
|
|
|
const TexCoord& uvA0 = scene.mesh.faceTexcoords[seam.faceA * 3 + eA]; |
|
|
|
|
|
|
|
const TexCoord& uvA1 = scene.mesh.faceTexcoords[seam.faceA * 3 + ((eA + 1) % 3)]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 沿接缝采样
|
|
|
|
|
|
|
|
const int samples = 30; |
|
|
|
|
|
|
|
for (int s = 0; s <= samples; ++s) { |
|
|
|
|
|
|
|
float t = (float)s / samples; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 接缝中心点(UV)
|
|
|
|
|
|
|
|
float u = uvA0.x + t * (uvA1.x - uvA0.x); |
|
|
|
|
|
|
|
float v = uvA0.y + t * (uvA1.y - uvA1.y); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 转像素坐标
|
|
|
|
|
|
|
|
int cx = (int)(u * textureSize + 0.5f); |
|
|
|
|
|
|
|
int cy = (int)(v * textureSize + 0.5f); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算接缝方向(用于方向性模糊)
|
|
|
|
|
|
|
|
float dx = (uvA1.x - uvA0.x) * textureSize; |
|
|
|
|
|
|
|
float dy = (uvA1.y - uvA0.y) * textureSize; |
|
|
|
|
|
|
|
float len = std::sqrt(dx * dx + dy * dy); |
|
|
|
|
|
|
|
if (len < 1e-6f) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 法线方向(垂直接缝)
|
|
|
|
|
|
|
|
float nx = -dy / len; |
|
|
|
|
|
|
|
float ny = dx / len; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在法线方向上标记羽化带
|
|
|
|
|
|
|
|
for (int offset = -featherRadius; offset <= featherRadius; ++offset) { |
|
|
|
|
|
|
|
if (offset == 0) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int px = cx + (int)(nx * offset); |
|
|
|
|
|
|
|
int py = cy + (int)(ny * offset); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (px < 0 || px >= textureSize || py < 0 || py >= textureSize) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 高斯权重:距离越远,权重越低
|
|
|
|
|
|
|
|
float gauss = std::exp(-(offset * offset) / (2.0f * (featherRadius / 2.0f) * (featherRadius / 2.0f))); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float& current = seamMask.at<float>(py, px); |
|
|
|
|
|
|
|
current = std::max(current, gauss); // 取最大值(多条接缝可能重叠)
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool MeshTexture::DetectSeamsForBlending( |
|
|
|
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
|
|
|
const std::vector<std::vector<IIndex>>& virtualFaceViews, |
|
|
|
|
|
|
|
std::vector<SeamInfo>& seams) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
seams.clear(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < virtualFaceMap.size(); ++i) { |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomA = m_virtualFaceGeometries[i]; |
|
|
|
|
|
|
|
if (!geomA.isValid) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IIndex viewA = virtualFaceViews[i].empty() ? NO_ID : virtualFaceViews[i][0]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t n = 0; n < geomA.neighborFaces.size(); ++n) { |
|
|
|
|
|
|
|
FIndex neighborID = geomA.neighborFaces[n]; |
|
|
|
|
|
|
|
int edgeIdxA = geomA.sharedEdgeIdx[n]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 避免重复处理(只处理一次)
|
|
|
|
|
|
|
|
if (neighborID < (FIndex)i) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomB = m_virtualFaceGeometries[neighborID]; |
|
|
|
|
|
|
|
if (!geomB.isValid) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IIndex viewB = virtualFaceViews[neighborID].empty() ? |
|
|
|
|
|
|
|
NO_ID : virtualFaceViews[neighborID][0]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 核心判断:不同视图才需要融合
|
|
|
|
|
|
|
|
if (viewA != viewB && viewA != NO_ID && viewB != NO_ID) { |
|
|
|
|
|
|
|
SeamInfo seam; |
|
|
|
|
|
|
|
seam.faceA = i; |
|
|
|
|
|
|
|
seam.faceB = neighborID; |
|
|
|
|
|
|
|
seam.edgeIdxA = edgeIdxA; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 找到边在 faceB 中的对应索引
|
|
|
|
|
|
|
|
const Mesh::Face& neighbors = scene.mesh.faceFaces[neighborID]; |
|
|
|
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
|
|
|
if (neighbors[e] == (FIndex)i) { |
|
|
|
|
|
|
|
seam.edgeIdxB = e; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
seams.push_back(seam); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Detected %zu seams requiring blending", seams.size()); |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void MeshTexture::ExtractSeamPixels( |
|
|
|
|
|
|
|
const SeamInfo& seam, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& /*geomA*/, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& /*geomB*/, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
std::vector<Point2i>& pixelsA, |
|
|
|
|
|
|
|
std::vector<Point2i>& pixelsB) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
pixelsA.clear(); |
|
|
|
|
|
|
|
pixelsB.clear(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 从 mesh 直接取 UV(唯一正确方式)
|
|
|
|
|
|
|
|
FIndex faceIDA = seam.faceA; |
|
|
|
|
|
|
|
int edgeIdxA = seam.edgeIdxA; |
|
|
|
|
|
|
|
const TexCoord& uvA0 = scene.mesh.faceTexcoords[faceIDA * 3 + edgeIdxA]; |
|
|
|
|
|
|
|
const TexCoord& uvA1 = scene.mesh.faceTexcoords[faceIDA * 3 + ((edgeIdxA + 1) % 3)]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FIndex faceIDB = seam.faceB; |
|
|
|
|
|
|
|
int edgeIdxB = seam.edgeIdxB; |
|
|
|
|
|
|
|
const TexCoord& uvB0 = scene.mesh.faceTexcoords[faceIDB * 3 + edgeIdxB]; |
|
|
|
|
|
|
|
const TexCoord& uvB1 = scene.mesh.faceTexcoords[faceIDB * 3 + ((edgeIdxB + 1) % 3)]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 在纹理空间中采样接缝线段
|
|
|
|
|
|
|
|
const int samples = 20; |
|
|
|
|
|
|
|
for (int s = 0; s <= samples; ++s) { |
|
|
|
|
|
|
|
float t = (float)s / samples; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 接缝上的点(UV 坐标)
|
|
|
|
|
|
|
|
Point2f uvOnSeamA( |
|
|
|
|
|
|
|
uvA0.x + t * (uvA1.x - uvA0.x), |
|
|
|
|
|
|
|
uvA0.y + t * (uvA1.y - uvA0.y) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
Point2f uvOnSeamB( |
|
|
|
|
|
|
|
uvB0.x + t * (uvB1.x - uvB0.x), |
|
|
|
|
|
|
|
uvB0.y + t * (uvB1.y - uvB0.y) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为纹理像素坐标
|
|
|
|
|
|
|
|
Point2i pixelA( |
|
|
|
|
|
|
|
(int)(uvOnSeamA.x * textureSize + 0.5f), |
|
|
|
|
|
|
|
(int)(uvOnSeamA.y * textureSize + 0.5f) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
Point2i pixelB( |
|
|
|
|
|
|
|
(int)(uvOnSeamB.x * textureSize + 0.5f), |
|
|
|
|
|
|
|
(int)(uvOnSeamB.y * textureSize + 0.5f) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 计算接缝方向向量
|
|
|
|
|
|
|
|
Point2f dirA(uvA1.x - uvA0.x, uvA1.y - uvA0.y); |
|
|
|
|
|
|
|
Point2f dirB(uvB1.x - uvB0.x, uvB1.y - uvB0.y); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算法线(逆时针旋转90度)
|
|
|
|
|
|
|
|
Point2f normalA(-dirA.y, dirA.x); |
|
|
|
|
|
|
|
Point2f normalB(-dirB.y, dirB.x); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 归一化
|
|
|
|
|
|
|
|
float lenA = std::sqrt(normalA.x*normalA.x + normalA.y*normalA.y); |
|
|
|
|
|
|
|
float lenB = std::sqrt(normalB.x*normalB.x + normalB.y*normalB.y); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (lenA > 1e-6f) { |
|
|
|
|
|
|
|
normalA.x /= lenA; normalA.y /= lenA; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (lenB > 1e-6f) { |
|
|
|
|
|
|
|
normalB.x /= lenB; normalB.y /= lenB; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 提取两侧像素带
|
|
|
|
|
|
|
|
const int blendWidth = 4; |
|
|
|
|
|
|
|
for (int offset = 1; offset <= blendWidth; ++offset) { |
|
|
|
|
|
|
|
// 面A侧
|
|
|
|
|
|
|
|
Point2f pixelOffsetA( |
|
|
|
|
|
|
|
uvOnSeamA.x + normalA.x * (offset / (float)textureSize), |
|
|
|
|
|
|
|
uvOnSeamA.y + normalA.y * (offset / (float)textureSize) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
Point2i pA( |
|
|
|
|
|
|
|
(int)(pixelOffsetA.x * textureSize + 0.5f), |
|
|
|
|
|
|
|
(int)(pixelOffsetA.y * textureSize + 0.5f) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (pA.x >= 0 && pA.x < textureSize && pA.y >= 0 && pA.y < textureSize) { |
|
|
|
|
|
|
|
pixelsA.push_back(pA); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 面B侧
|
|
|
|
|
|
|
|
Point2f pixelOffsetB( |
|
|
|
|
|
|
|
uvOnSeamB.x - normalB.x * (offset / (float)textureSize), |
|
|
|
|
|
|
|
uvOnSeamB.y - normalB.y * (offset / (float)textureSize) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
Point2i pB( |
|
|
|
|
|
|
|
(int)(pixelOffsetB.x * textureSize + 0.5f), |
|
|
|
|
|
|
|
(int)(pixelOffsetB.y * textureSize + 0.5f) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (pB.x >= 0 && pB.x < textureSize && pB.y >= 0 && pB.y < textureSize) { |
|
|
|
|
|
|
|
pixelsB.push_back(pB); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 去重
|
|
|
|
|
|
|
|
std::sort(pixelsA.begin(), pixelsA.end(), |
|
|
|
|
|
|
|
[](const Point2i& a, const Point2i& b) { |
|
|
|
|
|
|
|
return a.y < b.y || (a.y == b.y && a.x < b.x); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
pixelsA.erase(std::unique(pixelsA.begin(), pixelsA.end()), pixelsA.end()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::sort(pixelsB.begin(), pixelsB.end(), |
|
|
|
|
|
|
|
[](const Point2i& a, const Point2i& b) { |
|
|
|
|
|
|
|
return a.y < b.y || (a.y == b.y && a.x < b.x); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
pixelsB.erase(std::unique(pixelsB.begin(), pixelsB.end()), pixelsB.end()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void MeshTexture::BlendSeamPixels( |
|
|
|
|
|
|
|
const SeamInfo& seam, |
|
|
|
|
|
|
|
const std::vector<Point2i>& pixelsA, |
|
|
|
|
|
|
|
const std::vector<Point2i>& pixelsB, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomA, |
|
|
|
|
|
|
|
const VirtualFaceGeometry& geomB, |
|
|
|
|
|
|
|
int textureSize, |
|
|
|
|
|
|
|
Image8U3& atlas) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
const int GUTTER_PX = 6; // 4px gutter,可根据效果调3~6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
// 处理面A侧:只降低边缘像素的评分,不修改颜色
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
for (const Point2i& pixel : pixelsA) { |
|
|
|
|
|
|
|
Point2f uvPixel(pixel.x / (float)textureSize, pixel.y / (float)textureSize); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算到接缝边的距离(UV空间转像素距离)
|
|
|
|
|
|
|
|
int eA = seam.edgeIdxA; |
|
|
|
|
|
|
|
const TexCoord& uv0 = scene.mesh.faceTexcoords[seam.faceA * 3 + eA]; |
|
|
|
|
|
|
|
const TexCoord& uv1 = scene.mesh.faceTexcoords[seam.faceA * 3 + ((eA + 1) % 3)]; |
|
|
|
|
|
|
|
float dist = PointToLineDistance(uvPixel, Point2f(uv0.x, uv0.y), Point2f(uv1.x, uv1.y)) * textureSize; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 核心:只衰减评分,不碰颜色
|
|
|
|
|
|
|
|
// 距离接缝越近,评分越低,越容易被对面覆盖
|
|
|
|
|
|
|
|
if (dist < GUTTER_PX) { |
|
|
|
|
|
|
|
size_t idx = pixel.y * textureSize + pixel.x; |
|
|
|
|
|
|
|
if (idx < m_texelScores.size()) { |
|
|
|
|
|
|
|
// 二次曲线衰减,比线性衰减更平滑
|
|
|
|
|
|
|
|
float attenuation = (dist * dist * dist) / (float)(GUTTER_PX * GUTTER_PX * GUTTER_PX); |
|
|
|
|
|
|
|
m_texelScores[idx].score *= attenuation; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
// 处理面B侧:对称操作,只降低边缘像素评分
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
for (const Point2i& pixel : pixelsB) { |
|
|
|
|
|
|
|
Point2f uvPixel(pixel.x / (float)textureSize, pixel.y / (float)textureSize); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int eB = seam.edgeIdxB; |
|
|
|
|
|
|
|
const TexCoord& uv0 = scene.mesh.faceTexcoords[seam.faceB * 3 + eB]; |
|
|
|
|
|
|
|
const TexCoord& uv1 = scene.mesh.faceTexcoords[seam.faceB * 3 + ((eB + 1) % 3)]; |
|
|
|
|
|
|
|
float dist = PointToLineDistance(uvPixel, Point2f(uv0.x, uv0.y), Point2f(uv1.x, uv1.y)) * textureSize; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (dist < GUTTER_PX) { |
|
|
|
|
|
|
|
size_t idx = pixel.y * textureSize + pixel.x; |
|
|
|
|
|
|
|
if (idx < m_texelScores.size()) { |
|
|
|
|
|
|
|
float attenuation = (dist * dist * dist) / (float)(GUTTER_PX * GUTTER_PX * GUTTER_PX); |
|
|
|
|
|
|
|
m_texelScores[idx].score *= attenuation; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 辅助函数:点到直线距离
|
|
|
|
|
|
|
|
float MeshTexture::PointToLineDistance(const Point2f& p, const Point2f& a, const Point2f& b) { |
|
|
|
|
|
|
|
float dx = b.x - a.x; |
|
|
|
|
|
|
|
float dy = b.y - a.y; |
|
|
|
|
|
|
|
float len2 = dx*dx + dy*dy; |
|
|
|
|
|
|
|
if (len2 < 1e-12f) { |
|
|
|
|
|
|
|
float dx2 = p.x - a.x; |
|
|
|
|
|
|
|
float dy2 = p.y - a.y; |
|
|
|
|
|
|
|
return std::sqrt(dx2*dx2 + dy2*dy2); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
float t = ((p.x - a.x)*dx + (p.y - a.y)*dy) / len2; |
|
|
|
|
|
|
|
t = std::max(0.0f, std::min(1.0f, t)); |
|
|
|
|
|
|
|
float projx = a.x + t*dx; |
|
|
|
|
|
|
|
float projy = a.y + t*dy; |
|
|
|
|
|
|
|
float distx = p.x - projx; |
|
|
|
|
|
|
|
float disty = p.y - projy; |
|
|
|
|
|
|
|
return std::sqrt(distx*distx + disty*disty); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
// 2. 核心:计算单应矩阵 / 仿射矩阵
|
|
|
|
// 2. 核心:计算单应矩阵 / 仿射矩阵
|
|
|
|
// - 3 个点 → Affine(getAffineTransform)
|
|
|
|
// - 3 个点 → Affine(getAffineTransform)
|
|
|
|
|