diff --git a/Engine/src/Renderer/Cubemap.cpp b/Engine/src/Renderer/Cubemap.cpp index 2f59982..d5de00d 100644 --- a/Engine/src/Renderer/Cubemap.cpp +++ b/Engine/src/Renderer/Cubemap.cpp @@ -120,7 +120,9 @@ namespace Engine // ---- GGX 重要性采样(split-sum 镜面端)---- - // van der Corput 基底反转(位反转技巧) + // van der Corput 基底反转(位反转技巧)。 + // 注意是整数值转浮点(static_cast),不是位型重解读—— + // bit_cast 会把反转后的整数当 IEEE754 解读,序列完全错乱 f32 RadicalInverseVdC(u32 bits) { bits = (bits << 16u) | (bits >> 16u); @@ -128,7 +130,7 @@ namespace Engine bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return std::bit_cast(bits) * 2.3283064365386963e-10f; + return static_cast(bits) * 2.3283064365386963e-10f; } // n = 当次积分总采样数(xi.x = i/n 必须覆盖 [0,1] 全程) @@ -202,7 +204,10 @@ namespace Engine const f32 g = (nDotV / (nDotV * (1.0f - k) + k)) * (nDotL / (nDotL * (1.0f - k) + k)); - const f32 gVis = g * vDotH / (nDotH * nDotV); + // 高 roughness 时部分样本 H.z 恰为 0(g 也为 0), + // 0/0 = NaN 会累进 LUT——分母钳 epsilon + const f32 gVis = g * vDotH / + glm::max(nDotH * nDotV, 1e-6f); const f32 fc = 1.0f - vDotH; const f32 f = fc * fc * fc * fc * fc; // (1-vDotH)⁵,乘法链 @@ -509,6 +514,17 @@ namespace Engine 0, 1, 0, 1})); } + // fp16 NaN 自检(指数全 1 且尾数非 0):预积分数据上传前的 + // 最后一道防线——NaN 进 GPU 会污染整屏输出 + bool ContainsNaNHalf(const u16* data, size_t count) + { + for (size_t i = 0; i < count; ++i) { + const u32 abs = data[i] & 0x7FFFu; + if (abs > 0x7C00u) return true; + } + return false; + } + vk::Sampler CreateEnvSampler(Device& device, u32 maxLod) { vk::SamplerCreateInfo samplerInfo; @@ -592,20 +608,27 @@ namespace Engine } // ---- 2c. pre-filter cube:GGX 重要性采样 × mip 分级 roughness ---- - // 采样数随 mip 递增(高 roughness 分布更宽需要更多样本) - std::vector prefFaces; + // 采样数随 mip 递增(高 roughness 分布更宽需要更多样本)。 + // 注意:必须一次性分配总容量——逐 mip resize 会 realloc,使 + // prefMips 里已记录的 data 指针悬空(上传读到垃圾 = NaN 全黑) + u32 prefTotal = 0; + for (u32 mip = 0; mip < kPrefilterMips; ++mip) { + const u32 dim = kPrefilterFaceSize >> mip; + prefTotal += kFaceCount * dim * dim * 4; + } + std::vector prefFaces(prefTotal); std::vector prefMips; prefMips.reserve(kPrefilterMips); + size_t prefOffset = 0; for (u32 mip = 0; mip < kPrefilterMips; ++mip) { const u32 dim = kPrefilterFaceSize >> mip; const f32 roughness = static_cast(mip) / static_cast(kPrefilterMips - 1); const u32 samples = std::min(32u << mip, 256u); - const size_t offset = prefFaces.size(); - prefFaces.resize(offset + kFaceCount * dim * dim * 4); + u16* base = prefFaces.data() + prefOffset; for (u32 f = 0; f < kFaceCount; ++f) { - u16* dst = prefFaces.data() + offset + f * dim * dim * 4; + u16* dst = base + f * dim * dim * 4; for (u32 y = 0; y < dim; ++y) { for (u32 x = 0; x < dim; ++x) { const f32 u = (static_cast(x) + 0.5f) / static_cast(dim); @@ -621,7 +644,8 @@ namespace Engine } } } - prefMips.push_back({prefFaces.data() + offset, dim}); + prefMips.push_back({base, dim}); + prefOffset += kFaceCount * dim * dim * 4; } stbi_image_free(pixels); // 源数据消费完毕 @@ -639,7 +663,19 @@ namespace Engine } } - // ---- 3. 上传:env / irradiance / prefilter / LUT ---- + // ---- 3. 上传:env / irradiance / prefilter / LUT(NaN 自检)---- + if (ContainsNaNHalf(envFaces.data(), envFaces.size()) || + ContainsNaNHalf(irrFaces.data(), irrFaces.size()) || + ContainsNaNHalf(prefFaces.data(), prefFaces.size())) { + ENGINE_ERROR("Cubemap: NaN in pre-integrated data, " + "skipping IBL upload"); + return nullptr; + } + if (ContainsNaNHalf(lutTexels.data(), lutTexels.size())) { + ENGINE_ERROR("Cubemap: NaN in BRDF LUT"); + return nullptr; + } + vk::Image envImage, irrImage, prefImage, lutImage; auto envAlloc = UploadCube(device, allocator, {{envFaces.data(), faceSize}}, envImage); @@ -681,8 +717,9 @@ namespace Engine MemoryAllocator& allocator, const glm::vec3& rgb) { - // 1×1×6 常数 cube(prefilter 各 mip 同值);BRDF LUT 仍为真实 - // 积分(纯数学,无环境依赖)——描述符恒有效 + // 1×1×6 常数 cube(prefilter 仅 1 mip——1px 图不能建 mip 链, + // 采样越界 lod 会被 view range clamp 到 mip0,效果等同); + // BRDF LUT 仍为真实积分(纯数学,无环境依赖)——描述符恒有效 const u16 px[4] = { FloatToHalf(rgb.r), FloatToHalf(rgb.g), FloatToHalf(rgb.b), 0x3C00 }; std::vector faces(kFaceCount * 4, 0); @@ -690,15 +727,8 @@ namespace Engine std::memcpy(faces.data() + f * 4, px, sizeof(px)); } - std::vector prefFaces(kPrefilterMips * kFaceCount * 4, 0); - std::vector prefMips; - prefMips.reserve(kPrefilterMips); - for (u32 mip = 0; mip < kPrefilterMips; ++mip) { - const size_t offset = mip * kFaceCount * 4; - std::memcpy(prefFaces.data() + offset, faces.data(), - kFaceCount * 4 * sizeof(u16)); - prefMips.push_back({prefFaces.data() + offset, 1}); - } + std::vector prefFaces(kFaceCount * 4, 0); + std::vector prefMips = {{prefFaces.data(), 1}}; std::vector lutTexels(kBrdfLutSize * kBrdfLutSize * 2); for (u32 y = 0; y < kBrdfLutSize; ++y) { @@ -722,8 +752,7 @@ namespace Engine vk::ImageView envView = CreateCubeView(device, envImage, 1); vk::ImageView irrView = CreateCubeView(device, irrImage, 1); - vk::ImageView prefView = CreateCubeView(device, prefImage, - kPrefilterMips); + vk::ImageView prefView = CreateCubeView(device, prefImage, 1); vk::ImageView lutView = CreateLutView(device, lutImage); vk::Sampler sampler = CreateEnvSampler(device, kPrefilterMips - 1); diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 0446635..5b57e11 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -74,13 +74,14 @@ namespace Engine m_FullscreenGpu = CreateScope( *m_Device, *m_DescriptorManager, *m_ShaderManager); - // 3.5 环境贴图(IBL):HDR 等距柱状 → cube + irradiance 卷积。 - // 文件缺失则创建中性常数兜底(描述符恒有效,渲染退化为常数环境) + // 3.5 环境贴图(IBL):HDR 等距柱状 → cube + 预积分。 + // 加载失败/文件缺失一律中性常数兜底(描述符恒有效) const std::string envPath = "assets/textures/venice_dawn_1_4k.hdr"; if (std::filesystem::exists(envPath)) { m_Environment = Cubemap::CreateFromHdrEquirect( *m_Device, *m_Allocator, envPath, 1024); - } else { + } + if (!m_Environment) { m_Environment = Cubemap::CreateNeutral( *m_Device, *m_Allocator, glm::vec3(0.1f)); }