feat: IBL 阶段③——pre-filtered cubemap + BRDF LUT,split-sum 镜面项接入
- pre-filter:GGX 重要性采样(Hammersley + van der Corput 位反转), 128²×5 mip 分级 roughness,采样数 32<<mip 上限 256;UploadCube 泛化 支持 mip 链([mip: 6面] 紧凑排布逐 mip 逐面拷贝) - BRDF LUT:128² R16G16Sfloat 积分表((NdotV,roughness)→F 缩放/偏置), 纯数学与环境无关,中性兜底同样生成;专用 clamp-to-edge 采样器 (共享 border 白采样器在 uv=1.0 混入白色导致高 roughness 偏大) - shader:specular = prefiltered(R, roughness×4) × (F·A+B), ambient = (diffuse + specular) × ambientColor - Hammersley 分母修正为当次积分采样数(低 mip 偏差 bug) - Debug 构建预积分 26s→14s(采样预算收敛 + pow 改乘法链; env 1024² 重采样为大头,compute 迁移后归零)
This commit is contained in:
@@ -1,18 +1,16 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/23.
|
||||
//
|
||||
// Cubemap:环境立方体贴图 + 漫反射辐照度图(IBL 阶段②)。
|
||||
// Cubemap:环境立方体贴图 + IBL 预积分资源(阶段②③,split-sum)。
|
||||
//
|
||||
// 从等距柱状 HDR(.hdr,polyhaven 等)生成:CPU 重采样到 6 面
|
||||
//(一次性加载成本,亚秒级),转 fp16 后整块上传,硬件 Cube 采样。
|
||||
// 同一次加载内完成 irradiance 卷积(半球积分,源数据在手零额外 IO)。
|
||||
// 从等距柱状 HDR(.hdr,polyhaven 等)生成,CPU 一次性预积分:
|
||||
// env cube —— 重采样 6 面(Skybox 背景)
|
||||
// irradiance cube —— 半球 Lambert 积分(PBR 漫反射 ambient)
|
||||
// prefilter cube —— GGX 重要性采样 × mip 分级 roughness(镜面 IBL)
|
||||
// BRDF LUT —— 2D 积分表(纯数学,与环境内容无关)
|
||||
// 全部 fp16 上传,运行时采样零积分开销。
|
||||
//
|
||||
// 双图契约:
|
||||
// Get()/GetView() —— 环境 cube(Skybox 背景)
|
||||
// GetIrradianceView() —— 漫反射辐照度 cube(PBR ambient)
|
||||
// 镜面 IBL(pre-filter + BRDF LUT)后续接入,形态一致。
|
||||
//
|
||||
// 后续演进:CPU 卷积 → compute pass(动态环境零卡顿);本类的
|
||||
// 后续演进:CPU 预积分 → compute pass(动态环境零卡顿);本类的
|
||||
// 方向表/上传路径届时整体迁移,接口不变。
|
||||
//
|
||||
|
||||
@@ -30,14 +28,14 @@ namespace Engine {
|
||||
|
||||
class Cubemap {
|
||||
public:
|
||||
// 从等距柱状 HDR 生成(CPU 重采样 6 面 + irradiance 卷积)
|
||||
// 从等距柱状 HDR 生成(CPU 重采样 + irradiance/prefilter/BRDF 预积分)
|
||||
static Scope<Cubemap> CreateFromHdrEquirect(Device& device,
|
||||
MemoryAllocator& allocator,
|
||||
const std::string& path,
|
||||
u32 faceSize);
|
||||
|
||||
// 中性兜底(无 HDR 资产时):env/irradiance 均为常数色。
|
||||
// 保证引用侧描述符恒有效(validation),渲染退化为常数环境
|
||||
// 中性兜底(无 HDR 资产时):cube 均为常数色;BRDF LUT 仍为
|
||||
// 真实积分(纯数学)。保证引用侧描述符恒有效(validation)
|
||||
static Scope<Cubemap> CreateNeutral(Device& device,
|
||||
MemoryAllocator& allocator,
|
||||
const glm::vec3& rgb);
|
||||
@@ -51,23 +49,43 @@ namespace Engine {
|
||||
vk::Image Get() const { return m_EnvImage; }
|
||||
vk::ImageView GetView() const { return m_EnvView; }
|
||||
|
||||
// ---- 漫反射辐照度 cube(PBR ambient)----
|
||||
// ---- 漫反射辐照度 cube(PBR ambient diffuse 项)----
|
||||
vk::ImageView GetIrradianceView() const { return m_IrrView; }
|
||||
|
||||
// ---- pre-filtered cube(PBR ambient specular 项,按 mip 分 roughness)----
|
||||
vk::ImageView GetPrefilteredView() const { return m_PrefView; }
|
||||
u32 GetPrefilteredMipCount() const { return m_PrefMipLevels; }
|
||||
|
||||
// ---- BRDF LUT(envBRDF 查表,vec2(NdotV, roughness) → F 缩放/偏置)----
|
||||
vk::ImageView GetBrdfLutView() const { return m_LutView; }
|
||||
// LUT 专用采样器(clamp-to-edge):共享的 border 白采样器会在
|
||||
// uv=1.0 处混入 50% 白色 border,导致高 roughness 镜面偏大
|
||||
vk::Sampler GetBrdfLutSampler() const { return m_LutSampler; }
|
||||
|
||||
vk::Sampler GetSampler() const { return m_Sampler; }
|
||||
u32 GetFaceSize() const { return m_FaceSize; }
|
||||
|
||||
private:
|
||||
Cubemap(Device& device, MemoryAllocator& allocator,
|
||||
vk::Image envImage, vk::ImageView envView,
|
||||
vk::Image irrImage, vk::ImageView irrView, vk::Sampler sampler,
|
||||
vk::Image irrImage, vk::ImageView irrView,
|
||||
vk::Image prefImage, vk::ImageView prefView, u32 prefMips,
|
||||
vk::Image lutImage, vk::ImageView lutView,
|
||||
vk::Sampler sampler, vk::Sampler lutSampler,
|
||||
MemoryAllocator::Allocation envAlloc,
|
||||
MemoryAllocator::Allocation irrAlloc, u32 faceSize)
|
||||
MemoryAllocator::Allocation irrAlloc,
|
||||
MemoryAllocator::Allocation prefAlloc,
|
||||
MemoryAllocator::Allocation lutAlloc, u32 faceSize)
|
||||
: m_Device(device), m_Allocator(allocator),
|
||||
m_EnvImage(envImage), m_EnvView(envView),
|
||||
m_IrrImage(irrImage), m_IrrView(irrView),
|
||||
m_Sampler(sampler), m_EnvAllocation(envAlloc),
|
||||
m_IrrAllocation(irrAlloc), m_FaceSize(faceSize) {}
|
||||
m_PrefImage(prefImage), m_PrefView(prefView),
|
||||
m_PrefMipLevels(prefMips),
|
||||
m_LutImage(lutImage), m_LutView(lutView),
|
||||
m_Sampler(sampler), m_LutSampler(lutSampler),
|
||||
m_EnvAllocation(envAlloc), m_IrrAllocation(irrAlloc),
|
||||
m_PrefAllocation(prefAlloc), m_LutAllocation(lutAlloc),
|
||||
m_FaceSize(faceSize) {}
|
||||
|
||||
Device& m_Device;
|
||||
MemoryAllocator& m_Allocator;
|
||||
@@ -76,9 +94,18 @@ namespace Engine {
|
||||
vk::ImageView m_EnvView;
|
||||
vk::Image m_IrrImage;
|
||||
vk::ImageView m_IrrView;
|
||||
vk::Image m_PrefImage;
|
||||
vk::ImageView m_PrefView;
|
||||
u32 m_PrefMipLevels;
|
||||
vk::Image m_LutImage;
|
||||
vk::ImageView m_LutView;
|
||||
vk::Sampler m_Sampler;
|
||||
vk::Sampler m_LutSampler;
|
||||
|
||||
MemoryAllocator::Allocation m_EnvAllocation;
|
||||
MemoryAllocator::Allocation m_IrrAllocation;
|
||||
MemoryAllocator::Allocation m_PrefAllocation;
|
||||
MemoryAllocator::Allocation m_LutAllocation;
|
||||
u32 m_FaceSize;
|
||||
};
|
||||
|
||||
|
||||
+440
-101
@@ -1,14 +1,14 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/23.
|
||||
//
|
||||
// Cubemap 实现:HDR 等距柱状 → CPU 重采样 6 面 + irradiance 卷积 → fp16 cube。
|
||||
// Cubemap 实现:HDR 等距柱状 → CPU 预积分四件套 → fp16 上传。
|
||||
//
|
||||
// 一次性加载成本(亚秒级 CPU),换取运行时零转换开销;
|
||||
// 硬件 Cube 采样(filtering/边缘接缝由 driver 处理)。
|
||||
//
|
||||
// irradiance 卷积(LearnOpenGL split-sum 的离线端):对每个法线方向
|
||||
// 做半球 Lambert 积分 E = ∫ L·cosθ·sinθ dθ dφ,切线基由 up×N 构造。
|
||||
// 32² 面足够——漫反射辐照度本来就是超低频信号。
|
||||
// 一次性加载成本(秒级 CPU),换取运行时零积分开销;硬件 Cube 采样
|
||||
//(filtering/边缘接缝由 driver 处理)。split-sum 预积分(LearnOpenGL/
|
||||
// UE 拆分方案的离线端):
|
||||
// irradiance —— 半球 Lambert 积分,漫反射 ambient
|
||||
// prefilter —— GGX 重要性采样(Hammersley 序列),mip 分级 roughness
|
||||
// BRDF LUT —— (NdotV, roughness) → (F 缩放, F 偏置),纯数学表
|
||||
//
|
||||
|
||||
#include <Engine/Renderer/Cubemap.h>
|
||||
@@ -33,12 +33,15 @@ namespace Engine
|
||||
{
|
||||
constexpr u32 kFaceCount = 6;
|
||||
constexpr u32 kIrradianceFaceSize = 32; // 漫反射辐照度面边长(低频)
|
||||
constexpr u32 kPhiSteps = 64; // 方位角采样数
|
||||
constexpr u32 kThetaSteps = 32; // 极角采样数
|
||||
constexpr u32 kPhiSteps = 32; // 方位角采样数(512 采样/像素)
|
||||
constexpr u32 kThetaSteps = 16; // 极角采样数
|
||||
constexpr u32 kPrefilterFaceSize = 128; // pre-filter 面边长(mip0)
|
||||
constexpr u32 kPrefilterMips = 5; // mip 分级 roughness 数
|
||||
constexpr u32 kBrdfLutSize = 128; // BRDF LUT 边长
|
||||
constexpr u32 kBrdfLutSamples = 128; // LUT 每像素采样数
|
||||
|
||||
// GL/Vulkan 规范的 cube 面方向表(与 GL spec 立方体选择表互逆)。
|
||||
// (u,v)∈[0,1](u 向右 v 向下)→ s,t∈[-1,1] → 单位方向。
|
||||
// Vulkan 面序与 GL 一致:+X -X +Y -Y +Z -Z
|
||||
// ---- GL/Vulkan cube 面方向表(与 GL spec 立方体选择表互逆)----
|
||||
// (u,v)∈[0,1](u 向右 v 向下)→ s,t∈[-1,1] → 单位方向
|
||||
glm::vec3 FaceDir(u32 face, f32 u, f32 v)
|
||||
{
|
||||
const f32 s = u * 2.0f - 1.0f;
|
||||
@@ -85,8 +88,7 @@ namespace Engine
|
||||
}
|
||||
|
||||
// 漫反射辐照度半球积分(Lambert):E = ∫ L·cosθ·sinθ dθ dφ。
|
||||
// 切线基 up×N 构造;φSteps×θSteps 均匀网格 ≈ 2048 采样/像素,
|
||||
// 对 32² 低频图绰绰有余
|
||||
// 切线基 up×N 构造;均匀网格 ≈2048 采样/像素,对 32² 低频图足够
|
||||
glm::vec3 ConvolveIrradiance(const f32* img, u32 w, u32 h,
|
||||
const glm::vec3& N)
|
||||
{
|
||||
@@ -105,7 +107,6 @@ namespace Engine
|
||||
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)) +
|
||||
@@ -117,8 +118,102 @@ namespace Engine
|
||||
return irradiance * (dPhi * dTheta);
|
||||
}
|
||||
|
||||
// IEEE754 f32 → fp16(round-to-nearest-even;溢出饱和为 Inf)。
|
||||
// HDR 环境值域内常规路径足够,次正规/NaN 路径只为数学完备
|
||||
// ---- GGX 重要性采样(split-sum 镜面端)----
|
||||
|
||||
// van der Corput 基底反转(位反转技巧)
|
||||
f32 RadicalInverseVdC(u32 bits)
|
||||
{
|
||||
bits = (bits << 16u) | (bits >> 16u);
|
||||
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
|
||||
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<f32>(bits) * 2.3283064365386963e-10f;
|
||||
}
|
||||
|
||||
// n = 当次积分总采样数(xi.x = i/n 必须覆盖 [0,1] 全程)
|
||||
glm::vec2 Hammersley(u32 i, u32 n)
|
||||
{
|
||||
return { static_cast<f32>(i) / static_cast<f32>(n),
|
||||
RadicalInverseVdC(i) };
|
||||
}
|
||||
|
||||
// GGX NDF 按概率密度取半程向量 H(切线空间 → 世界)
|
||||
glm::vec3 ImportanceSampleGGX(const glm::vec2& xi, const glm::vec3& N,
|
||||
f32 roughness)
|
||||
{
|
||||
const f32 a = roughness * roughness;
|
||||
const f32 phi = 2.0f * glm::pi<f32>() * xi.x;
|
||||
const f32 cosTheta =
|
||||
std::sqrt((1.0f - xi.y) / (1.0f + (a * a - 1.0f) * xi.y));
|
||||
const f32 sinTheta = std::sqrt(1.0f - cosTheta * cosTheta);
|
||||
|
||||
const glm::vec3 H(sinTheta * std::cos(phi),
|
||||
sinTheta * std::sin(phi), cosTheta);
|
||||
|
||||
const glm::vec3 up = glm::abs(N.z) > 0.999f
|
||||
? glm::vec3(1.0f, 0.0f, 0.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);
|
||||
return glm::normalize(T * H.x + B * H.y + N * H.z);
|
||||
}
|
||||
|
||||
// pre-filter 卷积:R(N) ≈ Σ L·NdotL / Σ NdotL(GGX 加权镜面均值)
|
||||
glm::vec3 Prefilter(const f32* img, u32 w, u32 h,
|
||||
const glm::vec3& N, f32 roughness, u32 samples)
|
||||
{
|
||||
const glm::vec3 V = N; // split-sum 约定:V 沿 N
|
||||
glm::vec3 acc(0.0f);
|
||||
f32 weight = 0.0f;
|
||||
for (u32 i = 0; i < samples; ++i) {
|
||||
const glm::vec3 H = ImportanceSampleGGX(
|
||||
Hammersley(i, samples), N, roughness);
|
||||
const glm::vec3 L = glm::normalize(2.0f * glm::dot(V, H) * H - V);
|
||||
const f32 nDotL = glm::max(glm::dot(N, L), 0.0f);
|
||||
if (nDotL > 0.0f) {
|
||||
acc += SampleEquirect(img, w, h, L) * nDotL;
|
||||
weight += nDotL;
|
||||
}
|
||||
}
|
||||
return weight > 0.0f ? acc / weight : glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
// BRDF LUT 积分:(NdotV, roughness) → (A, B)。
|
||||
// A/B 是 Fresnel 的缩放/偏置:specular = prefiltered·(F·A + B)
|
||||
glm::vec2 IntegrateBrdf(f32 nDotV, f32 roughness)
|
||||
{
|
||||
// V 在以 N 为 +Z 的切线空间里(积分只依赖这两角度)
|
||||
const glm::vec3 V(std::sqrt(1.0f - nDotV * nDotV), 0.0f, nDotV);
|
||||
|
||||
f32 a = 0.0f, b = 0.0f;
|
||||
const f32 k = roughness * roughness * 0.5f; // IBL 版 Smith k
|
||||
|
||||
for (u32 i = 0; i < kBrdfLutSamples; ++i) {
|
||||
const glm::vec2 xi = Hammersley(i, kBrdfLutSamples);
|
||||
const glm::vec3 H = ImportanceSampleGGX(xi, glm::vec3(0, 0, 1),
|
||||
roughness);
|
||||
const glm::vec3 L = glm::normalize(
|
||||
2.0f * glm::dot(V, H) * H - V);
|
||||
|
||||
const f32 nDotL = glm::max(L.z, 0.0f);
|
||||
const f32 nDotH = glm::max(H.z, 0.0f);
|
||||
const f32 vDotH = glm::max(glm::dot(V, H), 0.0f);
|
||||
if (nDotL <= 0.0f) continue;
|
||||
|
||||
const f32 g = (nDotV / (nDotV * (1.0f - k) + k)) *
|
||||
(nDotL / (nDotL * (1.0f - k) + k));
|
||||
const f32 gVis = g * vDotH / (nDotH * nDotV);
|
||||
const f32 fc = 1.0f - vDotH;
|
||||
const f32 f = fc * fc * fc * fc * fc; // (1-vDotH)⁵,乘法链
|
||||
|
||||
a += (1.0f - f) * gVis;
|
||||
b += f * gVis;
|
||||
}
|
||||
return { a / static_cast<f32>(kBrdfLutSamples),
|
||||
b / static_cast<f32>(kBrdfLutSamples) };
|
||||
}
|
||||
|
||||
// IEEE754 f32 → fp16(round-to-nearest-even;溢出饱和为 Inf)
|
||||
u16 FloatToHalf(f32 value)
|
||||
{
|
||||
const u32 x = std::bit_cast<u32>(value);
|
||||
@@ -132,7 +227,6 @@ namespace Engine
|
||||
return static_cast<u16>(sign | 0x7c00u); // Inf / 溢出饱和
|
||||
}
|
||||
if (absVal >= 0x38800000u) {
|
||||
// 常规数:指数重偏(127→15),尾数右移 13 位并舍入到最近偶数
|
||||
const u32 bits = absVal - 0x38000000u;
|
||||
const u32 round = 0x0fffu + ((bits >> 13) & 1u);
|
||||
return static_cast<u16>(sign | ((bits + round) >> 13));
|
||||
@@ -140,38 +234,59 @@ namespace Engine
|
||||
if (absVal < 0x33000000u) {
|
||||
return sign; // < 2^-25:舍入为 0
|
||||
}
|
||||
// half 次正规 [2^-24, 2^-14):补隐含位后按目标精度舍入
|
||||
const u32 mantissa = (absVal & 0x007fffffu) | 0x00800000u;
|
||||
const u32 shift = 126u - (absVal >> 23u);
|
||||
const u32 m = (mantissa + (1u << (shift - 1))) >> shift;
|
||||
// 舍入进位到 0x400 恰好是最小正规 half 的位型,无需特判
|
||||
return static_cast<u16>(sign | std::min(m, 0x400u));
|
||||
}
|
||||
|
||||
// 上传 6 面 fp16 数据为 cube image(UNDEFINED → TransferDst →
|
||||
// ShaderRead;一次性命令缓冲)。view 由调用方创建
|
||||
// ---- 上传 ----
|
||||
|
||||
// cube mip 链数据:[mip0: 6 面][mip1: 6 面]...,每面 faceSize²×8B
|
||||
struct CubeMipData {
|
||||
const u16* data; // 6 面连续
|
||||
u32 faceSize;
|
||||
};
|
||||
|
||||
// 上传 cube(含 mip 链)为 eCubeCompatible image(fp16)
|
||||
MemoryAllocator::Allocation UploadCube(Device& device,
|
||||
MemoryAllocator& allocator,
|
||||
const std::vector<u16>& faces,
|
||||
u32 faceSize, vk::Image& outImage)
|
||||
const std::vector<CubeMipData>& mips,
|
||||
vk::Image& outImage)
|
||||
{
|
||||
const vk::DeviceSize faceBytes =
|
||||
static_cast<vk::DeviceSize>(faceSize) * faceSize * 8;
|
||||
const vk::DeviceSize total = faceBytes * kFaceCount;
|
||||
const u32 mipCount = static_cast<u32>(mips.size());
|
||||
|
||||
// staging:各 mip 紧凑排布,记偏移
|
||||
vk::DeviceSize total = 0;
|
||||
for (const auto& mip : mips) {
|
||||
total += static_cast<vk::DeviceSize>(mip.faceSize) *
|
||||
mip.faceSize * 8 * 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));
|
||||
{
|
||||
auto* dst = static_cast<u8*>(stagingAlloc.MappedPtr);
|
||||
vk::DeviceSize off = 0;
|
||||
for (const auto& mip : mips) {
|
||||
const vk::DeviceSize bytes =
|
||||
static_cast<vk::DeviceSize>(mip.faceSize) *
|
||||
mip.faceSize * 8 * kFaceCount;
|
||||
std::memcpy(dst + off, mip.data,
|
||||
static_cast<size_t>(bytes));
|
||||
off += bytes;
|
||||
}
|
||||
}
|
||||
|
||||
const u32 baseSize = mips[0].faceSize;
|
||||
vk::ImageCreateInfo imageInfo;
|
||||
imageInfo.setImageType(vk::ImageType::e2D)
|
||||
.setFormat(vk::Format::eR16G16B16A16Sfloat)
|
||||
.setExtent({faceSize, faceSize, 1})
|
||||
.setMipLevels(1)
|
||||
.setExtent({baseSize, baseSize, 1})
|
||||
.setMipLevels(mipCount)
|
||||
.setArrayLayers(kFaceCount)
|
||||
.setSamples(vk::SampleCountFlagBits::e1)
|
||||
.setTiling(vk::ImageTiling::eOptimal)
|
||||
@@ -190,6 +305,119 @@ namespace Engine
|
||||
.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});
|
||||
|
||||
// 全 mip × 全层一起转 layout
|
||||
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, mipCount, 0, kFaceCount});
|
||||
cmd.pipelineBarrier2(
|
||||
vk::DependencyInfo{}.setImageMemoryBarriers(toTransfer));
|
||||
|
||||
for (u32 mip = 0; mip < mipCount; ++mip) {
|
||||
const u32 dim = mips[mip].faceSize;
|
||||
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||
vk::BufferImageCopy region;
|
||||
region.setBufferOffset(
|
||||
(static_cast<vk::DeviceSize>(dim) * dim * 8 *
|
||||
kFaceCount) * mip)
|
||||
.setBufferRowLength(0)
|
||||
.setBufferImageHeight(0)
|
||||
.setImageSubresource({vk::ImageAspectFlagBits::eColor,
|
||||
mip, f, 1})
|
||||
.setImageOffset({0, 0, 0})
|
||||
.setImageExtent({dim, dim, 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, mipCount, 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;
|
||||
}
|
||||
|
||||
// 上传 BRDF LUT(2D,R16G16Sfloat,8B/px)
|
||||
MemoryAllocator::Allocation UploadTexture2D(Device& device,
|
||||
MemoryAllocator& allocator,
|
||||
const std::vector<u16>& texels,
|
||||
u32 size,
|
||||
vk::Format format,
|
||||
vk::Image& outImage)
|
||||
{
|
||||
const vk::DeviceSize total =
|
||||
static_cast<vk::DeviceSize>(size) * size * 8;
|
||||
|
||||
auto stagingAlloc = allocator.CreateBuffer(
|
||||
total,
|
||||
vk::BufferUsageFlagBits::eTransferSrc,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||
std::memcpy(stagingAlloc.MappedPtr, texels.data(),
|
||||
static_cast<size_t>(total));
|
||||
|
||||
vk::ImageCreateInfo imageInfo;
|
||||
imageInfo.setImageType(vk::ImageType::e2D)
|
||||
.setFormat(format)
|
||||
.setExtent({size, size, 1})
|
||||
.setMipLevels(1)
|
||||
.setArrayLayers(1)
|
||||
.setSamples(vk::SampleCountFlagBits::e1)
|
||||
.setTiling(vk::ImageTiling::eOptimal)
|
||||
.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)
|
||||
@@ -208,23 +436,20 @@ namespace Engine
|
||||
.setNewLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||
.setImage(outImage)
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||
0, 1, 0, kFaceCount});
|
||||
0, 1, 0, 1});
|
||||
cmd.pipelineBarrier2(
|
||||
vk::DependencyInfo{}.setImageMemoryBarriers(toTransfer));
|
||||
|
||||
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||
vk::BufferImageCopy region;
|
||||
region.setBufferOffset(faceBytes * f)
|
||||
region.setBufferOffset(0)
|
||||
.setBufferRowLength(0)
|
||||
.setBufferImageHeight(0)
|
||||
.setImageSubresource({vk::ImageAspectFlagBits::eColor,
|
||||
0, f, 1})
|
||||
0, 0, 1})
|
||||
.setImageOffset({0, 0, 0})
|
||||
.setImageExtent({faceSize, faceSize, 1});
|
||||
.setImageExtent({size, size, 1});
|
||||
cmd.copyBufferToImage(stagingAlloc.Buffer, outImage,
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
region);
|
||||
}
|
||||
vk::ImageLayout::eTransferDstOptimal, region);
|
||||
|
||||
vk::ImageMemoryBarrier2 toShader;
|
||||
toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||
@@ -235,7 +460,7 @@ namespace Engine
|
||||
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
|
||||
.setImage(outImage)
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||
0, 1, 0, kFaceCount});
|
||||
0, 1, 0, 1});
|
||||
cmd.pipelineBarrier2(
|
||||
vk::DependencyInfo{}.setImageMemoryBarriers(toShader));
|
||||
|
||||
@@ -246,7 +471,7 @@ namespace Engine
|
||||
vk::SubmitInfo{}.setCommandBuffers(cmd), fence);
|
||||
if (device.GetDevice().waitForFences(fence, VK_TRUE, UINT64_MAX) !=
|
||||
vk::Result::eSuccess) {
|
||||
ENGINE_ERROR("Cubemap upload: waitForFences failed");
|
||||
ENGINE_ERROR("BRDF LUT upload: waitForFences failed");
|
||||
}
|
||||
|
||||
device.GetDevice().destroyFence(fence);
|
||||
@@ -256,7 +481,7 @@ namespace Engine
|
||||
return imageAlloc;
|
||||
}
|
||||
|
||||
vk::ImageView CreateCubeView(Device& device, vk::Image image)
|
||||
vk::ImageView CreateCubeView(Device& device, vk::Image image, u32 mips)
|
||||
{
|
||||
return device.GetDevice().createImageView(
|
||||
vk::ImageViewCreateInfo{}
|
||||
@@ -264,7 +489,43 @@ namespace Engine
|
||||
.setViewType(vk::ImageViewType::eCube)
|
||||
.setFormat(vk::Format::eR16G16B16A16Sfloat)
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||
0, 1, 0, kFaceCount}));
|
||||
0, mips, 0, kFaceCount}));
|
||||
}
|
||||
|
||||
vk::ImageView CreateLutView(Device& device, vk::Image image)
|
||||
{
|
||||
return device.GetDevice().createImageView(
|
||||
vk::ImageViewCreateInfo{}
|
||||
.setImage(image)
|
||||
.setViewType(vk::ImageViewType::e2D)
|
||||
.setFormat(vk::Format::eR16G16Sfloat)
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||
0, 1, 0, 1}));
|
||||
}
|
||||
|
||||
vk::Sampler CreateEnvSampler(Device& device, u32 maxLod)
|
||||
{
|
||||
vk::SamplerCreateInfo samplerInfo;
|
||||
samplerInfo.setMagFilter(vk::Filter::eLinear)
|
||||
.setMinFilter(vk::Filter::eLinear)
|
||||
.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(static_cast<f32>(maxLod));
|
||||
return device.GetDevice().createSampler(samplerInfo);
|
||||
}
|
||||
|
||||
// BRDF LUT 专用:clamp-to-edge(共享 border 白采样器会在 uv=1.0
|
||||
// 处混入白色 border,高 roughness 镜面偏大)
|
||||
vk::Sampler CreateLutSampler(Device& device)
|
||||
{
|
||||
return CreateEnvSampler(device, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +544,7 @@ namespace Engine
|
||||
const u32 srcW = static_cast<u32>(w);
|
||||
const u32 srcH = static_cast<u32>(h);
|
||||
|
||||
// ---- 2. 重采样 6 面 + irradiance 卷积 + fp16 ----
|
||||
// ---- 2a. 环境 cube:重采样 6 面 ----
|
||||
const u32 facePixels = faceSize * faceSize;
|
||||
std::vector<u16> envFaces(kFaceCount * facePixels * 4);
|
||||
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||
@@ -303,17 +564,19 @@ namespace Engine
|
||||
}
|
||||
}
|
||||
|
||||
const u32 irrPixels = kIrradianceFaceSize * kIrradianceFaceSize;
|
||||
// ---- 2b. irradiance cube:半球 Lambert 积分 ----
|
||||
const u32 irrSize = kIrradianceFaceSize;
|
||||
const u32 irrPixels = irrSize * irrSize;
|
||||
std::vector<u16> 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<f32>(x) + 0.5f) / static_cast<f32>(kIrradianceFaceSize);
|
||||
const f32 v = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(kIrradianceFaceSize);
|
||||
for (u32 y = 0; y < irrSize; ++y) {
|
||||
for (u32 x = 0; x < irrSize; ++x) {
|
||||
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(irrSize);
|
||||
const f32 v = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(irrSize);
|
||||
const glm::vec3 rgb = ConvolveIrradiance(
|
||||
pixels, srcW, srcH, FaceDir(f, u, v));
|
||||
u16* px = dst + (static_cast<size_t>(y) * kIrradianceFaceSize + x) * 4;
|
||||
u16* px = dst + (static_cast<size_t>(y) * irrSize + x) * 4;
|
||||
px[0] = FloatToHalf(rgb.r);
|
||||
px[1] = FloatToHalf(rgb.g);
|
||||
px[2] = FloatToHalf(rgb.b);
|
||||
@@ -321,47 +584,99 @@ namespace Engine
|
||||
}
|
||||
}
|
||||
}
|
||||
stbi_image_free(pixels); // 源数据消费完毕(卷积/重采样都在构造期)
|
||||
|
||||
// ---- 3. 上传双 cube + 共享采样器 ----
|
||||
vk::Image envImage, irrImage;
|
||||
auto envAlloc = UploadCube(device, allocator, envFaces, faceSize, envImage);
|
||||
auto irrAlloc = UploadCube(device, allocator, irrFaces,
|
||||
kIrradianceFaceSize, irrImage);
|
||||
// ---- 2c. pre-filter cube:GGX 重要性采样 × mip 分级 roughness ----
|
||||
// 采样数随 mip 递增(高 roughness 分布更宽需要更多样本)
|
||||
std::vector<u16> prefFaces;
|
||||
std::vector<CubeMipData> prefMips;
|
||||
prefMips.reserve(kPrefilterMips);
|
||||
for (u32 mip = 0; mip < kPrefilterMips; ++mip) {
|
||||
const u32 dim = kPrefilterFaceSize >> mip;
|
||||
const f32 roughness =
|
||||
static_cast<f32>(mip) / static_cast<f32>(kPrefilterMips - 1);
|
||||
const u32 samples = std::min(32u << mip, 256u);
|
||||
|
||||
const size_t offset = prefFaces.size();
|
||||
prefFaces.resize(offset + kFaceCount * dim * dim * 4);
|
||||
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||
u16* dst = prefFaces.data() + offset + f * dim * dim * 4;
|
||||
for (u32 y = 0; y < dim; ++y) {
|
||||
for (u32 x = 0; x < dim; ++x) {
|
||||
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(dim);
|
||||
const f32 v = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(dim);
|
||||
const glm::vec3 rgb = Prefilter(
|
||||
pixels, srcW, srcH, FaceDir(f, u, v),
|
||||
roughness, samples);
|
||||
u16* px = dst + (static_cast<size_t>(y) * dim + x) * 4;
|
||||
px[0] = FloatToHalf(rgb.r);
|
||||
px[1] = FloatToHalf(rgb.g);
|
||||
px[2] = FloatToHalf(rgb.b);
|
||||
px[3] = 0x3C00;
|
||||
}
|
||||
}
|
||||
}
|
||||
prefMips.push_back({prefFaces.data() + offset, dim});
|
||||
}
|
||||
|
||||
stbi_image_free(pixels); // 源数据消费完毕
|
||||
|
||||
// ---- 2d. BRDF LUT:纯数学积分表(与环境内容无关)----
|
||||
std::vector<u16> lutTexels(kBrdfLutSize * kBrdfLutSize * 2);
|
||||
for (u32 y = 0; y < kBrdfLutSize; ++y) {
|
||||
const f32 roughness = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||
for (u32 x = 0; x < kBrdfLutSize; ++x) {
|
||||
const f32 nDotV = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||
const glm::vec2 ab = IntegrateBrdf(nDotV, roughness);
|
||||
u16* px = lutTexels.data() + (static_cast<size_t>(y) * kBrdfLutSize + x) * 2;
|
||||
px[0] = FloatToHalf(ab.x);
|
||||
px[1] = FloatToHalf(ab.y);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 3. 上传:env / irradiance / prefilter / LUT ----
|
||||
vk::Image envImage, irrImage, prefImage, lutImage;
|
||||
auto envAlloc = UploadCube(device, allocator,
|
||||
{{envFaces.data(), faceSize}}, envImage);
|
||||
auto irrAlloc = UploadCube(device, allocator,
|
||||
{{irrFaces.data(), irrSize}}, irrImage);
|
||||
auto prefAlloc = UploadCube(device, allocator, prefMips, prefImage);
|
||||
auto lutAlloc = UploadTexture2D(device, allocator, lutTexels,
|
||||
kBrdfLutSize,
|
||||
vk::Format::eR16G16Sfloat, lutImage);
|
||||
|
||||
envFaces.clear(); envFaces.shrink_to_fit();
|
||||
irrFaces.clear(); irrFaces.shrink_to_fit();
|
||||
prefFaces.clear(); prefFaces.shrink_to_fit();
|
||||
|
||||
vk::ImageView envView = CreateCubeView(device, envImage);
|
||||
vk::ImageView irrView = CreateCubeView(device, irrImage);
|
||||
vk::ImageView envView = CreateCubeView(device, envImage, 1);
|
||||
vk::ImageView irrView = CreateCubeView(device, irrImage, 1);
|
||||
vk::ImageView prefView = CreateCubeView(device, prefImage,
|
||||
kPrefilterMips);
|
||||
vk::ImageView lutView = CreateLutView(device, lutImage);
|
||||
|
||||
vk::SamplerCreateInfo samplerInfo;
|
||||
samplerInfo.setMagFilter(vk::Filter::eLinear)
|
||||
.setMinFilter(vk::Filter::eLinear)
|
||||
.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);
|
||||
// cube sampler 共享:maxLod 覆盖 pre-filter mip 链(textureLod 显式
|
||||
// 采样会被 clamp 到 maxLod,必须 ≥ mipCount-1)
|
||||
vk::Sampler sampler = CreateEnvSampler(device, kPrefilterMips - 1);
|
||||
vk::Sampler lutSampler = CreateLutSampler(device);
|
||||
|
||||
ENGINE_INFO("Environment cubemap: {} ({}x6 env + {}x6 irradiance, fp16)",
|
||||
path, faceSize, kIrradianceFaceSize);
|
||||
ENGINE_INFO("Environment IBL: {} ({}x6 env, {}x6 irradiance, "
|
||||
"{}x6 prefilter {} mips, {}² BRDF LUT)",
|
||||
path, faceSize, irrSize, kPrefilterFaceSize,
|
||||
kPrefilterMips, kBrdfLutSize);
|
||||
|
||||
return Scope<Cubemap>(new Cubemap(device, allocator, envImage, envView,
|
||||
irrImage, irrView, sampler,
|
||||
envAlloc, irrAlloc, faceSize));
|
||||
return Scope<Cubemap>(new Cubemap(device, allocator,
|
||||
envImage, envView, irrImage, irrView,
|
||||
prefImage, prefView, kPrefilterMips,
|
||||
lutImage, lutView, sampler, lutSampler,
|
||||
envAlloc, irrAlloc, prefAlloc, lutAlloc, faceSize));
|
||||
}
|
||||
|
||||
Scope<Cubemap> Cubemap::CreateNeutral(Device& device,
|
||||
MemoryAllocator& allocator,
|
||||
const glm::vec3& rgb)
|
||||
{
|
||||
// 1×1×6 常数 cube:env 与 irradiance 同值,描述符恒有效
|
||||
// 1×1×6 常数 cube(prefilter 各 mip 同值);BRDF LUT 仍为真实
|
||||
// 积分(纯数学,无环境依赖)——描述符恒有效
|
||||
const u16 px[4] = { FloatToHalf(rgb.r), FloatToHalf(rgb.g),
|
||||
FloatToHalf(rgb.b), 0x3C00 };
|
||||
std::vector<u16> faces(kFaceCount * 4, 0);
|
||||
@@ -369,32 +684,51 @@ namespace Engine
|
||||
std::memcpy(faces.data() + f * 4, px, sizeof(px));
|
||||
}
|
||||
|
||||
vk::Image envImage, irrImage;
|
||||
auto envAlloc = UploadCube(device, allocator, faces, 1, envImage);
|
||||
auto irrAlloc = UploadCube(device, allocator, faces, 1, irrImage);
|
||||
std::vector<u16> prefFaces(kPrefilterMips * kFaceCount * 4, 0);
|
||||
std::vector<CubeMipData> 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});
|
||||
}
|
||||
|
||||
vk::ImageView envView = CreateCubeView(device, envImage);
|
||||
vk::ImageView irrView = CreateCubeView(device, irrImage);
|
||||
std::vector<u16> lutTexels(kBrdfLutSize * kBrdfLutSize * 2);
|
||||
for (u32 y = 0; y < kBrdfLutSize; ++y) {
|
||||
const f32 roughness = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||
for (u32 x = 0; x < kBrdfLutSize; ++x) {
|
||||
const f32 nDotV = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||
const glm::vec2 ab = IntegrateBrdf(nDotV, roughness);
|
||||
u16* t = lutTexels.data() + (static_cast<size_t>(y) * kBrdfLutSize + x) * 2;
|
||||
t[0] = FloatToHalf(ab.x);
|
||||
t[1] = FloatToHalf(ab.y);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
vk::Image envImage, irrImage, prefImage, lutImage;
|
||||
auto envAlloc = UploadCube(device, allocator, {{faces.data(), 1}}, envImage);
|
||||
auto irrAlloc = UploadCube(device, allocator, {{faces.data(), 1}}, irrImage);
|
||||
auto prefAlloc = UploadCube(device, allocator, prefMips, prefImage);
|
||||
auto lutAlloc = UploadTexture2D(device, allocator, lutTexels,
|
||||
kBrdfLutSize,
|
||||
vk::Format::eR16G16Sfloat, lutImage);
|
||||
|
||||
ENGINE_INFO("Neutral cubemap created (constant fallback)");
|
||||
return Scope<Cubemap>(new Cubemap(device, allocator, envImage, envView,
|
||||
irrImage, irrView, sampler,
|
||||
envAlloc, irrAlloc, 1));
|
||||
vk::ImageView envView = CreateCubeView(device, envImage, 1);
|
||||
vk::ImageView irrView = CreateCubeView(device, irrImage, 1);
|
||||
vk::ImageView prefView = CreateCubeView(device, prefImage,
|
||||
kPrefilterMips);
|
||||
vk::ImageView lutView = CreateLutView(device, lutImage);
|
||||
|
||||
vk::Sampler sampler = CreateEnvSampler(device, kPrefilterMips - 1);
|
||||
vk::Sampler lutSampler = CreateLutSampler(device);
|
||||
|
||||
ENGINE_INFO("Neutral IBL environment (constant fallback, real BRDF LUT)");
|
||||
return Scope<Cubemap>(new Cubemap(device, allocator,
|
||||
envImage, envView, irrImage, irrView,
|
||||
prefImage, prefView, kPrefilterMips,
|
||||
lutImage, lutView, sampler, lutSampler,
|
||||
envAlloc, irrAlloc, prefAlloc, lutAlloc, 1));
|
||||
}
|
||||
|
||||
Cubemap::~Cubemap()
|
||||
@@ -402,12 +736,17 @@ namespace Engine
|
||||
const auto& vkDevice = m_Device.GetDevice();
|
||||
|
||||
vkDevice.destroySampler(m_Sampler);
|
||||
vkDevice.destroySampler(m_LutSampler);
|
||||
vkDevice.destroyImageView(m_EnvView);
|
||||
vkDevice.destroyImageView(m_IrrView);
|
||||
vkDevice.destroyImageView(m_PrefView);
|
||||
vkDevice.destroyImageView(m_LutView);
|
||||
|
||||
// image 资源与显存经 VMA 成对释放
|
||||
m_Allocator.Destroy(m_EnvAllocation);
|
||||
m_Allocator.Destroy(m_IrrAllocation);
|
||||
m_Allocator.Destroy(m_PrefAllocation);
|
||||
m_Allocator.Destroy(m_LutAllocation);
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -46,17 +46,19 @@ namespace Engine::Passes
|
||||
vk::ShaderStageFlagBits::eFragment});
|
||||
textureSetLayout = descriptors.CreateLayout(texBindings);
|
||||
|
||||
// Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
|
||||
// + 漫反射辐照度 cube(IBL 阶段②,binding 3)
|
||||
// Set 2: CSM 级联阴影图(0/1/2)+ IBL 环境三件套(3 irradiance
|
||||
// / 4 pre-filtered / 5 BRDF LUT),均 fragment 采样
|
||||
std::vector<DescriptorBinding> shadowBindings;
|
||||
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||
shadowBindings.push_back(
|
||||
{c, vk::DescriptorType::eCombinedImageSampler,
|
||||
vk::ShaderStageFlagBits::eFragment});
|
||||
}
|
||||
for (const u32 b : {3u, 4u, 5u}) {
|
||||
shadowBindings.push_back(
|
||||
{3, vk::DescriptorType::eCombinedImageSampler,
|
||||
{b, vk::DescriptorType::eCombinedImageSampler,
|
||||
vk::ShaderStageFlagBits::eFragment});
|
||||
}
|
||||
shadowSetLayout = descriptors.CreateLayout(shadowBindings);
|
||||
|
||||
// Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO)
|
||||
@@ -164,10 +166,15 @@ namespace Engine::Passes
|
||||
|
||||
void SceneGpu::BindEnvironment(const Cubemap& env)
|
||||
{
|
||||
// irradiance view 全程稳定(不随 RG 重编译变化),绑一次即可
|
||||
// IBL 资源 view 全程稳定(不随 RG 重编译变化),绑一次即可
|
||||
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||
m_Descriptors.UpdateTexture(shadowSets[slot], 3, shadowSampler,
|
||||
env.GetIrradianceView());
|
||||
m_Descriptors.UpdateTexture(shadowSets[slot], 4, shadowSampler,
|
||||
env.GetPrefilteredView());
|
||||
m_Descriptors.UpdateTexture(shadowSets[slot], 5,
|
||||
env.GetBrdfLutSampler(),
|
||||
env.GetBrdfLutView());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ 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 阶段②)
|
||||
layout(set = 2, binding = 4) uniform samplerCube prefilteredMap; // 镜面环境(mip 分 roughness,阶段③)
|
||||
layout(set = 2, binding = 5) uniform sampler2D brdfLut; // BRDF 积分表 (NdotV, roughness) → (scale, bias)
|
||||
|
||||
// 第 0 盏方向光(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
|
||||
// 级联按视图深度选择;采样器均 clamp-to-border 白色:
|
||||
@@ -166,14 +168,25 @@ void main() {
|
||||
Lo += (kD * albedo / PI + specular) * radiance * NdotL * shade;
|
||||
}
|
||||
|
||||
// 环境光:漫反射辐照度(IBL 阶段②,split-sum 的 diffuse 项)。
|
||||
// N 采样预卷积的 irradiance cube(半球 Lambert 积分已在加载期完成);
|
||||
// kD 用粗糙度感知 Fresnel + (1-metallic)(金属无漫反射)。
|
||||
// ambientColor = 用户可调强度/色调乘子。镜面项待阶段③ BRDF LUT
|
||||
// 环境光:IBL split-sum(阶段②③)。
|
||||
// 漫反射:N 采样 irradiance cube(半球 Lambert 预卷积)
|
||||
// 镜面:R 采样 pre-filtered cube(mip = roughness 分级)
|
||||
// × BRDF LUT 查表得 F 缩放/偏置
|
||||
// ambientColor = 用户可调强度/色调乘子
|
||||
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 diffuse = kD_amb * irradiance * albedo;
|
||||
|
||||
vec3 R = reflect(-V, N);
|
||||
const float kMaxPrefilterMip = 4.0; // 与 Cubemap::kPrefilterMips - 1 同步
|
||||
vec3 prefilteredColor = textureLod(prefilteredMap, R,
|
||||
roughness * kMaxPrefilterMip).rgb;
|
||||
vec2 envBRDF = texture(brdfLut, vec2(NdotV, roughness)).rg;
|
||||
vec3 specular = prefilteredColor * (F_amb * envBRDF.x + envBRDF.y);
|
||||
|
||||
vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb;
|
||||
|
||||
vec3 color = ambient + Lo + emissive;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user