diff --git a/Engine/include/Engine/Renderer/Cubemap.h b/Engine/include/Engine/Renderer/Cubemap.h index aa7c15b..836d6c4 100644 --- a/Engine/include/Engine/Renderer/Cubemap.h +++ b/Engine/include/Engine/Renderer/Cubemap.h @@ -1,11 +1,19 @@ // // Created by atdunbg on 2026/9/23. // -// Cubemap:环境立方体贴图。 +// Cubemap:环境立方体贴图 + 漫反射辐照度图(IBL 阶段②)。 // // 从等距柱状 HDR(.hdr,polyhaven 等)生成:CPU 重采样到 6 面 //(一次性加载成本,亚秒级),转 fp16 后整块上传,硬件 Cube 采样。 -// 后续 IBL 的 irradiance/prefilter 卷积也基于本类的数据形态。 +// 同一次加载内完成 irradiance 卷积(半球积分,源数据在手零额外 IO)。 +// +// 双图契约: +// Get()/GetView() —— 环境 cube(Skybox 背景) +// GetIrradianceView() —— 漫反射辐照度 cube(PBR ambient) +// 镜面 IBL(pre-filter + BRDF LUT)后续接入,形态一致。 +// +// 后续演进:CPU 卷积 → compute pass(动态环境零卡顿);本类的 +// 方向表/上传路径届时整体迁移,接口不变。 // #ifndef HELLOVULKAN_CUBEMAP_H @@ -13,6 +21,7 @@ #include #include +#include #include namespace Engine { @@ -21,36 +30,55 @@ namespace Engine { class Cubemap { public: - // 从等距柱状 HDR 生成(CPU 重采样到 6 面,faceSize 为每面边长) + // 从等距柱状 HDR 生成(CPU 重采样 6 面 + irradiance 卷积) static Scope CreateFromHdrEquirect(Device& device, MemoryAllocator& allocator, const std::string& path, u32 faceSize); + // 中性兜底(无 HDR 资产时):env/irradiance 均为常数色。 + // 保证引用侧描述符恒有效(validation),渲染退化为常数环境 + static Scope CreateNeutral(Device& device, + MemoryAllocator& allocator, + const glm::vec3& rgb); + ~Cubemap(); Cubemap(const Cubemap&) = delete; Cubemap& operator=(const Cubemap&) = delete; - vk::Image Get() const { return m_Image; } - vk::ImageView GetView() const { return m_View; } + // ---- 环境 cube(Skybox 背景)---- + vk::Image Get() const { return m_EnvImage; } + vk::ImageView GetView() const { return m_EnvView; } + + // ---- 漫反射辐照度 cube(PBR ambient)---- + vk::ImageView GetIrradianceView() const { return m_IrrView; } + vk::Sampler GetSampler() const { return m_Sampler; } u32 GetFaceSize() const { return m_FaceSize; } private: Cubemap(Device& device, MemoryAllocator& allocator, - vk::Image image, vk::ImageView view, vk::Sampler sampler, - MemoryAllocator::Allocation allocation, u32 faceSize) + vk::Image envImage, vk::ImageView envView, + vk::Image irrImage, vk::ImageView irrView, vk::Sampler sampler, + MemoryAllocator::Allocation envAlloc, + MemoryAllocator::Allocation irrAlloc, u32 faceSize) : m_Device(device), m_Allocator(allocator), - m_Image(image), m_View(view), m_Sampler(sampler), - m_Allocation(allocation), m_FaceSize(faceSize) {} + m_EnvImage(envImage), m_EnvView(envView), + m_IrrImage(irrImage), m_IrrView(irrView), + m_Sampler(sampler), m_EnvAllocation(envAlloc), + m_IrrAllocation(irrAlloc), m_FaceSize(faceSize) {} Device& m_Device; MemoryAllocator& m_Allocator; - vk::Image m_Image; - vk::ImageView m_View; + + vk::Image m_EnvImage; + vk::ImageView m_EnvView; + vk::Image m_IrrImage; + vk::ImageView m_IrrView; vk::Sampler m_Sampler; - MemoryAllocator::Allocation m_Allocation; + MemoryAllocator::Allocation m_EnvAllocation; + MemoryAllocator::Allocation m_IrrAllocation; u32 m_FaceSize; }; diff --git a/Engine/include/Engine/Renderer/Passes/SceneGpu.h b/Engine/include/Engine/Renderer/Passes/SceneGpu.h index 8e1cab2..8f61f3b 100644 --- a/Engine/include/Engine/Renderer/Passes/SceneGpu.h +++ b/Engine/include/Engine/Renderer/Passes/SceneGpu.h @@ -24,6 +24,7 @@ namespace Engine { class DescriptorManager; class ShaderManager; class UniformBuffer; + class Cubemap; namespace Passes { @@ -40,8 +41,8 @@ namespace Engine { // ---- set 布局(shader 契约,改绑定必须同步 shaders/)---- vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer} - vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo} - vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图} + vk::DescriptorSetLayout textureSetLayout; // Set1 = {材质 4 槽 + 参数 UBO} + vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图, irradiance cube} vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO) vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B @@ -60,6 +61,9 @@ namespace Engine { void BindShadowMaps(RG::RenderGraph& rg, const std::array& maps); + // 环境辐照度(Set2 binding 3):view 全程稳定,绑一次即可 + void BindEnvironment(const Cubemap& env); + private: Device& m_Device; DescriptorManager& m_Descriptors; diff --git a/Engine/src/Renderer/Cubemap.cpp b/Engine/src/Renderer/Cubemap.cpp index 752d152..7069225 100644 --- a/Engine/src/Renderer/Cubemap.cpp +++ b/Engine/src/Renderer/Cubemap.cpp @@ -1,11 +1,14 @@ // // Created by atdunbg on 2026/9/23. // -// Cubemap 实现:HDR 等距柱状 → CPU 重采样 6 面 → fp16 → cube image。 +// Cubemap 实现:HDR 等距柱状 → CPU 重采样 6 面 + irradiance 卷积 → fp16 cube。 // -// 一次性加载成本(亚秒级 CPU 重采样),换取运行时零转换开销; +// 一次性加载成本(亚秒级 CPU),换取运行时零转换开销; // 硬件 Cube 采样(filtering/边缘接缝由 driver 处理)。 -// 后续 IBL 阶段的 irradiance/prefilter 卷积复用 FaceDir 方向表。 +// +// irradiance 卷积(LearnOpenGL split-sum 的离线端):对每个法线方向 +// 做半球 Lambert 积分 E = ∫ L·cosθ·sinθ dθ dφ,切线基由 up×N 构造。 +// 32² 面足够——漫反射辐照度本来就是超低频信号。 // #include @@ -29,22 +32,27 @@ namespace Engine namespace { constexpr u32 kFaceCount = 6; + constexpr u32 kIrradianceFaceSize = 32; // 漫反射辐照度面边长(低频) + constexpr u32 kPhiSteps = 64; // 方位角采样数 + constexpr u32 kThetaSteps = 32; // 极角采样数 // GL/Vulkan 规范的 cube 面方向表(与 GL spec 立方体选择表互逆)。 - // (u,v)∈[0,1](u 向右 v 向下)→ s,t∈[-1,1] → 单位化前方向。 + // (u,v)∈[0,1](u 向右 v 向下)→ s,t∈[-1,1] → 单位方向。 // Vulkan 面序与 GL 一致:+X -X +Y -Y +Z -Z glm::vec3 FaceDir(u32 face, f32 u, f32 v) { const f32 s = u * 2.0f - 1.0f; const f32 t = v * 2.0f - 1.0f; + glm::vec3 dir; switch (face) { - case 0: return { 1.0f, -t, -s }; // +X(面片顶行 = +Y) - case 1: return {-1.0f, -t, s }; // -X - case 2: return { s, 1.0f, t }; // +Y - case 3: return { s,-1.0f, -t }; // -Y - case 4: return { s, -t, 1.0f }; // +Z - default: return {-s, -t,-1.0f }; // -Z + case 0: dir = { 1.0f, -t, -s }; break; // +X(面片顶行 = +Y) + case 1: dir = {-1.0f, -t, s }; break; // -X + case 2: dir = { s, 1.0f, t }; break; // +Y + case 3: dir = { s,-1.0f, -t }; break; // -Y + case 4: dir = { s, -t, 1.0f }; break; // +Z + default: dir = {-s, -t,-1.0f }; break; // -Z } + return glm::normalize(dir); // equirect 查表要求单位方向 } // 等距柱状双线性采样(x 环绕跨经度接缝,y 钳制到极点)。 @@ -76,6 +84,39 @@ namespace Engine return glm::mix(glm::mix(c00, c10, tx), glm::mix(c01, c11, tx), ty); } + // 漫反射辐照度半球积分(Lambert):E = ∫ L·cosθ·sinθ dθ dφ。 + // 切线基 up×N 构造;φSteps×θSteps 均匀网格 ≈ 2048 采样/像素, + // 对 32² 低频图绰绰有余 + glm::vec3 ConvolveIrradiance(const f32* img, u32 w, u32 h, + const glm::vec3& N) + { + const glm::vec3 up = glm::abs(N.y) > 0.999f + ? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f); + const glm::vec3 T = glm::normalize(glm::cross(up, N)); + const glm::vec3 B = glm::cross(N, T); + + const f32 dPhi = 2.0f * glm::pi() / static_cast(kPhiSteps); + const f32 dTheta = 0.5f * glm::pi() / static_cast(kThetaSteps); + + glm::vec3 irradiance(0.0f); + for (u32 p = 0; p < kPhiSteps; ++p) { + const f32 phi = (static_cast(p) + 0.5f) * dPhi; + for (u32 t = 0; t < kThetaSteps; ++t) { + const f32 theta = (static_cast(t) + 0.5f) * dTheta; + const f32 sinT = std::sin(theta); + const f32 cosT = std::cos(theta); + // 切线空间采样方向 → 世界 + const glm::vec3 sampleDir = + T * (sinT * std::cos(phi)) + + B * (sinT * std::sin(phi)) + + N * cosT; + irradiance += SampleEquirect(img, w, h, sampleDir) * + (cosT * sinT); + } + } + return irradiance * (dPhi * dTheta); + } + // IEEE754 f32 → fp16(round-to-nearest-even;溢出饱和为 Inf)。 // HDR 环境值域内常规路径足够,次正规/NaN 路径只为数学完备 u16 FloatToHalf(f32 value) @@ -106,6 +147,125 @@ namespace Engine // 舍入进位到 0x400 恰好是最小正规 half 的位型,无需特判 return static_cast(sign | std::min(m, 0x400u)); } + + // 上传 6 面 fp16 数据为 cube image(UNDEFINED → TransferDst → + // ShaderRead;一次性命令缓冲)。view 由调用方创建 + MemoryAllocator::Allocation UploadCube(Device& device, + MemoryAllocator& allocator, + const std::vector& faces, + u32 faceSize, vk::Image& outImage) + { + const vk::DeviceSize faceBytes = + static_cast(faceSize) * faceSize * 8; + const vk::DeviceSize total = faceBytes * kFaceCount; + + auto stagingAlloc = allocator.CreateBuffer( + total, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + std::memcpy(stagingAlloc.MappedPtr, faces.data(), + static_cast(total)); + + vk::ImageCreateInfo imageInfo; + imageInfo.setImageType(vk::ImageType::e2D) + .setFormat(vk::Format::eR16G16B16A16Sfloat) + .setExtent({faceSize, faceSize, 1}) + .setMipLevels(1) + .setArrayLayers(kFaceCount) + .setSamples(vk::SampleCountFlagBits::e1) + .setTiling(vk::ImageTiling::eOptimal) + .setFlags(vk::ImageCreateFlagBits::eCubeCompatible) + .setUsage(vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eSampled) + .setSharingMode(vk::SharingMode::eExclusive) + .setInitialLayout(vk::ImageLayout::eUndefined); + + auto imageAlloc = allocator.CreateImage(imageInfo, + vk::MemoryPropertyFlagBits::eDeviceLocal); + outImage = imageAlloc.Image; + + vk::CommandPoolCreateInfo poolInfo; + poolInfo.setQueueFamilyIndex(device.GetGraphicsFamily()) + .setFlags(vk::CommandPoolCreateFlagBits::eTransient); + vk::CommandPool pool = device.GetDevice().createCommandPool(poolInfo); + + vk::CommandBuffer cmd = device.GetDevice().allocateCommandBuffers( + vk::CommandBufferAllocateInfo{} + .setCommandPool(pool) + .setLevel(vk::CommandBufferLevel::ePrimary) + .setCommandBufferCount(1))[0]; + + cmd.begin(vk::CommandBufferBeginInfo{ + vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + + vk::ImageMemoryBarrier2 toTransfer; + toTransfer.setSrcStageMask(vk::PipelineStageFlagBits2::eNone) + .setSrcAccessMask(vk::AccessFlagBits2::eNone) + .setDstStageMask(vk::PipelineStageFlagBits2::eTransfer) + .setDstAccessMask(vk::AccessFlagBits2::eTransferWrite) + .setOldLayout(vk::ImageLayout::eUndefined) + .setNewLayout(vk::ImageLayout::eTransferDstOptimal) + .setImage(outImage) + .setSubresourceRange({vk::ImageAspectFlagBits::eColor, + 0, 1, 0, kFaceCount}); + cmd.pipelineBarrier2( + vk::DependencyInfo{}.setImageMemoryBarriers(toTransfer)); + + for (u32 f = 0; f < kFaceCount; ++f) { + vk::BufferImageCopy region; + region.setBufferOffset(faceBytes * f) + .setBufferRowLength(0) + .setBufferImageHeight(0) + .setImageSubresource({vk::ImageAspectFlagBits::eColor, + 0, f, 1}) + .setImageOffset({0, 0, 0}) + .setImageExtent({faceSize, faceSize, 1}); + cmd.copyBufferToImage(stagingAlloc.Buffer, outImage, + vk::ImageLayout::eTransferDstOptimal, + region); + } + + vk::ImageMemoryBarrier2 toShader; + toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer) + .setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite) + .setDstStageMask(vk::PipelineStageFlagBits2::eFragmentShader) + .setDstAccessMask(vk::AccessFlagBits2::eShaderRead) + .setOldLayout(vk::ImageLayout::eTransferDstOptimal) + .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) + .setImage(outImage) + .setSubresourceRange({vk::ImageAspectFlagBits::eColor, + 0, 1, 0, kFaceCount}); + cmd.pipelineBarrier2( + vk::DependencyInfo{}.setImageMemoryBarriers(toShader)); + + cmd.end(); + + vk::Fence fence = device.GetDevice().createFence({}); + device.GetGraphicsQueue().submit( + vk::SubmitInfo{}.setCommandBuffers(cmd), fence); + if (device.GetDevice().waitForFences(fence, VK_TRUE, UINT64_MAX) != + vk::Result::eSuccess) { + ENGINE_ERROR("Cubemap upload: waitForFences failed"); + } + + device.GetDevice().destroyFence(fence); + device.GetDevice().destroyCommandPool(pool); + allocator.Destroy(stagingAlloc); + + return imageAlloc; + } + + vk::ImageView CreateCubeView(Device& device, vk::Image image) + { + return device.GetDevice().createImageView( + vk::ImageViewCreateInfo{} + .setImage(image) + .setViewType(vk::ImageViewType::eCube) + .setFormat(vk::Format::eR16G16B16A16Sfloat) + .setSubresourceRange({vk::ImageAspectFlagBits::eColor, + 0, 1, 0, kFaceCount})); + } } Scope Cubemap::CreateFromHdrEquirect(Device& device, @@ -123,11 +283,11 @@ namespace Engine const u32 srcW = static_cast(w); const u32 srcH = static_cast(h); - // ---- 2. 重采样 6 面 + fp16(RGBA16F = 每像素 8B)---- + // ---- 2. 重采样 6 面 + irradiance 卷积 + fp16 ---- const u32 facePixels = faceSize * faceSize; - std::vector faces(kFaceCount * facePixels * 4); + std::vector envFaces(kFaceCount * facePixels * 4); for (u32 f = 0; f < kFaceCount; ++f) { - u16* dst = faces.data() + f * facePixels * 4; + u16* dst = envFaces.data() + f * facePixels * 4; for (u32 y = 0; y < faceSize; ++y) { for (u32 x = 0; x < faceSize; ++x) { const f32 u = (static_cast(x) + 0.5f) / static_cast(faceSize); @@ -142,50 +302,37 @@ namespace Engine } } } - stbi_image_free(pixels); - // ---- 3. staging(HOST_VISIBLE 持久映射,逐面偏移拷入)---- - const vk::DeviceSize faceBytes = - static_cast(facePixels) * 8; - const vk::DeviceSize total = faceBytes * kFaceCount; + const u32 irrPixels = kIrradianceFaceSize * kIrradianceFaceSize; + std::vector irrFaces(kFaceCount * irrPixels * 4); + for (u32 f = 0; f < kFaceCount; ++f) { + u16* dst = irrFaces.data() + f * irrPixels * 4; + for (u32 y = 0; y < kIrradianceFaceSize; ++y) { + for (u32 x = 0; x < kIrradianceFaceSize; ++x) { + const f32 u = (static_cast(x) + 0.5f) / static_cast(kIrradianceFaceSize); + const f32 v = (static_cast(y) + 0.5f) / static_cast(kIrradianceFaceSize); + const glm::vec3 rgb = ConvolveIrradiance( + pixels, srcW, srcH, FaceDir(f, u, v)); + u16* px = dst + (static_cast(y) * kIrradianceFaceSize + x) * 4; + px[0] = FloatToHalf(rgb.r); + px[1] = FloatToHalf(rgb.g); + px[2] = FloatToHalf(rgb.b); + px[3] = 0x3C00; + } + } + } + stbi_image_free(pixels); // 源数据消费完毕(卷积/重采样都在构造期) - auto stagingAlloc = allocator.CreateBuffer( - total, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | - vk::MemoryPropertyFlagBits::eHostCoherent); - std::memcpy(stagingAlloc.MappedPtr, faces.data(), - static_cast(total)); - faces.clear(); - faces.shrink_to_fit(); // CPU 侧临时数据尽早释放 + // ---- 3. 上传双 cube + 共享采样器 ---- + vk::Image envImage, irrImage; + auto envAlloc = UploadCube(device, allocator, envFaces, faceSize, envImage); + auto irrAlloc = UploadCube(device, allocator, irrFaces, + kIrradianceFaceSize, irrImage); + envFaces.clear(); envFaces.shrink_to_fit(); + irrFaces.clear(); irrFaces.shrink_to_fit(); - // ---- 4. cube image(eCubeCompatible,6 层,fp16)---- - vk::ImageCreateInfo imageInfo; - imageInfo.setImageType(vk::ImageType::e2D) - .setFormat(vk::Format::eR16G16B16A16Sfloat) - .setExtent({faceSize, faceSize, 1}) - .setMipLevels(1) - .setArrayLayers(kFaceCount) - .setSamples(vk::SampleCountFlagBits::e1) - .setTiling(vk::ImageTiling::eOptimal) - .setFlags(vk::ImageCreateFlagBits::eCubeCompatible) - .setUsage(vk::ImageUsageFlagBits::eTransferDst | - vk::ImageUsageFlagBits::eSampled) - .setSharingMode(vk::SharingMode::eExclusive) - .setInitialLayout(vk::ImageLayout::eUndefined); - - auto imageAlloc = allocator.CreateImage(imageInfo, - vk::MemoryPropertyFlagBits::eDeviceLocal); - const vk::Image image = imageAlloc.Image; - - // ---- 5. cube view + 环境采样器(clamp-to-edge:cube 采样寻址由面自动处理)---- - vk::ImageViewCreateInfo viewInfo; - viewInfo.setImage(image) - .setViewType(vk::ImageViewType::eCube) - .setFormat(vk::Format::eR16G16B16A16Sfloat) - .setSubresourceRange({ - vk::ImageAspectFlagBits::eColor, 0, 1, 0, kFaceCount}); - vk::ImageView view = device.GetDevice().createImageView(viewInfo); + vk::ImageView envView = CreateCubeView(device, envImage); + vk::ImageView irrView = CreateCubeView(device, irrImage); vk::SamplerCreateInfo samplerInfo; samplerInfo.setMagFilter(vk::Filter::eLinear) @@ -202,81 +349,52 @@ namespace Engine .setMaxLod(0.0f); vk::Sampler sampler = device.GetDevice().createSampler(samplerInfo); - // ---- 6. 一次性命令缓冲:整 cube → TransferDst,逐面拷贝 → ShaderRead ---- - vk::CommandPoolCreateInfo poolInfo; - poolInfo.setQueueFamilyIndex(device.GetGraphicsFamily()) - .setFlags(vk::CommandPoolCreateFlagBits::eTransient); - vk::CommandPool pool = device.GetDevice().createCommandPool(poolInfo); + ENGINE_INFO("Environment cubemap: {} ({}x6 env + {}x6 irradiance, fp16)", + path, faceSize, kIrradianceFaceSize); - vk::CommandBuffer cmd = device.GetDevice().allocateCommandBuffers( - vk::CommandBufferAllocateInfo{} - .setCommandPool(pool) - .setLevel(vk::CommandBufferLevel::ePrimary) - .setCommandBufferCount(1))[0]; - - cmd.begin(vk::CommandBufferBeginInfo{ - vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); - - // 6 层一起转 layout(subresource range layerCount = 6) - vk::ImageMemoryBarrier2 toTransfer; - toTransfer.setSrcStageMask(vk::PipelineStageFlagBits2::eNone) - .setSrcAccessMask(vk::AccessFlagBits2::eNone) - .setDstStageMask(vk::PipelineStageFlagBits2::eTransfer) - .setDstAccessMask(vk::AccessFlagBits2::eTransferWrite) - .setOldLayout(vk::ImageLayout::eUndefined) - .setNewLayout(vk::ImageLayout::eTransferDstOptimal) - .setImage(image) - .setSubresourceRange({vk::ImageAspectFlagBits::eColor, - 0, 1, 0, kFaceCount}); - cmd.pipelineBarrier2( - vk::DependencyInfo{}.setImageMemoryBarriers(toTransfer)); + return Scope(new Cubemap(device, allocator, envImage, envView, + irrImage, irrView, sampler, + envAlloc, irrAlloc, faceSize)); + } + Scope Cubemap::CreateNeutral(Device& device, + MemoryAllocator& allocator, + const glm::vec3& rgb) + { + // 1×1×6 常数 cube:env 与 irradiance 同值,描述符恒有效 + const u16 px[4] = { FloatToHalf(rgb.r), FloatToHalf(rgb.g), + FloatToHalf(rgb.b), 0x3C00 }; + std::vector faces(kFaceCount * 4, 0); for (u32 f = 0; f < kFaceCount; ++f) { - vk::BufferImageCopy region; - region.setBufferOffset(faceBytes * f) - .setBufferRowLength(0) - .setBufferImageHeight(0) - .setImageSubresource({vk::ImageAspectFlagBits::eColor, - 0, f, 1}) - .setImageOffset({0, 0, 0}) - .setImageExtent({faceSize, faceSize, 1}); - cmd.copyBufferToImage(stagingAlloc.Buffer, image, - vk::ImageLayout::eTransferDstOptimal, region); + std::memcpy(faces.data() + f * 4, px, sizeof(px)); } - vk::ImageMemoryBarrier2 toShader; - toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer) - .setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite) - .setDstStageMask(vk::PipelineStageFlagBits2::eFragmentShader) - .setDstAccessMask(vk::AccessFlagBits2::eShaderRead) - .setOldLayout(vk::ImageLayout::eTransferDstOptimal) - .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) - .setImage(image) - .setSubresourceRange({vk::ImageAspectFlagBits::eColor, - 0, 1, 0, kFaceCount}); - cmd.pipelineBarrier2( - vk::DependencyInfo{}.setImageMemoryBarriers(toShader)); + vk::Image envImage, irrImage; + auto envAlloc = UploadCube(device, allocator, faces, 1, envImage); + auto irrAlloc = UploadCube(device, allocator, faces, 1, irrImage); - cmd.end(); + vk::ImageView envView = CreateCubeView(device, envImage); + vk::ImageView irrView = CreateCubeView(device, irrImage); - // ---- 7. 提交并等待,清理 staging ---- - vk::Fence fence = device.GetDevice().createFence({}); - device.GetGraphicsQueue().submit( - vk::SubmitInfo{}.setCommandBuffers(cmd), fence); - if (device.GetDevice().waitForFences(fence, VK_TRUE, UINT64_MAX) != - vk::Result::eSuccess) { - ENGINE_ERROR("Cubemap upload: waitForFences failed"); - } + vk::SamplerCreateInfo samplerInfo; + samplerInfo.setMagFilter(vk::Filter::eNearest) + .setMinFilter(vk::Filter::eNearest) + .setMipmapMode(vk::SamplerMipmapMode::eNearest) + .setAddressModeU(vk::SamplerAddressMode::eClampToEdge) + .setAddressModeV(vk::SamplerAddressMode::eClampToEdge) + .setAddressModeW(vk::SamplerAddressMode::eClampToEdge) + .setAnisotropyEnable(VK_FALSE) + .setBorderColor(vk::BorderColor::eIntOpaqueBlack) + .setUnnormalizedCoordinates(VK_FALSE) + .setCompareEnable(VK_FALSE) + .setMinLod(0.0f) + .setMaxLod(0.0f); + vk::Sampler sampler = device.GetDevice().createSampler(samplerInfo); - device.GetDevice().destroyFence(fence); - device.GetDevice().destroyCommandPool(pool); - allocator.Destroy(stagingAlloc); - - ENGINE_INFO("Environment cubemap: {} ({}x6 {} faces, fp16)", - path, faceSize, kFaceCount); - - return Scope(new Cubemap(device, allocator, image, view, - sampler, imageAlloc, faceSize)); + ENGINE_INFO("Neutral cubemap created (constant fallback)"); + return Scope(new Cubemap(device, allocator, envImage, envView, + irrImage, irrView, sampler, + envAlloc, irrAlloc, 1)); } Cubemap::~Cubemap() @@ -284,10 +402,12 @@ namespace Engine const auto& vkDevice = m_Device.GetDevice(); vkDevice.destroySampler(m_Sampler); - vkDevice.destroyImageView(m_View); + vkDevice.destroyImageView(m_EnvView); + vkDevice.destroyImageView(m_IrrView); // image 资源与显存经 VMA 成对释放 - m_Allocator.Destroy(m_Allocation); + m_Allocator.Destroy(m_EnvAllocation); + m_Allocator.Destroy(m_IrrAllocation); } } // namespace Engine diff --git a/Engine/src/Renderer/Passes/SceneGpu.cpp b/Engine/src/Renderer/Passes/SceneGpu.cpp index 5c354c3..4dc1253 100644 --- a/Engine/src/Renderer/Passes/SceneGpu.cpp +++ b/Engine/src/Renderer/Passes/SceneGpu.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -46,12 +47,16 @@ namespace Engine::Passes textureSetLayout = descriptors.CreateLayout(texBindings); // Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号) + // + 漫反射辐照度 cube(IBL 阶段②,binding 3) std::vector shadowBindings; for (u32 c = 0; c < kMaxCascades; ++c) { shadowBindings.push_back( {c, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}); } + shadowBindings.push_back( + {3, vk::DescriptorType::eCombinedImageSampler, + vk::ShaderStageFlagBits::eFragment}); shadowSetLayout = descriptors.CreateLayout(shadowBindings); // Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO) @@ -157,4 +162,13 @@ namespace Engine::Passes } } + void SceneGpu::BindEnvironment(const Cubemap& env) + { + // irradiance view 全程稳定(不随 RG 重编译变化),绑一次即可 + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_Descriptors.UpdateTexture(shadowSets[slot], 3, shadowSampler, + env.GetIrradianceView()); + } + } + } // namespace Engine::Passes diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 5d3a10b..0446635 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -74,12 +74,15 @@ namespace Engine m_FullscreenGpu = CreateScope( *m_Device, *m_DescriptorManager, *m_ShaderManager); - // 3.5 环境贴图(IBL 阶段①):HDR 等距柱状 → cube。 - // 文件缺失则跳过(SkyboxPass 自动退化为纯清屏,Forward 不受影响) + // 3.5 环境贴图(IBL):HDR 等距柱状 → cube + irradiance 卷积。 + // 文件缺失则创建中性常数兜底(描述符恒有效,渲染退化为常数环境) 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 { + m_Environment = Cubemap::CreateNeutral( + *m_Device, *m_Allocator, glm::vec3(0.1f)); } // 4. Swapchain(只管呈现;渲染目标统一归 RenderGraph) @@ -107,6 +110,8 @@ namespace Engine for (auto& pass : m_Passes) { pass->OnTargetsRebuilt(*m_RenderGraph); } + // 环境辐照度绑定(view 全程稳定,绑一次) + m_SceneGpu->BindEnvironment(*m_Environment); // 9. GPU timestamp:每 pass 两枚(前后),按帧槽位分区 m_QueriesPerFrame = static_cast(m_RenderGraph->GetPassCount()) * 2; diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 2975c61..1490258 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -45,6 +45,7 @@ layout(set = 1, binding = 4) uniform MaterialParams { layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近) layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1 layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远) +layout(set = 2, binding = 3) uniform samplerCube irradianceMap; // 漫反射环境光(IBL 阶段②) // 第 0 盏方向光(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。 // 级联按视图深度选择;采样器均 clamp-to-border 白色: @@ -104,6 +105,12 @@ vec3 FresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); } +// 粗糙度感知的 Fresnel(IBL 用):粗糙表面掠射边缘的 F 上升更平缓 +vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) { + vec3 Fmax = max(vec3(1.0 - roughness), F0); + return F0 + (Fmax - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + void main() { vec3 albedo = texture(albedoMap, fragUV).rgb; vec4 mr = texture(metallicRoughnessMap, fragUV); @@ -159,8 +166,14 @@ void main() { Lo += (kD * albedo / PI + specular) * radiance * NdotL * shade; } - // 环境光:无 IBL 的简化版(半球常数);后续 IBL 替换 - vec3 ambient = ubo.ambientColor.rgb * albedo; + // 环境光:漫反射辐照度(IBL 阶段②,split-sum 的 diffuse 项)。 + // N 采样预卷积的 irradiance cube(半球 Lambert 积分已在加载期完成); + // kD 用粗糙度感知 Fresnel + (1-metallic)(金属无漫反射)。 + // ambientColor = 用户可调强度/色调乘子。镜面项待阶段③ BRDF LUT + vec3 F_amb = FresnelSchlickRoughness(NdotV, F0, roughness); + vec3 kD_amb = (1.0 - F_amb) * (1.0 - metallic); + vec3 irradiance = texture(irradianceMap, N).rgb; + vec3 ambient = kD_amb * irradiance * albedo * ubo.ambientColor.rgb; vec3 color = ambient + Lo + emissive;