Lighting System
GpuLight Struct
Each light is 64 bytes (std140 compatible).
LightUBO Layout
Bound at descriptor set 0, binding 1. Updated every frame in updateUniformBuffer().
C++ API
setLight()
Sets a light at index (0-7). Automatically recalculates numLights as the highest active index + 1 (determined by color.w > 0, i.e. intensity > 0).
clearLight()
Zeros the light at index and recalculates numLights.
setAmbientIntensity()
Sets lightData_.ambientIntensity. Default is 0.15.
Fragment Shader Algorithm
calcLight() (per-light)
Light Types
Directional (type = 0):
- No position, uses direction only
- No attenuation (infinite distance)
- Light direction =
normalize(-direction) - Use for sun/moon
Point (type = 1):
- Has position, no direction used
- Quadratic attenuation:
(1 - d^2/r^2)^2 - radius = 0 means no falloff (infinite range)
- Use for light bulbs, torches
Spot (type = 2):
- Has position AND direction
- Same attenuation as point, plus cone falloff
innerCone/outerConeare cosines of the cone angles (passcos(radians(angle)))- Smooth falloff between inner and outer cone
- Use for flashlights, stage lights
Main Shader
MAX_LIGHTS Constraint
The hard limit is MAX_LIGHTS = 8, defined in both renderer.h and shader.frag. The light array is fixed-size in the UBO (512 bytes). Only numLights entries are evaluated in the shader loop.
Adding shadow maps: Create a depth-only render pass for each shadow-casting light. Render the scene from the light's perspective. Pass the resulting depth textures to the fragment shader as additional samplers and perform PCF shadow testing in calcLight().
Changing attenuation formula: Modify the attenuation calculation in calcLight(). The current formula is (1 - d^2/r^2)^2. For inverse-square falloff, use 1.0 / (1.0 + d*d).
Increasing the light count: Change MAX_LIGHTS in both renderer.h and shader.frag. Update the _pad array in LightUBO if alignment changes. Note that more lights linearly increases per-fragment cost.