Thiago
Thiago

Reputation: 471

2D Software Lighting Issues in Java

I'm creating a 2D top-down tiled game in pure Java and by now I'm trying to implement a way to do lighting.

First, some details on how I render: there is a screen class which handles all the rendering. Pixels are put inside a int[width * height] array which is drawn on the actual screen using a BufferedImage and Graphics.drawImage().

The way I am doing lighting right now is by creating a second int[width * height] array which holds all the brightness that should be subtracted from the actual pixels. I try to use only floats ranging from 0f to 1f to represent RGB intensities.

So, as an example, there is the ambient lighting which is 0.3f. What I do is a pixel to pixel operation, where for each pixel that will be drawn, its RGB values are decomposed, decreased 70% (1f - 0.3f), recomposed back into a single int and stored inside the secondary int[width * height] array. When transferring the pixels to the BufferedImage, I subtract the corresponding value inside the secondary array and get a darker image, as expected.

That was an example of darkening the image and it works fine, the problem appears when actually lighting them up: I gave the player a lightRadius and 3 light color component floats (RGB). While rendering the player, I also render it's lighting inside the secondary array by doing the following:

int mask = subtractMask[xx + yy * width];

int mr = (mask >> 16) & 255;
int mg = (mask >> 8) & 255;
int mb = mask & 255;

mr -= (int) (dist * light.r * mr);
mg -= (int) (dist * light.g * mg);
mb -= (int) (dist * light.b * mb);

subtractMask[xx + yy * width] = mr << 16 | mg << 8 | mb;

Where dist is a number ranging from 0f to 1f which makes the light fade with the distance from the source and light.r/g/b are each of the components of the light that comes from the player. What this piece of code does is basically to "give a little bit of the light that was taken out of a pixel back to it". As you can see, mr, mg and mb are made smaller (-=) and then composed back inside the number that will be subtracted from the corresponding pixel, thus making the result of the subtraction bigger (lighter). For white light I get the desired result:

While light with 50 pixel radius

The problem is when I only use the blue component (r = 0f, g = 0f, b = 1f), getting this:Blue Light with the same 50 pixel radius

Of course this can be explained by the fact that the tiles have very little blue values, making the ambient light take a little part of it away and making the lighting give this little part of it back (as you can see the biggest effect is on the player's pants, which are blue). What I still couldn't think of is a way to make lighting independent of how much brightness was taken away from the pixels and something that simply looks good in any way I combine the components (the result I expected for the second image was luminance like in the first one, but colored blue). So how can I implement such an effect?

Also, for making the lighting circular, I simply compute something light x^2 + y^2 <= radius^2, where if this sentence is true, the pixel should light up, executing the code I've showed before. Is there a faster way to compute this? (I lost almost 50 FPS on this effect).

Edit: the effect I want is something like the 2D game Tibia. This is blue light over brown pixels: Tibia - Blue Light

Upvotes: 0

Views: 705

Answers (1)

Russell Zahniser
Russell Zahniser

Reputation: 16354

I think that what might work best is to add the ambient and player light values first, clamping to the [0...1] range, and then multiply the pixel values.

So:

Ambient light: [ 0.3, 0.3, 0.3 ]
Player light (at some location): [ 0.0, 0.0, 1.0 ]
Total light: [0.3, 0.3, 1.0 ]

Raw pixel value: [ 100, 200, 100 ]
Output pixel value: [ 30, 60, 100 ]

If you do this, you can also just have a static float[] representing the amount of light at each pixel, changing it only when the ambient or player light changes. Then, you don't need to compute light each frame, just apply it in a single pass over the whole image.

Upvotes: 1

Related Questions