Sign in to follow this  

2d lighting using multiply?

This topic is 3081 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all, I am working on lighting in my 2d game (XNA) but am running into some problems with the design. I have the following requirements: 1. The part of the scene not affected by lights must be darkened and tinted (at night it would be a dark blue tint). 2. Light sources must be additive. 3. Since the perspective is partially from the side and partially from the top, there can be occluding objects in front of the light sources that mask their effect. (An example could be a tree that blocks the light from a campfire). My first design idea is to produce a texture that I can multiply blend with the whole scene at the end. I call this my lighting map. The way multiply blend works is that it results in a lighter area where the lighting has a value higher than 0.5, no effect where it is exactly 0.5, and a darker area where the value is lower than 0.5. This makes it possible to achieve both the lighting and the darkening effect with the same light map. To start off, I clear the lighting map to a value of RGB (0.2, 0.2, 0.4) - I call this the ambient color - this gives me a dark blue night scene when multiply blend is used. Now I want to add light sources to the scene, so I render the light source sprites, which are basically gradient circles, using an additive blend. This brings the lighting map value above 0.5, and the effect is additive which is what I want. So far so good. But the problem occurs when I render an occluding object. Preferably, I want such an object to reset the lighting map value to the ambient color. But how can I do this in the pixel shader when the blend function is additive? This is the fundamental problem with this design. I have tried to use the alpha channel and specify a separate blend mode for the alpha channel in order to save this information, but it doesn't really work... What I am really after is a good way to design this... blend modes, processing steps, channels to use - any insight is appreciated. I have a few images that can show what I have so far. lighting map alpha channel: lighting map color channel - you can see here that the rendering of the trees (occluders) result in a lighter color than the ambient color because of the additive blend that is used. If I could avoid this somehow it would be very nice: Clearly, these images represent a technique that doesn't work. I can provide the shader code that was used in producing them if needed, but I think I need to start over. Do I need to ditch the multiply technique do you think?

Share this post


Link to post
Share on other sites
Welcome to the tricky world of ambient lighting :) Your direct lighting is additive, which is a good thing. Then just add the ambient portion as well, at all times (thus also when pixel is being hit by 1 or more direct lights).

You could create ambient-lightMaps. These lightMaps do not include dynamic lights, neither direct lighting. If you want day/night cycles, you could choose for an occlusion map instead. These maps contain occlusion values instead of colors. For each pixel in the lightMap, check how much (static) objects are surrounding it. The more occlusion, the lower the factor. Pixels in the open field get the highest value, 1.

When doing ambient lighting, the result would be
ambientColor = lightMap.occlusionFactor * overallAmbientLightColor;

This 'overallColor' could be darkblue at night, greenish when it storms, yellow/white for daylight, and so on. You can also include an overall incoming lightvector to fake normalMapping on the 'unlitten' ambient parts.

You can just add this color to the rest of your lights:
finalLighting = ambientColor + light1 + light2 + lightN...;


It must be said that ambient occlusion maps work for outdoor area's, but not that good for indoor area's. You can still measure occlusion for indoor scenes, but the 'overallLightColor' is not so simple. In reality, its a mixture of the sky, reflected sun/moon light, reflected lampposts, and so on. For example, the streets at night are not only illuminated by the moon, but also by the lights from buildings. If you want a more complex solution, you could try generating multiple ambient LightMaps. 1 for a daylight setting, 1 for a night setting. The "night-ambient-LightMap" is rendered by measuring incoming lights from building windows and lampposts.

As the day proceeds, you can mix between the 2 lightMaps. Finally you could get something like this:
moonSunAmbientColor = occlisionLightMap().x * currentDayCycleColor;
fixedEnvironmentAmbientColor = lerp( dayLightMap, nightLightMap, currentCyclePos );
result = moonSunAmbientColor + fixedEnvironmentAmbientColor + light1 + light2 + ...;

You could simplify with fixed sector ambient colors as well of course. As you can create a color scheme for outdoor area's, you can do the same for any other sector (inside buildings, inside a tunnel, in a forest, and so on).

Greetings,
Rick

Share this post


Link to post
Share on other sites
Thank you for the suggestions. But do you think ambient occlusion is really appropriate? Isn't that a 3d technique?
I mean, my entire 2d world is fake - trees and buildings are just billboard quads drawn over a flat tiled terrain. There are no normals, and stuff has only a point location in the world - no size or dimension.

My light sources are also just sprites/billboard textures containing a gradient in some form.

I'd be satisfied if I just had a way to fake the lighting with the requirements stated above. There is no need for drop shadows from these light sources.

And my entire game is outdoors.

Share this post


Link to post
Share on other sites
Whoops, forgot the 2D part. No need for lightMaps and all that stuff, and luckily its outdoor :)

Nevertheless, additive rendering with the ambient as well is still an idea. Here is one of the probably 1000 possible ideas.

1.- Diffuse Texture Pass
I would first render the entire thing with no lighting at all, just the texture colors.
pixelColor = textureColor;
So in this pass you actually render the ground, sprites, billboards, and any other effect. And remember, no lighting so far! This rendering should be stored in a (fullscreen) texture, either a FBO/RenderTarget or snapshot (copy screen to texture) for older hardware. This "diffuse texture" will be used for all lighting passes that follow. It's the basic info about your scene, and it also prevents that you have to render all thise billboards again and again for 1 frame.

2.- Ambient Pass
Simple, render the diffuse texture from step 1 on the screen, multiplied with your ambient color.
pixelColor = ambientOverallColor * diffuseTextureColor;
Just render a big screen-filling quad with that diffuseTexture on it, and apply a color. You don't even need a shader for this, although it makes life easier.

3.- Lights
On top of this basic ambient layer we render 1 or more lights. For each light, calculate the outer-bounds of a rectangle that covers the entire circle (or whatever shape you are using). Render (ADDITIVE) a quad on top of your 'ambient pass layer', with those coordinates, and apply the light mask texture + lighting shader on that quad. This shader grabs the background from the "diffuse Texture" (step 1) and multiplies it with its lightColor and mask texture:

quadPixel = maskTexture.x * lightColor * diffuseTexture(x,y);
// maskTexture could also be a gradient calculated in the shader itself

The x/y texcoords are projective, not those from the quad! Calculate for the 4 quad vertices their screen coordinates. If vertex1 would be at pixel 400,100 on a 800x600 screen, its xy coordinate would be {0.50 , 0.166}. You can do this quite easily in the vertex shader, or pass second texcoords via the CPU.


4.- Depth / Occlusion problem
This will give you an ambient background with hightlighted(litten parts) spots on it. So far the lighting works, except that lights also shine on the pixels that are "above" them. Here's a simple trick. In the first pass (rendering diffuse texture), use your alpha channel to store 'depth'. Well not really depth, as its a 2D game. The Z-orderning you use can also be stored in the pixels. Use a low value for the ground, as it should always be litten. Some thing for small stuff. Big objects such as trees and houses won't be litten if their Z-ordening value is higher than the lightQuad Z-Ordening value. You could use the screen(Y) coordinates and object.height properties to calculate this Z-ordening value for both lightQuad and Object.

So, in step 1 each pixel on the diffuseTexture also gets a Z-value, stored in its alpha channel. A pixel with Z value '10' can only be litten by lightQuads that have a higher Z-ordening value. BTW, keep in mind that you can only store 256 levels of depth/Z in normal RGBA textures that use 8-bit alpha channels.

In step 3, when rendering light quads, your pixelShader can grab these alpha values for a comparison:

pixelZ = diffuseTexture(x,y).a; // grab alpha

if (pixelZ < lightZ)
{
lightQuadPixel = maskTexture.x * lightColor * diffuseTexture(x,y);
} else
{
lightQuadPixel = 0; // No effect from this light on this pixel
}



Hope that matches better with your game!
Rick

Share this post


Link to post
Share on other sites
Holy shit, that looks like it might work. The idea with storing depth/height information in the alpha channel never occurred to me.

I just need to work out if I need to store 'height over the ground' or 'distance from viewer' in the alpha channel in order to solve the occlusion problem. I think it would be nice to have both in fact, but then I would need a second channel... maybe I can use the z-buffer for this in XNA (DirectX), I am not sure.

Thanks a lot!

Share this post


Link to post
Share on other sites
I think there's a problem with storing the distance to the viewer in the alpha channel in step 1 - I have alpha blending enabled, and I am using it to render translucent pixels, as well as the blank areas around my sprites.

So unfortunately I think it will be necessary to render the scene twice. Once for display, and once for construction of the lighting map. Or is there another way?

Share this post


Link to post
Share on other sites
I see. I don't know what kind of hardware you're targeting, but rendering a second pass with the Z values doesn't have to be that intensive. You can draw on 2 textures at the same time with MRT. Then instead of the alpha channel, write to the X value of the second texture. This also allows to get a bigger resolution, as you can also use the Y and Z value. XY together gives you 16 bit instead of 8. Eventually use the remaining channel(s) for other effects such as an emmissive value (pixels that illiminate themselves such as neon light or house windows at night).

Depending on how you calculate the Z values, you might be able to calculate and store the Z values it in step 2 (ambient pass). The ambient pass just renders to 1 big quad, so no transparency is required, so its alpha channel if free to use. However, the only information you have here is the diffuse texture from step 1 and texture coordinates...

Greetings,
Rick

Share this post


Link to post
Share on other sites
Hmm, I achieved a decent lighting scheme in an openGL 2d environment by using the framebuffer (or "canvas" however you wanna call it). Just to simplify the idea, I drew an alpha mask over my entire scene after using the framebuffer to "cut out" the locations of the lights.

Did colors and all that pretty well. But it's not an additive effect, nor is it "true" lighting, so it doesn't interact with any geometry or anything. For a 2d game, it was sufficient for me. But it allowed for day/night cycle with total ease, since I could manipulate the color of the "mask" (make it black with some alpha or totally transparent).

I'm sure there's faster and better ways, but it looked alright. It's also dead nuts simple (no shaders, nothing fancy really).

Let me see if I have a screen shot layin around here... Ah yes, here's the general affect:

Pic 1
Pic 2

The second pic is a blue tint, with white (or near white) lights, so it looks a little washy. Wish I had more lighting pictures, but I don't.

EDIT: Sorry, was messing with markup / linking.

Share this post


Link to post
Share on other sites
The technique is finished for billboards at least:

Image Hosted by ImageShack.us

I'm still improving the lighting effect on the 3d models (they are currently affected by lighting as if they are points) and also to take height over ground into account...

But I am very satisfied, so - thanks for all the help.

Share this post


Link to post
Share on other sites
I don't know how it's done using 3D techniques/API's but I used a different approach in a purely 2D approach. I used a per-pixel 'soft-light' approach:


// Where 'U' = Pixel Value of Upper Layer and 'L' = Pixel Value of Lower Layer
X = (((255-L)*U*L)+(L*(1-((255-U)*(255-L))/255)))/255


X is the resulting color of the calculation.

Anyway, I've been able to achieve these results with it: Clickie

The text labels I added after the fact to indicate blending modes used -- the three on top denote the purple box blend type and the two on the side denote the yellow light source blend mode.

I found that the soft-light produces more realistic results over the often used multiply at the cost of being rather slow (okay, extremely slow and unsuitable for real-time games in pure 2D approach without graphics acceleration). I decided to use a 'pre-rendered' method where the graphics engine knows where certain 'lights' are and rerenders the images necessary then simply draws the rendered image instead of going per-pixel every frame.

I have a feeling, however, that using pixel shaders (either cG, GLSL or even HLSL) this could be done very easily (and fast) with a programmable GPU (e.g., shaders).

Anyway, thought I'd throw in my two cents about other possible methods... ;) Hope this helps.

Share this post


Link to post
Share on other sites

This topic is 3081 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this