ADD: IBL added
This commit is contained in:
@@ -471,6 +471,28 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const std::string &name,
|
||||
return mesh;
|
||||
}
|
||||
|
||||
std::shared_ptr<GLTFMaterial> AssetManager::createMaterialFromConstants(
|
||||
const std::string &name,
|
||||
const GLTFMetallic_Roughness::MaterialConstants &constants,
|
||||
MaterialPass pass)
|
||||
{
|
||||
if (!_engine) return {};
|
||||
GLTFMetallic_Roughness::MaterialResources res{};
|
||||
res.colorImage = _engine->_whiteImage;
|
||||
res.colorSampler = _engine->_samplerManager->defaultLinear();
|
||||
res.metalRoughImage = _engine->_whiteImage;
|
||||
res.metalRoughSampler = _engine->_samplerManager->defaultLinear();
|
||||
res.normalImage = _engine->_flatNormalImage;
|
||||
res.normalSampler = _engine->_samplerManager->defaultLinear();
|
||||
|
||||
AllocatedBuffer buf = createMaterialBufferWithConstants(constants);
|
||||
res.dataBuffer = buf.buffer;
|
||||
res.dataBufferOffset = 0;
|
||||
_meshMaterialBuffers[name] = buf;
|
||||
|
||||
return createMaterial(pass, res);
|
||||
}
|
||||
|
||||
std::shared_ptr<MeshAsset> AssetManager::getMesh(const std::string &name) const
|
||||
{
|
||||
auto it = _meshCache.find(name);
|
||||
|
||||
@@ -96,6 +96,11 @@ public:
|
||||
|
||||
bool removeMesh(const std::string &name);
|
||||
|
||||
// Convenience: create a PBR material from constants using engine default textures
|
||||
std::shared_ptr<GLTFMaterial> createMaterialFromConstants(const std::string &name,
|
||||
const GLTFMetallic_Roughness::MaterialConstants &constants,
|
||||
MaterialPass pass = MaterialPass::MainColor);
|
||||
|
||||
const AssetPaths &paths() const { return _locator.paths(); }
|
||||
void setPaths(const AssetPaths &p) { _locator.setPaths(p); }
|
||||
|
||||
|
||||
@@ -3,15 +3,24 @@
|
||||
#include <core/vk_resource.h>
|
||||
#include <core/ktx_loader.h>
|
||||
#include <core/vk_sampler_manager.h>
|
||||
#include <core/vk_descriptors.h>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <ktx.h>
|
||||
#include <SDL_stdinc.h>
|
||||
|
||||
#include "vk_device.h"
|
||||
|
||||
bool IBLManager::load(const IBLPaths &paths)
|
||||
{
|
||||
if (_ctx == nullptr || _ctx->getResources() == nullptr) return false;
|
||||
ResourceManager* rm = _ctx->getResources();
|
||||
ensureLayout();
|
||||
ResourceManager *rm = _ctx->getResources();
|
||||
|
||||
// Specular cubemap
|
||||
// Load specular environment: prefer cubemap; fallback to 2D equirect with mips
|
||||
if (!paths.specularCube.empty())
|
||||
{
|
||||
// Try as cubemap first
|
||||
ktxutil::KtxCubemap kcm{};
|
||||
if (ktxutil::load_ktx2_cubemap(paths.specularCube.c_str(), kcm))
|
||||
{
|
||||
@@ -23,9 +32,177 @@ bool IBLManager::load(const IBLPaths &paths)
|
||||
kcm.imgFlags
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ktxutil::Ktx2D k2d{};
|
||||
if (ktxutil::load_ktx2_2d(paths.specularCube.c_str(), k2d))
|
||||
{
|
||||
std::vector<ResourceManager::MipLevelCopy> lv;
|
||||
lv.reserve(k2d.mipLevels);
|
||||
for (uint32_t mip = 0; mip < k2d.mipLevels; ++mip)
|
||||
{
|
||||
const auto &r = k2d.copies[mip];
|
||||
lv.push_back(ResourceManager::MipLevelCopy{
|
||||
.offset = r.bufferOffset,
|
||||
.length = 0,
|
||||
.width = r.imageExtent.width,
|
||||
.height = r.imageExtent.height,
|
||||
});
|
||||
}
|
||||
_spec = rm->create_image_compressed(k2d.bytes.data(), k2d.bytes.size(), k2d.fmt, lv,
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
|
||||
ktxTexture2 *ktex = nullptr;
|
||||
if (ktxTexture2_CreateFromNamedFile(paths.specularCube.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
|
||||
&ktex) == KTX_SUCCESS && ktex)
|
||||
{
|
||||
const VkFormat fmt = static_cast<VkFormat>(ktex->vkFormat);
|
||||
const bool isFloat16 = fmt == VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
const bool isFloat32 = fmt == VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
if (!ktxTexture2_NeedsTranscoding(ktex) && (isFloat16 || isFloat32) && ktex->baseWidth == 2 * ktex->
|
||||
baseHeight)
|
||||
{
|
||||
const uint32_t W = ktex->baseWidth;
|
||||
const uint32_t H = ktex->baseHeight;
|
||||
const uint8_t *dataPtr = reinterpret_cast<const uint8_t *>(
|
||||
ktxTexture_GetData(ktxTexture(ktex)));
|
||||
|
||||
// Compute 9 SH coefficients (irradiance) from equirect HDR
|
||||
struct Vec3
|
||||
{
|
||||
float x, y, z;
|
||||
};
|
||||
auto half_to_float = [](uint16_t h)-> float {
|
||||
uint16_t h_exp = (h & 0x7C00u) >> 10;
|
||||
uint16_t h_sig = h & 0x03FFu;
|
||||
uint32_t sign = (h & 0x8000u) << 16;
|
||||
uint32_t f_e, f_sig;
|
||||
if (h_exp == 0)
|
||||
{
|
||||
if (h_sig == 0)
|
||||
{
|
||||
f_e = 0;
|
||||
f_sig = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// subnormals
|
||||
int e = -1;
|
||||
uint16_t sig = h_sig;
|
||||
while ((sig & 0x0400u) == 0)
|
||||
{
|
||||
sig <<= 1;
|
||||
--e;
|
||||
}
|
||||
sig &= 0x03FFu;
|
||||
f_e = uint32_t(127 - 15 + e) << 23;
|
||||
f_sig = uint32_t(sig) << 13;
|
||||
}
|
||||
}
|
||||
else if (h_exp == 0x1Fu)
|
||||
{
|
||||
f_e = 0xFFu << 23;
|
||||
f_sig = uint32_t(h_sig) << 13;
|
||||
}
|
||||
else
|
||||
{
|
||||
f_e = uint32_t(h_exp - 15 + 127) << 23;
|
||||
f_sig = uint32_t(h_sig) << 13;
|
||||
}
|
||||
uint32_t f = sign | f_e | f_sig;
|
||||
float out;
|
||||
std::memcpy(&out, &f, 4);
|
||||
return out;
|
||||
};
|
||||
|
||||
auto sample_at = [&](uint32_t x, uint32_t y)-> Vec3 {
|
||||
if (isFloat32)
|
||||
{
|
||||
const float *px = reinterpret_cast<const float *>(dataPtr) + 4ull * (y * W + x);
|
||||
return {px[0], px[1], px[2]};
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint16_t *px = reinterpret_cast<const uint16_t *>(dataPtr) + 4ull * (y * W + x);
|
||||
return {half_to_float(px[0]), half_to_float(px[1]), half_to_float(px[2])};
|
||||
}
|
||||
};
|
||||
|
||||
constexpr int L = 2; // 2nd order (9 coeffs)
|
||||
const float dtheta = float(M_PI) / float(H);
|
||||
const float dphi = 2.f * float(M_PI) / float(W);
|
||||
// Accumulate RGB SH coeffs
|
||||
std::array<glm::vec3, 9> c{};
|
||||
for (auto &v: c) v = glm::vec3(0);
|
||||
|
||||
auto sh_basis = [](const glm::vec3 &d)-> std::array<float, 9> {
|
||||
const float x = d.x, y = d.y, z = d.z;
|
||||
// Real SH, unnormalized constants
|
||||
const float c0 = 0.2820947918f;
|
||||
const float c1 = 0.4886025119f;
|
||||
const float c2 = 1.0925484306f;
|
||||
const float c3 = 0.3153915653f;
|
||||
const float c4 = 0.5462742153f;
|
||||
return {
|
||||
c0,
|
||||
c1 * y,
|
||||
c1 * z,
|
||||
c1 * x,
|
||||
c2 * x * y,
|
||||
c2 * y * z,
|
||||
c3 * (3.f * z * z - 1.f),
|
||||
c2 * x * z,
|
||||
c4 * (x * x - y * y)
|
||||
};
|
||||
};
|
||||
|
||||
for (uint32_t y = 0; y < H; ++y)
|
||||
{
|
||||
float theta = (y + 0.5f) * dtheta; // [0,pi]
|
||||
float sinT = std::sin(theta);
|
||||
for (uint32_t x = 0; x < W; ++x)
|
||||
{
|
||||
float phi = (x + 0.5f) * dphi; // [0,2pi]
|
||||
glm::vec3 dir = glm::vec3(std::cos(phi) * sinT, std::cos(theta), std::sin(phi) * sinT);
|
||||
auto Lrgb = sample_at(x, y);
|
||||
glm::vec3 Lvec(Lrgb.x, Lrgb.y, Lrgb.z);
|
||||
auto Y = sh_basis(dir);
|
||||
float dOmega = dphi * dtheta * sinT; // solid angle per pixel
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
c[i] += Lvec * (Y[i] * dOmega);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convolve with Lambert kernel via per-band scale
|
||||
const float A0 = float(M_PI);
|
||||
const float A1 = 2.f * float(M_PI) / 3.f;
|
||||
const float A2 = float(M_PI) / 4.f;
|
||||
const float Aband[3] = {A0, A1, A2};
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
int band = (i == 0) ? 0 : (i < 4 ? 1 : 2);
|
||||
c[i] *= Aband[band];
|
||||
}
|
||||
|
||||
_shBuffer = rm->create_buffer(sizeof(glm::vec4) * 9, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
glm::vec4 v(c[i], 0.0f);
|
||||
std::memcpy(reinterpret_cast<char *>(_shBuffer.info.pMappedData) + i * sizeof(glm::vec4),
|
||||
&v, sizeof(glm::vec4));
|
||||
}
|
||||
vmaFlushAllocation(_ctx->getDevice()->allocator(), _shBuffer.allocation, 0,
|
||||
sizeof(glm::vec4) * 9);
|
||||
}
|
||||
ktxTexture_Destroy(ktxTexture(ktex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Diffuse cubemap
|
||||
// Diffuse cubemap (optional; if missing, reuse specular)
|
||||
if (!paths.diffuseCube.empty())
|
||||
{
|
||||
ktxutil::KtxCubemap kcm{};
|
||||
@@ -40,14 +217,17 @@ bool IBLManager::load(const IBLPaths &paths)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (_diff.image == VK_NULL_HANDLE && _spec.image != VK_NULL_HANDLE)
|
||||
{
|
||||
_diff = _spec;
|
||||
}
|
||||
|
||||
// BRDF LUT (optional)
|
||||
// BRDF LUT
|
||||
if (!paths.brdfLut2D.empty())
|
||||
{
|
||||
ktxutil::Ktx2D lut{};
|
||||
if (ktxutil::load_ktx2_2d(paths.brdfLut2D.c_str(), lut))
|
||||
{
|
||||
// Build regions into ResourceManager::MipLevelCopy to reuse compressed 2D helper
|
||||
std::vector<ResourceManager::MipLevelCopy> lv;
|
||||
lv.reserve(lut.mipLevels);
|
||||
for (uint32_t mip = 0; mip < lut.mipLevels; ++mip)
|
||||
@@ -55,7 +235,7 @@ bool IBLManager::load(const IBLPaths &paths)
|
||||
const auto &r = lut.copies[mip];
|
||||
lv.push_back(ResourceManager::MipLevelCopy{
|
||||
.offset = r.bufferOffset,
|
||||
.length = 0, // not needed for copy scheduling
|
||||
.length = 0,
|
||||
.width = r.imageExtent.width,
|
||||
.height = r.imageExtent.height,
|
||||
});
|
||||
@@ -71,9 +251,45 @@ bool IBLManager::load(const IBLPaths &paths)
|
||||
void IBLManager::unload()
|
||||
{
|
||||
if (_ctx == nullptr || _ctx->getResources() == nullptr) return;
|
||||
auto* rm = _ctx->getResources();
|
||||
if (_spec.image) { rm->destroy_image(_spec); _spec = {}; }
|
||||
if (_diff.image) { rm->destroy_image(_diff); _diff = {}; }
|
||||
if (_brdf.image) { rm->destroy_image(_brdf); _brdf = {}; }
|
||||
auto *rm = _ctx->getResources();
|
||||
if (_spec.image)
|
||||
{
|
||||
rm->destroy_image(_spec);
|
||||
_spec = {};
|
||||
}
|
||||
if (_diff.image && _diff.image != _spec.image) { rm->destroy_image(_diff); }
|
||||
_diff = {};
|
||||
if (_brdf.image)
|
||||
{
|
||||
rm->destroy_image(_brdf);
|
||||
_brdf = {};
|
||||
}
|
||||
if (_iblSetLayout && _ctx && _ctx->getDevice())
|
||||
{
|
||||
vkDestroyDescriptorSetLayout(_ctx->getDevice()->device(), _iblSetLayout, nullptr);
|
||||
_iblSetLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
if (_shBuffer.buffer)
|
||||
{
|
||||
rm->destroy_buffer(_shBuffer);
|
||||
_shBuffer = {};
|
||||
}
|
||||
}
|
||||
|
||||
bool IBLManager::ensureLayout()
|
||||
{
|
||||
if (_iblSetLayout != VK_NULL_HANDLE) return true;
|
||||
if (!_ctx || !_ctx->getDevice()) return false;
|
||||
|
||||
DescriptorLayoutBuilder builder;
|
||||
// binding 0: environment/specular as 2D equirect with mips
|
||||
builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
// binding 1: BRDF LUT 2D
|
||||
builder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
// binding 2: SH coefficients UBO (vec4[9])
|
||||
builder.add_binding(2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
_iblSetLayout = builder.build(
|
||||
_ctx->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
|
||||
return _iblSetLayout != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
@@ -8,18 +8,17 @@ class EngineContext;
|
||||
struct IBLPaths
|
||||
{
|
||||
std::string specularCube; // .ktx2 (GPU-ready BC6H or R16G16B16A16)
|
||||
std::string diffuseCube; // .ktx2
|
||||
std::string brdfLut2D; // .ktx2 (BC5 RG UNORM or similar)
|
||||
std::string diffuseCube; // .ktx2
|
||||
std::string brdfLut2D; // .ktx2 (BC5 RG UNORM or similar)
|
||||
};
|
||||
|
||||
// Minimal IBL asset owner with optional residency control.
|
||||
class IBLManager
|
||||
{
|
||||
public:
|
||||
void init(EngineContext* ctx) { _ctx = ctx; }
|
||||
void init(EngineContext *ctx) { _ctx = ctx; }
|
||||
|
||||
// Load all three textures. Returns true when specular+diffuse (and optional LUT) are resident.
|
||||
bool load(const IBLPaths& paths);
|
||||
bool load(const IBLPaths &paths);
|
||||
|
||||
// Release GPU memory and patch to fallbacks handled by the caller.
|
||||
void unload();
|
||||
@@ -27,13 +26,22 @@ public:
|
||||
bool resident() const { return _spec.image != VK_NULL_HANDLE || _diff.image != VK_NULL_HANDLE; }
|
||||
|
||||
AllocatedImage specular() const { return _spec; }
|
||||
AllocatedImage diffuse() const { return _diff; }
|
||||
AllocatedImage brdf() const { return _brdf; }
|
||||
AllocatedImage diffuse() const { return _diff; }
|
||||
AllocatedImage brdf() const { return _brdf; }
|
||||
AllocatedBuffer shBuffer() const { return _shBuffer; }
|
||||
bool hasSH() const { return _shBuffer.buffer != VK_NULL_HANDLE; }
|
||||
|
||||
// Descriptor set layout used by shaders (set=3)
|
||||
VkDescriptorSetLayout descriptorLayout() const { return _iblSetLayout; }
|
||||
|
||||
// Build descriptor set layout without loading images (for early pipeline creation)
|
||||
bool ensureLayout();
|
||||
|
||||
private:
|
||||
EngineContext* _ctx{nullptr};
|
||||
EngineContext *_ctx{nullptr};
|
||||
AllocatedImage _spec{};
|
||||
AllocatedImage _diff{};
|
||||
AllocatedImage _brdf{};
|
||||
VkDescriptorSetLayout _iblSetLayout = VK_NULL_HANDLE;
|
||||
AllocatedBuffer _shBuffer{}; // 9*vec4 coefficients (RGB in .xyz)
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ void DescriptorManager::init(DeviceManager *deviceManager)
|
||||
_deviceManager->device(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DescriptorManager::cleanup()
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "core/vk_pipeline_manager.h"
|
||||
#include "core/config.h"
|
||||
#include "core/texture_cache.h"
|
||||
#include "core/ibl_manager.h"
|
||||
|
||||
// Query a conservative streaming texture budget based on VMA-reported
|
||||
// device-local heap budgets. Uses ~35% of total device-local budget.
|
||||
@@ -116,6 +117,85 @@ namespace {
|
||||
ImGui::SliderFloat("Render Scale", &eng->renderScale, 0.3f, 1.f);
|
||||
}
|
||||
|
||||
// IBL test grid spawner (spheres varying metallic/roughness)
|
||||
static void spawn_ibl_test(VulkanEngine *eng)
|
||||
{
|
||||
if (!eng || !eng->_assetManager || !eng->_sceneManager) return;
|
||||
using MC = GLTFMetallic_Roughness::MaterialConstants;
|
||||
|
||||
std::vector<Vertex> verts; std::vector<uint32_t> inds;
|
||||
primitives::buildSphere(verts, inds, 24, 24);
|
||||
|
||||
const float mVals[5] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
|
||||
const float rVals[5] = {0.04f, 0.25f, 0.5f, 0.75f, 1.0f};
|
||||
const float spacing = 1.6f;
|
||||
const glm::vec3 origin(-spacing*2.0f, 0.0f, -spacing*2.0f);
|
||||
|
||||
for (int iy=0; iy<5; ++iy)
|
||||
{
|
||||
for (int ix=0; ix<5; ++ix)
|
||||
{
|
||||
MC c{};
|
||||
c.colorFactors = glm::vec4(0.82f, 0.82f, 0.82f, 1.0f);
|
||||
c.metal_rough_factors = glm::vec4(mVals[ix], rVals[iy], 0.0f, 0.0f);
|
||||
const std::string base = fmt::format("ibltest.m{}_r{}", ix, iy);
|
||||
auto mat = eng->_assetManager->createMaterialFromConstants(base+".mat", c, MaterialPass::MainColor);
|
||||
|
||||
auto mesh = eng->_assetManager->createMesh(base+".mesh", std::span<Vertex>(verts.data(), verts.size()),
|
||||
std::span<uint32_t>(inds.data(), inds.size()), mat);
|
||||
|
||||
const glm::vec3 pos = origin + glm::vec3(ix*spacing, 0.5f, iy*spacing);
|
||||
glm::mat4 M = glm::translate(glm::mat4(1.0f), pos);
|
||||
eng->_sceneManager->addMeshInstance(base+".inst", mesh, M);
|
||||
eng->_iblTestNames.push_back(base+".inst");
|
||||
eng->_iblTestNames.push_back(base+".mesh");
|
||||
eng->_iblTestNames.push_back(base+".mat");
|
||||
}
|
||||
}
|
||||
|
||||
// Chrome and glass extras
|
||||
{
|
||||
MC chrome{}; chrome.colorFactors = glm::vec4(0.9f,0.9f,0.9f,1.0f); chrome.metal_rough_factors = glm::vec4(1.0f, 0.06f,0,0);
|
||||
auto mat = eng->_assetManager->createMaterialFromConstants("ibltest.chrome.mat", chrome, MaterialPass::MainColor);
|
||||
auto mesh = eng->_assetManager->createMesh("ibltest.chrome.mesh", std::span<Vertex>(verts.data(), verts.size()),
|
||||
std::span<uint32_t>(inds.data(), inds.size()), mat);
|
||||
glm::mat4 M = glm::translate(glm::mat4(1.0f), origin + glm::vec3(5.5f, 0.5f, 0.0f));
|
||||
eng->_sceneManager->addMeshInstance("ibltest.chrome.inst", mesh, M);
|
||||
eng->_iblTestNames.insert(eng->_iblTestNames.end(), {"ibltest.chrome.inst","ibltest.chrome.mesh","ibltest.chrome.mat"});
|
||||
}
|
||||
{
|
||||
MC glass{}; glass.colorFactors = glm::vec4(0.9f,0.95f,1.0f,0.25f); glass.metal_rough_factors = glm::vec4(0.0f, 0.02f,0,0);
|
||||
auto mat = eng->_assetManager->createMaterialFromConstants("ibltest.glass.mat", glass, MaterialPass::Transparent);
|
||||
auto mesh = eng->_assetManager->createMesh("ibltest.glass.mesh", std::span<Vertex>(verts.data(), verts.size()),
|
||||
std::span<uint32_t>(inds.data(), inds.size()), mat);
|
||||
glm::mat4 M = glm::translate(glm::mat4(1.0f), origin + glm::vec3(5.5f, 0.5f, 2.0f));
|
||||
eng->_sceneManager->addMeshInstance("ibltest.glass.inst", mesh, M);
|
||||
eng->_iblTestNames.insert(eng->_iblTestNames.end(), {"ibltest.glass.inst","ibltest.glass.mesh","ibltest.glass.mat"});
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_ibl_test(VulkanEngine *eng)
|
||||
{
|
||||
if (!eng || !eng->_sceneManager || !eng->_assetManager) return;
|
||||
for (size_t i=0;i<eng->_iblTestNames.size(); ++i)
|
||||
{
|
||||
const std::string &n = eng->_iblTestNames[i];
|
||||
// Remove instances and meshes by prefix
|
||||
if (n.ends_with(".inst")) eng->_sceneManager->removeMeshInstance(n);
|
||||
else if (n.ends_with(".mesh")) eng->_assetManager->removeMesh(n);
|
||||
}
|
||||
eng->_iblTestNames.clear();
|
||||
}
|
||||
|
||||
static void ui_ibl(VulkanEngine *eng)
|
||||
{
|
||||
if (!eng) return;
|
||||
if (ImGui::Button("Spawn IBL Test Grid")) { spawn_ibl_test(eng); }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear IBL Test")) { clear_ibl_test(eng); }
|
||||
ImGui::TextUnformatted("5x5 spheres: metallic across columns, roughness across rows.\nExtra: chrome + glass.");
|
||||
}
|
||||
|
||||
// Quick stats & targets overview
|
||||
static void ui_overview(VulkanEngine *eng)
|
||||
{
|
||||
@@ -600,6 +680,21 @@ void VulkanEngine::init()
|
||||
_renderGraph->init(_context.get());
|
||||
_context->renderGraph = _renderGraph.get();
|
||||
|
||||
// Create IBL manager early so set=3 layout exists before pipelines are built
|
||||
_iblManager = std::make_unique<IBLManager>();
|
||||
_iblManager->init(_context.get());
|
||||
// Publish to context for passes and pipeline layout assembly
|
||||
_context->ibl = _iblManager.get();
|
||||
|
||||
// Try to load default IBL assets if present
|
||||
{
|
||||
IBLPaths ibl{};
|
||||
// ibl.specularCube = _assetManager->assetPath("ibl/docklands.ktx2");
|
||||
// ibl.diffuseCube = _assetManager->assetPath("ibl/docklands.ktx2"); // temporary: reuse if separate diffuse not provided
|
||||
ibl.brdfLut2D = _assetManager->assetPath("ibl/brdf_lut.ktx2");
|
||||
_iblManager->load(ibl);
|
||||
}
|
||||
|
||||
init_frame_resources();
|
||||
|
||||
// Build material pipelines early so materials can be created
|
||||
@@ -1062,6 +1157,11 @@ void VulkanEngine::run()
|
||||
ui_pipelines(this);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("IBL"))
|
||||
{
|
||||
ui_ibl(this);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("PostFX"))
|
||||
{
|
||||
ui_postfx(this);
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "render/rg_graph.h"
|
||||
#include "core/vk_raytracing.h"
|
||||
#include "core/texture_cache.h"
|
||||
#include "core/ibl_manager.h"
|
||||
|
||||
// Number of frames-in-flight. Affects per-frame command buffers, fences,
|
||||
// semaphores, and transient descriptor pools in FrameResources.
|
||||
@@ -69,6 +70,7 @@ public:
|
||||
std::unique_ptr<RenderGraph> _renderGraph;
|
||||
std::unique_ptr<RayTracingManager> _rayManager;
|
||||
std::unique_ptr<TextureCache> _textureCache;
|
||||
std::unique_ptr<IBLManager> _iblManager;
|
||||
|
||||
struct SDL_Window *_window{nullptr};
|
||||
|
||||
@@ -109,6 +111,9 @@ public:
|
||||
|
||||
std::vector<RenderPass> renderPasses;
|
||||
|
||||
// Debug helpers: track spawned IBL test meshes to remove them easily
|
||||
std::vector<std::string> _iblTestNames;
|
||||
|
||||
// Debug: persistent pass enable overrides (by pass name)
|
||||
std::unordered_map<std::string, bool> _rgPassToggles;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user