208 lines
6.4 KiB
Markdown
208 lines
6.4 KiB
Markdown
## Multi-Light System: Punctual Point Lights
|
|
|
|
Extends the rendering pipeline to support multiple dynamic point lights alongside the existing directional sun light. All lighting calculations use a shared PBR (Cook-Torrance) BRDF implementation.
|
|
|
|
- Files: `src/scene/vk_scene.h/.cpp`, `src/core/types.h`, `shaders/lighting_common.glsl`
|
|
|
|
### Data Structures
|
|
|
|
**CPU-side (C++)**
|
|
|
|
```cpp
|
|
// src/scene/vk_scene.h
|
|
struct PointLight {
|
|
glm::vec3 position;
|
|
float radius; // falloff radius
|
|
glm::vec3 color;
|
|
float intensity;
|
|
};
|
|
```
|
|
|
|
**GPU-side (GLSL)**
|
|
|
|
```glsl
|
|
// shaders/input_structures.glsl
|
|
#define MAX_PUNCTUAL_LIGHTS 64
|
|
|
|
struct GPUPunctualLight {
|
|
vec4 position_radius; // xyz: world position, w: radius
|
|
vec4 color_intensity; // rgb: color, a: intensity
|
|
};
|
|
```
|
|
|
|
The `GPUSceneData` uniform buffer includes:
|
|
- `punctualLights[MAX_PUNCTUAL_LIGHTS]`: array of packed light data
|
|
- `lightCounts.x`: number of active point lights
|
|
|
|
### Scene API
|
|
|
|
`SceneManager` exposes a simple API to manage point lights at runtime:
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| `addPointLight(const PointLight& light)` | Appends a point light to the scene |
|
|
| `clearPointLights()` | Removes all point lights |
|
|
| `getPointLights()` | Returns const reference to current lights |
|
|
|
|
Example usage:
|
|
```cpp
|
|
SceneManager::PointLight light{};
|
|
light.position = glm::vec3(5.0f, 3.0f, 0.0f);
|
|
light.radius = 15.0f;
|
|
light.color = glm::vec3(1.0f, 0.9f, 0.7f);
|
|
light.intensity = 20.0f;
|
|
sceneManager->addPointLight(light);
|
|
```
|
|
|
|
### GPU Upload
|
|
|
|
During `SceneManager::update_scene()`, point lights are packed into `GPUSceneData`:
|
|
|
|
1. Iterate over `pointLights` vector (capped at `kMaxPunctualLights = 64`)
|
|
2. Pack each light into `GPUPunctualLight` format
|
|
3. Zero-fill unused array slots
|
|
4. Set `lightCounts.x` to active count
|
|
|
|
This data is uploaded to the per-frame UBO and bound via `DescriptorManager::gpuSceneDataLayout()` at set 0, binding 0.
|
|
|
|
### Shader Implementation
|
|
|
|
**lighting_common.glsl**
|
|
|
|
Shared PBR functions extracted for reuse across all lighting passes:
|
|
|
|
| Function | Purpose |
|
|
|----------|---------|
|
|
| `fresnelSchlick()` | Fresnel term (Schlick approximation) |
|
|
| `DistributionGGX()` | Normal distribution function (GGX/Trowbridge-Reitz) |
|
|
| `GeometrySchlickGGX()` | Geometry term (Schlick-GGX) |
|
|
| `GeometrySmith()` | Combined geometry attenuation |
|
|
| `evaluate_brdf(N, V, L, albedo, roughness, metallic)` | Full Cook-Torrance BRDF evaluation |
|
|
| `eval_point_light(light, pos, N, V, albedo, roughness, metallic)` | Point light contribution with falloff |
|
|
|
|
**Point Light Falloff**
|
|
|
|
Uses smooth inverse-square attenuation with radius-based cutoff:
|
|
```glsl
|
|
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;
|
|
```
|
|
|
|
This provides physically-based falloff that smoothly reaches zero at the specified radius.
|
|
|
|
### Render Path Integration
|
|
|
|
**Deferred Lighting** (`deferred_lighting.frag`, `deferred_lighting_nort.frag`)
|
|
|
|
```glsl
|
|
// Directional sun
|
|
vec3 Lsun = normalize(-sceneData.sunlightDirection.xyz);
|
|
float sunVis = calcShadowVisibility(pos, N, Lsun);
|
|
vec3 sunBRDF = evaluate_brdf(N, V, Lsun, albedo, roughness, metallic);
|
|
vec3 direct = sunBRDF * sceneData.sunlightColor.rgb * sceneData.sunlightColor.a * sunVis;
|
|
|
|
// Accumulate point lights
|
|
uint pointCount = sceneData.lightCounts.x;
|
|
for (uint i = 0u; i < pointCount; ++i) {
|
|
direct += eval_point_light(sceneData.punctualLights[i], pos, N, V, albedo, roughness, metallic);
|
|
}
|
|
```
|
|
|
|
**Forward Pass** (`mesh.frag`)
|
|
|
|
Same accumulation loop, but without shadow visibility (used for transparent objects).
|
|
|
|
### Ray-Traced Point Light Shadows
|
|
|
|
When ray tracing is enabled (hybrid mode), the first 4 point lights receive RT shadows:
|
|
|
|
```glsl
|
|
if (sceneData.rtOptions.x == 1u && i < 4u) {
|
|
// Cast shadow ray from surface toward light
|
|
vec3 toL = light.position_radius.xyz - pos;
|
|
// ... rayQueryInitializeEXT / rayQueryProceedEXT ...
|
|
if (hit) contrib = vec3(0.0);
|
|
}
|
|
```
|
|
|
|
This provides accurate point light shadows without additional shadow maps.
|
|
|
|
### Performance Considerations
|
|
|
|
- **Light Count**: Up to 64 point lights supported; practical limit depends on scene complexity
|
|
- **Loop Unrolling**: Shader compilers typically unroll small loops; consider dynamic branching for very high light counts
|
|
- **Shadow Maps**: Point light shadows use RT only; traditional cubemap shadows are not implemented
|
|
- **Bandwidth**: Each light adds 32 bytes to the UBO; full array is 2KB
|
|
|
|
### Default Lights
|
|
|
|
`SceneManager::init()` creates two default point lights for testing:
|
|
|
|
| Light | Position | Radius | Color | Intensity |
|
|
|-------|----------|--------|-------|-----------|
|
|
| Warm Key | (0, 0, 0) | 25 | (1.0, 0.95, 0.8) | 15 |
|
|
| Cool Fill | (-10, 4, 10) | 20 | (0.6, 0.7, 1.0) | 10 |
|
|
|
|
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
|
|
|
|
- Shadow maps for spot lights (single frustum)
|
|
- IES profiles / photometric falloff
|