2D Dynamic Lighting in OpenGL! Sort of...

OK, first a little background. I was trying to add lighting via 2D textured blended quads, but that proved to be a headache: thread.

NelsonMandella suggested I implement my own dynamic lighting system using quads, and after procrastinating a while, I finally did! It looks great, much better than I was expecting, actually.

Instead of altering the luminosity of tiles directly however, I created an 'ambient light overlay'. Just a quad the size of the viewport with a grid of tiles that are selectively subdivided depending if they need to be lighted or not.

The lighting works by altering the alpha of these tiles, so an alpha of 1.0 is completely black, and an alpha of 0.0 is completely visible. This isn't true lighting I suppose, but I wanted variability in visibility, not actual amplification of the colors. And it is still fully possible to do a color multiplicative overlay in addition to this, so nothing lost.

My quads are 16x16 in coordinates, but 32x32 in pixels, so I subdivide 1 16x16 tile in to 16*4x4 tiles when they require lighting calculations. The problem is when I have two lights intersecting, I get a weird artifact, as seen here:

vTiles are subdivided tiles, i and j are grid coordinates. Note how I pick the lighter of the two values (smaller alpha) and clamp it between totally transparent and only as opaque as the ambient light allows. Also note that intensities within the radius will be negative, since the whole thing is inverted because I am using alphas.

Any thoughts on what I'm doing wrong in my calculations to cause this artifact? Thanks!

Sorry, I should have qualified this. I plan on doing more advanced lighting techniques later, such as animating it (already implemented, lighting equations can change etc. to create pulses and flickers that look much nicer than anything I could achieve by scaling a quad) as well as object light occlusion, color mixing, etc. etc., most of which isn't possible with the 2D quad solution. Also, when two light-mapped quads intersect, their are gradient artifacts due to the blending equation and there being only 256 levels of alpha, something that isn't a problem by doing the lighting procedurally.

Well, you can't change the falloff curve easily without changing the texture, but you can still trivially modulate the size or brightness. Regenerating a texture with a different curve wouldn't exactly be more expensive than recalculating all lights every frame. You could easily get away with a 64x64 or 128x128 lightmap texture without anybody noticing too.

As far as blending, you most certainly can do fancier blending than just adding the two together. I like to emulate Photoshop's screen blending mode as it blends nicely without oversaturating.

Edit: Found my 100% fixed function GPU accelerated shadow mapping demo app. GPUShadow No shadow calculation is done on the CPU, and it's less than 150 lines long so it's pretty simple. Really just a quick shadow masking demo compatible with the iPhone. As such it just uses linear falloff and additive blending. I'd be willing to share the code on request, but not post it here.

Skorche, thanks a LOT for the info! You've more or less totally changed my mind about how I'm going to deal with my lighting, textured quads for the win - and the dynamic equation alterations are something I can live without, or at least futz together with multiple textures/multiple quads blended together.

Here's my problem: I messed around with screen blending in photoshop, and it looks quite nice for colored lights (especially color mixing...blue + red actually makes glowing purple). However, if I just want to increase the visibility of something (like in the screen shot), it doesn't work so well. White painted over it just looks like solid white.

One trick I know is just blending an image with itself with screen to lighten it, but I have no idea where to start on implementing screen blending.

Unfortunately, I have no idea how to implement screen blending within the confines of OpenGL blending. The equation(in OpenGL color space) is Cr = 1- ((1-Sc)*(1-Dc)) where Cr = the resulting color, Sc and Dc are the source and destination colors, correct? It would be easy if there was a GL_MULTIPLY for glBlendEquation(), but...

I suppose I could accomplish the visibility increase using something along the lines of Cr = 1- ((1-Sa*Dc)*(1-Dc)) where Sa = source alpha.

I have a sinking feeling I'm going to have to learn how to use GLSL to make this work. Tell me it isn't true! =)

Just to be clear on what I'm looking at doing:

1. Have white lights that brighten the scene without washing out the color. (In my original implementation in the earlier thread, I was only able to accomplish this by using multiple quads blending over each other, which caused artifacts).
2. Properly mixing color lights (a red, green, and blue spotlight would appear like a while light) would be nice.

Again, thanks for all the help everyone. I know nothing about optics or the physics of light for visual purposes, so you'll have to bear with me. This whole lighting thing was supposed to be simple....dim a scene, have a lantern (spot light like in the screenie) and eventually a directional flashlight (crazy light occlusion shadow stuff...I'll worry about that later/never), but it's sure giving me a headache.

Well, you don't exactly just blend the lights over the top of the image. You build a lightmap first, then multiply that against your scene. However, blending a second copy of your lights over the top of the final lit image makes for a nice foggy effect.

The simplest way to do it is to render the lightmap into a texture. You can even halve the resolution if you want to save on fillrate and it will still look OK. When doing shadow masking, you can eat up a lot of fillrate at full resolution when you have more than a couple lights. Once you have your lightmap, you can draw your scene normally (without any sort of lighting) then multiply the lightmap texture over the top of the scene.

If you want to avoid the render to texture for some reason, then render your lightmap into the framebuffer before drawing anything else. Then draw all of your scene elements from front to back, using a multiply blend mode. You'll have to use the z or stencil buffer to ensure that you never have any overdraw because you only have one copy of the lightmap and you are drawing over the top of it. Doing it this way, you can't use any alpha blended sprites.

A final nifty trick that you can do to add a bit more depth to your lighting is to draw background and midground elements with lighting, then draw your foreground over the top of them using just a constant ambient lighting value.

I've always wanted to use this lighting effect (and a certain physics engine) in a 2D side scroller, but don't have the design skills to make such a game. It would totally be fantastic though I think.

Hmm, multiplying the a light map texture over the scene sounds interesting. Let me just make sure I have it in my head right:

1. Draw all lights currently visible in an empty frame buffer that has been cleared with an ambient light color (If I wanted a totally black scene except where there were lights, this would be black... totally visible, white, half intensity would be 0.5, 0.5, 0.5 etc.).

2. Write it to a texture using glCopyTexImage2D(), making sure to overwrite the old texture (save that VRAM!)

Finally, everything is working as it should. Those are 3 spot lights, but using 1-time procedural generation and a variety of attenuation equation (lambertian looks nice) you can get some nice looking effects. Thanks a lot for that method, Skorche. It solved all my problems.

Just to reiterate for anyone who wants to implement this themselves:

1. Clear the screen with your ambient light color, noting that higher values mean higher visibility. Make sure the alpha is set to 0.

2. Render quads textured with light maps with their alpha determining their intensity. Use glBlendFunc(GL_SRC_ALPHA, GL_ONE);

4. Copy the current buffer to a texture using glCopyTexImage2D() to generate the initial texture, and glCopyTexSubImage2D() to replace it. This was a bit of a hiccup for me, as I read glCopyTexSubImage2D() was faster, so I was just using that, but you have to setup the color space with glCopyTexImage2D() (using GL_RGBA) first.

I haven't tried this yet, but even faster is to use a frame buffer object if your card supports that extension, but dealing with extensions is something for a later date for me.

5. Clear the depth and color buffers and render the scene as normal.

6. Draw a quad over the view port with the texture you generated in step 4. Blend it with glBlendFunc(GL_DST_COLOR, GL_ZERO); Modulate the colors of the quad if you want to multiply in an additional ambient tint at this point, otherwise just use white.

7. Enjoy an episode of the A-Team, you've earned it!

I bet other cool things can be done with this, like mapping a caustic texture using multitexturing in addition to this to create interesting looking underwater scenes or something. I'll have to play around with it.