Open-source project
zaycev/bevy-magic-light-2d avatar
zaycev/bevy-magic-light-2d

bevy-magic-light-2d: a 2D global illumination system inside Bevy

Experiment with computing 2D shading, lighting and shadows with Bevy Engine

590 stars49 forksRustApache-2.0

At a glance

What is it?
bevy-magic-light-2d is an experimental Bevy plugin that computes dynamic 2D global illumination, using signed distance fields to test occlusion and a screen space cache of irradiance probes to capture bounced light. The project says plainly that it is heavily under construction, and its task list is long.
Who is it for?
bevy-magic-light-2d fits a Bevy developer prototyping a 2D game where flat lighting looks wrong, since the technique it implements, screen space irradiance with signed distance field occlusion, is the sort of thing that is hard to write from scratch and easier to adopt than to invent.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Global illumination, in two dimensions

The project is an experimental dynamic 2D global illumination system for the Bevy engine, built on SDF ray-marching and screen space irradiance cache probes. The description of what that means in practice is a 2D lighting model where light is not simply blocked by a sprite but also bounces, so a wall lit by a lamp picks up a warm colour and the surface next to it picks up a dimmer version of the same.

Global illumination is normally an expensive technique, and the usual cost is in solving how much light arrives at each point. Doing that per pixel is prohibitive, so this implementation makes two compromises that are visible in the design: it works in screen space, so the cache is tied to what the camera can see, and it approximates the solution with a sparse set of probes rather than a full solution.

The project's own framing is blunt about its state. A warning at the top of the notes says to beware, that the repository is heavily under construction and a lot of things may change. That framing should be read as a description of the API surface rather than of the algorithm, but it applies to both.

Four passes, from occluders to final colour

The implementation is described as several approaches, and the sequence is the interesting part.

The first pass computes a signed distance field for all occluders and stores it in a texture with one channel. The second pass computes irradiance from direct light: it checks the amount of light received by a probe, taking into account occlusion, which is checked using the distance field, and the final contribution for each light source is computed using a square falloff. The third pass computes the secondary bounced light, and the approach is similar to the second, except that instead of direct light it uses irradiance sampled from the probes, with exponential sampling to check how much reflected light a probe receives, and the same distance field for occlusion and the same square falloff for the contribution.

The last stage combines the results of the second and third passes, combining the cache from the previous eight frames, and then optionally filters the result using an edge-aware smoothing filter and applies gamma correction. The frame history is what makes the bounce stable rather than flickering, since the secondary pass is fed by a temporally accumulated estimate, and the eight frame window is a small amount of latency traded for a calmer image.

The probes are where the performance comes from

The stated main performance gain comes from computing the value of only one sixty-fourth of the pixels, for a probe size of eight by eight, with the rest of the pixels interpolated from the nearest probes.

That single number describes the whole architecture. Eight by eight blocks of screen space are evaluated directly, which is one value for every sixty-four pixels, and everything else is interpolated from those samples. The cost of a full per-pixel solve is therefore replaced by a per-block solve plus an interpolation, which is the classic trade in real time rendering and the reason the technique fits a game loop at all.

The design has an obvious consequence that the project acknowledges elsewhere in its task list. A screen space probe grid samples what is visible, so a surface that is off screen or occluded at the moment the probe was computed has no irradiance estimate, and light bouncing from an occluder that is not itself visible is the open item about light bouncing from occluders. The distance field work around that, computing the signed distance field for offscreen occluders, is marked as done.

Running the examples

There are three examples, and they are started with cargo:

shell
cargo run --example minimal
cargo run --example krypta
cargo run --example movement

The repository holds one file per example, and the krypta one is the one the usage notes point at. The controls are short: WASD moves the camera, holding shift and using the left mouse button places a light source, and the right mouse button changes the colour of the light source.

Those controls describe what the plugin actually computes. Because it is a screen space method, moving the camera changes what the probes can see, and placing lights interactively is the fastest way to judge whether the bounce term is doing anything useful in a given scene. The third example, movement, presumably exists for the same reason, to see the behaviour when the scene changes under a static light.

The examples are the documentation here, and the notes flag that as a known gap, with an open task to add examples and a heads-up display explaining how to use them.

The task list is the roadmap

The project keeps an explicit task list, and it is the best source of information about the limits of the technique.

Under optimisations, three items are open: using the jump flood algorithm for calculating the signed distance field, precomputed noise, and guided sampling for the secondary light. Under features, the open items include light bouncing from occluders, an arbitrary number of bounces through configuration, support for multiple layers, support for transparent occluders, colour transfer from occluders, emissive surfaces and other kinds of light source, and examples with a heads-up display. The items marked as done are handling camera scale and rotation, exposing settings instead of hardcoding them, support for resizing of the targets, an inspector for the global illumination settings, and the signed distance field for offscreen occluders.

Read as a whole, that list describes a technique that is close for a single layer of opaque sprites with a small number of lights, and unfinished for anything layered, transparent, or emissive. The transparent occluder item in particular matters, because a game with a partly transparent wall or a window is a common case where the current occlusion test would be wrong.

The Bevy version in the manifest is newer than the table

The compatibility table in the project notes pairs Bevy releases with plugin versions, with 0.16 mapping to 0.10, 0.15 to 0.9 and 0.14 to 0.8. The manifest in the repository, however, depends on the 0.17 line of Bevy:

code
[dependencies]
bevy = { version = "0.17.*", default-features = false, features = [
  "bevy_render",

So the table is out of date relative to the code, and the crate version in the manifest is 0.10.0 while the table still claims that 0.10 pairs with Bevy 0.16. Anyone pinning a version needs to read the manifest rather than the table.

The dependency list is also a good description of what the plugin needs from the engine, and it is a short one because the default features are switched off. The features enabled are the render pipeline, the core pipeline, the windowing, the asset handling, the sprite renderer, the physically based renderer, post processing, the embedded watcher and the multi threaded scheduler. Notably, the physically based renderer is enabled even though this is a 2D lighting system, presumably because the sprite renderer sits on top of it, and the embedded watcher is what makes the examples reload on a file change while you iterate.

The inspector is an optional feature, enabled by default, and it pulls in the Bevy inspector with the egui integration so that the global illumination settings can be adjusted at runtime rather than only in code.

Build profile, and a project that moves slowly

The release profile is tuned for a plugin that runs every frame. It sets a single codegen unit, which lets the optimiser work across the whole crate, thin link time optimisation, an optimisation level of three, and panic behaviour set to abort. The development profile inverts part of that, with sixteen codegen units and optimisation level zero for the crate itself, while a package level override sets optimisation level three for everything else, so dependencies are still built with optimisations while the plugin you are editing stays fast to compile.

The maintenance picture is modest. The tagged releases are 0.7.0 and 0.8.0 in August 2024 and 0.8.1 in September 2024, and the last push to the repository was on 2026-04-03. So the code is being touched, but the published versions have not moved since 2024, which means anyone adopting this is tracking the branch rather than a release, and should expect the API to shift under them, exactly as the warning at the top of the notes says.

The licence is Apache-2.0, with the copyright held by the author, and there is a Discord channel for questions.

Editorial conclusion

bevy-magic-light-2d fits a Bevy developer prototyping a 2D game where flat lighting looks wrong, since the technique it implements, screen space irradiance with signed distance field occlusion, is the sort of thing that is hard to write from scratch and easier to adopt than to invent. It is the wrong choice for a shipping project, because the project warns that it is heavily under construction and lists multiple layers, transparent occluders, emissive surfaces and arbitrary bounce counts as open work. Before you build on it, check the Bevy version yourself, since the manifest depends on the 0.17 line while the compatibility table in the project notes still lists older releases, and read the task list to see which of the limitations matter for your scene. The licence is Apache-2.0, the crate version is 0.10.0, and the last push was on 2026-04-03.

Frequently asked questions

What is bevy-magic-light-2d?

It is an experimental dynamic 2D global illumination system for the Bevy engine, based on SDF ray-marching and screen space irradiance cache probes. It computes bounced light in a 2D scene so that surfaces lit indirectly pick up colour from their surroundings.

How do I run the bevy-magic-light-2d examples?

With cargo, using the minimal, krypta or movement example. In the krypta example, WASD controls the camera, shift with the left mouse button places a light source, and the right mouse button changes the colour of that light source.

How does bevy-magic-light-2d compute bounced light?

In a third pass that samples irradiance from the probes rather than from direct light, using exponential sampling to estimate how much reflected light a probe receives. Occlusion is checked with the same signed distance field used in the direct pass, and the final contribution uses a square falloff.

Which version of Bevy does bevy-magic-light-2d target?

The manifest depends on the 0.17 line of Bevy with the default features turned off and a specific list of render, windowing, asset, sprite and post processing features enabled, while the compatibility table in the project notes still lists Bevy 0.16, 0.15 and 0.14, so the manifest is the reliable source.

Is bevy-magic-light-2d ready to use in a game?

The project notes warn that the repository is heavily under construction and that many things may change, and the task list still has open items for multiple layers, transparent occluders, emissive surfaces and a configurable number of bounces. The last push was on 2026-04-03 and the latest tagged release is from September 2024.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. zaycev/bevy-magic-light-2d on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zaycev-bevy-magic-light-2d.svg)](https://hysenlabs.com/projects/zaycev-bevy-magic-light-2d)

Was this useful?