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.
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:
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!
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.
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:
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: