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

Member
Posts: 30
Joined: 2009.02
Post: #1
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:

Light Artifacts

Here is how I calculate the alphas for each corner of each tile (oh, and feel free to use/steal this code, of course):

Code:
//NOTE: range = mahLight.radius*mahLight.radius
dx = (mahLight.pixelposx - vTiles[i][j].pixelposx); //delta x
dy = (mahLight.pixelposy - vTiles[i][j].pixelposy); //delta y
dist = dx*dx + dy*dy; // Distance squared

float blint, brint, tlint, trint; //Bottom Left, Bottom Right, Top Left, Top Right alpha intensity
blint = (dist - range) / range; //Linear falloff for an offset of 0,0

dx = (mahLight.pixelposx+4 - vTiles[i][j].pixelposx); //Offset delta x 4 pixels to the right
brint = (dx*dx + dy*dy);
brint = (brint-range) / range; //fall off for an offset of 4,0

dy = (mahLight.pixelposy+4 - vTiles[i][j].pixelposy); //Offset delta y by 4 pixels up
trint = (dx*dx + dy*dy);
trint = (trint-range) / range; //fall off for an offsent of 4,4

dx = (mahLight.pixelposx - vTiles[i][j].pixelposx); //Offset delta x back to 0
tlint = (dx*dx + dy*dy);
tlint = (tlint-range) / range; //fall off for an offset of 0,4


if((vTiles[i][j].a3 + blint) < vTiles[i][j].a3) //pick the brighter (small alpha) of the two
    vTiles[i][j].a3 += blint;

clamp(vTiles[i][j].a3, 0.0f, aii); //don't let it get darker than the ambient light

if((vTiles[i][j].a4 + brint) < vTiles[i][j].a4)
    vTiles[i][j].a4 += brint;

clamp(vTiles[i][j].a4, 0.0f, aii);

if((vTiles[i][j].a1 + trint) < vTiles[i][j].a1)
    vTiles[i][j].a1 += trint;

clamp(vTiles[i][j].a1, 0.0f, aii);

if((vTiles[i][j].a2 + tlint) < vTiles[i][j].a2)
    vTiles[i][j].a2 += tlint;

clamp(vTiles[i][j].a2, 0.0f, aii);

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!
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #2
What is the reasoning for using tiles and subdividing? Couldn't you just render a quad with a lightmap texture for each light? It's very simple and looks just fine.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Member
Posts: 30
Joined: 2009.02
Post: #3
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.

I could be full of bullcrap though. Feel free to educate me. =)
Quote this message in a reply
Member
Posts: 59
Joined: 2007.12
Post: #4
Make the falloff of the light intensity exponential, not linear, and it should blend fine.
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #5
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.

You can also cast shadows easily using the GPU: http://www.gamedev.net/reference/program...oftshadow/ Hard edged shadows are pretty trivial if that's all you want.

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.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Member
Posts: 30
Joined: 2009.02
Post: #6
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.
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #7
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. Annoyed It would totally be fantastic though I think.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Member
Posts: 30
Joined: 2009.02
Post: #8
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!)

3. Render scene then blend the light map with glBlendFunc(GL_ZERO, GL_SRC_COLOR). Or glBlendFunc(GL_DST_COLOR, GL_ZERO).

I'm going to try it right now and see what happens.
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #9
Yep. That's pretty much it. I forgot to mention clear the lightmap with the ambient color, but you seem two be a step ahead of me.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Member
Posts: 30
Joined: 2009.02
Post: #10
Woot! Lighting Success!

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.
Quote this message in a reply
Member
Posts: 283
Joined: 2006.05
Post: #11
Awesome stuff. Might have to have a play around myself.
Quote this message in a reply
Member
Posts: 30
Joined: 2009.02
Post: #12
Just one last shameless plug:
Video of lighting in action
Quote this message in a reply
Member
Posts: 353
Joined: 2002.04
Post: #13
Looks great metacollin! You could have a level set in a nightclub with that lighting! Wink
Quote this message in a reply
Moderator
Posts: 1,560
Joined: 2003.10
Post: #14
Fantastic. I'm looking forward to playing this. Grin
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #15
Now you just need some shadow casting and some pulsing or flickering lights and you're golden. Smile

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  OpenGL ES : Dynamic calculation of triangle indices akademiks 0 4,207 Sep 13, 2011 07:58 AM
Last Post: akademiks
  Lighting and changing texture colors in OpenGL agreendev 2 7,769 Aug 13, 2010 03:47 PM
Last Post: agreendev
  Lighting a sphere with OpenGL lights (normals wrong?) cecilkorik 3 8,188 Dec 27, 2007 02:40 PM
Last Post: _ibd_
  2d opengl lighting question Leroy 9 6,898 Jul 21, 2007 11:41 PM
Last Post: OneSadCookie
  GLSL now in 10.4 sort of.... Mars_999 34 14,861 May 24, 2005 12:26 AM
Last Post: Bachus