How to make lightsources (torch, fire, campfire etc) in dark area? (2D pixel game)

Hi, I made a topic on MonoGame’s subreddit but I figured that not everyone reads that.

First of all, I’m not native english speaker so bear with me. Please don’t just read the title and jump straight to writing answer, I know it is tl;dr but this thing is hard to write in few sentences.

What I’m doing is 2D platformer where player is in dark, but there is light source around player. Torches, fireballs, particles, campfires etc act as additional sources of light.
The gameplay is mostly there, but I have no idea how to make the Dark Effect.

Only tutorial/example I found looked good, but it used shaders and I had hard time following the tutorial, and dropped when the HLSL(?) example shader didn’t compile at all.
I have tried googling and searching, but no luck so far.

I want to make it so that I could adjust Dark’s intensify so that if the area is outside, I could make the dark just almost dark, making the outlines etc visible and in caves it would be completely dark.
I don’t need/want Terraria style light that does not light up area if there is wall or something blocking, I just need “round lights” with breathing effect.
Because it is pixel game, I don’t want the light to look “smooth”, but instead have nice clean intensify layers instead. I mean, that center of light circle is fully visible, center layer drops darker and then drops to almost completely dark.

Is there somekind of Masking system in MonoGame? I have zero experience with shaders/effects, I don’t know what they are or how to use them.

Pics

My current setup

  • MonoGame 3.5
  • Cross Platform Project (OpenGL)
  • Visual Studio 2015
  • Windows 10
  • Aimed for Windows, Linux and possibly Mac

All help is greatly appreciated! I’m still learning, so please if the answer is complex I’d like to have it explained for me like I was a child.

1 Like

To achieve this you can draw a transparent black rectangle over the screen. Change the transparency value to control how dark it will be. If you have the rectangle on a seperate render target first, you can mask out the lights by multiplying the alpha values. I think you can do that just by setting a custom BlendState and rendering the lights on top. If you only need a couple shapes of light (or even only round lights) it’s easiest to use a texture for those. I don’t have time to explain this more in depth right now, but I hope this helped :slight_smile:

2 Likes

I will be looking into something like this in the future actually, @Jjagg I would love to see a more detailed explanation sometime when you get chance.

I wonder if these two books are of any use, could any experienced shader guys tell if these will still be relevant or worth reading?

this one 2004
and
this one 2013

Thanks for answering.

I’m not using RenderTargets, is that a problem? I just use spriteBatch.Draw(stuff here);

Well, drawing rectangle over the screen is the easy part, I had that figured out already.
Sadly, my progress stops there, so more in depth explanation is greatly appreciated :slightly_smiling:

I currently have something like this, I’m probably completely lost

public void DrawDarkness(SpriteBatch spriteBatch) {

Color dark = new Color(Color.Black, 0.8f);
spriteBatch.Begin(SpriteSortMode.Deferred, <test blendstate here>, SamplerState.PointClamp, null, null, null, GetResolutionMatrix());

spriteBatch.Draw(pixel, destinationRectangleCoveringScreen, dark);

/* Nothing here in my actual code, am I on right track?
foreach(thing that is light in light things)
{
spriteBatch.draw(thing.getLight, thing.Position, thing.lightColor);
}
*/
spriteBatch.End();
}

I don’t think you can achieve this without using a render target. I’ll try and put up a little sample when I get the time.

1 Like

Ok, I know a few work-arounds…

For the simon bellmont style image, you could first draw your level, then the black layer, then your character… That way, he is lit up like normal…

For my game vector ball (link here) I used simple white to transparent gradient fills as sprite lights… They look like lights, making anything underneath brighter, they blend pretty well, and because they are white, you can draw them in any color… You can easily scale and dim them…

all done with sprites, no render targets…

Its not a shader, its not the best, but it looks pretty damn nice for something so simple…

1 Like

Cool! I didn’t know you could get it to look good by just blending the lights on top! :slight_smile:

Monopalle, I don’t want just the character sprite to be visible. I want small, round area to be visible. It would be easy just to have big black texture with “hole” for the light, and center the hole to player. But this way I can’t have multiple light sources :frowning:

As for blending example, I don’t understand how that works. I tried to get something working using different blending states etc but what I got makes game look ugly, screwing up the actual color from the textures.
This would be so easy with some kind of build-in masking system…

Ok, I understand what you want… and I’ve been doing work-arounds or altering design choices to avoid special shader coding. I try to rely on sprites and render-targets to keep things simple…

You should really look at drawing a gradient sprite as your light. If you look at my example pics, you see how nice they overlap, so many lights appear more intense…
When you use these gradient sprites on dark backgrounds, they appear brighter in the lights.
All of this is done without any special code besides a normal draw call with an adjusted alpha value.

The next step, I think the guy above was saying, is something like drawing these gradient sprites on a black render target, using subtraction mode blend state (or some such thing, where you draw like normal, only the sprites you draw remove color instead of adding or blending)…
-And then drawing that black render-target (now with all sorts of holes) on top of your scene.

Its a simple idea once you wrap you head around it, and all it really requires is a render-target, a gradient circle fill texture, and setting the blend state back and forth for drawing objects vs drawing lights…

1 Like

Yep, that is what I was saying :stuck_out_tongue:

2 Likes

cool. Now to remember “how when and where” to switch blend states…

I seem to remember it being pretty simple, like switching render targets?
But also, I think they changed it up in a monogame patch some time ago.

Do you remember what needs to be declared and such?

1 Like

You can either pass it to spriteBatch.Begin or set it in the GraphicsDevice directly. If you set it through spritebatch the older blendstate isn’t automatically restored when ending the batch, so IMO it’s clearer to set it in GD directly

1 Like

its that easy, huh?

I almost feel like doing it just to do it… maybe I should add it to my grinder game for the 3rd level… Which was going to be a night level anyway… :slight_smile:

2 Likes

ok, so in my test, I cant get this approach to work.

Can you remember the recipe? Otherwise, I’ll have to try and find a tutorial…

1 Like

If the light in the textures uses alpha (i.e. alpha=0 where there’s no light and not zero where there is) you can use

// this rendertarget will hold a black rectangle that gets masked with alphaMask
darkness = new RenderTarget2D(GraphicsDevice, 800, 600);

// the stuff we mask out of darkness, essentially this texture defines our lights
alphaMask = Content.Load<Texture2D>("AlphaMask");

// prepare the darkness
GraphicsDevice.SetRenderTarget(darkness);
GraphicsDevice.Clear(new Color(0 ,0 ,0 ,120)); // the 120 here is the darkness "intensity"

// create the blend state; THIS IS THE IMPORTANT PART
// we say we want to subtract alpha of the source (mask) from the destination (darkness) alpha!
// and make sure we have the full alpha values for both with the Blend.One values
// (AlphaSourceBlend is set to Blend.One by default, but it's nice to do this explicitly for clarity as it
// shows what's interesting in your custom blendstate. If you check out the XNA docs on BlendState, note that the 
// default values they mention there are wrong!)
var blend = new BlendState
{
    AlphaBlendFunction = BlendFunction.ReverseSubtract,
    AlphaSourceBlend = Blend.One,
    AlphaDestinationBlend = Blend.One,
};

spriteBatch.Begin(blendState: blend);
spriteBatch.Draw(alphaMask, darkness.Bounds, Color.White);
spriteBatch.End();

// set the render target back to the backbuffer
GraphicsDevice.SetRenderTarget(null);
// Clear it
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
// draw the masked darkness!
spriteBatch.Draw(darkness, Vector2.Zero, Color.White);
spriteBatch.End();

The texture looked like this:

with white being transparent here, but you can’t see that here. The black is 255, 255, 255 but with an alpha value. Color really doesn’t matter this way since it’s not used, only the alpha value is. The result looked like this:


I know I should’ve done a nice radial gradient to make this clearer, but you can obviously see the top right dot only blends away a bit of darkness. :stuck_out_tongue:

If you have lights rendered black on white or vice versa (no alpha channel or alpha=1 everywhere), this should also be possible. I thought the following blendstate would work

// create the blend state
var blend = new BlendState
{
    AlphaSourceBlend = Blend.Zero,
    AlphaDestinationBlend = Blend.[Inverse]SourceColor,

    ColorSourceBlend = Blend.Zero,
    ColorDestinationBlend = Blend.[Inverse]SourceColor,
};

But it didn’t… Sooooo… I’ll have another look at this when I get the chance, this should really be possible.

3 Likes

This looks really promising! I will give it a go as soon as I gather enough power cells.

Will post results!

1 Like

Your code works, and is very easy to use/follow… Thanks for taking the time, I searched far and wide, even followed 2 dead-end tutorials… :slight_smile:

So I had to use a black to transparent gradient for the light mask,
but I just inverted what I had…

The code looked bad on top of a naked cornflower blue, but on a texture, its very nice.
Seems you can adjust intensity by tweaking the alpha value when drawing the lights, so that’s nice… Also radius is set by scale, and everything is like normal sprites.

It doesn’t do colored lights though, but my guess would be to draw another layer containing color information, and super-imposing that?

You might have to find a better gradient, mine is just a 5 second gimp mouse-fill…

But here are my results ():

EDIT: Here I used 2 sprites for each light, one bigger and dimmer than the other.
I think it looks better… Demonstrating that its best to find a sprite different than a simple gradient fill… I think.

2 Likes

Jjagg, I followed through your example, tinkered around and got it working like I want! Of course It still needs work, but the darkness with light-around-lightsources is now in place :slightly_smiling:
At first it was confusing to use RenderTarget and drawing on it, but I think I got how it works.

Thanks!

1 Like

Check it out with colors added… When they move around, it looks like magic.

Still done just with blending, and no work on the actual textures

… This stuff is amazing.

All kinds of thoughts occur… 10 / 10 will use in future…