目前效果比较好的
This commit is contained in:
1 parent
c5dc504b1f
commit
990fff69c5
1 file changed
+147
-710
+147
-710
@@ -584,16 +584,6 @@ struct MeshTexture {
|
||||
*/
|
||||
std::vector<VirtualFaceGeometry> m_virtualFaceGeometries;
|
||||
|
||||
// ============================================================
|
||||
// 面片级全局增益(Global Gain)数据结构
|
||||
// ============================================================
|
||||
struct FaceGainInfo {
|
||||
float gain; // 亮度增益
|
||||
bool valid; // 是否有效(有相邻视角对比)
|
||||
};
|
||||
// 面片级增益数组(索引对应 face ID)
|
||||
std::vector<FaceGainInfo> m_faceGains;
|
||||
|
||||
public:
|
||||
MeshTexture(Scene& _scene, unsigned _nResolutionLevel=0, unsigned _nMinResolution=640);
|
||||
~MeshTexture();
|
||||
@@ -728,25 +718,6 @@ public:
|
||||
const VirtualFace& vf,
|
||||
IIndex viewID,
|
||||
VirtualFaceGeometry& geom);
|
||||
// ============================================================
|
||||
// ComputeGlobalGain - 计算面片级全局增益
|
||||
// 功能:对每个面片,比较其主视角与备选视角的平均亮度,
|
||||
// 计算增益使两者对齐。仅调整明暗,不改变色相。
|
||||
// 参数:virtualFaceViews 即光栅化循环中的 virtualFaceViews
|
||||
// ============================================================
|
||||
void ComputeGlobalGain(
|
||||
const Scene& scene,
|
||||
const Mesh& mesh,
|
||||
const std::vector<std::vector<IIndex>>& virtualFaceViews);
|
||||
float SampleLuminanceBilinear(const cv::Mat& img, float x, float y);
|
||||
// ============================================================
|
||||
// GetFaceMeanLuminance - 获取面片在指定视角下的平均亮度
|
||||
// ============================================================
|
||||
float GetFaceMeanLuminance(
|
||||
const Scene& scene,
|
||||
const Mesh& mesh,
|
||||
int faceID,
|
||||
int viewID) const;
|
||||
|
||||
std::vector<std::vector<IIndex>> faceViews;
|
||||
std::vector<std::vector<float>> faceViewWeights;
|
||||
@@ -16240,7 +16211,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
||||
// ★ 强制原样写入(正确图的效果)
|
||||
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = color;
|
||||
|
||||
//*
|
||||
/*
|
||||
// ★ 若后续需恢复仿射,请先打印参数确认:
|
||||
// printf("ac: kb=%.2f kg=%.2f kr=%.2f bb=%.2f bg=%.2f br=%.2f\n", ac.kb, ac.kg, ac.kr, ac.bb, ac.bg, ac.br);
|
||||
// 注意:OpenCV 是 BGR,确保 k/bb 对应 BGR 而非 RGB
|
||||
@@ -16256,7 +16227,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
||||
} else {
|
||||
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = color;
|
||||
}
|
||||
//*/
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16342,7 +16313,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
||||
|
||||
// 对标记为接缝的像素做高斯模糊
|
||||
cv::Mat blurred;
|
||||
cv::GaussianBlur(atlas, blurred, cv::Size(5, 5), 1.5); // 更大核
|
||||
cv::GaussianBlur(atlas, blurred, cv::Size(3, 3), 0.5);
|
||||
|
||||
// 只替换接缝像素
|
||||
for (int y = 0; y < textureSize; ++y) {
|
||||
@@ -16635,17 +16606,6 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
|
||||
if (!m_gainsEstimated) EstimateGlobalPhotometricCorrection();
|
||||
|
||||
// ===== 面片级全局增益(Global Gain)=====
|
||||
if (m_faceGains.empty()) {
|
||||
VERBOSE("Computing global gains for %zu faces...", virtualFaceViews.size());
|
||||
ComputeGlobalGain(scene, scene.mesh, virtualFaceViews);
|
||||
VERBOSE("Global gains computed: %zu valid, %zu invalid",
|
||||
std::count_if(m_faceGains.begin(), m_faceGains.end(),
|
||||
[](const FaceGainInfo& fg) { return fg.valid; }),
|
||||
std::count_if(m_faceGains.begin(), m_faceGains.end(),
|
||||
[](const FaceGainInfo& fg) { return !fg.valid; }));
|
||||
}
|
||||
|
||||
VERBOSE("[Raster] Step 2: starting patch loop, total faces=%zu", virtualFaceMap.size());
|
||||
|
||||
// 3. 逐三角形光栅化
|
||||
@@ -16714,7 +16674,7 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
// GSL4 会统一做全局颜色优化,这里乘了会导致双重校正
|
||||
// 如果后续不走 GSL4 流程,可以把下面 false 改为 true 恢复
|
||||
// ============================================================
|
||||
#if 1 // ← 改为 1 可临时恢复光栅化时乘 gain
|
||||
#if 0 // ← 改为 1 可临时恢复光栅化时乘 gain
|
||||
// 应用光度校正
|
||||
const cv::Vec3f& gain = (viewID < (IIndex)m_imageGains.size()) ? m_imageGains[viewID] : cv::Vec3f(1,1,1);
|
||||
const bool applyGain = (gain != cv::Vec3f(1,1,1));
|
||||
@@ -16731,41 +16691,43 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
}
|
||||
#endif
|
||||
|
||||
// 写入 atlas(最高分胜出,不做过程混合)
|
||||
#pragma omp critical (atlas_write)
|
||||
{
|
||||
for (int y = 0; y < patchH; ++y) {
|
||||
for (int x = 0; x < patchW; ++x) {
|
||||
cv::Vec3b color = patch.at<cv::Vec3b>(y, x);
|
||||
// 写入 atlas(最高分胜出)
|
||||
#pragma omp critical (atlas_write)
|
||||
{
|
||||
for (int y = 0; y < patchH; ++y) {
|
||||
for (int x = 0; x < patchW; ++x) {
|
||||
cv::Vec3b color = patch.at<cv::Vec3b>(y, x);
|
||||
|
||||
if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue;
|
||||
if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue;
|
||||
|
||||
int atlasX = x + minX, atlasY = y + minY;
|
||||
if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue;
|
||||
size_t idx = atlasY * textureSize + atlasX;
|
||||
int atlasX = x + minX, atlasY = y + minY;
|
||||
if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue;
|
||||
size_t idx = atlasY * textureSize + atlasX;
|
||||
|
||||
if (currentScore > m_texelScores[idx].score) {
|
||||
// ★ 应用面片级全局增益(仅调整亮度,不改变色相)
|
||||
float gain = 1.0f;
|
||||
if (i >= 0 && i < (int)m_faceGains.size() && m_faceGains[i].valid) {
|
||||
gain = m_faceGains[i].gain;
|
||||
// ★ 已删除错误放在这里的 m_texelPatchID.assign(...)
|
||||
|
||||
if (currentScore > m_texelScores[idx].score) {
|
||||
color[0] = cv::saturate_cast<uchar>(std::min(255.0f, color[0] * 1.0f));
|
||||
color[1] = cv::saturate_cast<uchar>(std::min(255.0f, color[1] * 1.0f));
|
||||
color[2] = cv::saturate_cast<uchar>(std::min(255.0f, color[2] * 1.0f));
|
||||
|
||||
// atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]};
|
||||
if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) {
|
||||
// 用 cv::Mat 的 at 方法,它内部有边界检查(Debug 模式下)
|
||||
cv::Vec3b& pixel = atlas.at<cv::Vec3b>(atlasY, atlasX);
|
||||
// pixel[0] = color[2];
|
||||
// pixel[1] = color[1];
|
||||
// pixel[2] = color[0];
|
||||
|
||||
pixel = color; // 原样写入,不再交换
|
||||
}
|
||||
|
||||
if (gain != 1.0f) {
|
||||
color[0] = cv::saturate_cast<uchar>(color[0] * gain); // B
|
||||
color[1] = cv::saturate_cast<uchar>(color[1] * gain); // G
|
||||
color[2] = cv::saturate_cast<uchar>(color[2] * gain); // R
|
||||
}
|
||||
|
||||
// 写入图集
|
||||
atlas.at<cv::Vec3b>(atlasY, atlasX) = color;
|
||||
m_texelScores[idx].score = currentScore;
|
||||
m_texelScores[idx].viewID = viewID;
|
||||
m_texelPatchID[idx] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_texelScores[idx].score = currentScore;
|
||||
m_texelScores[idx].viewID = viewID;
|
||||
m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VERBOSE("[Raster] Step 3: rasterization done, building RC patches...");
|
||||
@@ -16855,98 +16817,6 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
|
||||
// AlignPatchColors(atlas, m_texelPatchID, textureSize);
|
||||
// FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3);
|
||||
// ★★★ 后处理:接缝平滑 ★★★
|
||||
{
|
||||
Image8U3 smoothSrc = atlas.clone();
|
||||
int seamPixelCount = 0;
|
||||
|
||||
for (int y = 1; y < textureSize - 1; ++y) {
|
||||
for (int x = 1; x < textureSize - 1; ++x) {
|
||||
size_t centerIdx = y * textureSize + x;
|
||||
int centerPatch = m_texelPatchID[centerIdx];
|
||||
if (centerPatch == NO_ID) continue;
|
||||
|
||||
// 检查 4 邻域是否有不同 patch
|
||||
bool isSeam = false;
|
||||
int neighbors[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 k = 0; k < 4; ++k) {
|
||||
if (neighbors[k] != NO_ID && neighbors[k] != centerPatch) {
|
||||
isSeam = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSeam) {
|
||||
cv::Vec3i sum(0, 0, 0);
|
||||
int count = 0;
|
||||
|
||||
for (int dy = -1; dy <= 1; ++dy) {
|
||||
for (int dx = -1; dx <= 1; ++dx) {
|
||||
int nx = x + dx, ny = y + dy;
|
||||
if (nx < 0 || nx >= textureSize || ny < 0 || ny >= textureSize) continue;
|
||||
|
||||
size_t nidx = ny * textureSize + nx;
|
||||
int nPatch = m_texelPatchID[nidx];
|
||||
|
||||
if (nPatch != NO_ID && nPatch != centerPatch) {
|
||||
cv::Vec3b nc = smoothSrc.at<cv::Vec3b>(ny, nx);
|
||||
sum[0] += nc[0];
|
||||
sum[1] += nc[1];
|
||||
sum[2] += nc[2];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
cv::Vec3b avg(
|
||||
cv::saturate_cast<uchar>(sum[0] / count),
|
||||
cv::saturate_cast<uchar>(sum[1] / count),
|
||||
cv::saturate_cast<uchar>(sum[2] / count)
|
||||
);
|
||||
|
||||
cv::Vec3b original = smoothSrc.at<cv::Vec3b>(y, x);
|
||||
|
||||
// 计算到本patch内部像素的最小欧氏距离
|
||||
float minDistSq = 1e10f;
|
||||
for (int dy = -5; dy <= 5; ++dy) {
|
||||
for (int dx = -5; dx <= 5; ++dx) {
|
||||
int nx = x + dx, ny = y + dy;
|
||||
if (nx < 0 || nx >= textureSize || ny < 0 || ny >= textureSize) continue;
|
||||
|
||||
size_t nidx = ny * textureSize + nx;
|
||||
if (m_texelPatchID[nidx] == centerPatch) {
|
||||
float distSq = (float)(dx*dx + dy*dy);
|
||||
minDistSq = std::min(minDistSq, distSq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float minDist = std::sqrt(minDistSq);
|
||||
float sigma = 2.0f;
|
||||
float weight = std::exp(-minDist * minDist / (2.0f * sigma * sigma));
|
||||
weight = 1.0f - weight;
|
||||
weight = std::max(0.1f, std::min(0.5f, weight));
|
||||
|
||||
// 应用混合
|
||||
atlas.at<cv::Vec3b>(y, x) = cv::Vec3b(
|
||||
cv::saturate_cast<uchar>(original[0] * (1-weight) + avg[0] * weight),
|
||||
cv::saturate_cast<uchar>(original[1] * (1-weight) + avg[1] * weight),
|
||||
cv::saturate_cast<uchar>(original[2] * (1-weight) + avg[2] * weight)
|
||||
);
|
||||
seamPixelCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_EXTRA("Post-process seam smoothing done: %d seam pixels processed.", seamPixelCount);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18143,57 +18013,20 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive(
|
||||
cv::Vec3f MeshTexture::SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) {
|
||||
const Image& img = images[vid];
|
||||
if (img.image.empty()) return cv::Vec3f(-1, -1, -1);
|
||||
|
||||
Point2f proj = img.camera.ProjectPoint(Point3d(faceCenter));
|
||||
int cx = (int)(proj.x + 0.5f), cy = (int)(proj.y + 0.5f);
|
||||
|
||||
// ★★★ 3x3 区域平均采样 ★★★
|
||||
cv::Vec3f sum(0, 0, 0);
|
||||
int validCount = 0;
|
||||
|
||||
for (int dy = -1; dy <= 1; ++dy) {
|
||||
for (int dx = -1; dx <= 1; ++dx) {
|
||||
int sx = cx + dx;
|
||||
int sy = cy + dy;
|
||||
|
||||
// 边界检查
|
||||
if (sx < 0 || sx >= img.image.cols || sy < 0 || sy >= img.image.rows)
|
||||
continue;
|
||||
|
||||
const cv::Vec3b& c = img.image.at<cv::Vec3b>(sy, sx);
|
||||
|
||||
// 跳过黑色/无效像素(假设纯黑(0,0,0)为无效)
|
||||
if (c[0] == 0 && c[1] == 0 && c[2] == 0)
|
||||
continue;
|
||||
|
||||
sum[0] += c[2]; // B→R
|
||||
sum[1] += c[1]; // G→G
|
||||
sum[2] += c[0]; // R→B
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 3x3 区域内全部无效,退回单点采样
|
||||
if (validCount == 0) {
|
||||
if (cx < 0 || cy < 0 || cx >= img.image.cols || cy >= img.image.rows)
|
||||
return cv::Vec3f(-1, -1, -1);
|
||||
const cv::Vec3b& c = img.image.at<cv::Vec3b>(cy, cx);
|
||||
if (c[0] == 0 && c[1] == 0 && c[2] == 0)
|
||||
return cv::Vec3f(-1, -1, -1);
|
||||
return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f);
|
||||
}
|
||||
|
||||
// 返回归一化的平均值
|
||||
return cv::Vec3f(
|
||||
sum[0] / (validCount * 255.0f),
|
||||
sum[1] / (validCount * 255.0f),
|
||||
sum[2] / (validCount * 255.0f)
|
||||
);
|
||||
int px = (int)(proj.x + 0.5f), py = (int)(proj.y + 0.5f);
|
||||
if (px < 0 || py < 0 || px >= img.image.cols || py >= img.image.rows)
|
||||
return cv::Vec3f(-1, -1, -1);
|
||||
const cv::Vec3b& c = img.image.at<cv::Vec3b>(py, px);
|
||||
return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f); // BGR→RGB, normalize
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 5. SelectBestViewsForVirtualFaces(修复版)
|
||||
// ============================================================
|
||||
// ============================================================
|
||||
// 5. SelectBestViewsForVirtualFaces(局部采样颜色版)
|
||||
// ============================================================
|
||||
// 前置条件:MeshTexture 类中需声明并定义 SampleFaceCenterColor:
|
||||
// cv::Vec3f SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) const;
|
||||
// 实现见下方附录
|
||||
@@ -18420,80 +18253,67 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
- 0.1f * s_occlusion;
|
||||
float score = rawScore;
|
||||
|
||||
// ========== 颜色一致性代价(动态阈值版)==========
|
||||
{
|
||||
// ★ 判断是否为头发区域(法线朝下 + 位置偏高)
|
||||
bool isHair = (faceNormal.y < -0.3f); // 法线向下,典型头发区域
|
||||
// 可根据模型调整:也可加 faceCenter.y > 某个高度阈值
|
||||
// ========== 颜色一致性代价(局部采样版)==========
|
||||
{
|
||||
const float lambda = 8.0f; // 从 100 降到 0.5
|
||||
|
||||
// ★ 动态参数:头发区域放宽限制
|
||||
float dynamicLambda = isHair ? 60.0f : 120.0f;
|
||||
float dynamicMaxDiff = isHair ? 40.0f : 18.0f; // 头发放宽到 40
|
||||
// 采样当前候选视图下面中心的颜色
|
||||
cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter);
|
||||
if (candidateColor[0] < 0) {
|
||||
// 投影失败,跳过颜色代价但不 return
|
||||
// 保持 score 不变
|
||||
} else {
|
||||
// 收集邻居颜色:已分配的面,采样邻居面中心在邻居视图下的颜色
|
||||
std::vector<cv::Vec3f> neighborColors;
|
||||
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) {
|
||||
const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID];
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = topoNeighbors[k];
|
||||
if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue;
|
||||
|
||||
// 采样当前候选视图下面中心的颜色(3x3 平均已在 SampleFaceCenterColor 中实现)
|
||||
cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter);
|
||||
if (candidateColor[0] < 0) {
|
||||
// 投影失败,跳过颜色代价
|
||||
} else {
|
||||
// 收集邻居颜色
|
||||
std::vector<cv::Vec3f> neighborColors;
|
||||
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) {
|
||||
const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID];
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = topoNeighbors[k];
|
||||
if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue;
|
||||
IIndex nbView = faceToView[nb];
|
||||
if (nbView == NO_ID) {
|
||||
if (nb < (FIndex)faceNeighbors.size() && !faceNeighbors[nb].empty()) {
|
||||
nbView = faceNeighbors[nb][0];
|
||||
}
|
||||
}
|
||||
if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue;
|
||||
|
||||
IIndex nbView = faceToView[nb];
|
||||
if (nbView == NO_ID) {
|
||||
if (nb < (FIndex)faceNeighbors.size() && !faceNeighbors[nb].empty()) {
|
||||
nbView = faceNeighbors[nb][0];
|
||||
// 计算邻居面中心
|
||||
const Mesh::Face& nbFace = scene.mesh.faces[nb];
|
||||
Point3f nbCenter = (scene.mesh.vertices[nbFace[0]] +
|
||||
scene.mesh.vertices[nbFace[1]] +
|
||||
scene.mesh.vertices[nbFace[2]]) / 3.0f;
|
||||
|
||||
cv::Vec3f nbColor = SampleFaceCenterColor(nbView, nbCenter);
|
||||
if (nbColor[0] >= 0) {
|
||||
neighborColors.push_back(nbColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float colorDiff = 0.0f;
|
||||
if (!neighborColors.empty()) {
|
||||
for (const auto& nc : neighborColors) {
|
||||
cv::Vec3f d = candidateColor - nc;
|
||||
colorDiff += std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]);
|
||||
}
|
||||
colorDiff = colorDiff / neighborColors.size();
|
||||
colorDiff = std::min(colorDiff, 1.0f);
|
||||
|
||||
score -= lambda * colorDiff;
|
||||
}
|
||||
}
|
||||
if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue;
|
||||
|
||||
const Mesh::Face& nbFace = scene.mesh.faces[nb];
|
||||
Point3f nbCenter = (scene.mesh.vertices[nbFace[0]] +
|
||||
scene.mesh.vertices[nbFace[1]] +
|
||||
scene.mesh.vertices[nbFace[2]]) / 3.0f;
|
||||
|
||||
cv::Vec3f nbColor = SampleFaceCenterColor(nbView, nbCenter);
|
||||
if (nbColor[0] >= 0) {
|
||||
neighborColors.push_back(nbColor);
|
||||
// ---- 调试日志(前20个面 + 每5万面)----
|
||||
static int g_dbg = 0;
|
||||
g_dbg++;
|
||||
if (g_dbg <= 20 || faceID % 50000 == 0) {
|
||||
VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f",
|
||||
faceID, vid, rawScore, score);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float colorDiff = 0.0f;
|
||||
if (!neighborColors.empty()) {
|
||||
// ★ 使用中位数代替平均,抗高光干扰
|
||||
std::vector<float> diffs;
|
||||
for (const auto& nc : neighborColors) {
|
||||
cv::Vec3f d = candidateColor - nc;
|
||||
diffs.push_back(std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]));
|
||||
}
|
||||
std::sort(diffs.begin(), diffs.end());
|
||||
colorDiff = diffs[diffs.size() / 2]; // 中位数
|
||||
|
||||
// ★ 平滑惩罚:平方衰减,小差异不罚,大差异渐重
|
||||
float normDiff = std::min(colorDiff / dynamicMaxDiff, 1.0f);
|
||||
score -= dynamicLambda * normDiff * normDiff;
|
||||
|
||||
// ★ 极端的才重罚(避免硬截断导致碎片)
|
||||
if (colorDiff > dynamicMaxDiff * 1.8f) {
|
||||
score -= 300.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 调试日志 ----
|
||||
static int g_dbg = 0;
|
||||
g_dbg++;
|
||||
if (g_dbg <= 20 || faceID % 50000 == 0) {
|
||||
VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f colorDiff=%.1f isHair=%d",
|
||||
faceID, vid, rawScore, score, colorDiff, (int)isHair);
|
||||
}
|
||||
}
|
||||
}
|
||||
// ========== 颜色代价结束 ==========
|
||||
// ========== 颜色代价结束 ==========
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
@@ -18514,64 +18334,62 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 2. 改进的 Patch 一致性传播(削弱版)----------
|
||||
const int PROPAGATION_ITER = 3; // 从 6 降为 3,减少过度平滑
|
||||
for (int iter = 0; iter < PROPAGATION_ITER; ++iter) {
|
||||
std::vector<IIndex> newFaceToView = faceToView;
|
||||
std::vector<float> newFaceScores = faceScores;
|
||||
// ---------- 2. 改进的 Patch 一致性传播 ----------
|
||||
const int PROPAGATION_ITER = 6;
|
||||
for (int iter = 0; iter < PROPAGATION_ITER; ++iter) {
|
||||
std::vector<IIndex> newFaceToView = faceToView;
|
||||
std::vector<float> newFaceScores = faceScores;
|
||||
|
||||
for (FIndex fid = 0; fid < (FIndex)faceToView.size(); ++fid) {
|
||||
if (faceToView[fid] == NO_ID) continue;
|
||||
if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue;
|
||||
for (FIndex fid = 0; fid < (FIndex)faceToView.size(); ++fid) {
|
||||
if (faceToView[fid] == NO_ID) continue;
|
||||
if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue;
|
||||
|
||||
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid];
|
||||
float currentScore = faceScores[fid];
|
||||
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid];
|
||||
float currentScore = faceScores[fid];
|
||||
|
||||
// ★ 提高跳过门槛:高分面片不参与传播
|
||||
if (currentScore > 0.85f) continue; // 原 0.95,降低到 0.85
|
||||
if (currentScore > 0.95f) continue;
|
||||
|
||||
std::unordered_map<IIndex, int> vote;
|
||||
vote[faceToView[fid]] = 1;
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = neighbors[k];
|
||||
if (nb == NO_ID) continue;
|
||||
if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) {
|
||||
vote[faceToView[nb]]++;
|
||||
}
|
||||
}
|
||||
std::unordered_map<IIndex, int> vote;
|
||||
vote[faceToView[fid]] = 1;
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = neighbors[k];
|
||||
if (nb == NO_ID) continue;
|
||||
if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) {
|
||||
vote[faceToView[nb]]++;
|
||||
}
|
||||
}
|
||||
|
||||
IIndex majorityView = faceToView[fid];
|
||||
int maxVote = 0;
|
||||
for (const auto& v : vote) {
|
||||
if (v.second > maxVote) {
|
||||
maxVote = v.second;
|
||||
majorityView = v.first;
|
||||
}
|
||||
}
|
||||
IIndex majorityView = faceToView[fid];
|
||||
int maxVote = 0;
|
||||
for (const auto& v : vote) {
|
||||
if (v.second > maxVote) {
|
||||
maxVote = v.second;
|
||||
majorityView = v.first;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxVote >= 2 && majorityView != faceToView[fid]) {
|
||||
float majorityAvgScore = 0.0f;
|
||||
int count = 0;
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = neighbors[k];
|
||||
if (nb == NO_ID) continue;
|
||||
if (nb < (FIndex)faceToView.size() && faceToView[nb] == majorityView) {
|
||||
majorityAvgScore += faceScores[nb];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 0) majorityAvgScore /= count;
|
||||
if (maxVote >= 2 && majorityView != faceToView[fid]) {
|
||||
float majorityAvgScore = 0.0f;
|
||||
int count = 0;
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = neighbors[k];
|
||||
if (nb == NO_ID) continue;
|
||||
if (nb < (FIndex)faceToView.size() && faceToView[nb] == majorityView) {
|
||||
majorityAvgScore += faceScores[nb];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 0) majorityAvgScore /= count;
|
||||
|
||||
// ★ 提高切换门槛:多数派质量必须明显更好才切换
|
||||
if (majorityAvgScore > currentScore - 0.2f && majorityAvgScore > 0.75f) {
|
||||
newFaceToView[fid] = majorityView;
|
||||
newFaceScores[fid] = majorityAvgScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
faceToView.swap(newFaceToView);
|
||||
faceScores.swap(newFaceScores);
|
||||
}
|
||||
if (majorityAvgScore > currentScore - 0.5f) {
|
||||
newFaceToView[fid] = majorityView;
|
||||
newFaceScores[fid] = majorityAvgScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
faceToView.swap(newFaceToView);
|
||||
faceScores.swap(newFaceScores);
|
||||
}
|
||||
|
||||
// ---------- 3. 写回 ----------
|
||||
for (size_t i = 0; i < virtualFaceMap.size(); ++i) {
|
||||
@@ -18616,176 +18434,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
DEBUG_EXTRA("Fallback by center face : %zu", fallbackByCenterFace);
|
||||
DEBUG_EXTRA("=================================================");
|
||||
|
||||
// ================================================================
|
||||
// ★★★ 连通域强制统一视角(消除色差碎片)★★★
|
||||
// ================================================================
|
||||
{
|
||||
TD_TIMER_START();
|
||||
|
||||
const FIndex NF = (FIndex)faceToView.size();
|
||||
if (NF == 0) return true;
|
||||
|
||||
// 1. 构建面片邻接关系(只考虑有效面片)
|
||||
std::vector<std::vector<FIndex>> adjList(NF);
|
||||
for (FIndex fid = 0; fid < NF; ++fid) {
|
||||
if (faceToView[fid] == NO_ID) continue;
|
||||
if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue;
|
||||
|
||||
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid];
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
FIndex nb = neighbors[k];
|
||||
if (nb == NO_ID || nb >= NF) continue;
|
||||
if (faceToView[nb] == NO_ID) continue;
|
||||
adjList[fid].push_back(nb);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 连通域标记(BFS)
|
||||
std::vector<int> component(NF, -1);
|
||||
int numComponents = 0;
|
||||
|
||||
for (FIndex fid = 0; fid < NF; ++fid) {
|
||||
if (faceToView[fid] == NO_ID) continue;
|
||||
if (component[fid] >= 0) continue;
|
||||
|
||||
std::queue<FIndex> q;
|
||||
q.push(fid);
|
||||
component[fid] = numComponents;
|
||||
|
||||
while (!q.empty()) {
|
||||
FIndex cur = q.front(); q.pop();
|
||||
for (FIndex nb : adjList[cur]) {
|
||||
if (component[nb] < 0) {
|
||||
component[nb] = numComponents;
|
||||
q.push(nb);
|
||||
}
|
||||
}
|
||||
}
|
||||
numComponents++;
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Connected components: %d components from %d faces", numComponents, NF);
|
||||
|
||||
// 3. 统计每个连通域的视角分布
|
||||
struct CompInfo {
|
||||
std::vector<FIndex> faces;
|
||||
std::map<IIndex, int> viewVotes;
|
||||
std::map<IIndex, float> viewScoreSum;
|
||||
std::map<IIndex, float> viewAvgScore;
|
||||
};
|
||||
std::vector<CompInfo> comps(numComponents);
|
||||
|
||||
for (FIndex fid = 0; fid < NF; ++fid) {
|
||||
if (faceToView[fid] == NO_ID) continue;
|
||||
int c = component[fid];
|
||||
if (c < 0) continue;
|
||||
|
||||
IIndex vid = faceToView[fid];
|
||||
float score = faceScores[fid];
|
||||
|
||||
comps[c].faces.push_back(fid);
|
||||
comps[c].viewVotes[vid]++;
|
||||
comps[c].viewScoreSum[vid] += score;
|
||||
}
|
||||
|
||||
for (int c = 0; c < numComponents; ++c) {
|
||||
for (auto& vs : comps[c].viewScoreSum) {
|
||||
IIndex vid = vs.first;
|
||||
int votes = comps[c].viewVotes[vid];
|
||||
comps[c].viewAvgScore[vid] = vs.second / votes;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 每个连通域选最佳视角
|
||||
int changedFaces = 0;
|
||||
int smallComponents = 0;
|
||||
|
||||
for (int c = 0; c < numComponents; ++c) {
|
||||
auto& comp = comps[c];
|
||||
if (comp.faces.empty()) continue;
|
||||
|
||||
// 小连通域合并
|
||||
if ((int)comp.faces.size() < 5) {
|
||||
smallComponents++;
|
||||
|
||||
std::map<int, int> neighborCompVotes;
|
||||
for (FIndex fid : comp.faces) {
|
||||
for (FIndex nb : adjList[fid]) {
|
||||
int nc = component[nb];
|
||||
if (nc >= 0 && nc != c) {
|
||||
neighborCompVotes[nc]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!neighborCompVotes.empty()) {
|
||||
auto bestNb = std::max_element(neighborCompVotes.begin(), neighborCompVotes.end(),
|
||||
[](const auto& a, const auto& b) { return a.second < b.second; });
|
||||
|
||||
int targetComp = bestNb->first;
|
||||
IIndex targetView = NO_ID;
|
||||
int maxVotes = 0;
|
||||
for (const auto& vv : comps[targetComp].viewVotes) {
|
||||
if (vv.second > maxVotes) {
|
||||
maxVotes = vv.second;
|
||||
targetView = vv.first;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetView != NO_ID) {
|
||||
for (FIndex fid : comp.faces) {
|
||||
if (faceToView[fid] != targetView) {
|
||||
faceToView[fid] = targetView;
|
||||
faceScores[fid] = comps[targetComp].viewAvgScore[targetView];
|
||||
changedFaces++;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 正常连通域选视角
|
||||
IIndex bestView = NO_ID;
|
||||
float bestWeightedScore = -FLT_MAX;
|
||||
int totalFaces = (int)comp.faces.size();
|
||||
|
||||
for (const auto& vv : comp.viewVotes) {
|
||||
IIndex vid = vv.first;
|
||||
int votes = vv.second;
|
||||
float avgScore = comp.viewAvgScore[vid];
|
||||
|
||||
float voteRatio = (float)votes / totalFaces;
|
||||
float normalizedScore = std::max(0.0f, std::min(1.0f, avgScore));
|
||||
float weightedScore = 0.6f * voteRatio + 0.4f * normalizedScore;
|
||||
|
||||
if (weightedScore > bestWeightedScore) {
|
||||
bestWeightedScore = weightedScore;
|
||||
bestView = vid;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestView == NO_ID) continue;
|
||||
|
||||
for (FIndex fid : comp.faces) {
|
||||
if (faceToView[fid] != bestView) {
|
||||
faceToView[fid] = bestView;
|
||||
faceScores[fid] = comp.viewAvgScore[bestView];
|
||||
changedFaces++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Component unification: %d faces changed, %d small components merged (%s)",
|
||||
changedFaces, smallComponents, TD_TIMER_GET_FMT().c_str());
|
||||
|
||||
// ★★★ 注意:不要在这里更新 faceViews/faceViewWeights!
|
||||
// faceViews 会在视图选择阶段的末尾,由 SelectBestViewsForVirtualFaces 根据 faceToView 重建
|
||||
// 如果在这里强行同步,会破坏虚拟面片与视图的映射关系,导致采样坐标错误
|
||||
|
||||
DEBUG_EXTRA("Unification complete: %d components processed.", numComponents);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19229,69 +18877,6 @@ float MeshTexture::PointToLineDistance(const Point2f& p, const Point2f& a, const
|
||||
float disty = p.y - projy;
|
||||
return std::sqrt(distx*distx + disty*disty);
|
||||
}
|
||||
// ============================================================
|
||||
// GetFaceMeanLuminance - 获取面片在指定视角下的平均亮度
|
||||
// ============================================================
|
||||
float MeshTexture::GetFaceMeanLuminance(
|
||||
const Scene& scene,
|
||||
const Mesh& mesh,
|
||||
int faceID,
|
||||
int viewID) const
|
||||
{
|
||||
if (viewID < 0 || viewID >= (int)scene.images.size()) return 0.0f;
|
||||
|
||||
const Image8U3& image = scene.images[viewID].image;
|
||||
if (image.empty()) return 0.0f;
|
||||
|
||||
// 获取面片顶点(直接索引)
|
||||
Point3f v0 = mesh.vertices[faceID * 3];
|
||||
Point3f v1 = mesh.vertices[faceID * 3 + 1];
|
||||
Point3f v2 = mesh.vertices[faceID * 3 + 2];
|
||||
|
||||
// 获取相机参数
|
||||
const Camera& camera = scene.images[viewID].camera;
|
||||
|
||||
// ★ 修复:显式转为 Point3d(double)再投影
|
||||
// 世界坐标 → 相机坐标
|
||||
Point3d c0 = camera.TransformPointW2C(Point3d(v0));
|
||||
Point3d c1 = camera.TransformPointW2C(Point3d(v1));
|
||||
Point3d c2 = camera.TransformPointW2C(Point3d(v2));
|
||||
|
||||
// 检查是否在相机前方
|
||||
if (c0.z <= 0 || c1.z <= 0 || c2.z <= 0) return 0.0f;
|
||||
|
||||
// 相机坐标 → 图像坐标(归一化)
|
||||
Point2f p0 = camera.TransformPointC2I(c0);
|
||||
Point2f p1 = camera.TransformPointC2I(c1);
|
||||
Point2f p2 = camera.TransformPointC2I(c2);
|
||||
|
||||
// 计算面片在图像上的包围盒
|
||||
int minX = std::max(0, (int)std::floor(std::min({p0.x, p1.x, p2.x})));
|
||||
int maxX = std::min(image.width() - 1, (int)std::ceil(std::max({p0.x, p1.x, p2.x})));
|
||||
int minY = std::max(0, (int)std::floor(std::min({p0.y, p1.y, p2.y})));
|
||||
int maxY = std::min(image.height() - 1, (int)std::ceil(std::max({p0.y, p1.y, p2.y})));
|
||||
|
||||
if (minX >= maxX || minY >= maxY) return 0.0f;
|
||||
|
||||
// 采样包围盒内的像素,计算平均亮度
|
||||
float totalLum = 0.0f;
|
||||
int count = 0;
|
||||
|
||||
for (int y = minY; y <= maxY; ++y) {
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
const Pixel8U& pixel = image(y, x);
|
||||
// 跳过纯黑(无效区域)
|
||||
if (pixel.r == 0 && pixel.g == 0 && pixel.b == 0) continue;
|
||||
|
||||
// 计算亮度(BT.709 标准)
|
||||
float lum = 0.2126f * pixel.r + 0.7152f * pixel.g + 0.0722f * pixel.b;
|
||||
totalLum += lum;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return (count > 0) ? (totalLum / count) : 0.0f;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. 核心:计算单应矩阵 / 仿射矩阵
|
||||
@@ -19401,154 +18986,6 @@ bool MeshTexture::ComputeHomographyForVirtualFace(
|
||||
return geom.isValid;
|
||||
}
|
||||
|
||||
// 辅助函数:双线性插值采样亮度
|
||||
float MeshTexture::SampleLuminanceBilinear(const cv::Mat& img, float x, float y) {
|
||||
int ix = (int)x;
|
||||
int iy = (int)y;
|
||||
float fx = x - ix;
|
||||
float fy = y - iy;
|
||||
|
||||
const uchar* p00 = img.ptr<uchar>(iy, ix);
|
||||
const uchar* p10 = img.ptr<uchar>(iy, ix + 1);
|
||||
const uchar* p01 = img.ptr<uchar>(iy + 1, ix);
|
||||
const uchar* p11 = img.ptr<uchar>(iy + 1, ix + 1);
|
||||
|
||||
float c00 = 0.299f * p00[2] + 0.587f * p00[1] + 0.114f * p00[0];
|
||||
float c10 = 0.299f * p10[2] + 0.587f * p10[1] + 0.114f * p10[0];
|
||||
float c01 = 0.299f * p01[2] + 0.587f * p01[1] + 0.114f * p01[0];
|
||||
float c11 = 0.299f * p11[2] + 0.587f * p11[1] + 0.114f * p11[0];
|
||||
|
||||
float c0 = c00 * (1-fx) + c10 * fx;
|
||||
float c1 = c01 * (1-fx) + c11 * fx;
|
||||
|
||||
return c0 * (1-fy) + c1 * fy;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ComputeGlobalGain - 计算面片级全局增益
|
||||
// 功能:对每个面片,比较其主视角与备选视角的平均亮度,
|
||||
// 计算增益使两者对齐。仅调整明暗,不改变色相。
|
||||
// 参数:virtualFaceViews 即光栅化循环中的 virtualFaceViews
|
||||
// ============================================================
|
||||
void MeshTexture::ComputeGlobalGain(
|
||||
const Scene& scene,
|
||||
const Mesh& mesh,
|
||||
const std::vector<std::vector<IIndex>>& virtualFaceViews)
|
||||
{
|
||||
VERBOSE("[GlobalGain] Starting fast computation for %zu faces...", virtualFaceViews.size());
|
||||
|
||||
const size_t numFaces = virtualFaceViews.size();
|
||||
m_faceGains.resize(numFaces);
|
||||
|
||||
int progressInterval = std::max(1, (int)numFaces / 100);
|
||||
|
||||
#pragma omp parallel for schedule(dynamic, 1000)
|
||||
for (int faceID = 0; faceID < (int)numFaces; ++faceID) {
|
||||
if (faceID % progressInterval == 0) {
|
||||
#pragma omp critical
|
||||
VERBOSE("[GlobalGain] Progress: %d/%zu (%.1f%%)",
|
||||
faceID, numFaces, 100.0 * faceID / numFaces);
|
||||
}
|
||||
|
||||
const auto& views = virtualFaceViews[faceID];
|
||||
if (views.size() < 2) {
|
||||
m_faceGains[faceID].valid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 只采样三角形三个顶点和中心共4个点
|
||||
float lumMain = 0.0f;
|
||||
float lumAlt = 0.0f;
|
||||
int sampleCount = 0;
|
||||
|
||||
// 获取三角形的三个顶点
|
||||
const Point3f& v0f = mesh.vertices[faceID * 3];
|
||||
const Point3f& v1f = mesh.vertices[faceID * 3 + 1];
|
||||
const Point3f& v2f = mesh.vertices[faceID * 3 + 2];
|
||||
|
||||
// 转为 Point3d(因为 Camera::TransformPointW2C 内部用 double)
|
||||
Point3d v0(v0f.x, v0f.y, v0f.z);
|
||||
Point3d v1(v1f.x, v1f.y, v1f.z);
|
||||
Point3d v2(v2f.x, v2f.y, v2f.z);
|
||||
|
||||
// 采样点:三个顶点 + 中心(都用 Point3d)
|
||||
Point3d samplePoints[4] = {
|
||||
v0, v1, v2,
|
||||
Point3d((v0.x + v1.x + v2.x) / 3.0,
|
||||
(v0.y + v1.y + v2.y) / 3.0,
|
||||
(v0.z + v1.z + v2.z) / 3.0)
|
||||
};
|
||||
|
||||
for (int s = 0; s < 4; ++s) {
|
||||
const Point3d& pt = samplePoints[s];
|
||||
|
||||
// 主视角采样
|
||||
int mainViewID = views[0];
|
||||
const Camera& mainCam = scene.images[mainViewID].camera;
|
||||
|
||||
Point3d ptCam = mainCam.TransformPointW2C(pt);
|
||||
if (ptCam.z <= 0) continue;
|
||||
|
||||
Point2f ptImg = mainCam.TransformPointC2I(ptCam);
|
||||
if (ptImg.x < 0 || ptImg.x >= scene.images[mainViewID].image.cols - 1 ||
|
||||
ptImg.y < 0 || ptImg.y >= scene.images[mainViewID].image.rows - 1) continue;
|
||||
|
||||
// 双线性插值采样主视角亮度
|
||||
float lum = SampleLuminanceBilinear(scene.images[mainViewID].image, ptImg.x, ptImg.y);
|
||||
if (lum < 5.0f) continue; // 跳过纯黑
|
||||
|
||||
// 备选视角采样(取第一个有效的)
|
||||
float lumAltSample = 0.0f;
|
||||
bool foundAlt = false;
|
||||
for (size_t v = 1; v < views.size(); ++v) {
|
||||
int altViewID = views[v];
|
||||
const Camera& altCam = scene.images[altViewID].camera;
|
||||
|
||||
Point3d altPtCam = altCam.TransformPointW2C(pt);
|
||||
if (altPtCam.z <= 0) continue;
|
||||
|
||||
Point2f altPtImg = altCam.TransformPointC2I(altPtCam);
|
||||
if (altPtImg.x < 0 || altPtImg.x >= scene.images[altViewID].image.cols - 1 ||
|
||||
altPtImg.y < 0 || altPtImg.y >= scene.images[altViewID].image.rows - 1) continue;
|
||||
|
||||
lumAltSample = SampleLuminanceBilinear(scene.images[altViewID].image, altPtImg.x, altPtImg.y);
|
||||
if (lumAltSample > 5.0f) {
|
||||
foundAlt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundAlt) {
|
||||
lumMain += lum;
|
||||
lumAlt += lumAltSample;
|
||||
sampleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleCount > 0) {
|
||||
float avgMain = lumMain / sampleCount;
|
||||
float avgAlt = lumAlt / sampleCount;
|
||||
|
||||
if (avgMain > 1.0f && avgAlt > 1.0f) {
|
||||
float gain = avgAlt / avgMain;
|
||||
m_faceGains[faceID].gain = std::max(0.25f, std::min(4.0f, gain));
|
||||
m_faceGains[faceID].valid = true;
|
||||
} else {
|
||||
m_faceGains[faceID].valid = false;
|
||||
}
|
||||
} else {
|
||||
m_faceGains[faceID].valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
int validCount = 0;
|
||||
for (const auto& fg : m_faceGains) {
|
||||
if (fg.valid) validCount++;
|
||||
}
|
||||
|
||||
VERBOSE("[GlobalGain] Done. Valid: %d/%zu", validCount, numFaces);
|
||||
}
|
||||
|
||||
bool MeshTexture::GenerateTextureWithVirtualFacesInternal(bool bGlobalSeamLeveling, bool bLocalSeamLeveling,
|
||||
unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic,
|
||||
Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize,
|
||||
|
||||
Reference in new issue
Block a user