|
|
|
|
@ -10860,96 +10860,140 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromExistingTextures(const La
@@ -10860,96 +10860,140 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromExistingTextures(const La
|
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
|
int textureSize, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
|
{ |
|
|
|
|
DEBUG_EXTRA("===== 开始从图像生成纹理图集 ====="); |
|
|
|
|
DEBUG_EXTRA("纹理尺寸: %dx%d", textureSize, textureSize); |
|
|
|
|
|
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
|
|
|
|
|
|
// 创建纹理
|
|
|
|
|
textures.push_back(Image8U3()); |
|
|
|
|
Image8U3& textureAtlas = textures.back(); |
|
|
|
|
textureAtlas.create(textureSize, textureSize); |
|
|
|
|
|
|
|
|
|
if (textureAtlas.empty()) { |
|
|
|
|
DEBUG_EXTRA("错误: 无法创建纹理!"); |
|
|
|
|
return textures; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 设置背景色
|
|
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
// 计算整个模型的UV范围
|
|
|
|
|
// 计算UV坐标范围
|
|
|
|
|
AABB2f globalUVBounds(true); |
|
|
|
|
FOREACH(i, scene.mesh.faceTexcoords) { |
|
|
|
|
const TexCoord& uv = scene.mesh.faceTexcoords[i]; |
|
|
|
|
globalUVBounds.InsertFull(uv); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("全局UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", |
|
|
|
|
globalUVBounds.ptMin.x(), globalUVBounds.ptMin.y(), |
|
|
|
|
globalUVBounds.ptMax.x(), globalUVBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
float uvWidth = globalUVBounds.ptMax.x() - globalUVBounds.ptMin.x(); |
|
|
|
|
float uvHeight = globalUVBounds.ptMax.y() - globalUVBounds.ptMin.y(); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("全局UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f), 宽度=%.3f, 高度=%.3f", |
|
|
|
|
globalUVBounds.ptMin.x(), globalUVBounds.ptMin.y(), |
|
|
|
|
globalUVBounds.ptMax.x(), globalUVBounds.ptMax.y(), |
|
|
|
|
uvWidth, uvHeight); |
|
|
|
|
DEBUG_EXTRA("UV尺寸: 宽度=%.3f, 高度=%.3f", uvWidth, uvHeight); |
|
|
|
|
|
|
|
|
|
bool normalizeUV = (uvWidth > 100.0f || uvHeight > 100.0f); |
|
|
|
|
DEBUG_EXTRA("是否需要归一化UV坐标: %s", normalizeUV ? "是" : "否"); |
|
|
|
|
// 检查是否需要归一化
|
|
|
|
|
bool needNormalize = (globalUVBounds.ptMin.x() < 0.0f || globalUVBounds.ptMin.y() < 0.0f || |
|
|
|
|
globalUVBounds.ptMax.x() > 1.0f || globalUVBounds.ptMax.y() > 1.0f); |
|
|
|
|
|
|
|
|
|
if (needNormalize) { |
|
|
|
|
DEBUG_EXTRA("警告: UV坐标不在[0,1]范围内,需要进行归一化处理"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 为每个面片从原始图像采样颜色
|
|
|
|
|
int totalFaces = (int)scene.mesh.faces.size(); |
|
|
|
|
int processedFaces = 0; |
|
|
|
|
int sampledPixels = 0; |
|
|
|
|
int totalPixelsFilled = 0; |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("开始处理 %d 个面片", totalFaces); |
|
|
|
|
|
|
|
|
|
// 为每个面片生成纹理
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) reduction(+:processedFaces,sampledPixels) |
|
|
|
|
for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { |
|
|
|
|
#pragma omp parallel for schedule(dynamic) reduction(+:processedFaces,totalPixelsFilled) |
|
|
|
|
for (int idxFace = 0; idxFace < totalFaces; ++idxFace) { |
|
|
|
|
#else |
|
|
|
|
FOREACH(idxFace, scene.mesh.faces) { |
|
|
|
|
#endif |
|
|
|
|
const FIndex faceID = (FIndex)idxFace; |
|
|
|
|
const Label label = faceLabels[faceID]; |
|
|
|
|
if (label == 0) continue; // 跳过无视图的面片
|
|
|
|
|
|
|
|
|
|
if (label == 0) { |
|
|
|
|
// 跳过无视图的面片
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const IIndex idxView = label - 1; |
|
|
|
|
if (idxView >= images.size()) continue; |
|
|
|
|
if (idxView >= images.size()) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 获取面的UV坐标
|
|
|
|
|
if (faceID * 3 + 2 >= scene.mesh.faceTexcoords.size()) continue; |
|
|
|
|
if (faceID * 3 + 2 >= scene.mesh.faceTexcoords.size()) { |
|
|
|
|
DEBUG_EXTRA("警告: 面片 %d 缺少UV坐标", faceID); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
|
const Image& sourceImage = images[idxView]; |
|
|
|
|
|
|
|
|
|
// 将UV坐标归一化到[0,1]范围
|
|
|
|
|
TexCoord normalizedUVs[3]; |
|
|
|
|
// 将UV坐标转换到纹理空间
|
|
|
|
|
TexCoord texCoords[3]; |
|
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
|
if (normalizeUV) { |
|
|
|
|
// 归一化到[0,1]范围
|
|
|
|
|
normalizedUVs[i].x = (uvCoords[i].x - globalUVBounds.ptMin.x()) / uvWidth; |
|
|
|
|
normalizedUVs[i].y = (uvCoords[i].y - globalUVBounds.ptMin.y()) / uvHeight; |
|
|
|
|
} else { |
|
|
|
|
// 如果UV已经是[0,1]范围内,直接使用
|
|
|
|
|
normalizedUVs[i] = uvCoords[i]; |
|
|
|
|
// 计算归一化的UV坐标
|
|
|
|
|
float u = uvCoords[i].x; |
|
|
|
|
float v = uvCoords[i].y; |
|
|
|
|
|
|
|
|
|
// 如果UV坐标不在[0,1]范围内,进行归一化
|
|
|
|
|
if (needNormalize) { |
|
|
|
|
u = (u - globalUVBounds.ptMin.x()) / uvWidth; |
|
|
|
|
v = (v - globalUVBounds.ptMin.y()) / uvHeight; |
|
|
|
|
|
|
|
|
|
// 确保在[0,1]范围内
|
|
|
|
|
u = std::max(0.0f, std::min(1.0f, u)); |
|
|
|
|
v = std::max(0.0f, std::min(1.0f, v)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 确保在[0,1]范围内
|
|
|
|
|
normalizedUVs[i].x = std::max(0.0f, std::min(1.0f, normalizedUVs[i].x)); |
|
|
|
|
normalizedUVs[i].y = std::max(0.0f, std::min(1.0f, normalizedUVs[i].y)); |
|
|
|
|
} |
|
|
|
|
// 转换到像素坐标
|
|
|
|
|
texCoords[i].x = u * (textureSize - 1); |
|
|
|
|
texCoords[i].y = v * (textureSize - 1); |
|
|
|
|
|
|
|
|
|
// 计算归一化后的UV边界框
|
|
|
|
|
AABB2f faceUVBounds(true); |
|
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
|
faceUVBounds.InsertFull(normalizedUVs[i]); |
|
|
|
|
// 调试:输出前几个面片的UV坐标
|
|
|
|
|
if (processedFaces < 5 && i == 0) { |
|
|
|
|
DEBUG_EXTRA("面片 %d: 原始UV=(%.6f,%.6f), 归一化UV=(%.6f,%.6f), 像素坐标=(%.1f,%.1f)", |
|
|
|
|
faceID, uvCoords[i].x, uvCoords[i].y, u, v, |
|
|
|
|
texCoords[i].x, texCoords[i].y); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 将UV坐标转换到纹理像素坐标
|
|
|
|
|
const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize)); |
|
|
|
|
const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize)); |
|
|
|
|
const int endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize)); |
|
|
|
|
const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * textureSize)); |
|
|
|
|
|
|
|
|
|
// 检查边界框是否有效
|
|
|
|
|
if (startX >= endX || startY >= endY) { |
|
|
|
|
continue; |
|
|
|
|
// 计算三角形在纹理空间中的边界框
|
|
|
|
|
float minX = std::min(std::min(texCoords[0].x, texCoords[1].x), texCoords[2].x); |
|
|
|
|
float minY = std::min(std::min(texCoords[0].y, texCoords[1].y), texCoords[2].y); |
|
|
|
|
float maxX = std::max(std::max(texCoords[0].x, texCoords[1].x), texCoords[2].x); |
|
|
|
|
float maxY = std::max(std::max(texCoords[0].y, texCoords[1].y), texCoords[2].y); |
|
|
|
|
|
|
|
|
|
// 转换到像素坐标,并扩展1个像素确保覆盖边缘
|
|
|
|
|
int startX = std::max(0, (int)std::floor(minX) - 1); |
|
|
|
|
int startY = std::max(0, (int)std::floor(minY) - 1); |
|
|
|
|
int endX = std::min(textureSize - 1, (int)std::ceil(maxX) + 1); |
|
|
|
|
int endY = std::min(textureSize - 1, (int)std::ceil(maxY) + 1); |
|
|
|
|
|
|
|
|
|
// 调试:输出前几个面片的边界框
|
|
|
|
|
if (processedFaces < 5) { |
|
|
|
|
DEBUG_EXTRA("面片 %d 纹理边界框: [%d,%d]-[%d,%d]", faceID, startX, startY, endX, endY); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 对每个像素进行采样
|
|
|
|
|
int facePixelsFilled = 0; |
|
|
|
|
|
|
|
|
|
// 对边界框内的每个像素进行处理
|
|
|
|
|
for (int y = startY; y <= endY; ++y) { |
|
|
|
|
for (int x = startX; x <= endX; ++x) { |
|
|
|
|
const Point2f texCoord((float)x / textureSize, (float)y / textureSize); |
|
|
|
|
const Point2f texPoint(x, y); |
|
|
|
|
|
|
|
|
|
// 检查是否在三角形内
|
|
|
|
|
// 计算重心坐标
|
|
|
|
|
Point3f barycentric; |
|
|
|
|
if (PointInTriangle(texCoord, normalizedUVs[0], normalizedUVs[1], normalizedUVs[2], barycentric)) { |
|
|
|
|
if (PointInTriangle(texPoint, texCoords[0], texCoords[1], texCoords[2], barycentric)) { |
|
|
|
|
// 计算3D空间中的对应点
|
|
|
|
|
const Vertex worldPoint = |
|
|
|
|
vertices[face[0]] * barycentric.x + |
|
|
|
|
@ -10967,39 +11011,33 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& fa
@@ -10967,39 +11011,33 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& fa
|
|
|
|
|
Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); |
|
|
|
|
|
|
|
|
|
// 写入纹理图集
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp critical |
|
|
|
|
#endif |
|
|
|
|
{ |
|
|
|
|
textureAtlas(y, x) = sampledColor; |
|
|
|
|
sampledPixels++; |
|
|
|
|
} |
|
|
|
|
textureAtlas(y, x) = sampledColor; |
|
|
|
|
facePixelsFilled++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
processedFaces++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("纹理生成统计: 处理了%d个面片, 采样了%d个像素", processedFaces, sampledPixels); |
|
|
|
|
// 更新统计
|
|
|
|
|
if (facePixelsFilled > 0) { |
|
|
|
|
totalPixelsFilled += facePixelsFilled; |
|
|
|
|
processedFaces++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 检查生成的纹理
|
|
|
|
|
int coloredPixels = 0; |
|
|
|
|
for (int y = 0; y < textureAtlas.rows; ++y) { |
|
|
|
|
for (int x = 0; x < textureAtlas.cols; ++x) { |
|
|
|
|
const Pixel8U& pixel = textureAtlas(y, x); |
|
|
|
|
if (!(pixel[0] == colEmpty.b && pixel[1] == colEmpty.g && pixel[2] == colEmpty.r)) { |
|
|
|
|
coloredPixels++; |
|
|
|
|
} |
|
|
|
|
// 显示进度
|
|
|
|
|
if ((processedFaces % 1000) == 0) { |
|
|
|
|
DEBUG_EXTRA("处理进度: %d/%d 个面片, 填充了 %d 个像素", |
|
|
|
|
processedFaces, totalFaces, totalPixelsFilled); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("纹理填充率: %d/%d (%.2f%%)", |
|
|
|
|
coloredPixels, textureAtlas.rows * textureAtlas.cols, |
|
|
|
|
(coloredPixels * 100.0) / (textureAtlas.rows * textureAtlas.cols)); |
|
|
|
|
DEBUG_EXTRA("纹理生成完成: 处理了 %d/%d 个面片, 总共填充了 %d 个像素", |
|
|
|
|
processedFaces, totalFaces, totalPixelsFilled); |
|
|
|
|
|
|
|
|
|
ValidateGeneratedTexture(textureAtlas, "生成的纹理"); |
|
|
|
|
// 计算填充率
|
|
|
|
|
int totalPixels = textureSize * textureSize; |
|
|
|
|
float fillRate = (totalPixelsFilled * 100.0f) / totalPixels; |
|
|
|
|
DEBUG_EXTRA("纹理填充率: %.2f%% (%d/%d)", fillRate, totalPixelsFilled, totalPixels); |
|
|
|
|
|
|
|
|
|
return textures; |
|
|
|
|
} |
|
|
|
|
|