A powerful 2D lighting engine for LibGDX, combining dynamic per-pixel lighting, normal mapping, and Box2D shadows. Supports both scene2d and ECS-based architectures like Fleks, with a focus on flexibility and real-time performance.
-
Dynamic Per-Pixel Lighting
Smooth, realistic lighting effects calculated per pixel. -
Normal Mapping
Adds depth and surface detail to 2D sprites using normal maps. -
Specular Mapping
Enables shiny, reflective surfaces using specular maps. -
Multiple Light Types
- Point Lights: Omnidirectional, with customizable falloff.
- Spot Lights: Cone-shaped, for flashlights or beams.
- Directional Lights: Global light from a direction, like sunlight.
-
Box2D Integration
Usesbox2dlightsfor real-time, physics-based shadow casting. -
Scene2d Integration
Seamless lighting forscene2dactors and stages. -
Light culling
Automatically deactivates distant lights to optimize performance. -
Variable Refresh Rate Optional Box2D light update capping via refreshRateHz (fixed cadence) to stabilize performance spikes; can be changed at runtime using setRefreshRate(hz)
-
Multipass Rendering Multi-pass friendly pipeline: render Box2D lights via renderBox2dLights() independently from sprite drawing/compositing
-
Effect System
Lights can have built-in effects like:FIREPULSEFAULTY_LAMPLIGHTNINGCOLOR_CYCLENONE
-
Flexible API Design
LightEngine: Low-level, ECS-compatible engine.Scene2dLightEngine: High-level, plug-and-play engine for scene2d users.
// In core/build.gradle.kts
dependencies {
implementation("io.github.bennyoe:gdx-normal-light:1.0.5")
}// The following dependencies are required only for pure Java LibGDX projects (not Kotlin):
dependencies {
implementation("io.github.bennyoe:gdx-normal-light:1.0.1")
api("io.github.libktx:ktx-math:$ktxVersion")
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
}val rayHandler = RayHandler(world)
val lightEngine = LightEngine(rayHandler, cam, batch, viewport)val pointLight = lightEngine.addPointLight(
position = Vector2(5f, 5f),
color = Color(1f, 0.5f, 0.2f, 1f),
initialIntensity = 10f, // Base intensity for the light
b2dDistance = 15f, // Range for Box2D shadow casting
shaderIntensityMultiplier = 0.8f // Fine-tune the visual brightness in the shader
)
pointLight.effect = LightEffectType.FIRElightEngine.update()
lightEngine.renderLights { engine ->
engine.draw(diffuseTexture, normalMapTexture, specularTexture, x, y, width, height)
engine.draw(diffuseTexture, normalMapTexture, x, y, width, height)
engine.draw(diffuseTexture, x, y, width, height) // Unlit
}val stage = Stage(viewport, batch)
val lightEngine = Scene2dLightEngine(rayHandler, cam, batch, viewport, stage)val myActor = NormalMappedActor(diffuseTexture, normalMapTexture)
stage.addActor(myActor)val pointLight = lightEngine.addPointLight(...)
val lightActor = LightActor(pointLight)
stage.addActor(lightActor)stage.act(delta)
lightEngine.update()
lightEngine.renderLights { engine ->
for (actor in stage.actors) {
engine.draw(actor)
}
}The engine automatically performs light culling every frame based on proximity to a specified center (usually the player or camera). Lights that are too far away from this center are temporarily deactivated unless they are directional lights, which are always active.
This mechanism:
- Improves performance by limiting shader computations to only nearby lights and turning off box2d-lights that are not in range.
- Ensures the engine respects the configured
maxShaderLightslimit (default: 32). - Is configurable via
lightActivationRadius, which defines the maximum distance for lights to be considered "active". Set to-1fto disable distance-based culling.
Directional lights are always included in the lighting pass, regardless of distance.
The repo includes two demos:
LightDemo: UsesLightEnginewith ECS-style renderingScene2dLightDemo: UsesScene2dLightEnginewith actors
To run a demo, open GgdxNormalMapExample.kt and modify the demo class:
addScreen(LightDemo())
setScreen<LightDemo>()
// or
addScreen(Scene2dLightDemo())
setScreen<Scene2dLightDemo>()Then execute:
./gradlew run| Key | Action |
|---|---|
1, 2 |
Switch between Point and Spot lights |
BACKSPACE |
Toggle directional light |
SPACE |
Toggle diffuse lighting |
N |
Toggle normal map lighting |
Q / A |
Increase / decrease shader intensity |
W / S |
Increase / decrease light distance |
E / D |
Adjust shader intensity multiplier |
R / F |
Adjust spot cone angle |
T / G |
Rotate spotlight cone |
I / K |
Adjust directional light intensity |
O / L |
Rotate directional light |
Y / H |
Adjust specular intensity |
| Mouse Wheel | Change light hue |
| Mouse Move | Move active light |
- Language: Kotlin
- Framework: LibGDX
- Physics: Box2D
- Lighting: box2dlights
- Optional: LibKTX (for demos)
| Engine Type | Description |
|---|---|
LightEngine |
Low-level core renderer, no assumptions about actors/entities |
Scene2dLightEngine |
Wraps LightEngine, works with scene2d stages and actors |
- Based on mattdesl's shader gist
- Thanks to Quillraven for inspiration YouTube Tutorials

