fix: IBL 全黑根因——RadicalInverseVdC 误用 bit_cast,Hammersley 序列错乱
- bit_cast<f32>(bits) 把反转后的整数当 IEEE754 位型解读(LearnOpenGL 原文 float(bits) 是整数值转换)→ xi.y 越出 [0,1] → sqrt(负数) → BRDF LUT NaN + pre-filter 数据全垃圾 → ambient NaN 整屏黑 - 分离出的积分程序实测:texel(0,2) 样本 10 xi.y=2 → NaN;修后无 NaN - 加 CPU 侧 fp16 NaN 自检(上传前最后一道防线),数据异常拒绝加载 走中性兜底(不再解引用空指针) - 中性兜底 1×1 图只建 1 mip(修 mipLevels=5 validation 警告) - prefilter mip 拷贝偏移改累积制(此前用当前 dim×序号错位) - prefilter/irradiance 绑定换带 mip 的环境采样器(shadowSampler maxLod=0 把 textureLod clamp 到最锐 mip)
This commit is contained in:
@@ -120,7 +120,9 @@ namespace Engine
|
|||||||
|
|
||||||
// ---- GGX 重要性采样(split-sum 镜面端)----
|
// ---- GGX 重要性采样(split-sum 镜面端)----
|
||||||
|
|
||||||
// van der Corput 基底反转(位反转技巧)
|
// van der Corput 基底反转(位反转技巧)。
|
||||||
|
// 注意是整数值转浮点(static_cast),不是位型重解读——
|
||||||
|
// bit_cast 会把反转后的整数当 IEEE754 解读,序列完全错乱
|
||||||
f32 RadicalInverseVdC(u32 bits)
|
f32 RadicalInverseVdC(u32 bits)
|
||||||
{
|
{
|
||||||
bits = (bits << 16u) | (bits >> 16u);
|
bits = (bits << 16u) | (bits >> 16u);
|
||||||
@@ -128,7 +130,7 @@ namespace Engine
|
|||||||
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
||||||
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
||||||
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
||||||
return std::bit_cast<f32>(bits) * 2.3283064365386963e-10f;
|
return static_cast<f32>(bits) * 2.3283064365386963e-10f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// n = 当次积分总采样数(xi.x = i/n 必须覆盖 [0,1] 全程)
|
// n = 当次积分总采样数(xi.x = i/n 必须覆盖 [0,1] 全程)
|
||||||
@@ -202,7 +204,10 @@ namespace Engine
|
|||||||
|
|
||||||
const f32 g = (nDotV / (nDotV * (1.0f - k) + k)) *
|
const f32 g = (nDotV / (nDotV * (1.0f - k) + k)) *
|
||||||
(nDotL / (nDotL * (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 fc = 1.0f - vDotH;
|
||||||
const f32 f = fc * fc * fc * fc * fc; // (1-vDotH)⁵,乘法链
|
const f32 f = fc * fc * fc * fc * fc; // (1-vDotH)⁵,乘法链
|
||||||
|
|
||||||
@@ -509,6 +514,17 @@ namespace Engine
|
|||||||
0, 1, 0, 1}));
|
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::Sampler CreateEnvSampler(Device& device, u32 maxLod)
|
||||||
{
|
{
|
||||||
vk::SamplerCreateInfo samplerInfo;
|
vk::SamplerCreateInfo samplerInfo;
|
||||||
@@ -592,20 +608,27 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- 2c. pre-filter cube:GGX 重要性采样 × mip 分级 roughness ----
|
// ---- 2c. pre-filter cube:GGX 重要性采样 × mip 分级 roughness ----
|
||||||
// 采样数随 mip 递增(高 roughness 分布更宽需要更多样本)
|
// 采样数随 mip 递增(高 roughness 分布更宽需要更多样本)。
|
||||||
std::vector<u16> prefFaces;
|
// 注意:必须一次性分配总容量——逐 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<u16> prefFaces(prefTotal);
|
||||||
std::vector<CubeMipData> prefMips;
|
std::vector<CubeMipData> prefMips;
|
||||||
prefMips.reserve(kPrefilterMips);
|
prefMips.reserve(kPrefilterMips);
|
||||||
|
size_t prefOffset = 0;
|
||||||
for (u32 mip = 0; mip < kPrefilterMips; ++mip) {
|
for (u32 mip = 0; mip < kPrefilterMips; ++mip) {
|
||||||
const u32 dim = kPrefilterFaceSize >> mip;
|
const u32 dim = kPrefilterFaceSize >> mip;
|
||||||
const f32 roughness =
|
const f32 roughness =
|
||||||
static_cast<f32>(mip) / static_cast<f32>(kPrefilterMips - 1);
|
static_cast<f32>(mip) / static_cast<f32>(kPrefilterMips - 1);
|
||||||
const u32 samples = std::min(32u << mip, 256u);
|
const u32 samples = std::min(32u << mip, 256u);
|
||||||
|
|
||||||
const size_t offset = prefFaces.size();
|
u16* base = prefFaces.data() + prefOffset;
|
||||||
prefFaces.resize(offset + kFaceCount * dim * dim * 4);
|
|
||||||
for (u32 f = 0; f < kFaceCount; ++f) {
|
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 y = 0; y < dim; ++y) {
|
||||||
for (u32 x = 0; x < dim; ++x) {
|
for (u32 x = 0; x < dim; ++x) {
|
||||||
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(dim);
|
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(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); // 源数据消费完毕
|
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;
|
vk::Image envImage, irrImage, prefImage, lutImage;
|
||||||
auto envAlloc = UploadCube(device, allocator,
|
auto envAlloc = UploadCube(device, allocator,
|
||||||
{{envFaces.data(), faceSize}}, envImage);
|
{{envFaces.data(), faceSize}}, envImage);
|
||||||
@@ -681,8 +717,9 @@ namespace Engine
|
|||||||
MemoryAllocator& allocator,
|
MemoryAllocator& allocator,
|
||||||
const glm::vec3& rgb)
|
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),
|
const u16 px[4] = { FloatToHalf(rgb.r), FloatToHalf(rgb.g),
|
||||||
FloatToHalf(rgb.b), 0x3C00 };
|
FloatToHalf(rgb.b), 0x3C00 };
|
||||||
std::vector<u16> faces(kFaceCount * 4, 0);
|
std::vector<u16> faces(kFaceCount * 4, 0);
|
||||||
@@ -690,15 +727,8 @@ namespace Engine
|
|||||||
std::memcpy(faces.data() + f * 4, px, sizeof(px));
|
std::memcpy(faces.data() + f * 4, px, sizeof(px));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u16> prefFaces(kPrefilterMips * kFaceCount * 4, 0);
|
std::vector<u16> prefFaces(kFaceCount * 4, 0);
|
||||||
std::vector<CubeMipData> prefMips;
|
std::vector<CubeMipData> prefMips = {{prefFaces.data(), 1}};
|
||||||
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<u16> lutTexels(kBrdfLutSize * kBrdfLutSize * 2);
|
std::vector<u16> lutTexels(kBrdfLutSize * kBrdfLutSize * 2);
|
||||||
for (u32 y = 0; y < kBrdfLutSize; ++y) {
|
for (u32 y = 0; y < kBrdfLutSize; ++y) {
|
||||||
@@ -722,8 +752,7 @@ namespace Engine
|
|||||||
|
|
||||||
vk::ImageView envView = CreateCubeView(device, envImage, 1);
|
vk::ImageView envView = CreateCubeView(device, envImage, 1);
|
||||||
vk::ImageView irrView = CreateCubeView(device, irrImage, 1);
|
vk::ImageView irrView = CreateCubeView(device, irrImage, 1);
|
||||||
vk::ImageView prefView = CreateCubeView(device, prefImage,
|
vk::ImageView prefView = CreateCubeView(device, prefImage, 1);
|
||||||
kPrefilterMips);
|
|
||||||
vk::ImageView lutView = CreateLutView(device, lutImage);
|
vk::ImageView lutView = CreateLutView(device, lutImage);
|
||||||
|
|
||||||
vk::Sampler sampler = CreateEnvSampler(device, kPrefilterMips - 1);
|
vk::Sampler sampler = CreateEnvSampler(device, kPrefilterMips - 1);
|
||||||
|
|||||||
@@ -74,13 +74,14 @@ namespace Engine
|
|||||||
m_FullscreenGpu = CreateScope<Passes::FullscreenGpu>(
|
m_FullscreenGpu = CreateScope<Passes::FullscreenGpu>(
|
||||||
*m_Device, *m_DescriptorManager, *m_ShaderManager);
|
*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";
|
const std::string envPath = "assets/textures/venice_dawn_1_4k.hdr";
|
||||||
if (std::filesystem::exists(envPath)) {
|
if (std::filesystem::exists(envPath)) {
|
||||||
m_Environment = Cubemap::CreateFromHdrEquirect(
|
m_Environment = Cubemap::CreateFromHdrEquirect(
|
||||||
*m_Device, *m_Allocator, envPath, 1024);
|
*m_Device, *m_Allocator, envPath, 1024);
|
||||||
} else {
|
}
|
||||||
|
if (!m_Environment) {
|
||||||
m_Environment = Cubemap::CreateNeutral(
|
m_Environment = Cubemap::CreateNeutral(
|
||||||
*m_Device, *m_Allocator, glm::vec3(0.1f));
|
*m_Device, *m_Allocator, glm::vec3(0.1f));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user