diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index e8dbe11..31f5ade 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -17174,6 +17174,170 @@ if (patchID == NO_ID) { } } + // ========== 内置 AO 计算与暗部提亮(无需外部图片)========== + if (false) + { + DEBUG_EXTRA("[AO] Computing ambient occlusion directly..."); + + // 1. 构建深度图(用顶点位置的 UV 空间投影) + cv::Mat depthMap(textureSize, textureSize, CV_32FC1, std::numeric_limits::max()); + cv::Mat normalMap(textureSize, textureSize, CV_32FC3, cv::Vec3f(0, 0, 0)); + cv::Mat faceIDMap(textureSize, textureSize, CV_32SC1, -1); + + // 遍历所有面,在 UV 空间 rasterize 深度和法线 + for (size_t fi = 0; fi < scene.mesh.faces.size(); ++fi) { + const Face& face = scene.mesh.faces[fi]; + + // 获取三个顶点的 UV 和 3D 位置 + Point2f uv[3]; + Point3f pos[3]; + for (int j = 0; j < 3; ++j) { + uv[j] = scene.mesh.faceTexcoords[fi * 3 + j]; + pos[j] = scene.mesh.vertices[face[j]]; + } + + // 计算法线 + Vertex v0 = Vertex(pos[1]) - Vertex(pos[0]); + Vertex v1 = Vertex(pos[2]) - Vertex(pos[0]); + Vertex normal = v0.cross(v1); + float len = sqrtf(normal.dot(normal)); + if (len > 0.0f) { + normal /= len; + } + + // 计算 UV 包围盒 + float minU = std::min({uv[0].x, uv[1].x, uv[2].x}); + float maxU = std::max({uv[0].x, uv[1].x, uv[2].x}); + float minV = std::min({uv[0].y, uv[1].y, uv[2].y}); + float maxV = std::max({uv[0].y, uv[1].y, uv[2].y}); + + int minPX = std::max(0, (int)(minU * textureSize)); + int maxPX = std::min(textureSize - 1, (int)(maxU * textureSize)); + int minPY = std::max(0, (int)(minV * textureSize)); + int maxPY = std::min(textureSize - 1, (int)(maxV * textureSize)); + + // 对每个像素做重心坐标插值 + for (int py = minPY; py <= maxPY; ++py) { + for (int px = minPX; px <= maxPX; ++px) { + float u = (float)px / textureSize; + float v = (float)py / textureSize; + + float b0, b1, b2; + bool inside = ComputeBarycentric(u, v, uv[0], uv[1], uv[2], b0, b1, b2); + if (!inside) continue; + + // 插值得到 3D 位置(深度) + Point3f P = pos[0] * b0 + pos[1] * b1 + pos[2] * b2; + float depth = sqrt(P.dot(P)); // 到原点的距离作为深度 + + // 记录最小深度(离相机最近的面) + if (depth < depthMap.at(py, px)) { + depthMap.at(py, px) = depth; + normalMap.at(py, px) = cv::Vec3f(normal.x, normal.y, normal.z); + faceIDMap.at(py, px) = (int)fi; + } + } + } + } + + // 2. SSAO 计算 + cv::Mat aoMap(textureSize, textureSize, CV_32FC1, cv::Scalar(1.0f)); // 初始全白(无遮挡) + + int sampleRadius = 8; // 采样半径(像素) + int numSamples = 24; // 每个像素的采样点数 + float bias = 0.005f; // 深度偏差,防止自身遮挡 + float intensity = 1.5f; // AO 强度 + + for (int y = 0; y < textureSize; ++y) { + for (int x = 0; x < textureSize; ++x) { + float centerDepth = depthMap.at(y, x); + if (centerDepth >= std::numeric_limits::max() - 1.0f) continue; + + cv::Vec3f centerNormal = normalMap.at(y, x); + if (centerNormal[0] == 0 && centerNormal[1] == 0 && centerNormal[2] == 0) continue; + + float occlusion = 0.0f; + + // 在半球方向随机采样 + for (int s = 0; s < numSamples; ++s) { + // 生成随机采样方向(在法线朝向的半球内) + float theta = 2.0f * M_PI * (float)(rand() % 10000) / 10000.0f; + float phi = acos((float)(rand() % 10000) / 10000.0f); + + // 采样点偏移 + float radius = (float)(rand() % 10000) / 10000.0f * sampleRadius; + int sx = x + (int)(radius * sin(phi) * cos(theta)); + int sy = y + (int)(radius * sin(phi) * sin(theta)); + + // 边界检查 + if (sx < 0 || sx >= textureSize || sy < 0 || sy >= textureSize) continue; + + float sampleDepth = depthMap.at(sy, sx); + if (sampleDepth >= std::numeric_limits::max() - 1.0f) continue; + + // 判断采样点是否遮挡了中心点 + float depthDiff = sampleDepth - centerDepth; + if (depthDiff > bias && depthDiff < sampleRadius * 0.1f) { + // 采样点在中心点前方且距离适中,算作遮挡 + occlusion += 1.0f / (1.0f + depthDiff * depthDiff); + } + } + + // 归一化 AO 值 + float ao = 1.0f - occlusion / (float)numSamples * intensity; + ao = std::max(0.0f, std::min(1.0f, ao)); + aoMap.at(y, x) = ao; + } + } + + // 3. 高斯模糊 AO 贴图,使其更平滑 + cv::GaussianBlur(aoMap, aoMap, cv::Size(0, 0), 3.0); + + // 4. 用 AO 贴图做暗部提亮 + float strength = 0.35f; // 提亮强度 + float threshold = 0.60f; // AO 阈值,低于此值才提亮 + float exponent = 1.5f; // 非线性指数 + + cv::Mat atlasFloat; + atlas.convertTo(atlasFloat, CV_32FC3, 1.0f / 255.0f); + + for (int y = 0; y < textureSize; ++y) { + for (int x = 0; x < textureSize; ++x) { + float aoVal = aoMap.at(y, x); + + // 只处理 AO 值低于阈值的区域 + if (aoVal >= threshold) continue; + + // 计算提亮权重 + float darkness = (threshold - aoVal) / threshold; + darkness = std::pow(darkness, exponent); + float weight = darkness * strength; + + if (weight < 0.01f) continue; + + cv::Vec3f color = atlasFloat.at(y, x); + + // 提亮:color = color + weight * (1 - color) + color[0] = color[0] + weight * (1.0f - color[0]); + color[1] = color[1] + weight * (1.0f - color[1]); + color[2] = color[2] + weight * (1.0f - color[2]); + + // 钳制 + color[0] = std::min(1.0f, std::max(0.0f, color[0])); + color[1] = std::min(1.0f, std::max(0.0f, color[1])); + color[2] = std::min(1.0f, std::max(0.0f, color[2])); + + atlasFloat.at(y, x) = color; + } + } + + atlasFloat.convertTo(atlas, CV_8UC3, 255.0f); + + DEBUG_EXTRA("[AO] In-engine AO lightening applied: strength=%.2f, threshold=%.2f", + strength, threshold); + } + + return true; }