|
|
|
|
@ -669,6 +669,14 @@ public:
@@ -669,6 +669,14 @@ public:
|
|
|
|
|
const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数
|
|
|
|
|
const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数
|
|
|
|
|
); |
|
|
|
|
bool RasterizeVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
const std::vector<std::vector<IIndex>>& virtualFaceViews, |
|
|
|
|
const std::vector<std::vector<float>>& virtualFaceViewWeights, |
|
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
|
Pixel8U colEmpty, |
|
|
|
|
Mesh::Image8U3Arr& outTextures); |
|
|
|
|
|
|
|
|
|
bool TextureWithExistingUVVirtualFaces(const IIndexArr& views, int nIgnoreMaskLabel, |
|
|
|
|
float fOutlierThreshold, unsigned nTextureSizeMultiple, |
|
|
|
|
Pixel8U colEmpty, float fSharpnessWeight); |
|
|
|
|
@ -14049,11 +14057,164 @@ void MeshTexture::FillTextureHoles(std::vector<Image8U3>& textures, Pixel8U colE
@@ -14049,11 +14057,164 @@ void MeshTexture::FillTextureHoles(std::vector<Image8U3>& textures, Pixel8U colE
|
|
|
|
|
DEBUG_EXTRA("Hole filling completed"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
const std::vector<std::vector<IIndex>>& virtualFaceViews, |
|
|
|
|
const std::vector<std::vector<float>>& virtualFaceViewWeights, |
|
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
|
Pixel8U colEmpty, |
|
|
|
|
Mesh::Image8U3Arr& outTextures) |
|
|
|
|
{ |
|
|
|
|
DEBUG_EXTRA("Forward Rasterization Engine: Starting..."); |
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
if (virtualFaceMap.empty() || virtualFaceViews.size() != virtualFaceMap.size()) |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 1. UV 布局分析
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
AABB2f uvBounds(true); |
|
|
|
|
for (const TexCoord& uv : scene.mesh.faceTexcoords) |
|
|
|
|
uvBounds.InsertFull(uv); |
|
|
|
|
|
|
|
|
|
float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); |
|
|
|
|
float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); |
|
|
|
|
if (uvWidth < 0.001f) uvWidth = 1.0f; |
|
|
|
|
if (uvHeight < 0.001f) uvHeight = 1.0f; |
|
|
|
|
|
|
|
|
|
int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 2. 创建纹理与累积缓冲区
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
outTextures.emplace_back(textureSize, textureSize); |
|
|
|
|
Image8U3& atlas = outTextures.back(); |
|
|
|
|
atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
cv::Mat1f weightAccum(textureSize, textureSize, 0.f); |
|
|
|
|
cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0.f, 0.f, 0.f)); |
|
|
|
|
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 3. 光栅化参数
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
constexpr int SUPER_SAMPLE = 2; |
|
|
|
|
constexpr float STEP = 1.f / SUPER_SAMPLE; |
|
|
|
|
const float MAX_DIST_SQ = 25.f / (textureSize * textureSize); // 距离阈值
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 4. 正向光栅化主循环
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
#endif |
|
|
|
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) |
|
|
|
|
{ |
|
|
|
|
if (virtualFaceViews[idxVF].empty()) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
const IIndex viewID = virtualFaceViews[idxVF][0]; // 单视图
|
|
|
|
|
const Image& srcImg = images[viewID]; |
|
|
|
|
const Camera& cam = srcImg.camera; |
|
|
|
|
|
|
|
|
|
for (FIndex faceID : virtualFaceMap[idxVF].faces) |
|
|
|
|
{ |
|
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
|
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
|
const Vertex* verts[3] = { |
|
|
|
|
&scene.mesh.vertices[face[0]], |
|
|
|
|
&scene.mesh.vertices[face[1]], |
|
|
|
|
&scene.mesh.vertices[face[2]] |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 纹理空间包围盒
|
|
|
|
|
cv::Rect bbox; |
|
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
|
int px = int(uv[i].x * textureSize); |
|
|
|
|
int py = int(uv[i].y * textureSize); |
|
|
|
|
if (bbox.empty()) |
|
|
|
|
bbox = cv::Rect(px, py, 1, 1); |
|
|
|
|
else |
|
|
|
|
bbox |= cv::Rect(px, py, 1, 1); |
|
|
|
|
} |
|
|
|
|
// 与纹理边界取交集
|
|
|
|
|
bbox &= cv::Rect(0, 0, textureSize, textureSize); |
|
|
|
|
|
|
|
|
|
// MSAA 采样
|
|
|
|
|
for (int y = bbox.y; y < bbox.y + bbox.height; ++y) { |
|
|
|
|
for (int x = bbox.x; x < bbox.x + bbox.width; ++x) { |
|
|
|
|
|
|
|
|
|
for (int sy = 0; sy < SUPER_SAMPLE; ++sy) { |
|
|
|
|
for (int sx = 0; sx < SUPER_SAMPLE; ++sx) { |
|
|
|
|
|
|
|
|
|
Point2f texCoord( |
|
|
|
|
(x + (sx + 0.5f) * STEP) / textureSize, |
|
|
|
|
(y + (sy + 0.5f) * STEP) / textureSize |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
Point3f bary; |
|
|
|
|
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary)) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// 3D 世界坐标
|
|
|
|
|
Point3f P = |
|
|
|
|
*verts[0] * bary.x + |
|
|
|
|
*verts[1] * bary.y + |
|
|
|
|
*verts[2] * bary.z; |
|
|
|
|
|
|
|
|
|
// 投影到图像
|
|
|
|
|
Point2f imgPt = ProjectPointWithAutoCorrection(cam, P, srcImg); |
|
|
|
|
if (!srcImg.image.isInside(imgPt) || !cam.IsInFront(P)) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// 双三次采样
|
|
|
|
|
Sampler sampler; |
|
|
|
|
Color c = srcImg.image.sample<Sampler, Color>(sampler, imgPt); |
|
|
|
|
|
|
|
|
|
// 累加到原子操作区域
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp critical |
|
|
|
|
#endif |
|
|
|
|
{ |
|
|
|
|
cv::Vec3f& acc = colorAccum(y, x); |
|
|
|
|
acc[0] += c[2]; |
|
|
|
|
acc[1] += c[1]; |
|
|
|
|
acc[2] += c[0]; |
|
|
|
|
weightAccum(y, x) += 1.f; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 5. 权重归一化
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
|
float w = weightAccum(y, x); |
|
|
|
|
if (w > 0.f) { |
|
|
|
|
cv::Vec3f c = colorAccum(y, x) / w; |
|
|
|
|
atlas(y, x) = Pixel8U{ |
|
|
|
|
(unsigned char)cv::saturate_cast<uchar>(c[0]), |
|
|
|
|
(unsigned char)cv::saturate_cast<uchar>(c[1]), |
|
|
|
|
(unsigned char)cv::saturate_cast<uchar>(c[2]) |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Forward Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int nIgnoreMaskLabel, |
|
|
|
|
float fOutlierThreshold, unsigned nTextureSizeMultiple, |
|
|
|
|
Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
|
{ |
|
|
|
|
DEBUG_EXTRA("TextureWithExistingUVVirtualFaces (STABLE MODE: 1 Face = 1 VirtualFace)"); |
|
|
|
|
DEBUG_EXTRA("Texture Pipeline: Steps 3-4 (View Selection + VirtualFace Mapping)"); |
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
// 1. 验证输入
|
|
|
|
|
@ -14071,8 +14232,11 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
@@ -14071,8 +14232,11 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
|
|
|
|
const size_t numFaces = scene.mesh.faces.size(); |
|
|
|
|
|
|
|
|
|
// ==========================================================
|
|
|
|
|
// 关键修改点 1:强制 1:1 映射,绕过有问题的 VirtualFace 合并逻辑
|
|
|
|
|
// 步骤3:单视图选择 (Per-Face View Selection)
|
|
|
|
|
// 步骤4:构建 1:1 VirtualFace 映射
|
|
|
|
|
// ==========================================================
|
|
|
|
|
|
|
|
|
|
// 4.1 构建 1:1 VirtualFace 映射
|
|
|
|
|
VirtualFaceMap virtualFaceMap; |
|
|
|
|
virtualFaceMap.reserve(numFaces); |
|
|
|
|
for (FIndex fid = 0; fid < numFaces; ++fid) { |
|
|
|
|
@ -14080,27 +14244,26 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
@@ -14080,27 +14244,26 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
|
|
|
|
vf.faces.push_back(fid); |
|
|
|
|
virtualFaceMap.push_back(vf); |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Forced 1-to-1 mapping: %zu virtual faces for %zu original faces", |
|
|
|
|
DEBUG_EXTRA("Step 4: Forced 1-to-1 mapping: %zu virtual faces for %zu original faces", |
|
|
|
|
virtualFaceMap.size(), numFaces); |
|
|
|
|
|
|
|
|
|
// 2. 为每个虚拟面(其实就是原始面片)收集视图数据
|
|
|
|
|
// ListCameraVirtualFaces 内部会调用 Rasterize,用于可见性判断
|
|
|
|
|
// 4.2 为每个虚拟面收集视图数据(用于可见性判断)
|
|
|
|
|
VirtualFaceDataArr virtualFaceDatas; |
|
|
|
|
if (!ListCameraVirtualFaces(virtualFaceMap, virtualFaceDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ==========================================================
|
|
|
|
|
// 关键修改点 2:不再做多视图融合,直接选 Best View
|
|
|
|
|
// ==========================================================
|
|
|
|
|
// 4.3 单视图选择:为每个虚拟面选择最佳视图
|
|
|
|
|
std::vector<std::vector<IIndex>> virtualFaceViews(virtualFaceMap.size()); |
|
|
|
|
std::vector<std::vector<float>> virtualFaceViewWeights(virtualFaceMap.size()); |
|
|
|
|
|
|
|
|
|
size_t unassignedFaces = 0; |
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
#pragma omp parallel for reduction(+:unassignedFaces) schedule(dynamic) |
|
|
|
|
#endif |
|
|
|
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) { |
|
|
|
|
const FIndex fid = virtualFaceMap[idxVF].faces[0]; // 取出唯一的原始面片ID
|
|
|
|
|
const FIndex fid = virtualFaceMap[idxVF].faces[0]; |
|
|
|
|
const FaceDataArr& vfDatas = virtualFaceDatas[idxVF]; |
|
|
|
|
|
|
|
|
|
float bestWeight = -1.0f; |
|
|
|
|
@ -14115,36 +14278,29 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
@@ -14115,36 +14278,29 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
|
|
|
|
|
|
|
|
|
// 加上法线夹角权重(正对相机的权重更高)
|
|
|
|
|
const Image& image = images[data.idxView]; |
|
|
|
|
// ✅ 使用 double 向量,和 RMatrix 精度一致
|
|
|
|
|
Point3d camDir(0, 0, -1); |
|
|
|
|
camDir = image.camera.R * camDir; // ✅ RMatrix × Point3d
|
|
|
|
|
camDir = image.camera.R * camDir; |
|
|
|
|
|
|
|
|
|
// ✅ 归一化(double 版本)
|
|
|
|
|
const double len = sqrt( |
|
|
|
|
camDir.x * camDir.x + |
|
|
|
|
camDir.y * camDir.y + |
|
|
|
|
camDir.z * camDir.z |
|
|
|
|
); |
|
|
|
|
const double len = sqrt(camDir.x*camDir.x + camDir.y*camDir.y + camDir.z*camDir.z); |
|
|
|
|
if (len > 0.0) { |
|
|
|
|
camDir.x /= len; |
|
|
|
|
camDir.y /= len; |
|
|
|
|
camDir.z /= len; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ✅ 转回 float,用于和法线点积
|
|
|
|
|
Point3f cameraForward( |
|
|
|
|
static_cast<float>(camDir.x), |
|
|
|
|
Point3f cameraForward(static_cast<float>(camDir.x), |
|
|
|
|
static_cast<float>(camDir.y), |
|
|
|
|
static_cast<float>(camDir.z) |
|
|
|
|
); |
|
|
|
|
static_cast<float>(camDir.z)); |
|
|
|
|
|
|
|
|
|
// ✅ 法线夹角权重
|
|
|
|
|
const Normal& faceNormal = scene.mesh.faceNormals[fid]; |
|
|
|
|
float dotProduct = faceNormal.dot(cameraForward); |
|
|
|
|
dotProduct = std::clamp(dotProduct, -1.f, 1.f); |
|
|
|
|
float angleWeight = (dotProduct + 1.0f) * 0.5f; |
|
|
|
|
|
|
|
|
|
// 综合权重
|
|
|
|
|
float finalWeight = weight * 0.7f + angleWeight * 0.3f; |
|
|
|
|
const float wQuality = 0.7f; |
|
|
|
|
const float wAngle = 0.3f; |
|
|
|
|
float finalWeight = weight * wQuality + angleWeight * wAngle; |
|
|
|
|
|
|
|
|
|
if (finalWeight > bestWeight) { |
|
|
|
|
bestWeight = finalWeight; |
|
|
|
|
@ -14156,39 +14312,62 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
@@ -14156,39 +14312,62 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
|
|
|
|
if (bestViewID != IIndex(-1) && bestWeight > 0.1f) { |
|
|
|
|
virtualFaceViews[idxVF] = { bestViewID }; |
|
|
|
|
virtualFaceViewWeights[idxVF] = { 1.0f }; // 单视图权重设为1
|
|
|
|
|
} else { |
|
|
|
|
virtualFaceViews[idxVF].clear(); |
|
|
|
|
virtualFaceViewWeights[idxVF].clear(); |
|
|
|
|
++unassignedFaces; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 3. 生成纹理图集
|
|
|
|
|
// 此时调用的 GenerateMultiViewTextureAtlasWithVirtualFaces 内部,
|
|
|
|
|
// 虽然名字带 MultiView,但因为权重都是1且只有一个View,
|
|
|
|
|
// 实际上执行的是 Single View Rendering。
|
|
|
|
|
// 配合之前的 MSAA 代码,就能得到清晰且无锯齿的结果。
|
|
|
|
|
Mesh::Image8U3Arr textures = GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
|
DEBUG_EXTRA("Step 3-4 completed: %zu faces processed, %zu unassigned", |
|
|
|
|
numFaces, unassignedFaces); |
|
|
|
|
DEBUG_EXTRA("Proceeding to Step 5: Forward Rasterization"); |
|
|
|
|
|
|
|
|
|
// ==========================================================
|
|
|
|
|
// 步骤5:正向光栅化引擎
|
|
|
|
|
// ==========================================================
|
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
|
if (!RasterizeVirtualFaces( |
|
|
|
|
virtualFaceMap, |
|
|
|
|
virtualFaceDatas, |
|
|
|
|
virtualFaceViews, |
|
|
|
|
virtualFaceViewWeights, |
|
|
|
|
nTextureSizeMultiple, |
|
|
|
|
colEmpty, |
|
|
|
|
fSharpnessWeight |
|
|
|
|
); |
|
|
|
|
textures)) |
|
|
|
|
{ |
|
|
|
|
DEBUG_EXTRA("Step 5 failed: Forward rasterization error"); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Step 5 completed. Proceeding to Step 6: Post-processing"); |
|
|
|
|
|
|
|
|
|
// ==========================================================
|
|
|
|
|
// 步骤6:后处理
|
|
|
|
|
// ==========================================================
|
|
|
|
|
if (!textures.empty()) { |
|
|
|
|
// 6.1 设置纹理
|
|
|
|
|
scene.mesh.texturesDiffuse = std::move(textures); |
|
|
|
|
|
|
|
|
|
// 设置纹理索引(所有面片使用第0张纹理图)
|
|
|
|
|
// 6.2 设置纹理索引(所有面片使用第0张纹理图)
|
|
|
|
|
scene.mesh.faceTexindices.resize(numFaces); |
|
|
|
|
for (size_t i = 0; i < numFaces; ++i) { |
|
|
|
|
scene.mesh.faceTexindices[i] = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Successfully generated %zu texture atlases (Stable Mode)", scene.mesh.texturesDiffuse.size()); |
|
|
|
|
// 6.3 可选的锐化处理
|
|
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
|
DEBUG_EXTRA("Applying sharpness filter (weight: %.2f)", fSharpnessWeight); |
|
|
|
|
// ApplySharpening(scene.mesh.texturesDiffuse[0], fSharpnessWeight);
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 保存纹理
|
|
|
|
|
// 6.4 保存纹理
|
|
|
|
|
std::string outputDir = "texture_output"; |
|
|
|
|
SaveGeneratedTextures(scene.mesh.texturesDiffuse, outputDir); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Successfully generated %zu texture atlases (Stable Mode)", |
|
|
|
|
scene.mesh.texturesDiffuse.size()); |
|
|
|
|
DEBUG_EXTRA("Total texture generation time: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|