Jump to content
  • Advertisement
  • Remove ads and support GameDev.net for only $3. Learn more: The New GDNet+: No Ads!

  • 04/20/16 01:14 PM
    Sign in to follow this  

    2D Lighting System in Monogame

    Engines and Middleware

    Penkovskiy

    This tutorial will walk you through a simple lighting/shadow system. Go into your current Monogame project, and make a new file called lighteffect.fx This file will control the way our light will be drawn to the screen. This is an HLSL style program at this point. Other tutorials on HLSL will be available in the main website which will allow you to do some wicked cool things like; distorting space and the map, spinning, dizzyness, neon glowing, perception warping, and a bunch of other f?#%! amazing things! Here is the full lighteffect file. sampler s0; texture lightMask; sampler lightSampler = sampler_state{Texture = lightMask;}; float4 PixelShaderLight(float2 coords: TEXCOORD0) : COLOR0 { float4 color = tex2D(s0, coords); float4 lightColor = tex2D(lightSampler, coords); return color * lightColor; } technique Technique1 { pass Pass1 { PixelShader = compile ps_2_0 PixelShaderLight(); } } Now, don't get overwhelmed at this code if you aren't familiar with HLSL. Basically, this effect will be called every time we draw the screen (in the Draw() function). This .fx file manipulates each pixel on the texture that is loaded into it, in this case it would be the sampler variable. sampler s0; This represents the texture that you are manipulating. It will be automatically loaded when we call the effect. s0 is a sample register that SpriteBatch uses to draw textures, so it is already initialized. Your last draw function initializes this register, so you don't need to worry about it! (I explain more about this below) RenderTarget2D Render targets are textures that are made on the fly by drawing onto them using spriteBatch, rather than drawing directly to the back buffer. texture lightMask; sampler lightSampler = sampler_state{Texture = lightMask;}; The lightMask variable is our render target that will be created on the fly using additive blending and our light's locations. I'll explain more about this soon, here we are just putting the render target into a register that HLSL can use (called lightSampler). Before I can explain the main part of the HLSL effect, I need to show you what exactly is happening behind the scenes. First, we need the actual light effect that will appear over our lights. lightmask.png I'm showing you this version because the one that I use in the demo is a white transparent gradient, it won't show up on the website. If you want a link to the gradient that I used in the demos above, you can find that at my main website. Otherwise, your demo will look like the image below. You can see black outlines around the circles if you look close. lightmaskdemo.png Whatever gradient you download, call it lightmask.png Moving into your main game's class, create a couple variables to store your textures in: public static Texture2D lightMask; public static Effect effect1; RenderTarget2D lightsTarget; RenderTarget2D mainTarget; Now load these in the LoadContent() function. lightMask is going to be lightmask.png effect1 will be lighteffect.fx This is how I initialize my render targets: var pp = GraphicsDevice.PresentationParameters; lightsTarget = new RenderTarget2D( GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight); mainTarget = new RenderTarget2D( GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight); With that stuff out of the way, now we can finally focus on the drawing. In your Draw() function, lets begin by drawing the lightsTarget: GraphicsDevice.SetRenderTarget(lightsTarget); GraphicsDevice.Clear(Color.Black); spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive); //draw light mask where there should be torches etc... spriteBatch.Draw(lightMask, new Vector2(X, Y), Color.White); spriteBatch.Draw(lightMask, new Vector2(X, Y), Color.White); spriteBatch.End(); Some of that is psuedo code, you have to put in your own coordinates for the lightMask. Basically you want to draw a lightMask at every location you want a light, simple right? What you get is something like this: (The light gradient is highlighted in red just for demonstration) lightmaskdemo2.png Now in simple, basic theory, we want to draw the game under this texture, with the ability to blend into it so it looks like a natural lighting scene. If you noticed above, we draw the light render scene with BlendState.Additive because we will end up adding this on top of our main scene. What I do next is I draw the main game scene onto mainTarget. GraphicsDevice.SetRenderTarget(mainTarget); GraphicsDevice.Clear(Color.Transparent); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, cam.Transform); cam.Draw(gameTime, spriteBatch); spriteBatch.End(); Okay, we are in the home stretch! Note: All this code is sequential to the last bit and is all located under the Draw function, just so I don't lose any of you. So we have our light scene drawn and our main scene drawn. Now we need to surgically splice them together, without anything getting too bloody. We set our program's render target to the screen's back buffer. This is just the default drawing space for the client's screen. Then we color it black. GraphicsDevice.SetRenderTarget(null); GraphicsDevice.Clear(Color.Black); Now we are ready to begin our splice! spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); effect1.Parameters["lightMask"].SetValue(lightsTarget); effect1.CurrentTechnique.Passes[1].Apply(); spriteBatch.Draw(mainTarget, Vector2.Zero, Color.White); spriteBatch.End(); We begin a spriteBatch whose blendstate is AlphaBlend, which is how we can blend this light scene so smoothly on top of our game. Now we can begin to understand the lighteffect.fx file. Remember from earlier; sampler s0; texture lightMask; sampler lightSampler = sampler_state{Texture = lightMask;}; We pass the lightsTarget texture into our effect's lightMask texture, so lightSampler will hold our light rendered scene. tex2D is a built-in HLSL function that grabs a pixel on the texture at coords vector. Looking back at the main guts of the effect function: float4 color = tex2D(s0, coords); float4 lightColor = tex2D(lightSampler, coords); return color * lightColor; Each pixel that we find in the game's main scene (s0 variable), we look for the pixel in the same coordinates on our second render scene -- the light mask (lightSampler variable). This is where the magic happens, this line of code; return color * lightColor; Takes the color from our main scene and multiplies it by the color in our light rendered scene, the gradient. If lightColor is pure white (very center of the light), it leaves the color alone. If lightColor is completely black, it turns that pixel black. Colors in between(grey) simply tint the final color, which is how our light effect works! Our final result (honoring the color red for demonstration): Screenshot-2015-07-23-01.47.07.png One more thing worth mentioning, effect1.Apply() only gets the next Draw() function ready. When we finally call spritebatch.Draw(mainTarget), it kicks in the effect file. s0's register is loaded with this mainTarget and the final color effect is applied to the texture as it is drawn to the player's screen. Be careful using this in your existing games, changing drawing blend states and sort modes could funk up some of your game's visuals. You can see a live example of what this system does in a top-down 2D rpg. girlnextdoor.gifScreenshot-2015-06-24-18.29.06.png The live gif lost some quality, the second screen shot shows you how it really looks. You can learn more and ask questions at the original post; http://www.xnahub.com/simple-2d-lighting-system-in-c-and-monogame/



      Report Article
    Sign in to follow this  


    User Feedback


    Since your shader only has one pass, the following:

    effect1.CurrentTechnique.Passes[1].Apply();
    

    should be:

    effect1.CurrentTechnique.Passes[0].Apply();
    

    Share this comment


    Link to comment
    Share on other sites

    Also, you could use the SpriteBatch.Begin overload that accepts an effect parameter and avoid the Pass.Apply altogether. That method also doesn't require SpriteSortMode.Immediate.

    Share this comment


    Link to comment
    Share on other sites

    Hi! Nice tutorial! We used same technique for our game http://store.steampowered.com/app/405950/ Instead of HLSL, we used GLSL. If someone is using OpenGL I can share our light shader:

    #ifdef GL_ES
    precision lowp float;
    #endif
    
    varying vec2 v_texCoord;
    
    uniform sampler2D u_texture0;
    uniform sampler2D u_texture1;
    
    #ifndef GL_ES
    #define AMB_LIGHT 0.15
    #endif
    
    #ifdef GL_ES
    #define AMB_LIGHT 0.15
    #endif
    
    void main()
    {
        vec4 color = texture2D(u_texture0, v_texCoord);
        vec4 mask = texture2D(u_texture1, v_texCoord);
        
        float AMBIENT = AMB_LIGHT-abs( (v_texCoord.x - 0.5))*AMB_LIGHT*0.5;
        AMBIENT =+ AMBIENT-abs( v_texCoord.y - 0.5)*AMB_LIGHT*0.5;
        
        gl_FragColor = vec4( color.r*(clamp(mask.r+AMBIENT, 0.0, 1.0)), color.g*(clamp(mask.g+AMBIENT, 0.0, 1.0)), color.b*(clamp(mask.b+AMBIENT, 0.0, 1.0)), color.a*(clamp(mask.a+AMBIENT, 0.0, 1.0)) );
    }
    

    In addition, we use ambient light to prevent completely black areas. Ambient light calculation in shader is for getting vingette-effect so ambient is darker at the corners of the screen.

    Share this comment


    Link to comment
    Share on other sites

    I would like to thank you for the efforts you've put in writing this web site. I'm hoping new balance 574 pas cher the same high-grade web site post from you in the upcoming also. Actually your creative writing skillslunette ray ban pas cher has encouraged me to get my own web site now. Actually the blogging is spreading its wings rapidly. Your write up is a great example of it.

    Share this comment


    Link to comment
    Share on other sites


    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

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!