1
\$\begingroup\$

I have a 2D tile-based lighting system which is drawn onto a Render Target. I am also drawing a background which involves mountains, sun/moon, and clouds. There is also the unlit game; blocks, character, etc.

Here is the order in which everything is drawn:

1. Background
2. Game World
3. Lighting

The problem is that the LIGHTING is covering up the BACKGROUND when it's dark:

The moon and background is covered by darkness.

Although it's perfectly fine in the day because the light is full: The alpha of the light is 1, so the background is visible

You might ask, well, why don't you just blend each block, not drawing the lighting on a RenderTarget?

Because that would prevent me from performing smooth lighting, as seen in the pictures. To produce the smooth lighting, I draw squares of light onto a RenderTarget, perform a Gaussian blur and then draw that RenderTarget.

How about not drawing a square of light in empty spaces?

The light blurs onto any adjacent blocks, and not all objects in my game affected by lighting are square, so they would look like a sprite with a blurry square on top of it.

Light NOT being drawn in empty spaces: Light NOT being drawn in empty spaces

Light being drawn everywhere: Light being drawn everywhere

Is there any way to keep the background visible, or something else that would help my predicament?

USING THE STENCIL BUFFER

I put in the stencil buffer code that you said should cause "little overhead"

As you can see the background blocks, due to the effect, are not darkened like they should be. The FPS here is ~15 as opposed the the steady 60 FPS it runs at without the stencil method. Also the character is somehow not being drawn correctly. Day with stencil

It does in fact work though despite the more than annoying lag. Night with stencil

\$\endgroup\$
1
  • \$\begingroup\$ I figured out what was cuasing lag! Defining s1 and s2 and a and m in the draw method was lagging out the game. \$\endgroup\$
    – ben
    Dec 13, 2013 at 17:34

1 Answer 1

1
\$\begingroup\$

The issue seems to be that you are applying lighting to an object which should not be affected by it. It makes no logical sense that something in the local scene should be able to affect the sky, like the shadows in your sample do during the day. I think this doesn't make logical sense but perhaps it does make aesthetic sense.

If I have understood correctly what you want, then you should consider the following method:-

  1. Break your system into two sets; things you want affected by lighting and things you don't want affected.

  2. Render the set of light affected objects to a stencil buffer (for implementation details see this tutorial).

    • You should add this to the system that currently renders the scene and simply enable and disable writing to the stencil buffer depending on whether you want lighting on the object you are currently drawing.
  3. Use this stencil buffer when rendering the lighting buffer to only draw to areas that you want shadows (pixels occupied by objects we want illuminated).

What this will do is make sure that you only render lighting to pixels which are used to display an object affected by lighting and not pixels being used to display the sky.

At the end your draw method should resemble (code taken from here);

var a = new AlphaTestEffect(graphics.GraphicsDevice) {
    Projection = m
};

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

// not drawing to the stencil buffer here
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, null, null, a);
// draw all NON-light affected objects here 
foreach (Sprite s in nonIlluminatedSprites)
{
    spriteBatch.Draw(s.texture, s.rectangle, Color.White);
}
spriteBatch.End();

// drawing to the stencil buffer here...
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
// draw all light affected objects here  
foreach (Sprite s in illuminatedSprites)
{
    spriteBatch.Draw(s.texture, s.rectangle, Color.White);
}
spriteBatch.End();

// using the mask here
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);            
// Blend the light buffer with scene here
spriteBatch.Draw(lightBuffer.texture, Vector2.zero, Color.White);
spriteBatch.End();

Note: while a sky/mountain backdrop might not be affected by shadows you may want to have shadows cast on other background objects; such as the background walls when the game is an interior location like a house. So you might want to split the background into two things, background objects and sky/mountain backdrop.

Alternative Method

In a comment it was mentioned that the stencil buffer approach was being slow. This shouldn't be the case if generated in the same pass as rendering the main game objects as it should add very little overhead. However, there may be another method in this particular case.

If you are using a pixel shader to render all scene objects then add a new "float illuminate" attribute. Before the draw call for a non-illuminated object set this attribute to 1 and before the draw call for illuminated objects set it to 0. Then, in the pixel shader, set the alpha channel of the lighting buffer to this value.

This should mean that when you blend using alpha blending at the end of your rendering pass the lighting buffer will have transparent areas where we don't want illumination applied on the screen.

Note: I may have the 1 and 0 round the wrong way. I get this wrong most of the time and wrote this assuming that 1 means "completely transparent".

\$\endgroup\$
6
  • \$\begingroup\$ Thank you, this answers my question exactly, but I don't really know how to use stencil buffers. Could you explain a bit more maybe? Sorry for not being experienced with them. \$\endgroup\$
    – ben
    Dec 12, 2013 at 17:05
  • \$\begingroup\$ I feel that your follow-up question is beyond the scope of this answer and so should probably be raised as a new question. There are also plenty of tutorials available; I'll add a link to the answer though. \$\endgroup\$ Dec 12, 2013 at 17:30
  • \$\begingroup\$ I found a good tutorial and implemented it, but it is SO SLOW. It's scarily slow because I'm drawing a ton of objects at once. I think I'll use a pixel shader instead. \$\endgroup\$
    – ben
    Dec 12, 2013 at 18:46
  • \$\begingroup\$ You can render the stencil buffer in the same pass as drawingthe scene objects so it should add little overhead, but with pixel shaders you can do essentially the same thing. Create a new render target and in the pixel shader write a 1 to it when drawing an illuminated object and 0 for non-illuminated. Then when rendering the light buffer just check for a 1. \$\endgroup\$ Dec 13, 2013 at 2:06
  • \$\begingroup\$ Oh yeah that seems like it would work. I found a pixel shader in this topic, but I'm not sure how to implement pixel shaders using .fx files. \$\endgroup\$
    – ben
    Dec 13, 2013 at 2:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .