ADD: Lighting modifier and IBL probe

This commit is contained in:
2025-11-27 21:49:25 +09:00
parent 2182b02f4a
commit 67ab04f798
11 changed files with 401 additions and 45 deletions

View File

@@ -1,7 +0,0 @@
[requires]
taskflow/3.10.0
[generators]
CMakeDeps
CMakeToolchain
[layout]
cmake_layout

View File

@@ -16,6 +16,8 @@ void main()
vec3 worldDir = normalize((inverse(sceneData.view) * vec4(viewDir, 0.0)).xyz); vec3 worldDir = normalize((inverse(sceneData.view) * vec4(viewDir, 0.0)).xyz);
vec2 uv = dir_to_equirect(worldDir); vec2 uv = dir_to_equirect(worldDir);
vec3 col = textureLod(iblSpec2D, uv, 0.0).rgb; // Sample a dedicated background environment map when available.
// The engine binds iblBackground2D to a texture that may differ from the IBL specular map.
vec3 col = textureLod(iblBackground2D, uv, 0.0).rgb;
outColor = vec4(col, 1.0); outColor = vec4(col, 1.0);
} }

View File

@@ -1,10 +1,11 @@
#ifndef IBL_COMMON_GLSL #ifndef IBL_COMMON_GLSL
#define IBL_COMMON_GLSL #define IBL_COMMON_GLSL
// IBL bindings (set=3): specular equirect 2D, BRDF LUT, SH UBO. // IBL bindings (set=3): specular equirect 2D, BRDF LUT, SH UBO, optional background map.
layout(set=3, binding=0) uniform sampler2D iblSpec2D; layout(set=3, binding=0) uniform sampler2D iblSpec2D;
layout(set=3, binding=1) uniform sampler2D iblBRDF; layout(set=3, binding=1) uniform sampler2D iblBRDF;
layout(std140, set=3, binding=2) uniform IBL_SH { vec4 sh[9]; } iblSH; layout(std140, set=3, binding=2) uniform IBL_SH { vec4 sh[9]; } iblSH;
layout(set=3, binding=3) uniform sampler2D iblBackground2D;
// Evaluate diffuse irradiance from 2nd-order SH coefficients (9 coeffs). // Evaluate diffuse irradiance from 2nd-order SH coefficients (9 coeffs).
// Coefficients are pre-convolved with the Lambert kernel on the CPU. // Coefficients are pre-convolved with the Lambert kernel on the CPU.

View File

@@ -10,14 +10,29 @@
#include <SDL_stdinc.h> #include <SDL_stdinc.h>
#include "core/device/device.h" #include "core/device/device.h"
#include "core/assets/texture_cache.h"
bool IBLManager::load(const IBLPaths &paths) bool IBLManager::load(const IBLPaths &paths)
{ {
if (_ctx == nullptr || _ctx->getResources() == nullptr) return false; if (_ctx == nullptr || _ctx->getResources() == nullptr) return false;
ensureLayout();
ResourceManager *rm = _ctx->getResources(); ResourceManager *rm = _ctx->getResources();
// Load specular environment: prefer cubemap; fallback to 2D equirect with mips // When uploads are deferred into the RenderGraph, any previously queued
// image uploads might still reference VkImage handles owned by this
// manager. Before destroying or recreating IBL images, flush those
// uploads via the immediate path so we never record barriers or copies
// for images that have been destroyed.
if (rm->deferred_uploads() && rm->has_pending_uploads())
{
rm->process_queued_uploads_immediate();
}
// Allow reloading at runtime: destroy previous images/SH but keep layout.
destroy_images_and_sh();
ensureLayout();
// Load specular environment: prefer cubemap; fallback to 2D equirect with mips.
// Also hint the TextureCache (if present) so future switches are cheap.
if (!paths.specularCube.empty()) if (!paths.specularCube.empty())
{ {
// Try as cubemap first // Try as cubemap first
@@ -222,6 +237,12 @@ bool IBLManager::load(const IBLPaths &paths)
_diff = _spec; _diff = _spec;
} }
// If background is still missing but specular is valid, reuse the specular environment.
if (_background.image == VK_NULL_HANDLE && _spec.image != VK_NULL_HANDLE)
{
_background = _spec;
}
// BRDF LUT // BRDF LUT
if (!paths.brdfLut2D.empty()) if (!paths.brdfLut2D.empty())
{ {
@@ -251,34 +272,16 @@ bool IBLManager::load(const IBLPaths &paths)
void IBLManager::unload() void IBLManager::unload()
{ {
if (_ctx == nullptr || _ctx->getResources() == nullptr) return; if (_ctx == nullptr || _ctx->getResources() == nullptr) return;
auto *rm = _ctx->getResources();
if (_spec.image)
{
rm->destroy_image(_spec);
}
// Handle potential aliasing: _diff may have been set to _spec in load().
if (_diff.image && _diff.image != _spec.image)
{
rm->destroy_image(_diff);
}
if (_brdf.image)
{
rm->destroy_image(_brdf);
}
_spec = {}; // Destroy images and SH buffer first.
_diff = {}; destroy_images_and_sh();
_brdf = {};
// Then release descriptor layout.
if (_iblSetLayout && _ctx && _ctx->getDevice()) if (_iblSetLayout && _ctx && _ctx->getDevice())
{ {
vkDestroyDescriptorSetLayout(_ctx->getDevice()->device(), _iblSetLayout, nullptr); vkDestroyDescriptorSetLayout(_ctx->getDevice()->device(), _iblSetLayout, nullptr);
_iblSetLayout = VK_NULL_HANDLE; _iblSetLayout = VK_NULL_HANDLE;
} }
if (_shBuffer.buffer)
{
rm->destroy_buffer(_shBuffer);
_shBuffer = {};
}
} }
bool IBLManager::ensureLayout() bool IBLManager::ensureLayout()
@@ -293,8 +296,48 @@ bool IBLManager::ensureLayout()
builder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); builder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
// binding 2: SH coefficients UBO (vec4[9]) // binding 2: SH coefficients UBO (vec4[9])
builder.add_binding(2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); builder.add_binding(2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
// binding 3: optional background environment texture (2D equirect)
builder.add_binding(3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
_iblSetLayout = builder.build( _iblSetLayout = builder.build(
_ctx->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT, _ctx->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT,
nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT); nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
return _iblSetLayout != VK_NULL_HANDLE; return _iblSetLayout != VK_NULL_HANDLE;
} }
void IBLManager::destroy_images_and_sh()
{
if (_ctx == nullptr || _ctx->getResources() == nullptr) return;
auto *rm = _ctx->getResources();
if (_spec.image)
{
rm->destroy_image(_spec);
}
// Handle potential aliasing: _diff may have been set to _spec in load().
if (_diff.image && _diff.image != _spec.image)
{
rm->destroy_image(_diff);
}
// _background may alias _spec or _diff; only destroy when unique.
if (_background.image &&
_background.image != _spec.image &&
_background.image != _diff.image)
{
rm->destroy_image(_background);
}
if (_brdf.image)
{
rm->destroy_image(_brdf);
}
if (_shBuffer.buffer)
{
rm->destroy_buffer(_shBuffer);
_shBuffer = {};
}
_spec = {};
_diff = {};
_background = {};
_brdf = {};
}

View File

@@ -3,6 +3,8 @@
#include <core/types.h> #include <core/types.h>
#include <string> #include <string>
class TextureCache;
class EngineContext; class EngineContext;
struct IBLPaths struct IBLPaths
@@ -10,6 +12,9 @@ struct IBLPaths
std::string specularCube; // .ktx2 (GPU-ready BC6H or R16G16B16A16) std::string specularCube; // .ktx2 (GPU-ready BC6H or R16G16B16A16)
std::string diffuseCube; // .ktx2 std::string diffuseCube; // .ktx2
std::string brdfLut2D; // .ktx2 (BC5 RG UNORM or similar) std::string brdfLut2D; // .ktx2 (BC5 RG UNORM or similar)
// Optional separate background environment map (2D equirect .ktx2).
// When empty, the IBL system falls back to using specularCube for the background.
std::string background2D;
}; };
class IBLManager class IBLManager
@@ -17,6 +22,8 @@ class IBLManager
public: public:
void init(EngineContext *ctx) { _ctx = ctx; } void init(EngineContext *ctx) { _ctx = ctx; }
void set_texture_cache(TextureCache *cache) { _cache = cache; }
// Load all three textures. Returns true when specular+diffuse (and optional LUT) are resident. // Load all three textures. Returns true when specular+diffuse (and optional LUT) are resident.
bool load(const IBLPaths &paths); bool load(const IBLPaths &paths);
@@ -28,6 +35,9 @@ public:
AllocatedImage specular() const { return _spec; } AllocatedImage specular() const { return _spec; }
AllocatedImage diffuse() const { return _diff; } AllocatedImage diffuse() const { return _diff; }
AllocatedImage brdf() const { return _brdf; } AllocatedImage brdf() const { return _brdf; }
// Background environment texture used by the background pass.
// May alias specular() when a dedicated background is not provided.
AllocatedImage background() const { return _background; }
AllocatedBuffer shBuffer() const { return _shBuffer; } AllocatedBuffer shBuffer() const { return _shBuffer; }
bool hasSH() const { return _shBuffer.buffer != VK_NULL_HANDLE; } bool hasSH() const { return _shBuffer.buffer != VK_NULL_HANDLE; }
@@ -39,9 +49,14 @@ public:
private: private:
EngineContext *_ctx{nullptr}; EngineContext *_ctx{nullptr};
TextureCache *_cache{nullptr};
AllocatedImage _spec{}; AllocatedImage _spec{};
AllocatedImage _diff{}; AllocatedImage _diff{};
AllocatedImage _brdf{}; AllocatedImage _brdf{};
AllocatedImage _background{};
VkDescriptorSetLayout _iblSetLayout = VK_NULL_HANDLE; VkDescriptorSetLayout _iblSetLayout = VK_NULL_HANDLE;
AllocatedBuffer _shBuffer{}; // 9*vec4 coefficients (RGB in .xyz) AllocatedBuffer _shBuffer{}; // 9*vec4 coefficients (RGB in .xyz)
// Destroy current GPU images/SH buffer but keep descriptor layout alive.
void destroy_images_and_sh();
}; };

View File

@@ -28,6 +28,7 @@
#include <array> #include <array>
#include <iostream> #include <iostream>
#include <cmath>
#include <glm/gtx/transform.hpp> #include <glm/gtx/transform.hpp>
#include "config.h" #include "config.h"
@@ -229,6 +230,10 @@ void VulkanEngine::init()
// Create IBL manager early so set=3 layout exists before pipelines are built // Create IBL manager early so set=3 layout exists before pipelines are built
_iblManager = std::make_unique<IBLManager>(); _iblManager = std::make_unique<IBLManager>();
_iblManager->init(_context.get()); _iblManager->init(_context.get());
if (_textureCache)
{
_iblManager->set_texture_cache(_textureCache.get());
}
// Publish to context for passes and pipeline layout assembly // Publish to context for passes and pipeline layout assembly
_context->ibl = _iblManager.get(); _context->ibl = _iblManager.get();
@@ -238,6 +243,13 @@ void VulkanEngine::init()
ibl.specularCube = _assetManager->assetPath("ibl/docklands.ktx2"); ibl.specularCube = _assetManager->assetPath("ibl/docklands.ktx2");
ibl.diffuseCube = _assetManager->assetPath("ibl/docklands.ktx2"); // temporary: reuse if separate diffuse not provided ibl.diffuseCube = _assetManager->assetPath("ibl/docklands.ktx2"); // temporary: reuse if separate diffuse not provided
ibl.brdfLut2D = _assetManager->assetPath("ibl/brdf_lut.ktx2"); ibl.brdfLut2D = _assetManager->assetPath("ibl/brdf_lut.ktx2");
// By default, use the same texture for lighting and background; users can point background2D
// at a different .ktx2 to decouple them.
ibl.background2D = ibl.specularCube;
// Treat this as the global/fallback IBL used outside any local volume.
_globalIBLPaths = ibl;
_hasGlobalIBL = true;
_activeIBLVolume = -1;
_iblManager->load(ibl); _iblManager->load(ibl);
} }
@@ -469,6 +481,44 @@ void VulkanEngine::draw()
{ {
_sceneManager->update_scene(); _sceneManager->update_scene();
// Update IBL based on camera position and user-defined reflection volumes.
if (_iblManager && _sceneManager)
{
glm::vec3 camPos = _sceneManager->getMainCamera().position;
int newVolume = -1;
for (size_t i = 0; i < _iblVolumes.size(); ++i)
{
const IBLVolume &v = _iblVolumes[i];
if (!v.enabled) continue;
glm::vec3 local = camPos - v.center;
if (std::abs(local.x) <= v.halfExtents.x &&
std::abs(local.y) <= v.halfExtents.y &&
std::abs(local.z) <= v.halfExtents.z)
{
newVolume = static_cast<int>(i);
break;
}
}
if (newVolume != _activeIBLVolume)
{
const IBLPaths *paths = nullptr;
if (newVolume >= 0)
{
paths = &_iblVolumes[newVolume].paths;
}
else if (_hasGlobalIBL)
{
paths = &_globalIBLPaths;
}
if (paths)
{
_iblManager->load(*paths);
}
_activeIBLVolume = newVolume;
}
}
// Per-frame hover raycast based on last mouse position. // Per-frame hover raycast based on last mouse position.
if (_sceneManager && _mousePosPixels.x >= 0.0f && _mousePosPixels.y >= 0.0f) if (_sceneManager && _mousePosPixels.x >= 0.0f && _mousePosPixels.y >= 0.0f)
{ {
@@ -558,6 +608,8 @@ void VulkanEngine::draw()
RGImageHandle hGBufferNormal = _renderGraph->import_gbuffer_normal(); RGImageHandle hGBufferNormal = _renderGraph->import_gbuffer_normal();
RGImageHandle hGBufferAlbedo = _renderGraph->import_gbuffer_albedo(); RGImageHandle hGBufferAlbedo = _renderGraph->import_gbuffer_albedo();
RGImageHandle hSwapchain = _renderGraph->import_swapchain_image(swapchainImageIndex); RGImageHandle hSwapchain = _renderGraph->import_swapchain_image(swapchainImageIndex);
// For debug overlays (IBL volumes), re-use HDR draw image as a color target.
RGImageHandle hDebugColor = hDraw;
// Create transient depth targets for cascaded shadow maps (even if RT-only, to keep descriptors stable) // Create transient depth targets for cascaded shadow maps (even if RT-only, to keep descriptors stable)
const VkExtent2D shadowExtent{2048, 2048}; const VkExtent2D shadowExtent{2048, 2048};

View File

@@ -116,6 +116,21 @@ public:
// Debug helpers: track spawned IBL test meshes to remove them easily // Debug helpers: track spawned IBL test meshes to remove them easily
std::vector<std::string> _iblTestNames; std::vector<std::string> _iblTestNames;
// Simple world-space IBL reflection volumes (axis-aligned boxes).
struct IBLVolume
{
glm::vec3 center{0.0f, 0.0f, 0.0f};
glm::vec3 halfExtents{10.0f, 10.0f, 10.0f};
IBLPaths paths{}; // HDRI paths for this volume
bool enabled{true};
};
// Global/default IBL used when no volume contains the camera.
IBLPaths _globalIBLPaths{};
bool _hasGlobalIBL{false};
// User-defined local IBL volumes and currently active index (-1 = global).
std::vector<IBLVolume> _iblVolumes;
int _activeIBLVolume{-1};
struct PickInfo struct PickInfo
{ {
MeshAsset *mesh = nullptr; MeshAsset *mesh = nullptr;

View File

@@ -21,6 +21,7 @@
#include "core/assets/ibl_manager.h" #include "core/assets/ibl_manager.h"
#include "context.h" #include "context.h"
#include <core/types.h> #include <core/types.h>
#include <cstring>
#include "mesh_bvh.h" #include "mesh_bvh.h"
@@ -138,11 +139,102 @@ namespace
static void ui_ibl(VulkanEngine *eng) static void ui_ibl(VulkanEngine *eng)
{ {
if (!eng) return; if (!eng) return;
if (ImGui::Button("Spawn IBL Test Grid")) { spawn_ibl_test(eng); } if (ImGui::Button("Spawn IBL Test Grid")) { spawn_ibl_test(eng); }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Clear IBL Test")) { clear_ibl_test(eng); } if (ImGui::Button("Clear IBL Test")) { clear_ibl_test(eng); }
ImGui::TextUnformatted( ImGui::TextUnformatted(
"5x5 spheres: metallic across columns, roughness across rows.\nExtra: chrome + glass."); "5x5 spheres: metallic across columns, roughness across rows.\nExtra: chrome + glass.");
ImGui::Separator();
ImGui::TextUnformatted("IBL Volumes (reflection probes)");
if (!eng->_iblManager)
{
ImGui::TextUnformatted("IBLManager not available");
return;
}
if (eng->_activeIBLVolume < 0)
{
ImGui::TextUnformatted("Active IBL: Global");
}
else
{
ImGui::Text("Active IBL: Volume %d", eng->_activeIBLVolume);
}
if (ImGui::Button("Add IBL Volume"))
{
VulkanEngine::IBLVolume vol{};
if (eng->_sceneManager)
{
vol.center = eng->_sceneManager->getMainCamera().position;
}
vol.halfExtents = glm::vec3(10.0f, 10.0f, 10.0f);
vol.paths = eng->_globalIBLPaths;
eng->_iblVolumes.push_back(vol);
}
for (size_t i = 0; i < eng->_iblVolumes.size(); ++i)
{
auto &vol = eng->_iblVolumes[i];
ImGui::PushID(static_cast<int>(i));
ImGui::Separator();
ImGui::Text("Volume %zu", i);
ImGui::Checkbox("Enabled", &vol.enabled);
ImGui::InputFloat3("Center", &vol.center.x);
ImGui::InputFloat3("Half Extents", &vol.halfExtents.x);
// Simple path editors; store absolute or engine-local paths.
char specBuf[256]{};
char diffBuf[256]{};
char bgBuf[256]{};
char brdfBuf[256]{};
std::strncpy(specBuf, vol.paths.specularCube.c_str(), sizeof(specBuf) - 1);
std::strncpy(diffBuf, vol.paths.diffuseCube.c_str(), sizeof(diffBuf) - 1);
std::strncpy(bgBuf, vol.paths.background2D.c_str(), sizeof(bgBuf) - 1);
std::strncpy(brdfBuf, vol.paths.brdfLut2D.c_str(), sizeof(brdfBuf) - 1);
if (ImGui::InputText("Specular path", specBuf, IM_ARRAYSIZE(specBuf)))
{
vol.paths.specularCube = specBuf;
}
if (ImGui::InputText("Diffuse path", diffBuf, IM_ARRAYSIZE(diffBuf)))
{
vol.paths.diffuseCube = diffBuf;
}
if (ImGui::InputText("Background path", bgBuf, IM_ARRAYSIZE(bgBuf)))
{
vol.paths.background2D = bgBuf;
}
if (ImGui::InputText("BRDF LUT path", brdfBuf, IM_ARRAYSIZE(brdfBuf)))
{
vol.paths.brdfLut2D = brdfBuf;
}
if (ImGui::Button("Reload This Volume IBL"))
{
if (eng->_iblManager && vol.enabled)
{
eng->_iblManager->load(vol.paths);
eng->_activeIBLVolume = static_cast<int>(i);
}
}
ImGui::SameLine();
if (ImGui::Button("Set As Global IBL"))
{
eng->_globalIBLPaths = vol.paths;
eng->_hasGlobalIBL = true;
eng->_activeIBLVolume = -1;
if (eng->_iblManager)
{
eng->_iblManager->load(eng->_globalIBLPaths);
}
}
ImGui::PopID();
}
} }
// Quick stats & targets overview // Quick stats & targets overview
@@ -600,6 +692,99 @@ namespace
} }
} }
// Point light editor
if (eng->_sceneManager)
{
ImGui::Separator();
ImGui::TextUnformatted("Point lights");
SceneManager *sceneMgr = eng->_sceneManager.get();
const auto &lights = sceneMgr->getPointLights();
ImGui::Text("Active lights: %zu", lights.size());
static int selectedLight = -1;
if (selectedLight >= static_cast<int>(lights.size()))
{
selectedLight = static_cast<int>(lights.size()) - 1;
}
if (ImGui::BeginListBox("Light list"))
{
for (size_t i = 0; i < lights.size(); ++i)
{
std::string label = fmt::format("Light {}", i);
const bool isSelected = (selectedLight == static_cast<int>(i));
if (ImGui::Selectable(label.c_str(), isSelected))
{
selectedLight = static_cast<int>(i);
}
}
ImGui::EndListBox();
}
// Controls for the selected light
if (selectedLight >= 0 && selectedLight < static_cast<int>(lights.size()))
{
SceneManager::PointLight pl{};
if (sceneMgr->getPointLight(static_cast<size_t>(selectedLight), pl))
{
float pos[3] = {pl.position.x, pl.position.y, pl.position.z};
float col[3] = {pl.color.r, pl.color.g, pl.color.b};
bool changed = false;
changed |= ImGui::InputFloat3("Position", pos);
changed |= ImGui::SliderFloat("Radius", &pl.radius, 0.1f, 1000.0f);
changed |= ImGui::ColorEdit3("Color", col);
changed |= ImGui::SliderFloat("Intensity", &pl.intensity, 0.0f, 100.0f);
if (changed)
{
pl.position = glm::vec3(pos[0], pos[1], pos[2]);
pl.color = glm::vec3(col[0], col[1], col[2]);
sceneMgr->setPointLight(static_cast<size_t>(selectedLight), pl);
}
if (ImGui::Button("Remove selected light"))
{
sceneMgr->removePointLight(static_cast<size_t>(selectedLight));
selectedLight = -1;
}
}
}
// Controls for adding a new light
ImGui::Separator();
ImGui::TextUnformatted("Add point light");
static float newPos[3] = {0.0f, 1.0f, 0.0f};
static float newRadius = 10.0f;
static float newColor[3] = {1.0f, 1.0f, 1.0f};
static float newIntensity = 5.0f;
ImGui::InputFloat3("New position", newPos);
ImGui::SliderFloat("New radius", &newRadius, 0.1f, 1000.0f);
ImGui::ColorEdit3("New color", newColor);
ImGui::SliderFloat("New intensity", &newIntensity, 0.0f, 100.0f);
if (ImGui::Button("Add point light"))
{
SceneManager::PointLight pl{};
pl.position = glm::vec3(newPos[0], newPos[1], newPos[2]);
pl.radius = newRadius;
pl.color = glm::vec3(newColor[0], newColor[1], newColor[2]);
pl.intensity = newIntensity;
const size_t oldCount = sceneMgr->getPointLightCount();
sceneMgr->addPointLight(pl);
selectedLight = static_cast<int>(oldCount);
}
if (ImGui::Button("Clear all lights"))
{
sceneMgr->clearPointLights();
selectedLight = -1;
}
}
ImGui::Separator(); ImGui::Separator();
// Delete selected model/primitive (uses last pick if valid, otherwise hover) // Delete selected model/primitive (uses last pick if valid, otherwise hover)
static std::string deleteStatus; static std::string deleteStatus;

View File

@@ -151,21 +151,37 @@ void BackgroundPass::register_graph(RenderGraph *graph, RGImageHandle drawHandle
DescriptorWriter w0; w0.write_buffer(0, ubo.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); DescriptorWriter w0; w0.write_buffer(0, ubo.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
w0.update_set(ctx->getDevice()->device(), global); w0.update_set(ctx->getDevice()->device(), global);
// IBL set // IBL/background set (set = 3)
VkImageView specView = _fallbackIblCube.imageView; VkDescriptorSet ibl = VK_NULL_HANDLE;
if (ctx->ibl && ctx->ibl->specular().imageView) specView = ctx->ibl->specular().imageView; if (ctx->ibl)
VkDescriptorSetLayout iblLayout = (ctx->ibl ? ctx->ibl->descriptorLayout() : _emptySetLayout); {
VkDescriptorSet ibl = ctx->currentFrame->_frameDescriptors.allocate( VkImageView envView = _fallbackIblCube.imageView;
// Prefer a dedicated background texture when available, otherwise reuse specular.
if (ctx->ibl->background().imageView)
{
envView = ctx->ibl->background().imageView;
}
else if (ctx->ibl->specular().imageView)
{
envView = ctx->ibl->specular().imageView;
}
VkDescriptorSetLayout iblLayout = ctx->ibl->descriptorLayout();
ibl = ctx->currentFrame->_frameDescriptors.allocate(
ctx->getDevice()->device(), iblLayout); ctx->getDevice()->device(), iblLayout);
DescriptorWriter w3; DescriptorWriter w3;
// Bind only specular at binding 0; other bindings are unused in this shader // Bind background map at binding 3; other bindings are unused in this shader.
w3.write_image(0, specView, ctx->getSamplers()->defaultLinear(), w3.write_image(3, envView, ctx->getSamplers()->defaultLinear(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
w3.update_set(ctx->getDevice()->device(), ibl); w3.update_set(ctx->getDevice()->device(), ibl);
}
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipeline); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipeline);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipelineLayout, 0, 1, &global, 0, nullptr); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipelineLayout, 0, 1, &global, 0, nullptr);
if (ibl != VK_NULL_HANDLE)
{
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipelineLayout, 3, 1, &ibl, 0, nullptr); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipelineLayout, 3, 1, &ibl, 0, nullptr);
}
VkExtent2D extent = ctx->getDrawExtent(); VkExtent2D extent = ctx->getDrawExtent();
VkViewport vp{0.f, 0.f, float(extent.width), float(extent.height), 0.f, 1.f}; VkViewport vp{0.f, 0.f, float(extent.width), float(extent.height), 0.f, 1.f};

View File

@@ -37,6 +37,36 @@ void SceneManager::clearPointLights()
pointLights.clear(); pointLights.clear();
} }
bool SceneManager::getPointLight(size_t index, PointLight &outLight) const
{
if (index >= pointLights.size())
{
return false;
}
outLight = pointLights[index];
return true;
}
bool SceneManager::setPointLight(size_t index, const PointLight &light)
{
if (index >= pointLights.size())
{
return false;
}
pointLights[index] = light;
return true;
}
bool SceneManager::removePointLight(size_t index)
{
if (index >= pointLights.size())
{
return false;
}
pointLights.erase(pointLights.begin() + index);
return true;
}
void SceneManager::init(EngineContext *context) void SceneManager::init(EngineContext *context)
{ {
_context = context; _context = context;

View File

@@ -136,6 +136,10 @@ public:
void addPointLight(const PointLight &light); void addPointLight(const PointLight &light);
void clearPointLights(); void clearPointLights();
size_t getPointLightCount() const { return pointLights.size(); }
bool getPointLight(size_t index, PointLight &outLight) const;
bool setPointLight(size_t index, const PointLight &light);
bool removePointLight(size_t index);
const std::vector<PointLight> &getPointLights() const { return pointLights; } const std::vector<PointLight> &getPointLights() const { return pointLights; }
struct SceneStats struct SceneStats