Files
QuaternionEngine/docs/MultiLighting.md
2025-12-20 23:43:34 +09:00

6.4 KiB

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++)

// src/scene/vk_scene.h
struct PointLight {
    glm::vec3 position;
    float radius;       // falloff radius
    glm::vec3 color;
    float intensity;
};

GPU-side (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:

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:

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)

// 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:

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++)

// 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)

// 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