Physical Light 🔆

LibGDX Box2d Light

Vladislav Shesternin
5 min readApr 23, 2024
Physical Light

⬇️ Download: Physical Light — Apps on Google Play. 📱

In this article, I will tell you how to create and adjust the Light in LibGDX using Box2dLight.

Let’s first understand what light is.
Light is simply many rays scattered in different directions that can reflect off physical bodies or pass through them.

Light

And where does all the work on light take place?

RayHandler — Handler that manages everything related to lights updating and rendering.

In order to create light, the first thing you need to do is create and configure RayHandler:

1️⃣ We create an object RayHandler and transfer our Box2d world to it:

private val rayHandler = RayHandler(world)

2️⃣ Configure Viewport in method resize for correct calculation and display:

override fun resize(width: Int, height: Int) {
super.resize(width, height)
viewportBox2d.apply {
rayHandler.useCustomViewport(screenX, screenY, screenWidth, screenHeight)
}
}

I use the same viewport (FitViewport) as for the Box2d world.

3️⃣ We set the matrix, update and render in the render method:

override fun render(delta: Float) {
super.render(delta)
rayHandler.setCombinedMatrix(viewportBox2d.camera as OrthographicCamera)
rayHandler.updateAndRender()
}

As you can see, the sequence is important, first it goes super.render because the world and actors are rendered there, then the light is rendered, you can change the sequence and see what happens.

If you run this code now, no matter what your main background is, it will still be completely black, to change it, you need to configure setAmbientLight.

setAmbientLight — Sets ambient light brightness.

There are many overloads of this method where you can both set the brightness and change the color in general.

  • If you want to leave the background of your game as it was before the implementation of light:
private val rayHandler = RayHandler(worldUtil.world).apply {
setAmbientLight(1f)
}
  • If you want to set the background color:
private val rayHandler = RayHandler(worldUtil.world).apply {
setAmbientLight(Color.BLUE.apply { a = 0.5f })
}

RayHandler — there are many more methods, but in this article I will not explain them, they are simple, it will not be a problem for you to understand them.

Now let’s go directly to the creation of light, but first we need to choose what kind of light we want to create:

There are 4 types of light in Box2dLight:

  • PointLight;
  • ConeLight;
  • ChainLight;
  • DirectionalLight.

PointLight — light shaped as a circle with given radius.

PointLight
private var pointLight: PointLight? = null

override fun show() {
super.show()

pointLight = PointLight(rayHandler, 360, GdxColor.light, 700f.toB2, 500f.toB2, 500f.toB2)
}

1️⃣ rayHandler — regardless of the type of light, we always pass the RayHandler we created earlier as the first parameter.

2️⃣ rays (360) — this is the number of emitted rays, the rays are necessary to calculate the collision of light with a ray, the more rays there are, the more realistic the light will be, but also more expensive, so the number of rays depends on your needs, but usually this number is from 100 to 200, you can also set it to 1000, but is it worth doing it this way it already depends on you (MIN_RAYS = 3).

3️⃣ color (GdxColor.light)— color of light.

4️⃣ distance (700f.toB2) — light coverage radius.

5️⃣,6️⃣ x, y (500f.toB2, 500f.toB2) — position (center) of light.

Float.toB2 — this is the transformation of my coordinates into Box2d World coordinates.
You can read more about this in my article 🔗LBJT: Сommon in Joints 1️⃣.

ConeLight — light shaped as a circle’s sector with given radius, direction and angle.

ConeLight
private var coneLight: ConeLight? = null

override fun show() {
super.show()

coneLight = ConeLight(rayHandler, 360, GdxColor.light, 700f.toB2, 500f.toB2, 500f.toB2
directionDegree: 0f,
coneDegree: 30f
)
}

1️⃣ to 6️⃣ — The same as in PointLight.

7️⃣ directionDegree — initial angle of light (measured in degrees).

8️⃣ coneDegreehalf the sector size (measured in degrees).

ConeLight: directionDegree | coneDegree

I think now you understand that if you set coneDegree = 90, the light will take 180 degrees, if you set coneDegree = 180, the light will take 360 degrees.

Everything is simple 😉

ChainLight — the light shaped as a line that is placed on the line (chain) that you provide.

ChainLight
private var chainLight: ChainLight? = null

override fun show() {
super.show()

val chainArr = arrayOf(
10, -10, 10, 10,
).map { it.toFloat().toB2 }.toFloatArray()

chainLight = ChainLight(rayHandler, 360, GdxColor.light, 700f.toB2,
rayDirection: -1f,
chain: chainArr
)
}

1️⃣ to 4️⃣ — The same as in PointLight.

5️⃣ rayDirection — direction of rays (1 left | -1 right).

6️⃣ chain — float array of (x, y) vertices from which rays will be evenly distributed.

ChainLight: chain | rayDirection

DirectionalLight — light which source is at infinite distance (like the sun).

DirectionalLight
private var directionalLight: DirectionalLight? = null

override fun show() {
super.show()

directionalLight = DirectionalLight(rayHandler, 360, GdxColor.light,
directionDegree: 270f, (-90f)
)
}

1️⃣ to 3️⃣ — The same as in PointLight.

4️⃣ directionalDegree — initial angle of light (measured in degrees).

General light settings:

  • setSoftnessLength
  • isSoft
  • isXray
  • isStaticLight

setSoftnessLength — works as a light transmission effect throughout the body, if set to a large value, it can work as a magnifying glass (default 2.5).

setSoftnessLength

isSoft — accepts or does not accept the value setSoftnessLength, if true then the values setSoftnessLength work, if false then the same as setSoftnessLength(0f).

isXray — it works like an X-ray by simply passing light through bodies.

isStaticLight — static lights are useful for lights that you want to collide with static geometry but ignore all the dynamic objects. Reduce CPU burden of light about 90%.

There is 1 more very useful method:

attachToBody(body: Body) — attaches the light to the body.

How to manage the world’s contacts with bodies?

Everything is very simple, each light has a method setContactFilter.

PS. Vel_daN: Love what You DO 💚.

--

--