• interested in more? sign up to the newsletter

     

Shine a light on it

Tweet about this on TwitterShare on RedditShare on Facebook

Schermafbeelding 2015-07-25 om 14.33.47

Some people asked me how I “faked” the nice lights you see in Space Grunts with LibGDX.  I originally used a combination of Box2DLights and Box2D , which gives a lot of nice dynamic shadows where the walls really have mass and block light.  The downside however was that it ran pretty slow on my older iPad.

So I needed something a lot quicker, and I had some idea’s on how to do it, but Simon from Robotality pushed me into looking into it since he mentioned they used something similar (or exactly the same) in Halfway.

Alpha blending

Basically the lights are pre-rendered images and you alpha blend them on top of the game screen in different sizes, colors, and translucency. Here’s an example of how such an image might look:

light

Really simple to create in paint-programs, just take a soft brush with anti-aliasing on the edges, in a white color.. and draw a dot in the middle: tada!

Now this is a pretty “soft” lighting, and I have various other images in my game. One has rougher edges so there is much more white, and another one is a “beam upwards” used for the shine of monitors and such. Let your imagination do the work!

Initializing it

I’ll just explain the theory of it here, so I don’t have a working library to share or anything, cause that would mean a lot of extra work on my end to clean things up and make it user friendly.. maybe after I’m done with Space Grunts.

Again, this is for LibGDX users, but it should all work the same if you use another OpenGL based code or engine.

You’ll need these two in your main class:

FrameBuffer lightBuffer;
TextureRegion lightBufferRegion;

First you’ll need to set up an extra FrameBuffer on which we render the lights,  so place this in your onResize() method to make sure it get’s (re)created whenever the resolution changes :

// Fakedlight system (alpha blending)

// if lightBuffer was created before, dispose, we recreate a new one
if (lightBuffer!=null) lightBuffer.dispose();
lightBuffer = new FrameBuffer(Format.RGBA8888, PowerOf2(lowDisplayW), PowerOf2(lowDisplayH), false);

lightBuffer.getColorBufferTexture().setFilter(TextureFilter.Nearest, TextureFilter.Nearest);

lightBufferRegion = new TextureRegion(lightBuffer.getColorBufferTexture(),0,lightBuffer.getHeight()-lowDisplayH,lowDisplayW,lowDisplayH);

lightBufferRegion.flip(false, false);

The lowDisplayW and lowDisplayH values are usually used for my pixel-art rendering. So no matter what the actual screen resolution is, stuff happens in the lowDisplayW x lowDisplayH size. You can make them equal to your displayWidth and height if you prefer or what ever you are used to for your own games. Just as long as your lightBuffer covers the same amount of screen space as your game canvas.

Rendering it

Now in your game’s render() method, is where the magic happens. First you render you game screen like always (most likely to the standard frame buffer, or to your own created framebuffer, doesn’t matter).

Once all that is done, we start rendering the lights to our lightBuffer. For this example, I will just render a single light at the center of the screen. Once you get this working you will want to put a bunch of lights in an object-list to maintain their position, size and color.

The result of this should be a darkened game-canvas with a bright spot in the middle of the screen

	        	
// start rendering to the lightBuffer
lightBuffer.begin();

// setup the right blending
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
Gdx.gl.glEnable(GL20.GL_BLEND);
		        
// set the ambient color values, this is the "global" light of your scene
// imagine it being the sun.  Usually the alpha value is just 1, and you change the darkness/brightness with the Red, Green and Blue values for best effect

Gdx.gl.glClearColor(0.3f,0.38f,0.4f,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
			        
// start rendering the lights to our spriteBatch
batch.begin();


// set the color of your light (red,green,blue,alpha values)
batch.setColor(0.9f, 0.4f, 0f, 1f);

// tx and ty contain the center of the light source
float tx= (lowDisplayW/2);
float ty= (lowDisplayH/2);

// tw will be the size of the light source based on the "distance"
// (the light image is 128x128)
// and 96 is the "distance"  
// Experiment with this value between based on your game resolution 
// my lights are 8 up to 128 in distance
float tw=(128/100f)*96;

// make sure the center is still the center based on the "distance"
tx-=(tw/2);
ty-=(tw/2);

// and render the sprite
batch.draw(lightSprite, tx,ty,tw,tw,0,0,128,128,false,true);

batch.end();
lightBuffer.end();


// now we render the lightBuffer to the default "frame buffer"
// with the right blending !

Gdx.gl.glBlendFunc(GL20.GL_DST_COLOR, GL20.GL_ZERO);
batch.begin();
batch.draw(lightBufferRegion, 0, 0,displayW,displayH);               
batch.end();

// post light-rendering
// you might want to render your statusbar stuff here

That’s the basic theory behind it. Of course you’ll want to put your lights into some sort of object pool so you can easily add lights in various shapes, distances, and colors. By varying the distance you can make bigger or smaller glows. Here’s another scene showing some of the possibilities:

Schermafbeelding 2015-07-25 om 21.21.45

The lights are fairly dynamic in that they are rendered every frame, so you can make the colors fluctuate a bit, have lights move around in patterns just like any other sprites.

Thanks to the sprite batch, the rendering is extremely fast and all lights are rendered in one single call. Some of the Space Grunts scenes have over 100 lights without any frame-rate drops on any of my test devices.

The only down side is that you don’t have dynamic shadows, in fact you don’t have shadows at all. For most games that’s a small tradeoff compared to the frame-rate bump, especially if you also target mobile. Lights can also have more different shapes, so you can have laser-beams, star-shaped lights, and much more.

This was mostly just a copy+paste code from Space Grunts, not exactly tested so let me know if I made some typo’s or anything so that I can fix it.

Here’s the light stuff in motion:

Bookmark the permalink.
  • gemserk

    Hi, I think you should add the video you have on youtube to the blog post, it is easy to see the lighting technique than looking at a static image, just a suggestion.

  • Steveo

    So basically just render all the lights/lighting to an FBO then alpha blend that with he screen? Sounds good if you don’t need shadows. It kind of has the illusion of shadows from the lack of lighting in the other areas. I might use this in my next 2d game.

    • yep that’s all :) and indeed you don’t get shadows, but it’s a fairly small trade-off – I had the same system running with box2dlights and shadows being casted, but in most scenes the shadows were hardly noticeable.

  • Guilherme Cherem Grillo

    Really cool.
    All my java games I just used the standard library and make the light manipulating the pixels and making the blend. Now I’m starting to release games for mobile devices and trying to learn libGDX.
    I released 2 small games on the Apple Store but using Game Maker for the simplicity to export. But now I’m thinking about libGDX :S

    I really admire your work, can you give me a suggestion for who is starting on the market?

  • Devis

    Hello, I’m trying and I can change the “global light of the scene”, but the other light is not working for me. Just appears a square in the location of the lightBufferRegion.

    The lightSprite is an texture with the alpha blending image? I create my own in photoshop with transparent background (equal to the beginning of the post)

    Pseudo-code (render):

    – Your lightbuffer.begin/end batch.begin/end code
    – Batch.begin, draw my background, character. Draw the lightBufferRegion, batch.end.
    – Draw interface (scene2d with different batch).

    • yes the sprite is alpha-blending image with a transparent background.

      Do you do your normal rendering (game screen) to a framebuffer? if so, make sure to end that framebuffer before starting the LightBuffer.

      • Devis

        Hello, now is working, I just changed Gdx.gl.glBlendFunc(); =)

        But I’m afraid of performance, I’m using batch.begin()/end() three times (the game itself, inside fbo, lightbuffer), I’m using a stage (with other batch) and ShapeRenderer for some geometries.

        Thank you so much, the effect is amazing! :D

  • Christian Zimmer

    I tried to follow your approach but sadly the blending does work as intended.
    When drawing a “background” and rendering multiple “lights” above that, the set color for the light(e.g. Blue) will not be rendered in that color.

    Rendering the lights as sprites is working as intented, but i assume this doesn’t scale very good performance wise when increasing the light sources.

    Still investigating about FBO usage …

    Nevertheless thank you for your inspiring work …

    • you do set the color of the light in batch.setColor(…) ? That’s basically all that’s needed to change the color of the light sprites

      • Christian Zimmer

        Yes i did. But where’s the place to render the tilemap/background ? I’ll elaborate there more … Thank you.

  • Lars Matthäus

    Hmmm … what do you mean with “PowerOf2” ? Never heard about this and also dont know what that method did

  • Lars Matthäus

    Hey there ! Im currently trying your alpha blending, but i have a big problem … Could you help me please ? :/ What is wrong with that ?

  • Andrew Hung

    Great stuff. I ported this solution to Unity for my game using RenderTextures and it’s working beautifully. Thanks for the writeup.

  • I had to make one or two changes to get this working but helped me loads!
    https://gist.github.com/tyler6699/58a00ba6e679990984f8b090558ac3b4

    Main change was how I set the blend function of the spritebatch
    batch.setBlendFunction(GL20.GL_DST_COLOR, GL20.GL_ZERO);

    https://uploads.disquscdn.com/images/16aa7700b141554cde0b4896855720cebcc0045e547a5192d8e455e2030be833.gif