feat: IBL 阶段②——漫反射辐照度卷积,PBR ambient 接入环境光
- Cubemap 构造期同步 CPU 半球 Lambert 积分(64×32 网格 ≈2048 采样/像素) 生成 32² irradiance cube(漫反射是超低频信号,分辨率无需高) - shader:kD 用粗糙度感知 FresnelSchlickRoughness + (1-metallic), N 采样 irradianceMap(Set2 binding 3)替换常数 ambient; ambientColor 保留为强度/色调乘子 - CreateNeutral 兜底:无 HDR 资产时 1×1 常数 cube,描述符恒有效 - FaceDir 改为返回单位方向(equirect 查表要求,修掉未归一化的畸变)
This commit is contained in:
@@ -1,11 +1,19 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/23.
|
// Created by atdunbg on 2026/9/23.
|
||||||
//
|
//
|
||||||
// Cubemap:环境立方体贴图。
|
// Cubemap:环境立方体贴图 + 漫反射辐照度图(IBL 阶段②)。
|
||||||
//
|
//
|
||||||
// 从等距柱状 HDR(.hdr,polyhaven 等)生成:CPU 重采样到 6 面
|
// 从等距柱状 HDR(.hdr,polyhaven 等)生成:CPU 重采样到 6 面
|
||||||
//(一次性加载成本,亚秒级),转 fp16 后整块上传,硬件 Cube 采样。
|
//(一次性加载成本,亚秒级),转 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
|
#ifndef HELLOVULKAN_CUBEMAP_H
|
||||||
@@ -13,6 +21,7 @@
|
|||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
@@ -21,36 +30,55 @@ namespace Engine {
|
|||||||
|
|
||||||
class Cubemap {
|
class Cubemap {
|
||||||
public:
|
public:
|
||||||
// 从等距柱状 HDR 生成(CPU 重采样到 6 面,faceSize 为每面边长)
|
// 从等距柱状 HDR 生成(CPU 重采样 6 面 + irradiance 卷积)
|
||||||
static Scope<Cubemap> CreateFromHdrEquirect(Device& device,
|
static Scope<Cubemap> CreateFromHdrEquirect(Device& device,
|
||||||
MemoryAllocator& allocator,
|
MemoryAllocator& allocator,
|
||||||
const std::string& path,
|
const std::string& path,
|
||||||
u32 faceSize);
|
u32 faceSize);
|
||||||
|
|
||||||
|
// 中性兜底(无 HDR 资产时):env/irradiance 均为常数色。
|
||||||
|
// 保证引用侧描述符恒有效(validation),渲染退化为常数环境
|
||||||
|
static Scope<Cubemap> CreateNeutral(Device& device,
|
||||||
|
MemoryAllocator& allocator,
|
||||||
|
const glm::vec3& rgb);
|
||||||
|
|
||||||
~Cubemap();
|
~Cubemap();
|
||||||
|
|
||||||
Cubemap(const Cubemap&) = delete;
|
Cubemap(const Cubemap&) = delete;
|
||||||
Cubemap& operator=(const Cubemap&) = delete;
|
Cubemap& operator=(const Cubemap&) = delete;
|
||||||
|
|
||||||
vk::Image Get() const { return m_Image; }
|
// ---- 环境 cube(Skybox 背景)----
|
||||||
vk::ImageView GetView() const { return m_View; }
|
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; }
|
vk::Sampler GetSampler() const { return m_Sampler; }
|
||||||
u32 GetFaceSize() const { return m_FaceSize; }
|
u32 GetFaceSize() const { return m_FaceSize; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Cubemap(Device& device, MemoryAllocator& allocator,
|
Cubemap(Device& device, MemoryAllocator& allocator,
|
||||||
vk::Image image, vk::ImageView view, vk::Sampler sampler,
|
vk::Image envImage, vk::ImageView envView,
|
||||||
MemoryAllocator::Allocation allocation, u32 faceSize)
|
vk::Image irrImage, vk::ImageView irrView, vk::Sampler sampler,
|
||||||
|
MemoryAllocator::Allocation envAlloc,
|
||||||
|
MemoryAllocator::Allocation irrAlloc, u32 faceSize)
|
||||||
: m_Device(device), m_Allocator(allocator),
|
: m_Device(device), m_Allocator(allocator),
|
||||||
m_Image(image), m_View(view), m_Sampler(sampler),
|
m_EnvImage(envImage), m_EnvView(envView),
|
||||||
m_Allocation(allocation), m_FaceSize(faceSize) {}
|
m_IrrImage(irrImage), m_IrrView(irrView),
|
||||||
|
m_Sampler(sampler), m_EnvAllocation(envAlloc),
|
||||||
|
m_IrrAllocation(irrAlloc), m_FaceSize(faceSize) {}
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
MemoryAllocator& m_Allocator;
|
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;
|
vk::Sampler m_Sampler;
|
||||||
MemoryAllocator::Allocation m_Allocation;
|
MemoryAllocator::Allocation m_EnvAllocation;
|
||||||
|
MemoryAllocator::Allocation m_IrrAllocation;
|
||||||
u32 m_FaceSize;
|
u32 m_FaceSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace Engine {
|
|||||||
class DescriptorManager;
|
class DescriptorManager;
|
||||||
class ShaderManager;
|
class ShaderManager;
|
||||||
class UniformBuffer;
|
class UniformBuffer;
|
||||||
|
class Cubemap;
|
||||||
|
|
||||||
namespace Passes {
|
namespace Passes {
|
||||||
|
|
||||||
@@ -40,8 +41,8 @@ namespace Engine {
|
|||||||
|
|
||||||
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
|
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
|
||||||
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
|
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
|
||||||
vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo}
|
vk::DescriptorSetLayout textureSetLayout; // Set1 = {材质 4 槽 + 参数 UBO}
|
||||||
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图}
|
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图, irradiance cube}
|
||||||
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
|
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
|
||||||
vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B
|
vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B
|
||||||
|
|
||||||
@@ -60,6 +61,9 @@ namespace Engine {
|
|||||||
void BindShadowMaps(RG::RenderGraph& rg,
|
void BindShadowMaps(RG::RenderGraph& rg,
|
||||||
const std::array<RG::TextureHandle, kMaxCascades>& maps);
|
const std::array<RG::TextureHandle, kMaxCascades>& maps);
|
||||||
|
|
||||||
|
// 环境辐照度(Set2 binding 3):view 全程稳定,绑一次即可
|
||||||
|
void BindEnvironment(const Cubemap& env);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
DescriptorManager& m_Descriptors;
|
DescriptorManager& m_Descriptors;
|
||||||
|
|||||||
+243
-123
@@ -1,11 +1,14 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/23.
|
// 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 处理)。
|
// 硬件 Cube 采样(filtering/边缘接缝由 driver 处理)。
|
||||||
// 后续 IBL 阶段的 irradiance/prefilter 卷积复用 FaceDir 方向表。
|
//
|
||||||
|
// irradiance 卷积(LearnOpenGL split-sum 的离线端):对每个法线方向
|
||||||
|
// 做半球 Lambert 积分 E = ∫ L·cosθ·sinθ dθ dφ,切线基由 up×N 构造。
|
||||||
|
// 32² 面足够——漫反射辐照度本来就是超低频信号。
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <Engine/Renderer/Cubemap.h>
|
#include <Engine/Renderer/Cubemap.h>
|
||||||
@@ -29,22 +32,27 @@ namespace Engine
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr u32 kFaceCount = 6;
|
constexpr u32 kFaceCount = 6;
|
||||||
|
constexpr u32 kIrradianceFaceSize = 32; // 漫反射辐照度面边长(低频)
|
||||||
|
constexpr u32 kPhiSteps = 64; // 方位角采样数
|
||||||
|
constexpr u32 kThetaSteps = 32; // 极角采样数
|
||||||
|
|
||||||
// GL/Vulkan 规范的 cube 面方向表(与 GL spec 立方体选择表互逆)。
|
// 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
|
// Vulkan 面序与 GL 一致:+X -X +Y -Y +Z -Z
|
||||||
glm::vec3 FaceDir(u32 face, f32 u, f32 v)
|
glm::vec3 FaceDir(u32 face, f32 u, f32 v)
|
||||||
{
|
{
|
||||||
const f32 s = u * 2.0f - 1.0f;
|
const f32 s = u * 2.0f - 1.0f;
|
||||||
const f32 t = v * 2.0f - 1.0f;
|
const f32 t = v * 2.0f - 1.0f;
|
||||||
|
glm::vec3 dir;
|
||||||
switch (face) {
|
switch (face) {
|
||||||
case 0: return { 1.0f, -t, -s }; // +X(面片顶行 = +Y)
|
case 0: dir = { 1.0f, -t, -s }; break; // +X(面片顶行 = +Y)
|
||||||
case 1: return {-1.0f, -t, s }; // -X
|
case 1: dir = {-1.0f, -t, s }; break; // -X
|
||||||
case 2: return { s, 1.0f, t }; // +Y
|
case 2: dir = { s, 1.0f, t }; break; // +Y
|
||||||
case 3: return { s,-1.0f, -t }; // -Y
|
case 3: dir = { s,-1.0f, -t }; break; // -Y
|
||||||
case 4: return { s, -t, 1.0f }; // +Z
|
case 4: dir = { s, -t, 1.0f }; break; // +Z
|
||||||
default: return {-s, -t,-1.0f }; // -Z
|
default: dir = {-s, -t,-1.0f }; break; // -Z
|
||||||
}
|
}
|
||||||
|
return glm::normalize(dir); // equirect 查表要求单位方向
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等距柱状双线性采样(x 环绕跨经度接缝,y 钳制到极点)。
|
// 等距柱状双线性采样(x 环绕跨经度接缝,y 钳制到极点)。
|
||||||
@@ -76,6 +84,39 @@ namespace Engine
|
|||||||
return glm::mix(glm::mix(c00, c10, tx), glm::mix(c01, c11, tx), ty);
|
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<f32>() / static_cast<f32>(kPhiSteps);
|
||||||
|
const f32 dTheta = 0.5f * glm::pi<f32>() / static_cast<f32>(kThetaSteps);
|
||||||
|
|
||||||
|
glm::vec3 irradiance(0.0f);
|
||||||
|
for (u32 p = 0; p < kPhiSteps; ++p) {
|
||||||
|
const f32 phi = (static_cast<f32>(p) + 0.5f) * dPhi;
|
||||||
|
for (u32 t = 0; t < kThetaSteps; ++t) {
|
||||||
|
const f32 theta = (static_cast<f32>(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)。
|
// IEEE754 f32 → fp16(round-to-nearest-even;溢出饱和为 Inf)。
|
||||||
// HDR 环境值域内常规路径足够,次正规/NaN 路径只为数学完备
|
// HDR 环境值域内常规路径足够,次正规/NaN 路径只为数学完备
|
||||||
u16 FloatToHalf(f32 value)
|
u16 FloatToHalf(f32 value)
|
||||||
@@ -106,6 +147,125 @@ namespace Engine
|
|||||||
// 舍入进位到 0x400 恰好是最小正规 half 的位型,无需特判
|
// 舍入进位到 0x400 恰好是最小正规 half 的位型,无需特判
|
||||||
return static_cast<u16>(sign | std::min(m, 0x400u));
|
return static_cast<u16>(sign | std::min(m, 0x400u));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上传 6 面 fp16 数据为 cube image(UNDEFINED → TransferDst →
|
||||||
|
// ShaderRead;一次性命令缓冲)。view 由调用方创建
|
||||||
|
MemoryAllocator::Allocation UploadCube(Device& device,
|
||||||
|
MemoryAllocator& allocator,
|
||||||
|
const std::vector<u16>& faces,
|
||||||
|
u32 faceSize, vk::Image& outImage)
|
||||||
|
{
|
||||||
|
const vk::DeviceSize faceBytes =
|
||||||
|
static_cast<vk::DeviceSize>(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<size_t>(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> Cubemap::CreateFromHdrEquirect(Device& device,
|
Scope<Cubemap> Cubemap::CreateFromHdrEquirect(Device& device,
|
||||||
@@ -123,11 +283,11 @@ namespace Engine
|
|||||||
const u32 srcW = static_cast<u32>(w);
|
const u32 srcW = static_cast<u32>(w);
|
||||||
const u32 srcH = static_cast<u32>(h);
|
const u32 srcH = static_cast<u32>(h);
|
||||||
|
|
||||||
// ---- 2. 重采样 6 面 + fp16(RGBA16F = 每像素 8B)----
|
// ---- 2. 重采样 6 面 + irradiance 卷积 + fp16 ----
|
||||||
const u32 facePixels = faceSize * faceSize;
|
const u32 facePixels = faceSize * faceSize;
|
||||||
std::vector<u16> faces(kFaceCount * facePixels * 4);
|
std::vector<u16> envFaces(kFaceCount * facePixels * 4);
|
||||||
for (u32 f = 0; f < kFaceCount; ++f) {
|
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 y = 0; y < faceSize; ++y) {
|
||||||
for (u32 x = 0; x < faceSize; ++x) {
|
for (u32 x = 0; x < faceSize; ++x) {
|
||||||
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(faceSize);
|
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(faceSize);
|
||||||
@@ -142,50 +302,37 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stbi_image_free(pixels);
|
|
||||||
|
|
||||||
// ---- 3. staging(HOST_VISIBLE 持久映射,逐面偏移拷入)----
|
const u32 irrPixels = kIrradianceFaceSize * kIrradianceFaceSize;
|
||||||
const vk::DeviceSize faceBytes =
|
std::vector<u16> irrFaces(kFaceCount * irrPixels * 4);
|
||||||
static_cast<vk::DeviceSize>(facePixels) * 8;
|
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||||
const vk::DeviceSize total = faceBytes * kFaceCount;
|
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<f32>(x) + 0.5f) / static_cast<f32>(kIrradianceFaceSize);
|
||||||
|
const f32 v = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(kIrradianceFaceSize);
|
||||||
|
const glm::vec3 rgb = ConvolveIrradiance(
|
||||||
|
pixels, srcW, srcH, FaceDir(f, u, v));
|
||||||
|
u16* px = dst + (static_cast<size_t>(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(
|
// ---- 3. 上传双 cube + 共享采样器 ----
|
||||||
total,
|
vk::Image envImage, irrImage;
|
||||||
vk::BufferUsageFlagBits::eTransferSrc,
|
auto envAlloc = UploadCube(device, allocator, envFaces, faceSize, envImage);
|
||||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
auto irrAlloc = UploadCube(device, allocator, irrFaces,
|
||||||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
kIrradianceFaceSize, irrImage);
|
||||||
std::memcpy(stagingAlloc.MappedPtr, faces.data(),
|
envFaces.clear(); envFaces.shrink_to_fit();
|
||||||
static_cast<size_t>(total));
|
irrFaces.clear(); irrFaces.shrink_to_fit();
|
||||||
faces.clear();
|
|
||||||
faces.shrink_to_fit(); // CPU 侧临时数据尽早释放
|
|
||||||
|
|
||||||
// ---- 4. cube image(eCubeCompatible,6 层,fp16)----
|
vk::ImageView envView = CreateCubeView(device, envImage);
|
||||||
vk::ImageCreateInfo imageInfo;
|
vk::ImageView irrView = CreateCubeView(device, irrImage);
|
||||||
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::SamplerCreateInfo samplerInfo;
|
vk::SamplerCreateInfo samplerInfo;
|
||||||
samplerInfo.setMagFilter(vk::Filter::eLinear)
|
samplerInfo.setMagFilter(vk::Filter::eLinear)
|
||||||
@@ -202,81 +349,52 @@ namespace Engine
|
|||||||
.setMaxLod(0.0f);
|
.setMaxLod(0.0f);
|
||||||
vk::Sampler sampler = device.GetDevice().createSampler(samplerInfo);
|
vk::Sampler sampler = device.GetDevice().createSampler(samplerInfo);
|
||||||
|
|
||||||
// ---- 6. 一次性命令缓冲:整 cube → TransferDst,逐面拷贝 → ShaderRead ----
|
ENGINE_INFO("Environment cubemap: {} ({}x6 env + {}x6 irradiance, fp16)",
|
||||||
vk::CommandPoolCreateInfo poolInfo;
|
path, faceSize, kIrradianceFaceSize);
|
||||||
poolInfo.setQueueFamilyIndex(device.GetGraphicsFamily())
|
|
||||||
.setFlags(vk::CommandPoolCreateFlagBits::eTransient);
|
|
||||||
vk::CommandPool pool = device.GetDevice().createCommandPool(poolInfo);
|
|
||||||
|
|
||||||
vk::CommandBuffer cmd = device.GetDevice().allocateCommandBuffers(
|
return Scope<Cubemap>(new Cubemap(device, allocator, envImage, envView,
|
||||||
vk::CommandBufferAllocateInfo{}
|
irrImage, irrView, sampler,
|
||||||
.setCommandPool(pool)
|
envAlloc, irrAlloc, faceSize));
|
||||||
.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));
|
|
||||||
|
|
||||||
|
Scope<Cubemap> 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<u16> faces(kFaceCount * 4, 0);
|
||||||
for (u32 f = 0; f < kFaceCount; ++f) {
|
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||||
vk::BufferImageCopy region;
|
std::memcpy(faces.data() + f * 4, px, sizeof(px));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::ImageMemoryBarrier2 toShader;
|
vk::Image envImage, irrImage;
|
||||||
toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
auto envAlloc = UploadCube(device, allocator, faces, 1, envImage);
|
||||||
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
auto irrAlloc = UploadCube(device, allocator, faces, 1, irrImage);
|
||||||
.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));
|
|
||||||
|
|
||||||
cmd.end();
|
vk::ImageView envView = CreateCubeView(device, envImage);
|
||||||
|
vk::ImageView irrView = CreateCubeView(device, irrImage);
|
||||||
|
|
||||||
// ---- 7. 提交并等待,清理 staging ----
|
vk::SamplerCreateInfo samplerInfo;
|
||||||
vk::Fence fence = device.GetDevice().createFence({});
|
samplerInfo.setMagFilter(vk::Filter::eNearest)
|
||||||
device.GetGraphicsQueue().submit(
|
.setMinFilter(vk::Filter::eNearest)
|
||||||
vk::SubmitInfo{}.setCommandBuffers(cmd), fence);
|
.setMipmapMode(vk::SamplerMipmapMode::eNearest)
|
||||||
if (device.GetDevice().waitForFences(fence, VK_TRUE, UINT64_MAX) !=
|
.setAddressModeU(vk::SamplerAddressMode::eClampToEdge)
|
||||||
vk::Result::eSuccess) {
|
.setAddressModeV(vk::SamplerAddressMode::eClampToEdge)
|
||||||
ENGINE_ERROR("Cubemap upload: waitForFences failed");
|
.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);
|
ENGINE_INFO("Neutral cubemap created (constant fallback)");
|
||||||
device.GetDevice().destroyCommandPool(pool);
|
return Scope<Cubemap>(new Cubemap(device, allocator, envImage, envView,
|
||||||
allocator.Destroy(stagingAlloc);
|
irrImage, irrView, sampler,
|
||||||
|
envAlloc, irrAlloc, 1));
|
||||||
ENGINE_INFO("Environment cubemap: {} ({}x6 {} faces, fp16)",
|
|
||||||
path, faceSize, kFaceCount);
|
|
||||||
|
|
||||||
return Scope<Cubemap>(new Cubemap(device, allocator, image, view,
|
|
||||||
sampler, imageAlloc, faceSize));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Cubemap::~Cubemap()
|
Cubemap::~Cubemap()
|
||||||
@@ -284,10 +402,12 @@ namespace Engine
|
|||||||
const auto& vkDevice = m_Device.GetDevice();
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
vkDevice.destroySampler(m_Sampler);
|
vkDevice.destroySampler(m_Sampler);
|
||||||
vkDevice.destroyImageView(m_View);
|
vkDevice.destroyImageView(m_EnvView);
|
||||||
|
vkDevice.destroyImageView(m_IrrView);
|
||||||
|
|
||||||
// image 资源与显存经 VMA 成对释放
|
// image 资源与显存经 VMA 成对释放
|
||||||
m_Allocator.Destroy(m_Allocation);
|
m_Allocator.Destroy(m_EnvAllocation);
|
||||||
|
m_Allocator.Destroy(m_IrrAllocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <Engine/Renderer/Passes/SceneGpu.h>
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
#include <Engine/Renderer/Material.h>
|
#include <Engine/Renderer/Material.h>
|
||||||
|
#include <Engine/Renderer/Cubemap.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
#include <Engine/Vulkan/DescriptorManager.h>
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
#include <Engine/Vulkan/ShaderManager.h>
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
@@ -46,12 +47,16 @@ namespace Engine::Passes
|
|||||||
textureSetLayout = descriptors.CreateLayout(texBindings);
|
textureSetLayout = descriptors.CreateLayout(texBindings);
|
||||||
|
|
||||||
// Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
|
// Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
|
||||||
|
// + 漫反射辐照度 cube(IBL 阶段②,binding 3)
|
||||||
std::vector<DescriptorBinding> shadowBindings;
|
std::vector<DescriptorBinding> shadowBindings;
|
||||||
for (u32 c = 0; c < kMaxCascades; ++c) {
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
shadowBindings.push_back(
|
shadowBindings.push_back(
|
||||||
{c, vk::DescriptorType::eCombinedImageSampler,
|
{c, vk::DescriptorType::eCombinedImageSampler,
|
||||||
vk::ShaderStageFlagBits::eFragment});
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
}
|
}
|
||||||
|
shadowBindings.push_back(
|
||||||
|
{3, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
shadowSetLayout = descriptors.CreateLayout(shadowBindings);
|
shadowSetLayout = descriptors.CreateLayout(shadowBindings);
|
||||||
|
|
||||||
// Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO)
|
// 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
|
} // namespace Engine::Passes
|
||||||
|
|||||||
@@ -74,12 +74,15 @@ 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。
|
// 3.5 环境贴图(IBL):HDR 等距柱状 → cube + irradiance 卷积。
|
||||||
// 文件缺失则跳过(SkyboxPass 自动退化为纯清屏,Forward 不受影响)
|
// 文件缺失则创建中性常数兜底(描述符恒有效,渲染退化为常数环境)
|
||||||
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 {
|
||||||
|
m_Environment = Cubemap::CreateNeutral(
|
||||||
|
*m_Device, *m_Allocator, glm::vec3(0.1f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Swapchain(只管呈现;渲染目标统一归 RenderGraph)
|
// 4. Swapchain(只管呈现;渲染目标统一归 RenderGraph)
|
||||||
@@ -107,6 +110,8 @@ namespace Engine
|
|||||||
for (auto& pass : m_Passes) {
|
for (auto& pass : m_Passes) {
|
||||||
pass->OnTargetsRebuilt(*m_RenderGraph);
|
pass->OnTargetsRebuilt(*m_RenderGraph);
|
||||||
}
|
}
|
||||||
|
// 环境辐照度绑定(view 全程稳定,绑一次)
|
||||||
|
m_SceneGpu->BindEnvironment(*m_Environment);
|
||||||
|
|
||||||
// 9. GPU timestamp:每 pass 两枚(前后),按帧槽位分区
|
// 9. GPU timestamp:每 pass 两枚(前后),按帧槽位分区
|
||||||
m_QueriesPerFrame = static_cast<u32>(m_RenderGraph->GetPassCount()) * 2;
|
m_QueriesPerFrame = static_cast<u32>(m_RenderGraph->GetPassCount()) * 2;
|
||||||
|
|||||||
@@ -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 = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近)
|
||||||
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
|
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
|
||||||
layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远)
|
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 受光。
|
// 第 0 盏方向光(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
|
||||||
// 级联按视图深度选择;采样器均 clamp-to-border 白色:
|
// 级联按视图深度选择;采样器均 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);
|
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() {
|
void main() {
|
||||||
vec3 albedo = texture(albedoMap, fragUV).rgb;
|
vec3 albedo = texture(albedoMap, fragUV).rgb;
|
||||||
vec4 mr = texture(metallicRoughnessMap, fragUV);
|
vec4 mr = texture(metallicRoughnessMap, fragUV);
|
||||||
@@ -159,8 +166,14 @@ void main() {
|
|||||||
Lo += (kD * albedo / PI + specular) * radiance * NdotL * shade;
|
Lo += (kD * albedo / PI + specular) * radiance * NdotL * shade;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 环境光:无 IBL 的简化版(半球常数);后续 IBL 替换
|
// 环境光:漫反射辐照度(IBL 阶段②,split-sum 的 diffuse 项)。
|
||||||
vec3 ambient = ubo.ambientColor.rgb * albedo;
|
// 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;
|
vec3 color = ambient + Lo + emissive;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user