ADD: spot light

This commit is contained in:
2025-12-20 23:43:34 +09:00
parent 9ebc01b3a9
commit 0ec865e0ee
15 changed files with 585 additions and 5 deletions

View File

@@ -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. 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 ### Future Extensions
- Spot lights (add cone angle to `GPUPunctualLight`) - Shadow maps for spot lights (single frustum)
- IES profiles / photometric falloff

View File

@@ -127,6 +127,23 @@ Notes:
- On level load: call `addPointLight` for each baked/runtime point light. - On level load: call `addPointLight` for each baked/runtime point light.
- At runtime (e.g. gameplay): read/modify lights via the indexed helpers. - At runtime (e.g. gameplay): read/modify lights via the indexed helpers.
### Spot Lights
- `SceneManager::SpotLight`
- `position_world` worldspace position.
- `direction` worldspace unit direction (cone axis).
- `radius` approximate influence radius (used for falloff).
- `inner_angle_deg`, `outer_angle_deg` cone halfangles 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 (GameFacing) ### Picking & Selection (GameFacing)
The scene system exposes CPU raybased picking and rectangle selection that the engine uses for editor tools, but you can also call them directly from game code. The scene system exposes CPU raybased picking and rectangle selection that the engine uses for editor tools, but you can also call them directly from game code.

View File

@@ -340,6 +340,52 @@ void main(){
direct += contrib; 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 // Image-Based Lighting: split-sum approximation
vec3 R = reflect(-V, N); vec3 R = reflect(-V, N);
float levels = float(textureQueryLevels(iblSpec2D)); float levels = float(textureQueryLevels(iblSpec2D));

View File

@@ -235,6 +235,13 @@ void main(){
direct += eval_point_light(sceneData.punctualLights[i], pos, N, V, albedo, roughness, metallic); 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 // Image-Based Lighting: split-sum approximation
vec3 R = reflect(-V, N); vec3 R = reflect(-V, N);
float levels = float(textureQueryLevels(iblSpec2D)); float levels = float(textureQueryLevels(iblSpec2D));

View File

@@ -2,12 +2,21 @@
#define MAX_CASCADES 4 #define MAX_CASCADES 4
// Maximum number of punctual (point) lights // Maximum number of punctual (point) lights
#define MAX_PUNCTUAL_LIGHTS 64 #define MAX_PUNCTUAL_LIGHTS 64
// Maximum number of spot lights
#define MAX_SPOT_LIGHTS 32
struct GPUPunctualLight { struct GPUPunctualLight {
vec4 position_radius; vec4 position_radius;
vec4 color_intensity; 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{ layout(set = 0, binding = 0) uniform SceneData{
mat4 view; mat4 view;
@@ -32,6 +41,9 @@ layout(set = 0, binding = 0) uniform SceneData{
vec4 rtParams; vec4 rtParams;
GPUPunctualLight punctualLights[MAX_PUNCTUAL_LIGHTS]; GPUPunctualLight punctualLights[MAX_PUNCTUAL_LIGHTS];
GPUSpotLight spotLights[MAX_SPOT_LIGHTS];
// lightCounts.x = point light count
// lightCounts.y = spot light count
uvec4 lightCounts; uvec4 lightCounts;
} sceneData; } sceneData;

View File

@@ -82,5 +82,41 @@ vec3 eval_point_light(GPUPunctualLight light, vec3 pos, vec3 N, vec3 V, vec3 alb
return brdf * lightColor * falloff; 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

View File

@@ -58,6 +58,13 @@ void main()
direct += eval_point_light(sceneData.punctualLights[i], inWorldPos, N, V, albedo, roughness, metallic); 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 // IBL: specular from equirect 2D mips; diffuse from SH
vec3 R = reflect(-V, N); vec3 R = reflect(-V, N);
float levels = float(textureQueryLevels(iblSpec2D)); float levels = float(textureQueryLevels(iblSpec2D));

View File

@@ -66,7 +66,7 @@ inline constexpr float kShadowDepthBiasSlope = 1.5f;
// Texture streaming / VRAM budget configuration // Texture streaming / VRAM budget configuration
// Fraction of total device-local VRAM reserved for streamed textures. // Fraction of total device-local VRAM reserved for streamed textures.
// The remaining budget is left for attachments, swapchain images, meshes, AS, etc. // 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. // Fallback texture budget in bytes when Vulkan memory properties are unavailable.
inline constexpr size_t kTextureBudgetFallbackBytes = 512ull * 1024ull * 1024ull; inline constexpr size_t kTextureBudgetFallbackBytes = 512ull * 1024ull * 1024ull;
// Minimum texture budget clamp in bytes. // Minimum texture budget clamp in bytes.

View File

@@ -1267,7 +1267,7 @@ void VulkanEngine::draw()
get_current_frame()._renderSemaphore); get_current_frame()._renderSemaphore);
VkSubmitInfo2 submit = vkinit::submit_info(&cmdinfo, &signalInfo, &waitInfo); VkSubmitInfo2 submit = vkinit::submit_info(&cmdinfo, &signalInfo, &waitInfo);
//------------
VK_CHECK(vkQueueSubmit2(_deviceManager->graphicsQueue(), 1, &submit, get_current_frame()._renderFence)); VK_CHECK(vkQueueSubmit2(_deviceManager->graphicsQueue(), 1, &submit, get_current_frame()._renderFence));
VkPresentInfoKHR presentInfo = vkinit::present_info(); VkPresentInfoKHR presentInfo = vkinit::present_info();

View File

@@ -1425,6 +1425,115 @@ namespace
sceneMgr->clearPointLights(); sceneMgr->clearPointLights();
selectedLight = -1; 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<int>(spotLights.size()))
{
selectedSpot = static_cast<int>(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<int>(i));
if (ImGui::Selectable(label.c_str(), isSelected))
{
selectedSpot = static_cast<int>(i);
}
}
ImGui::EndListBox();
}
if (selectedSpot >= 0 && selectedSpot < static_cast<int>(spotLights.size()))
{
SceneManager::SpotLight sl{};
if (sceneMgr->getSpotLight(static_cast<size_t>(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<size_t>(selectedSpot), sl);
}
if (ImGui::Button("Remove selected spot light##spot_remove"))
{
sceneMgr->removeSpotLight(static_cast<size_t>(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<int>(oldCount);
}
if (ImGui::Button("Clear all spot lights##spot_clear"))
{
sceneMgr->clearSpotLights();
selectedSpot = -1;
}
} }
ImGui::Separator(); ImGui::Separator();

View File

@@ -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 // Post Processing - FXAA
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@@ -69,6 +69,30 @@ struct PointLightD
float intensity{1.0f}; 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 // IBL (Image-Based Lighting) paths
struct IBLPaths struct IBLPaths
{ {
@@ -333,6 +357,29 @@ public:
// Clear all point lights // Clear all point lights
void clear_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 // Post Processing - FXAA
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@@ -117,6 +117,15 @@ struct GPUPunctualLight {
static constexpr uint32_t kMaxPunctualLights = 64; 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 { struct GPUSceneData {
glm::mat4 view; glm::mat4 view;
glm::mat4 proj; glm::mat4 proj;
@@ -139,6 +148,7 @@ struct GPUSceneData {
glm::vec4 rtParams; glm::vec4 rtParams;
GPUPunctualLight punctualLights[kMaxPunctualLights]; GPUPunctualLight punctualLights[kMaxPunctualLights];
GPUSpotLight spotLights[kMaxSpotLights];
glm::uvec4 lightCounts; glm::uvec4 lightCounts;
}; };

View File

@@ -67,6 +67,46 @@ bool SceneManager::removePointLight(size_t index)
return true; 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) void SceneManager::init(EngineContext *context)
{ {
_context = context; _context = context;
@@ -414,7 +454,45 @@ void SceneManager::update_scene()
sceneData.punctualLights[i].position_radius = glm::vec4(0.0f); sceneData.punctualLights[i].position_radius = glm::vec4(0.0f);
sceneData.punctualLights[i].color_intensity = 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<uint32_t>(std::min(spotLights.size(), static_cast<size_t>(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 end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start); auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

View File

@@ -184,6 +184,26 @@ public:
bool removePointLight(size_t index); bool removePointLight(size_t index);
const std::vector<PointLight> &getPointLights() const { return pointLights; } const std::vector<PointLight> &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<SpotLight> &getSpotLights() const { return spotLights; }
struct SceneStats struct SceneStats
{ {
float scene_update_time = 0.f; float scene_update_time = 0.f;
@@ -210,6 +230,7 @@ private:
GPUSceneData sceneData = {}; GPUSceneData sceneData = {};
DrawContext mainDrawContext; DrawContext mainDrawContext;
std::vector<PointLight> pointLights; std::vector<PointLight> pointLights;
std::vector<SpotLight> spotLights;
WorldVec3 _origin_world{0.0, 0.0, 0.0}; WorldVec3 _origin_world{0.0, 0.0, 0.0};
glm::vec3 _camera_position_local{0.0f, 0.0f, 0.0f}; glm::vec3 _camera_position_local{0.0f, 0.0f, 0.0f};
double _floating_origin_recenter_threshold = 1000.0; double _floating_origin_recenter_threshold = 1000.0;