Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Adding awesome lighting effects to your XNA game...

This XNA project shows off how the author added some awesome lighting effect to their games. And of course you get some code to play with too. Smiley

XNA Light Pre-Pass: Cascade Shadow Maps

On this post I will talk about my implementation of Cascade Shadow Maps, a common technique used to handle directional light shadows. Some good descriptions can be found here and here. As before, the full source code for this sample can be found here: use it at your own risk!!

The basic idea is to split the view frustum into smaller volumes (usually smaller frustums or frusta), and to generate a shadow map for each volume. This way, we can have a better distribution of our precious shadow map’s texels into each region: the volume closer to the camera is smaller than the farthest one, so we have more shadow resolution closer to the camera. The drawback is that we have more draw calls/state changes, since we render to more than one shadow map. We can use shadow map atlasing to optimize things up, we just need to create a big shadow map that fits all our smaller shadow maps and offset the texel fetch on pixel shader.

The basic idea is to split the view frustum into smaller volumes (usually smaller frustums or frusta), and to generate a shadow map for each volume. This way, we can have a better distribution of our precious shadow map’s texels into each region: the volume closer to the camera is smaller than the farthest one, so we have more shadow resolution closer to the camera. The drawback is that we have more draw calls/state changes, since we render to more than one shadow map. We can use shadow map atlasing to optimize things up, we just need to create a big shadow map that fits all our smaller shadow maps and offset the texel fetch on pixel shader.

image

cascade1

Here's a snap of the Solution;

image

Where much of the magic happens, the XNA Draw loop;

protected override void Draw(GameTime gameTime)
  {
      GraphicsDevice.BlendState = BlendState.Opaque;
      GraphicsDevice.DepthStencilState = DepthStencilState.Default;

      //clear our visible lights from previous frame
      _visibleLights.Clear();
      //perform a basic frustum test on our lights
      //we could do the same for the meshes, but its enough for this tutorial
      foreach (Light light in _lights)
      {
          if (light.LightType == Light.Type.Directional)
          {
              _visibleLights.Add(light);
          }
          else if (light.LightType == Light.Type.Spot)
          {
              if(_camera.Frustum.Intersects(light.Frustum))
                  _visibleLights.Add(light);
          }
          else if (light.LightType == Light.Type.Point)
          {
              if (_camera.Frustum.Intersects(light.BoundingSphere))
                  _visibleLights.Add(light);
          }
      }

      RenderTarget2D output = _renderer.RenderScene(_camera, _visibleLights, _meshes);

      GraphicsDevice.SetRenderTarget(null);

      GraphicsDevice.BlendState = BlendState.Opaque;
      GraphicsDevice.DepthStencilState = DepthStencilState.Default;

      //output our final composition into the backbuffer. We could output it into a
      //post-process fx, or to an object diffuse texture, or or or...well, its up to you =)
      //Just remember that we are using a floating point buffer, so we should use Point Clamp here
      _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise);
      
      _spriteBatch.Draw(output,new Rectangle(0,0,GraphicsDevice.Viewport.Width,GraphicsDevice.Viewport.Height), Color.White);
    
      if (_renderGBuffer)
      {
          int smallWidth = GraphicsDevice.Viewport.Width / 3;
          int smallHeigth = GraphicsDevice.Viewport.Height / 3;

          //draw the intermediate steps into screen
          _spriteBatch.Draw(_renderer.NormalBuffer, new Rectangle(0, 0, smallWidth, smallHeigth),
                              Color.White);
          _spriteBatch.Draw(_renderer.DepthBuffer, new Rectangle(smallWidth, 0, smallWidth, smallHeigth),
                              Color.White);
          _spriteBatch.Draw(_renderer.LightBuffer, new Rectangle(smallWidth * 2, 0, smallWidth, smallHeigth),
                            Color.White);
          /*if (_renderer.GetShadowMap(0) != null)
              _spriteBatch.Draw(_renderer.GetShadowMap(0), new Rectangle(0, smallHeigth, smallWidth, smallHeigth),
                            Color.White);
          if (_renderer.GetShadowMap(1) != null)
              _spriteBatch.Draw(_renderer.GetShadowMap(1), new Rectangle(smallWidth, smallHeigth, smallWidth, smallHeigth),
                            Color.White);*/

      }
      _spriteBatch.End();


      //legacy, left here just in case
      if  (_renderScreenSpaceLightQuads)
      {
          _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise);
      
          
          Rectangle rect = new Rectangle();
          foreach (Light light in _visibleLights)
          {
              //redundant, but its ok for now
              Vector2 topLeftCorner, bottomRightCorner, size;
              //compute a screen-space quad that fits the light's bounding sphere
              _camera.ProjectBoundingSphereOnScreen(light.BoundingSphere, out topLeftCorner, out size);
              rect.X = (int) ((topLeftCorner.X*0.5f + 0.5f)*GraphicsDevice.Viewport.Width);
              rect.Y = (int)((topLeftCorner.Y*0.5f + 0.5f) * GraphicsDevice.Viewport.Height);
              rect.Width = (int)((size.X*0.5f) * GraphicsDevice.Viewport.Width);
              rect.Height = (int)(( size.Y*0.5f) * GraphicsDevice.Viewport.Height);
              Color color = light.Color;
              color.A = 150;
              _spriteBatch.Draw(_lightDebugTexture, rect, color);
          }

          _spriteBatch.End();
      }

      bool drawText = true;
      if (drawText)
      {
          _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearClamp,
                             DepthStencilState.Default, RasterizerState.CullCounterClockwise);

          _spriteBatch.DrawString(_spriteFont, "Press O / P to visualize the GBuffer", new Vector2(0, 0),
                                  Color.White);
          _spriteBatch.DrawString(_spriteFont, "Hold space to stop the lights", new Vector2(0, 20), Color.White);
          _spriteBatch.DrawString(_spriteFont,
                                  String.Format("Update:{0:00.0}, Draw:{1:00.0}, GPU:{2:00.0}", updateMs, drawMs,
                                                gpuMs), new Vector2(0, 40), Color.White);

          RenderStatistics renderStatistics = _renderer.RenderStatistics;
          _spriteBatch.DrawString(_spriteFont, "Visible lights: " + renderStatistics.VisibleLights,
                                  new Vector2(0, 60), Color.White);
          _spriteBatch.DrawString(_spriteFont, "Draw calls: " + renderStatistics.DrawCalls, new Vector2(0, 80),
                                  Color.White);
          _spriteBatch.DrawString(_spriteFont, "Shadow Lights: " + renderStatistics.ShadowCasterLights,
                                  new Vector2(0, 100), Color.White);
          _spriteBatch.DrawString(_spriteFont, "Shadow Meshes: " + renderStatistics.ShadowCasterMeshes,
                                  new Vector2(0, 120), Color.White);

          _spriteBatch.End();
      }

      base.Draw(gameTime);

      swDraw.Stop(); 
      updateMs = swUpdate.Elapsed.TotalMilliseconds;
      drawMs = swDraw.Elapsed.TotalMilliseconds;

      swGPU.Reset();
      swGPU.Start(); 

  }

If you're looking to add some cool lighting effects to your XNA game, this is a project you might want to take a peek at. Also while you're there, make sure you check out the other cool related posts too. Here's just a few of the recent ones;

Follow the Discussion

  • I am looking for "awesome lighting effects" for 2D graphics.  Perhaps like a glowing effect?  has anyone seen an example like that?  in more details, if I have a circle or a square.png and I bring it in, and I draw it to the screen.. how can I surround that same texture with lighting?  I could probably do the effect externally (like in photoshop), but would love to do it in code if its possible, so that I can apply it to anything.. without mocking around with photoshop and working with 100 different images.  thanks!

  • 000000

    I'm a super-noob at this, but I imagine you could get the effect with blending settings. For example, if you wanted the light to reveal otherwise-hidden areas of the screen you would have a screen-sized "Lighting" image drawn on to the screen every frame with a 'multiply' blending. You might also be able to software-generate halos by colorizing and then blurring the image you want to be glowing, and then drawing the image on top.

    Never implemented any of these, but all of the graphics packages in the programming languages I know better have the required tools.

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.