4
\$\begingroup\$

A year ago I implemented 2d lighting in XNA using sprites. The sprites I used were created by placing a soft white photoshop brush on a transparant background.

The result was this: (lights only) enter image description here

And this seemed right to me.

I returned to the subject this week to try replacing the sprites with a texture created with code and a formula: enter image description here

As you can see, there is a black ring around every light.

This is the code I used for creating the textures:

int diameter = 250;
int radius = diameter / 2;

Color[] colors = new Color[diameter * diameter];

for (int y = 0; y < diameter; y++)
{
    for (int x = 0; x < diameter; x++)
    {
        Vector2 l = new Vector2(radius, radius) - new Vector2(x, y);
        float attenuation = MathHelper.Clamp(1 - Vector2.Dot(l / radius, l / radius), 0, 1);

        colors[x + y * diameter] = Color.White;
        colors[x + y * diameter].A = (byte)(attenuation * 255);
    }
}

Texture2D texture = new Texture2D(device, diameter, diameter);
texture.SetData(colors);

I got the attenuation formula from here.

Aside from that I now create the textures using code, the rest of the code has remained the same.

Where do the black rings come from, and how do I prevent them from appearing?

\$\endgroup\$
  • 1
    \$\begingroup\$ looks like a problem with the exponent of the brighness calculation in my opinion \$\endgroup\$ – Quonux Jan 16 '14 at 20:42
  • 2
    \$\begingroup\$ My wild guess would be that you need to use a different blend mode (maybe with premultiplied alpha) for drawing the textures. But this is just a really wild guess. \$\endgroup\$ – amitp Jan 17 '14 at 2:36
  • \$\begingroup\$ Yes, it's the blend mode. I just did this the other day, and while both the texture AND the blend mode need to specify non-premultiplied, there are some other settings that have to be adjusted. @Berry If you don't figure it out shortly, I'll write up an answer when I get off work. \$\endgroup\$ – jzx Jan 20 '14 at 23:35
  • \$\begingroup\$ This may be optical illusion. Measure actual pixel brightness. Overlay it with something other to check if light effect is working. \$\endgroup\$ – Shadows In Rain Jan 21 '14 at 11:49
  • \$\begingroup\$ You need multiple buffers. Either sum all the lights together, and then multiply that by the image being lit. Or else multiply the image to be lit by the current light, and add that result. One light should never have to multiply by a pre-lit image effected by another light. Also, you'll want to think about doing things in a linear color space. Physical lights add together linearly, but you are probably using sRGB images to be lit, and perception and displays are very nonlinear. \$\endgroup\$ – wrosecrans Jun 17 '16 at 20:08
2
\$\begingroup\$

Okay, here are the settings you'll want to use to additive blending, plus some extra info in case anyone else wants to do something similar.

I'm not sure how to get this to work with pre-multiplied alpha (or if that's even necessary, since I'm guessing you plan to do mask-based lighting), so you'll want to make sure that if you have any resource based "brushes" (also known as "cookies") that you set the pre-multiplied alpha property like so:

I altered your texture declaration a bit:

_texture = new Texture2D(device,
                         diameter,
                         diameter,
                         false,
                         SurfaceFormat.Color);

Here's the set up:

protected override void LoadContent()
{
    // This is the texture we'll draw to,
    //     and then we'll use this texture as our mask.
    _canvas = new RenderTarget2D(GraphicsDevice,
                                 GraphicsDevice.Viewport.Width,
                                 GraphicsDevice.Viewport.Height,
                                 false,
                                 SurfaceFormat.Color,
                                 DepthFormat.None,
                                 0,
                                 RenderTargetUsage.PreserveContents);
    // This is a kind of additive blend.
    // It will clamp to the max channel once that channel reaches full "brightness."
    _addBlend = new BlendState
                    {
                        ColorSourceBlend = Blend.SourceAlpha,
                        AlphaSourceBlend = Blend.SourceAlpha,
                        ColorDestinationBlend = Blend.InverseSourceAlpha,
                        AlphaDestinationBlend = Blend.One
                    };
}

Then, your SpriteBatch.Begin call will look like this:

// SpriteSortMode is practically useless here.
_spriteBatch.Begin(SpriteSortMode.Immediate, 
                   _addBlend, // <--
                   SamplerState.PointWrap,
                   DepthStencilState.None,
                   RasterizerState.CullNone);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.