ADD: spot light
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<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();
|
||||
|
||||
@@ -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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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<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 elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
@@ -184,6 +184,26 @@ public:
|
||||
bool removePointLight(size_t index);
|
||||
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
|
||||
{
|
||||
float scene_update_time = 0.f;
|
||||
@@ -210,6 +230,7 @@ private:
|
||||
GPUSceneData sceneData = {};
|
||||
DrawContext mainDrawContext;
|
||||
std::vector<PointLight> pointLights;
|
||||
std::vector<SpotLight> 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;
|
||||
|
||||
Reference in New Issue
Block a user