diff --git a/docs/MultiLighting.md b/docs/MultiLighting.md index 04f5609..c11c946 100644 --- a/docs/MultiLighting.md +++ b/docs/MultiLighting.md @@ -148,6 +148,60 @@ This provides accurate point light shadows without additional shadow maps. Call `clearPointLights()` before adding your own lights to remove these defaults. +## Spot Lights + +Adds cone-limited spot lights alongside point lights, sharing the same BRDF helpers. + +### Data Structures + +**CPU-side (C++)** + +```cpp +// src/scene/vk_scene.h +struct SpotLight { + WorldVec3 position_world; + glm::vec3 direction; // world-space unit direction (cone axis) + float radius; + glm::vec3 color; + float intensity; + float inner_angle_deg; // cone half-angle (deg) + float outer_angle_deg; // cone half-angle (deg), >= inner +}; +``` + +**GPU-side (GLSL)** + +```glsl +// shaders/input_structures.glsl +#define MAX_SPOT_LIGHTS 32 + +struct GPUSpotLight { + vec4 position_radius; // xyz: position, w: radius + vec4 direction_cos_outer; // xyz: direction (unit), w: cos(outer_angle) + vec4 color_intensity; // rgb: color, a: intensity + vec4 cone; // x: cos(inner_angle), yzw: unused +}; +``` + +The `GPUSceneData` uniform buffer includes: +- `spotLights[MAX_SPOT_LIGHTS]`: array of packed spot lights +- `lightCounts.y`: number of active spot lights + +### Shader Implementation + +**lighting_common.glsl** + +- `eval_spot_light(light, pos, N, V, albedo, roughness, metallic)`: + - Applies the same smooth inverse-square falloff as point lights + - Multiplies by a soft cone attenuation between `outer_angle` and `inner_angle` + +### Render Path Integration + +- Deferred lighting (`deferred_lighting*.frag`) and forward (`mesh.frag`) both accumulate spot lights: + - `uint spotCount = sceneData.lightCounts.y;` + - `for (uint i = 0u; i < spotCount; ++i) { direct += eval_spot_light(...); }` + ### Future Extensions -- Spot lights (add cone angle to `GPUPunctualLight`) +- Shadow maps for spot lights (single frustum) +- IES profiles / photometric falloff diff --git a/docs/Scene.md b/docs/Scene.md index 72bad91..d1aec00 100644 --- a/docs/Scene.md +++ b/docs/Scene.md @@ -127,6 +127,23 @@ Notes: - On level load: call `addPointLight` for each baked/runtime point light. - At runtime (e.g. gameplay): read/modify lights via the indexed helpers. +### Spot Lights + +- `SceneManager::SpotLight` + - `position_world` – world‑space position. + - `direction` – world‑space unit direction (cone axis). + - `radius` – approximate influence radius (used for falloff). + - `inner_angle_deg`, `outer_angle_deg` – cone half‑angles in degrees (inner ≤ outer). + - `color` – RGB color. + - `intensity` – scalar brightness. +- API + - `addSpotLight(const SpotLight &light)` + - `clearSpotLights()` + - `getSpotLightCount()`, `getSpotLight(index, outLight)`, `setSpotLight(index, light)`, `removeSpotLight(index)` +- Usage pattern + - On level load: call `addSpotLight` for each flashlight/beam/cone light. + - At runtime: read/modify lights via the indexed helpers. + ### Picking & Selection (Game‑Facing) The scene system exposes CPU ray‑based picking and rectangle selection that the engine uses for editor tools, but you can also call them directly from game code. diff --git a/shaders/deferred_lighting.frag b/shaders/deferred_lighting.frag index d15e8d9..fbb3b36 100644 --- a/shaders/deferred_lighting.frag +++ b/shaders/deferred_lighting.frag @@ -340,6 +340,52 @@ void main(){ direct += contrib; } + // Spot lights + uint spotCount = sceneData.lightCounts.y; + for (uint i = 0u; i < spotCount; ++i) + { + vec3 contrib = eval_spot_light(sceneData.spotLights[i], pos, N, V, albedo, roughness, metallic); + + // Optional RT shadow for the first few spot lights (hybrid mode) + #ifdef GL_EXT_ray_query + if (sceneData.rtOptions.x == 1u && sceneData.rtParams.y > 0.0 && i < 4u) + { + vec3 toL = sceneData.spotLights[i].position_radius.xyz - pos; + float maxT = length(toL); + if (maxT > 0.01) + { + vec3 L = toL / maxT; + vec3 dir = normalize(sceneData.spotLights[i].direction_cos_outer.xyz); + float cosTheta = dot(-L, dir); + if (cosTheta > sceneData.spotLights[i].direction_cos_outer.w) + { + vec3 origin = pos + N * SHADOW_RAY_ORIGIN_BIAS; + + rayQueryEXT rq; + rayQueryInitializeEXT( + rq, + topLevelAS, + gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, + 0xFF, + origin, + SHADOW_RAY_TMIN, + L, + maxT + ); + while (rayQueryProceedEXT(rq)) { } + bool hit = (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionNoneEXT); + if (hit) + { + contrib = vec3(0.0); + } + } + } + } + #endif + + direct += contrib; + } + // Image-Based Lighting: split-sum approximation vec3 R = reflect(-V, N); float levels = float(textureQueryLevels(iblSpec2D)); diff --git a/shaders/deferred_lighting_nort.frag b/shaders/deferred_lighting_nort.frag index 00c2311..e4412b3 100644 --- a/shaders/deferred_lighting_nort.frag +++ b/shaders/deferred_lighting_nort.frag @@ -235,6 +235,13 @@ void main(){ direct += eval_point_light(sceneData.punctualLights[i], pos, N, V, albedo, roughness, metallic); } + // Spot lights + uint spotCount = sceneData.lightCounts.y; + for (uint i = 0u; i < spotCount; ++i) + { + direct += eval_spot_light(sceneData.spotLights[i], pos, N, V, albedo, roughness, metallic); + } + // Image-Based Lighting: split-sum approximation vec3 R = reflect(-V, N); float levels = float(textureQueryLevels(iblSpec2D)); diff --git a/shaders/input_structures.glsl b/shaders/input_structures.glsl index 51009b8..afadf40 100644 --- a/shaders/input_structures.glsl +++ b/shaders/input_structures.glsl @@ -2,12 +2,21 @@ #define MAX_CASCADES 4 // Maximum number of punctual (point) lights #define MAX_PUNCTUAL_LIGHTS 64 +// Maximum number of spot lights +#define MAX_SPOT_LIGHTS 32 struct GPUPunctualLight { vec4 position_radius; vec4 color_intensity; }; +struct GPUSpotLight { + vec4 position_radius; // xyz: position, w: radius + vec4 direction_cos_outer; // xyz: direction (unit), w: cos(outer_angle) + vec4 color_intensity; // rgb: color, a: intensity + vec4 cone; // x: cos(inner_angle), yzw: unused +}; + layout(set = 0, binding = 0) uniform SceneData{ mat4 view; @@ -32,6 +41,9 @@ layout(set = 0, binding = 0) uniform SceneData{ vec4 rtParams; GPUPunctualLight punctualLights[MAX_PUNCTUAL_LIGHTS]; + GPUSpotLight spotLights[MAX_SPOT_LIGHTS]; + // lightCounts.x = point light count + // lightCounts.y = spot light count uvec4 lightCounts; } sceneData; diff --git a/shaders/lighting_common.glsl b/shaders/lighting_common.glsl index 1159922..5a1a0d7 100644 --- a/shaders/lighting_common.glsl +++ b/shaders/lighting_common.glsl @@ -82,5 +82,41 @@ vec3 eval_point_light(GPUPunctualLight light, vec3 pos, vec3 N, vec3 V, vec3 alb return brdf * lightColor * falloff; } -#endif // LIGHTING_COMMON_GLSL +vec3 eval_spot_light(GPUSpotLight light, vec3 pos, vec3 N, vec3 V, vec3 albedo, float roughness, float metallic) +{ + vec3 lightPos = light.position_radius.xyz; + float radius = max(light.position_radius.w, 0.0001); + vec3 toLight = lightPos - pos; + float dist = length(toLight); + if (dist <= 0.0001) + { + return vec3(0.0); + } + vec3 L = toLight / dist; // surface -> light + + vec3 dir = normalize(light.direction_cos_outer.xyz); // light -> forward + float cosOuter = light.direction_cos_outer.w; + float cosInner = light.cone.x; + float cosTheta = dot(-L, dir); // light -> surface vs light forward + if (cosTheta <= cosOuter) + { + return vec3(0.0); + } + float denom = max(cosInner - cosOuter, 0.0001); + float spot = clamp((cosTheta - cosOuter) / denom, 0.0, 1.0); + spot *= spot; + + // Smooth falloff: inverse-square with soft clamp at radius + float att = 1.0 / max(dist * dist, 0.0001); + float x = clamp(dist / radius, 0.0, 1.0); + float smth = (1.0 - x * x); + smth *= smth; + float falloff = att * smth; + + vec3 brdf = evaluate_brdf(N, V, L, albedo, roughness, metallic); + vec3 lightColor = light.color_intensity.rgb * light.color_intensity.a; + return brdf * lightColor * falloff * spot; +} + +#endif // LIGHTING_COMMON_GLSL diff --git a/shaders/mesh.frag b/shaders/mesh.frag index 511715d..6d7bb18 100644 --- a/shaders/mesh.frag +++ b/shaders/mesh.frag @@ -58,6 +58,13 @@ void main() direct += eval_point_light(sceneData.punctualLights[i], inWorldPos, N, V, albedo, roughness, metallic); } + // Spot lights + uint spotCount = sceneData.lightCounts.y; + for (uint i = 0u; i < spotCount; ++i) + { + direct += eval_spot_light(sceneData.spotLights[i], inWorldPos, N, V, albedo, roughness, metallic); + } + // IBL: specular from equirect 2D mips; diffuse from SH vec3 R = reflect(-V, N); float levels = float(textureQueryLevels(iblSpec2D)); diff --git a/src/core/config.h b/src/core/config.h index 5762fab..8b7b886 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -66,7 +66,7 @@ inline constexpr float kShadowDepthBiasSlope = 1.5f; // Texture streaming / VRAM budget configuration // Fraction of total device-local VRAM reserved for streamed textures. // The remaining budget is left for attachments, swapchain images, meshes, AS, etc. -inline constexpr double kTextureBudgetFraction = 0.35; +inline constexpr double kTextureBudgetFraction = 0.7; // Fallback texture budget in bytes when Vulkan memory properties are unavailable. inline constexpr size_t kTextureBudgetFallbackBytes = 512ull * 1024ull * 1024ull; // Minimum texture budget clamp in bytes. diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 6f25973..4053534 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -1267,7 +1267,7 @@ void VulkanEngine::draw() get_current_frame()._renderSemaphore); VkSubmitInfo2 submit = vkinit::submit_info(&cmdinfo, &signalInfo, &waitInfo); - + //------------ VK_CHECK(vkQueueSubmit2(_deviceManager->graphicsQueue(), 1, &submit, get_current_frame()._renderFence)); VkPresentInfoKHR presentInfo = vkinit::present_info(); diff --git a/src/core/engine_ui.cpp b/src/core/engine_ui.cpp index 192c924..f15dc65 100644 --- a/src/core/engine_ui.cpp +++ b/src/core/engine_ui.cpp @@ -1425,6 +1425,115 @@ namespace sceneMgr->clearPointLights(); selectedLight = -1; } + + // Spot light editor + ImGui::Separator(); + ImGui::TextUnformatted("Spot lights"); + + const auto &spotLights = sceneMgr->getSpotLights(); + ImGui::Text("Active spot lights: %zu", spotLights.size()); + + static int selectedSpot = -1; + if (selectedSpot >= static_cast(spotLights.size())) + { + selectedSpot = static_cast(spotLights.size()) - 1; + } + + if (ImGui::BeginListBox("Spot light list##spot_list")) + { + for (size_t i = 0; i < spotLights.size(); ++i) + { + std::string label = fmt::format("Spot {}", i); + const bool isSelected = (selectedSpot == static_cast(i)); + if (ImGui::Selectable(label.c_str(), isSelected)) + { + selectedSpot = static_cast(i); + } + } + ImGui::EndListBox(); + } + + if (selectedSpot >= 0 && selectedSpot < static_cast(spotLights.size())) + { + SceneManager::SpotLight sl{}; + if (sceneMgr->getSpotLight(static_cast(selectedSpot), sl)) + { + double pos[3] = {sl.position_world.x, sl.position_world.y, sl.position_world.z}; + float dir[3] = {sl.direction.x, sl.direction.y, sl.direction.z}; + float col[3] = {sl.color.r, sl.color.g, sl.color.b}; + bool changed = false; + + changed |= ImGui::InputScalarN("Position (world)##spot_pos", ImGuiDataType_Double, pos, 3, nullptr, nullptr, "%.3f"); + changed |= ImGui::InputFloat3("Direction##spot_dir", dir, "%.3f"); + changed |= ImGui::SliderFloat("Radius##spot_radius", &sl.radius, 0.1f, 1000.0f); + changed |= ImGui::SliderFloat("Inner angle (deg)##spot_inner", &sl.inner_angle_deg, 0.0f, 89.0f); + changed |= ImGui::SliderFloat("Outer angle (deg)##spot_outer", &sl.outer_angle_deg, 0.0f, 89.9f); + changed |= ImGui::ColorEdit3("Color##spot_color", col); + changed |= ImGui::SliderFloat("Intensity##spot_intensity", &sl.intensity, 0.0f, 100.0f); + + if (changed) + { + sl.position_world = WorldVec3(pos[0], pos[1], pos[2]); + glm::vec3 d{dir[0], dir[1], dir[2]}; + sl.direction = (glm::length(d) > 1.0e-6f) ? glm::normalize(d) : glm::vec3(0.0f, -1.0f, 0.0f); + sl.color = glm::vec3(col[0], col[1], col[2]); + sl.inner_angle_deg = std::clamp(sl.inner_angle_deg, 0.0f, 89.0f); + sl.outer_angle_deg = std::clamp(sl.outer_angle_deg, sl.inner_angle_deg, 89.9f); + sceneMgr->setSpotLight(static_cast(selectedSpot), sl); + } + + if (ImGui::Button("Remove selected spot light##spot_remove")) + { + sceneMgr->removeSpotLight(static_cast(selectedSpot)); + selectedSpot = -1; + } + } + } + + ImGui::Separator(); + ImGui::TextUnformatted("Add spot light"); + static double newSpotPos[3] = {0.0, 2.0, 0.0}; + static float newSpotDir[3] = {0.0f, -1.0f, 0.0f}; + static float newSpotRadius = 10.0f; + static float newSpotInner = 15.0f; + static float newSpotOuter = 25.0f; + static float newSpotColor[3] = {1.0f, 1.0f, 1.0f}; + static float newSpotIntensity = 10.0f; + + ImGui::InputScalarN("New position (world)##spot_new_pos", ImGuiDataType_Double, newSpotPos, 3, nullptr, nullptr, "%.3f"); + ImGui::InputFloat3("New direction##spot_new_dir", newSpotDir, "%.3f"); + ImGui::SliderFloat("New radius##spot_new_radius", &newSpotRadius, 0.1f, 1000.0f); + ImGui::SliderFloat("New inner angle (deg)##spot_new_inner", &newSpotInner, 0.0f, 89.0f); + ImGui::SliderFloat("New outer angle (deg)##spot_new_outer", &newSpotOuter, 0.0f, 89.9f); + if (newSpotInner > newSpotOuter) + { + newSpotOuter = newSpotInner; + } + ImGui::ColorEdit3("New color##spot_new_color", newSpotColor); + ImGui::SliderFloat("New intensity##spot_new_intensity", &newSpotIntensity, 0.0f, 100.0f); + + if (ImGui::Button("Add spot light##spot_add")) + { + SceneManager::SpotLight sl{}; + sl.position_world = WorldVec3(newSpotPos[0], newSpotPos[1], newSpotPos[2]); + glm::vec3 d{newSpotDir[0], newSpotDir[1], newSpotDir[2]}; + sl.direction = (glm::length(d) > 1.0e-6f) ? glm::normalize(d) : glm::vec3(0.0f, -1.0f, 0.0f); + sl.radius = newSpotRadius; + sl.color = glm::vec3(newSpotColor[0], newSpotColor[1], newSpotColor[2]); + sl.intensity = newSpotIntensity; + sl.inner_angle_deg = std::clamp(newSpotInner, 0.0f, 89.0f); + sl.outer_angle_deg = std::clamp(newSpotOuter, sl.inner_angle_deg, 89.9f); + + const size_t oldCount = sceneMgr->getSpotLightCount(); + sceneMgr->addSpotLight(sl); + selectedSpot = static_cast(oldCount); + } + + if (ImGui::Button("Clear all spot lights##spot_clear")) + { + sceneMgr->clearSpotLights(); + selectedSpot = -1; + } } ImGui::Separator(); diff --git a/src/core/game_api.cpp b/src/core/game_api.cpp index 426f568..e2edb92 100644 --- a/src/core/game_api.cpp +++ b/src/core/game_api.cpp @@ -796,6 +796,142 @@ void Engine::clear_point_lights() } } +// ---------------------------------------------------------------------------- +// Lighting - Spot Lights +// ---------------------------------------------------------------------------- + +size_t Engine::add_spot_light(const SpotLight& light) +{ + if (!_engine->_sceneManager) return 0; + + SceneManager::SpotLight sl; + sl.position_world = WorldVec3(light.position); + sl.direction = (glm::length(light.direction) > 1.0e-6f) + ? glm::normalize(light.direction) + : glm::vec3(0.0f, -1.0f, 0.0f); + sl.radius = light.radius; + sl.color = light.color; + sl.intensity = light.intensity; + sl.inner_angle_deg = light.inner_angle_deg; + sl.outer_angle_deg = light.outer_angle_deg; + + size_t idx = _engine->_sceneManager->getSpotLightCount(); + _engine->_sceneManager->addSpotLight(sl); + return idx; +} + +size_t Engine::add_spot_light(const SpotLightD& light) +{ + if (!_engine->_sceneManager) return 0; + + SceneManager::SpotLight sl; + sl.position_world = WorldVec3(light.position); + sl.direction = (glm::length(light.direction) > 1.0e-6f) + ? glm::normalize(light.direction) + : glm::vec3(0.0f, -1.0f, 0.0f); + sl.radius = light.radius; + sl.color = light.color; + sl.intensity = light.intensity; + sl.inner_angle_deg = light.inner_angle_deg; + sl.outer_angle_deg = light.outer_angle_deg; + + size_t idx = _engine->_sceneManager->getSpotLightCount(); + _engine->_sceneManager->addSpotLight(sl); + return idx; +} + +bool Engine::remove_spot_light(size_t index) +{ + return _engine->_sceneManager ? _engine->_sceneManager->removeSpotLight(index) : false; +} + +bool Engine::get_spot_light(size_t index, SpotLight& out) const +{ + if (!_engine->_sceneManager) return false; + + SceneManager::SpotLight sl; + if (_engine->_sceneManager->getSpotLight(index, sl)) + { + out.position = glm::vec3(sl.position_world); + out.direction = sl.direction; + out.radius = sl.radius; + out.color = sl.color; + out.intensity = sl.intensity; + out.inner_angle_deg = sl.inner_angle_deg; + out.outer_angle_deg = sl.outer_angle_deg; + return true; + } + return false; +} + +bool Engine::get_spot_light(size_t index, SpotLightD& out) const +{ + if (!_engine->_sceneManager) return false; + + SceneManager::SpotLight sl; + if (_engine->_sceneManager->getSpotLight(index, sl)) + { + out.position = sl.position_world; + out.direction = sl.direction; + out.radius = sl.radius; + out.color = sl.color; + out.intensity = sl.intensity; + out.inner_angle_deg = sl.inner_angle_deg; + out.outer_angle_deg = sl.outer_angle_deg; + return true; + } + return false; +} + +bool Engine::set_spot_light(size_t index, const SpotLight& light) +{ + if (!_engine->_sceneManager) return false; + + SceneManager::SpotLight sl; + sl.position_world = WorldVec3(light.position); + sl.direction = (glm::length(light.direction) > 1.0e-6f) + ? glm::normalize(light.direction) + : glm::vec3(0.0f, -1.0f, 0.0f); + sl.radius = light.radius; + sl.color = light.color; + sl.intensity = light.intensity; + sl.inner_angle_deg = light.inner_angle_deg; + sl.outer_angle_deg = light.outer_angle_deg; + + return _engine->_sceneManager->setSpotLight(index, sl); +} + +bool Engine::set_spot_light(size_t index, const SpotLightD& light) +{ + if (!_engine->_sceneManager) return false; + + SceneManager::SpotLight sl; + sl.position_world = WorldVec3(light.position); + sl.direction = (glm::length(light.direction) > 1.0e-6f) + ? glm::normalize(light.direction) + : glm::vec3(0.0f, -1.0f, 0.0f); + sl.radius = light.radius; + sl.color = light.color; + sl.intensity = light.intensity; + sl.inner_angle_deg = light.inner_angle_deg; + sl.outer_angle_deg = light.outer_angle_deg; + + return _engine->_sceneManager->setSpotLight(index, sl); +} + +size_t Engine::get_spot_light_count() const +{ + return _engine->_sceneManager ? _engine->_sceneManager->getSpotLightCount() : 0; +} + +void Engine::clear_spot_lights() +{ + if (_engine->_sceneManager) + { + _engine->_sceneManager->clearSpotLights(); + } +} + // ---------------------------------------------------------------------------- // Post Processing - FXAA // ---------------------------------------------------------------------------- diff --git a/src/core/game_api.h b/src/core/game_api.h index 18c8b90..4c9bf58 100644 --- a/src/core/game_api.h +++ b/src/core/game_api.h @@ -69,6 +69,30 @@ struct PointLightD float intensity{1.0f}; }; +// Spot light data (cone half-angles in degrees; inner <= outer) +struct SpotLight +{ + glm::vec3 position{0.0f}; + glm::vec3 direction{0.0f, -1.0f, 0.0f}; + float radius{10.0f}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; + float inner_angle_deg{15.0f}; + float outer_angle_deg{25.0f}; +}; + +// Double-precision world-space spot light data (position only). +struct SpotLightD +{ + glm::dvec3 position{0.0}; + glm::vec3 direction{0.0f, -1.0f, 0.0f}; + float radius{10.0f}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; + float inner_angle_deg{15.0f}; + float outer_angle_deg{25.0f}; +}; + // IBL (Image-Based Lighting) paths struct IBLPaths { @@ -333,6 +357,29 @@ public: // Clear all point lights void clear_point_lights(); + // ------------------------------------------------------------------------ + // Lighting - Spot Lights + // ------------------------------------------------------------------------ + + // Add spot light (returns index) + size_t add_spot_light(const SpotLight& light); + size_t add_spot_light(const SpotLightD& light); + + // Remove spot light by index + bool remove_spot_light(size_t index); + + // Get/set spot light properties + bool get_spot_light(size_t index, SpotLight& out) const; + bool set_spot_light(size_t index, const SpotLight& light); + bool get_spot_light(size_t index, SpotLightD& out) const; + bool set_spot_light(size_t index, const SpotLightD& light); + + // Get spot light count + size_t get_spot_light_count() const; + + // Clear all spot lights + void clear_spot_lights(); + // ------------------------------------------------------------------------ // Post Processing - FXAA // ------------------------------------------------------------------------ diff --git a/src/core/types.h b/src/core/types.h index 6425b34..1deaf62 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -117,6 +117,15 @@ struct GPUPunctualLight { static constexpr uint32_t kMaxPunctualLights = 64; +struct GPUSpotLight { + glm::vec4 position_radius; // xyz: position (local), w: radius + glm::vec4 direction_cos_outer; // xyz: direction (unit), w: cos(outer_angle) + glm::vec4 color_intensity; // rgb: color, a: intensity + glm::vec4 cone; // x: cos(inner_angle), yzw: unused +}; + +static constexpr uint32_t kMaxSpotLights = 32; + struct GPUSceneData { glm::mat4 view; glm::mat4 proj; @@ -139,6 +148,7 @@ struct GPUSceneData { glm::vec4 rtParams; GPUPunctualLight punctualLights[kMaxPunctualLights]; + GPUSpotLight spotLights[kMaxSpotLights]; glm::uvec4 lightCounts; }; diff --git a/src/scene/vk_scene.cpp b/src/scene/vk_scene.cpp index 639f36b..dee487a 100644 --- a/src/scene/vk_scene.cpp +++ b/src/scene/vk_scene.cpp @@ -67,6 +67,46 @@ bool SceneManager::removePointLight(size_t index) return true; } +void SceneManager::addSpotLight(const SpotLight &light) +{ + spotLights.push_back(light); +} + +void SceneManager::clearSpotLights() +{ + spotLights.clear(); +} + +bool SceneManager::getSpotLight(size_t index, SpotLight &outLight) const +{ + if (index >= spotLights.size()) + { + return false; + } + outLight = spotLights[index]; + return true; +} + +bool SceneManager::setSpotLight(size_t index, const SpotLight &light) +{ + if (index >= spotLights.size()) + { + return false; + } + spotLights[index] = light; + return true; +} + +bool SceneManager::removeSpotLight(size_t index) +{ + if (index >= spotLights.size()) + { + return false; + } + spotLights.erase(spotLights.begin() + index); + return true; +} + void SceneManager::init(EngineContext *context) { _context = context; @@ -414,7 +454,45 @@ void SceneManager::update_scene() sceneData.punctualLights[i].position_radius = glm::vec4(0.0f); sceneData.punctualLights[i].color_intensity = glm::vec4(0.0f); } - sceneData.lightCounts = glm::uvec4(lightCount, 0u, 0u, 0u); + + // Fill spot lights into GPUSceneData + const uint32_t spotCount = static_cast(std::min(spotLights.size(), static_cast(kMaxSpotLights))); + for (uint32_t i = 0; i < spotCount; ++i) + { + const SpotLight &sl = spotLights[i]; + glm::vec3 posLocal = world_to_local(sl.position_world, _origin_world); + + glm::vec3 dir = sl.direction; + const float dirLen2 = glm::length2(dir); + if (dirLen2 > 1.0e-8f) + { + dir *= 1.0f / std::sqrt(dirLen2); + } + else + { + dir = glm::vec3(0.0f, -1.0f, 0.0f); + } + + const float radius = std::max(sl.radius, 0.0001f); + const float innerDeg = std::clamp(sl.inner_angle_deg, 0.0f, 89.0f); + const float outerDeg = std::clamp(sl.outer_angle_deg, innerDeg, 89.9f); + const float cosInner = glm::cos(glm::radians(innerDeg)); + const float cosOuter = glm::cos(glm::radians(outerDeg)); + + sceneData.spotLights[i].position_radius = glm::vec4(posLocal, radius); + sceneData.spotLights[i].direction_cos_outer = glm::vec4(dir, cosOuter); + sceneData.spotLights[i].color_intensity = glm::vec4(sl.color, sl.intensity); + sceneData.spotLights[i].cone = glm::vec4(cosInner, 0.0f, 0.0f, 0.0f); + } + for (uint32_t i = spotCount; i < kMaxSpotLights; ++i) + { + sceneData.spotLights[i].position_radius = glm::vec4(0.0f); + sceneData.spotLights[i].direction_cos_outer = glm::vec4(0.0f); + sceneData.spotLights[i].color_intensity = glm::vec4(0.0f); + sceneData.spotLights[i].cone = glm::vec4(0.0f); + } + + sceneData.lightCounts = glm::uvec4(lightCount, spotCount, 0u, 0u); auto end = std::chrono::system_clock::now(); auto elapsed = std::chrono::duration_cast(end - start); diff --git a/src/scene/vk_scene.h b/src/scene/vk_scene.h index 7175de8..f12018f 100644 --- a/src/scene/vk_scene.h +++ b/src/scene/vk_scene.h @@ -184,6 +184,26 @@ public: bool removePointLight(size_t index); const std::vector &getPointLights() const { return pointLights; } + struct SpotLight + { + WorldVec3 position_world; + glm::vec3 direction{0.0f, -1.0f, 0.0f}; // world-space unit vector + float radius = 10.0f; + glm::vec3 color{1.0f, 1.0f, 1.0f}; + float intensity = 1.0f; + // Cone half-angles in degrees (inner <= outer). + float inner_angle_deg = 15.0f; + float outer_angle_deg = 25.0f; + }; + + void addSpotLight(const SpotLight &light); + void clearSpotLights(); + size_t getSpotLightCount() const { return spotLights.size(); } + bool getSpotLight(size_t index, SpotLight &outLight) const; + bool setSpotLight(size_t index, const SpotLight &light); + bool removeSpotLight(size_t index); + const std::vector &getSpotLights() const { return spotLights; } + struct SceneStats { float scene_update_time = 0.f; @@ -210,6 +230,7 @@ private: GPUSceneData sceneData = {}; DrawContext mainDrawContext; std::vector pointLights; + std::vector spotLights; WorldVec3 _origin_world{0.0, 0.0, 0.0}; glm::vec3 _camera_position_local{0.0f, 0.0f, 0.0f}; double _floating_origin_recenter_threshold = 1000.0;