1
\$\begingroup\$

I would like to know, how to make that effect, I have seen in the game named Yomawari. The little girl has a lamp and its cone revals the dark spirits around her. But only if they are in the light. I think it's like the additive blending, but I dont get it to work. I use monogame and c#.

Here is a sample: https://youtu.be/kPLRXP7ovB0?t=1241

All ideas are wellcome (sourcecode preferred ;))

Greetings Kai

My current test code: (DL: https://github.com/EnemyArea/yomawari-lights)

    protected override void Draw(GameTime gameTime)
    {
        var blend = new BlendState
        {
            ColorBlendFunction = BlendFunction.Add,
            ColorSourceBlend = Blend.DestinationColor,
            ColorDestinationBlend = Blend.Zero
        };

        var lightBlendState = new BlendState
        {
            ColorSourceBlend = Blend.Zero,
            ColorDestinationBlend = Blend.SourceColor
        };

        var mousePosition = Mouse.GetState().Position;

        // Draw lights
        this.GraphicsDevice.SetRenderTarget(this.blackpixel);
        this.GraphicsDevice.Clear(Color.DarkGray);
        this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
        this.spriteBatch.Draw(this.lichtRund, new Vector2(mousePosition.X, mousePosition.Y), null, Color.White, 0f, new Vector2(this.lichtRund.Width * 0.5f, this.lichtRund.Height * 0.5f), 1f, SpriteEffects.None, 0);
        this.spriteBatch.End();
        this.GraphicsDevice.SetRenderTarget(null);

        // Draw hidden
        this.GraphicsDevice.SetRenderTarget(this.renderTarget);
        this.GraphicsDevice.Clear(Color.White);
        this.spriteBatch.Begin(SpriteSortMode.Deferred, blend);
        this.spriteBatch.Draw(this.testeye, new Vector2(this.GraphicsDevice.Viewport.Width / 2f, this.GraphicsDevice.Viewport.Height / 2f), null, Color.White, 0f, new Vector2(this.testeye.Width * 0.5f, this.testeye.Height * 0.5f), 1f, SpriteEffects.None, 0);
        this.spriteBatch.Draw(this.blackpixel, Vector2.Zero, Color.White);
        this.spriteBatch.End();
        this.GraphicsDevice.SetRenderTarget(null);

        // Combine all
        this.GraphicsDevice.Clear(Color.Black);

        this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
        this.spriteBatch.Draw(this.background, Vector2.Zero, Color.White);
        this.spriteBatch.End();

        this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
        this.spriteBatch.Draw(this.renderTarget, Vector2.Zero, Color.White);
        this.spriteBatch.End();

        this.spriteBatch.Begin(SpriteSortMode.Deferred, lightBlendState);
        this.spriteBatch.Draw(this.blackpixel, Vector2.Zero, Color.White);
        this.spriteBatch.End();

        base.Draw(gameTime);
    }

it results into that: Wrong result but is should look like enter image description here

another sample transperant

\$\endgroup\$
9
  • \$\begingroup\$ Is it the deferred lighting effect? I wrote an article about it: felsirworld.net/deferred-2d-lights (if not can you point to the timestamp in the video what specific effect you’re looking for?) \$\endgroup\$
    – Felsir
    Commented Nov 24, 2018 at 20:43
  • \$\begingroup\$ is 2 seconds later, when she lights up the dark spirit. \$\endgroup\$
    – EnemyArea
    Commented Nov 24, 2018 at 20:47
  • \$\begingroup\$ Likely still the deferred lighting effect. The way the lightcone works is that a separate texture controls what is visible and what is invisible. So the ‘spirit’ is also made visible via that texture. \$\endgroup\$
    – Felsir
    Commented Nov 24, 2018 at 20:55
  • \$\begingroup\$ but isn't it the same as if you use the additive blending? Having a floor, a black texture over it and a "lightsource" ? I just want to make the pixels from a texture visible, proportional to the "lightsource"-texture \$\endgroup\$
    – EnemyArea
    Commented Nov 24, 2018 at 21:07
  • \$\begingroup\$ Yes. If you have a black texture with a white circle on it, you could use it as a texture mask. In the game, they already use the deferred effect for the flashlight so it is a bonus of an effect they already have in place. \$\endgroup\$
    – Felsir
    Commented Nov 24, 2018 at 21:17

2 Answers 2

2
\$\begingroup\$

Here is a solution using a shader. Basically the shader uses the lightmap to mask pixels. The lightmap is black and white texture and the R (red) component is used to determine if the mask should be applied or not. This leaves the G and B components usable for a different effect.

First the shader:

    #if OPENGL
    #define SV_POSITION POSITION
    #define VS_SHADERMODEL vs_3_0
    #define PS_SHADERMODEL ps_3_0
#else
    #define VS_SHADERMODEL vs_4_0_level_9_1
    #define PS_SHADERMODEL ps_4_0_level_9_1
#endif

Texture2D SpriteTexture;

sampler2D SpriteTextureSampler = sampler_state
{
    Texture = <SpriteTexture>;
};

Texture2D MaskTexture;

sampler2D MaskTextureSampler = sampler_state
{
    Texture = <MaskTexture>;
};



struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
    float2 TextureCoordinates : TEXCOORD0;
};

float4 MainPS(VertexShaderOutput input) : COLOR
{
    float4 col = tex2D(SpriteTextureSampler,input.TextureCoordinates) *input.Color; //sample the source image
    float mask = tex2D(MaskTextureSampler, input.TextureCoordinates).r; //sample the R component of the mask image.

    if(mask>0) // if the mask is >0 apply it.
        return float4(col.r,col.g,col.b,col.a*mask); // keep all colors, and use the alpha of the source.

    return float4(0, 0, 0, 0); //otherwise return a transparent (masked) pixel- without color information.
}

technique SpriteDrawing
{
    pass P0
    {
        PixelShader = compile PS_SHADERMODEL MainPS();
    }
};

This is the Draw function:

    /// <summary>
    ///     This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {

        // Draw lights
        this.GraphicsDevice.SetRenderTarget(this.LightmapLayer);
        this.GraphicsDevice.Clear(Color.Black);
        this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
        this.spriteBatch.Draw(this.Lightmap, new Vector2(this.GraphicsDevice.Viewport.Width / 2f, this.GraphicsDevice.Viewport.Height / 2f), null, Color.White, 0f, new Vector2(this.Lightmap.Width * 0.5f, this.Lightmap.Height * 0.5f), 1f, SpriteEffects.None, 0);
        this.spriteBatch.End();
        this.GraphicsDevice.SetRenderTarget(null);

        //I created a simple rendertarget 'GhostLayer':
        this.GraphicsDevice.SetRenderTarget(this.GhostLayer);
        this.GraphicsDevice.Clear(Color.Transparent);
        this.spriteBatch.Begin();
        this.spriteBatch.Draw(this.Ghost, new Vector2(this.GraphicsDevice.Viewport.Width / 2f, this.GraphicsDevice.Viewport.Height / 2f), null, Color.White, 0f, new Vector2(this.Ghost.Width * 0.5f, this.Ghost.Height * 0.5f), 1f, SpriteEffects.None, 0);
        this.spriteBatch.End();


        //show the result on the screen:
        this.GraphicsDevice.SetRenderTarget(null);
        this.GraphicsDevice.Clear(Color.Purple);

        //Draw the game scene:
        this.spriteBatch.Begin();
        this.spriteBatch.Draw(this.Background, Vector2.Zero, Color.White);
        this.spriteBatch.End();

        //Draw the ghosts using a mask effect:
        this.MaskEffect.Parameters["MaskTexture"].SetValue(LightmapLayer);

        this.spriteBatch.Begin(SpriteSortMode.Immediate, null, null, null, null, MaskEffect);
        this.spriteBatch.Draw(this.GhostLayer, Vector2.Zero, Color.White);
        this.spriteBatch.End();

        //Draw the lights:
        this.spriteBatch.Begin();
        this.spriteBatch.Draw(this.LightmapLayer, Vector2.Zero, Color.White*0.25f);
        this.spriteBatch.End();


        base.Draw(gameTime);
    }

Based on the code in your github, it produces this result:

TestEyeGame with masked lightbeam

The final light is now simply drawn in, this could be made more subtle with another shader effect.

\$\endgroup\$
1
  • \$\begingroup\$ Thank u, thats a very nice solution too. I came to a little bit diffrent solution today too. Many thanks for your help. \$\endgroup\$
    – EnemyArea
    Commented Nov 26, 2018 at 18:43
2
\$\begingroup\$

Answer with the use of a shader elsewhere. I leave this answer because the technique may be of interest to someone.

You can create your own blendstate like this:

        MultiplicativeBlend = new BlendState();
        //deal with transparency
        MultiplicativeBlend.AlphaBlendFunction = BlendFunction.ReverseSubtract;
        MultiplicativeBlend.AlphaSourceBlend = Blend.SourceAlpha;
        MultiplicativeBlend.AlphaDestinationBlend = Blend.Zero;
        //deal with color
        MultiplicativeBlend.ColorBlendFunction = BlendFunction.Add;
        MultiplicativeBlend.ColorSourceBlend = Blend.DestinationColor;
        MultiplicativeBlend.ColorDestinationBlend = Blend.Zero;

This creates a multiplicative blend, while keeping the transparency of the source.

With these textures:

The lightmap:

Lightmap

The ghost (this texture represent all the 'ghosts' in the scene only visible in the lightbeam:

Ghost picture

The game scene, as drawn normally without the ghosts:

Backdrop

The image can be drawn with the mask (using the multiply blendstate). In the code below I set up the components and two custom blendstates. One is the "multiply" blendstate to mask the ghost. The other is the "Light" blendstate to render the lightbeam later:

    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        Ghost = Content.Load<Texture2D>("ghost");
        Lightmap = Content.Load<Texture2D>("lightmask");
        Background = Content.Load<Texture2D>("background");

        // TODO: use this.Content to load your game content here
        MultiplicativeBlend = new BlendState();
        //deal with transparency
        MultiplicativeBlend.AlphaBlendFunction = BlendFunction.ReverseSubtract;
        MultiplicativeBlend.AlphaSourceBlend = Blend.SourceAlpha;
        MultiplicativeBlend.AlphaDestinationBlend = Blend.Zero;
        //deal with color
        MultiplicativeBlend.ColorBlendFunction = BlendFunction.Add;
        MultiplicativeBlend.ColorSourceBlend = Blend.DestinationColor;
        MultiplicativeBlend.ColorDestinationBlend = Blend.Zero;

        //Another blendstate to deal with the lightmap later:
        LightBlend = new BlendState();
        LightBlend.ColorBlendFunction = BlendFunction.Subtract;
        LightBlend.ColorSourceBlend = Blend.DestinationColor;
        LightBlend.ColorDestinationBlend = Blend.Zero;

        GhostLayer = new RenderTarget2D(GraphicsDevice, 250, 250);
    }

Now putting it in use like this:

    protected override void Draw(GameTime gameTime)
    {

        //I created a simple 250x250 pixel rendertarget 'GhostLayer':
        GraphicsDevice.SetRenderTarget(GhostLayer);
        GraphicsDevice.Clear(Color.Transparent);

        //first draw the ghost in the normal way
        spriteBatch.Begin();
        spriteBatch.Draw(Ghost, Vector2.Zero, Color.White); 
        spriteBatch.End();

        //now draw the lightmap to mask so only the 'ghost', 
        //this uses the blendstate created above.
        spriteBatch.Begin(SpriteSortMode.Immediate, MultiplicativeBlend);
        spriteBatch.Draw(Lightmap, Vector2.Zero, Color.White); 
        spriteBatch.End();


        //show the result on the screen:
        GraphicsDevice.SetRenderTarget(null);
        GraphicsDevice.Clear(Color.Black);

        spriteBatch.Begin();
        //Draw the game scene:
        spriteBatch.Draw(Background, Vector2.Zero, Color.White);
        //Draw the (masked) ghosts:
        spriteBatch.Draw(GhostLayer, Vector2.Zero, Color.White);

        // uncomment the next 'Draw' line and comment out the 'lightblend' 
        // section to see the alternate result.
        //spriteBatch.Draw(Lightmap, Vector2.Zero, Color.White*0.5f);  

        spriteBatch.End();

        //Lightblend section:
        //Draw the lightbeam using the lightBlend (though a shader might be better for this part).
        spriteBatch.Begin(SpriteSortMode.Immediate,LightBlend);
        spriteBatch.Draw(Lightmap, Vector2.Zero, Color.White);
        spriteBatch.End();
        //End lightblend section.

        base.Draw(gameTime);
    }

Now the final result is the ghost is clipped according to the lightmap, with the lightmap showing.

The result of the above code

I think, this produces the effect you're looking for.

This is the effect where the lightmap is drawn without the "lightblend" effect, to demonstrate the red circle is indeed clipped:

Lightmap without lightblend

Though I recommend taking a look at the sample code https://github.com/Felsir/LightingEngine as it shows a C#, Monogame example how the light effect is achieved by using a shader. This produces a more subtle effect with better control over ambient light and light flow.

It produces this effect:

Light effect using shaders

\$\endgroup\$
14
  • \$\begingroup\$ thank you, I try it :) looks promising. Please wait. \$\endgroup\$
    – EnemyArea
    Commented Nov 24, 2018 at 21:37
  • \$\begingroup\$ Ok, something is wrong.. I tried to use it within my lighting engine but got only a black screen. I edit my post. \$\endgroup\$
    – EnemyArea
    Commented Nov 24, 2018 at 22:09
  • \$\begingroup\$ Updated the answer with sample code and a screenshot of the produced effect. \$\endgroup\$
    – Felsir
    Commented Nov 25, 2018 at 18:29
  • \$\begingroup\$ awesome! It works :D But one last thing, why is it yellow? its a red circle. \$\endgroup\$
    – EnemyArea
    Commented Nov 25, 2018 at 19:50
  • \$\begingroup\$ That's because the light effect is blended over it in the final draw step- mixing it to that hue. The final light beam pass can be done better than in this example. I think the best part it to demonstrate the mask for the red circle using a different texture- the lightbeam can be blended in another pass like in your sample code. \$\endgroup\$
    – Felsir
    Commented Nov 25, 2018 at 20:06

You must log in to answer this question.

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