Пример 2D освещения в XNA Game Studio 4.0. Эффект Glow

В этой статье мы рассмотрим еще одну тему, которая относится к 2D освещению. Это будет эффект Glow или, по крайней мере, то, что я под этим понимаю.

Мы будем делать некоторые части изображения более яркими и светящимися.

Для начала мы, как обычно, создадим проект и напишем в нем немного кода для загрузки, управления и рисования спрайта.

 

Texture2D clown;

Rectangle position = new Rectangle(200, 100, 384/2, 640/2);

SpriteEffects spriteEffect;

 

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
 
            graphics.PreferredBackBufferWidth = 640;
            graphics.PreferredBackBufferHeight = 480;
        }

 

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            // TODO: use this.Content to load your game content here
            clown = Content.Load<Texture2D>("evil_clown1");

 

        }

 

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
 
            // TODO: Add your update logic here
            KeyboardState kb = Keyboard.GetState();
 
            int speed = (int)(100 * gameTime.ElapsedGameTime.TotalSeconds);
            if (kb.IsKeyDown(Keys.Up))
                position.Offset(0, -speed);
            if (kb.IsKeyDown(Keys.Down))
                position.Offset(0, +speed);
            if (kb.IsKeyDown(Keys.Left))
            {
                position.Offset(-speed, 0);
                spriteEffect = SpriteEffects.FlipHorizontally;
            }
            if (kb.IsKeyDown(Keys.Right))
            {
                position.Offset(+speed, 0);
                spriteEffect = SpriteEffects.None;
            }
 
 
 
            base.Update(gameTime);
        }

 

        protected override void Draw(GameTime gameTime)
        {            GraphicsDevice.Clear(Color.Black);
 
            spriteBatch.Begin();
            spriteBatch.Draw(clown, position, null, Color.White, 0, Vector2.Zero, spriteEffect, 0);
            spriteBatch.End();

            base.Draw(gameTime);
        }

 

Получится вот такая картинка.

Ничего лучше придумать я смог. Теперь представим, что все белые объекты – это или небольшие источники света или просто хорошо отражающие поверхности.

Собственно алгоритм работы нашего шейдера будет таким:

  1. Выделить все наиболее яркие области изображения
  2. Размыть каким-нибудь способом полученную картинку
  3. Объединить исходное изображение с изображением с размытыми областями

Сделаем мы это при помощи трех различных техник, результаты работы каждой из которых будут выводиться на свою поверхность рисования.

 
sampler  ColorSampler  : register(s0);

float4 FilterPS(float2 TexCoords : TEXCOORD0) : COLOR0
{
          float3 luminance = float3(0.299, 0.587, 0.114);
          float4 color = tex2D(ColorSampler, TexCoords);
          float intensity = dot(color, luminance);
          if (intensity < 0.9)
                    return float4(0,0,0,1);
          else
                    return float4(1,1,1,1);
}

technique Filter
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 FilterPS();
    }
}

Первая техника просто вычисляет яркость каждого пикселя и «стирает» все пиксели с яркостью меньше некоторого порога (в данном случае 0.9). Если интенсивность больше этого порога, то я просто закрашиваю пиксель белым цветом. На самом деле, это не правильно (стоило бы оставлять исходный цвет), но я знаю, что все нужные нам области белые, так что можно в данном случае поступить именно так.

Применим шейдер к текущему изображению: (я также создам сразу все три поверхности рисования)

 
        Effect glowEffect;
        RenderTarget2D target1;
        RenderTarget2D target2; 
        RenderTarget2D target3;

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            target1 = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
             target2 = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
             target3 = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            // TODO: use this.Content to load your game content here
            clown = Content.Load<Texture2D>("evil_clown1");
 
 
            glowEffect = Content.Load<Effect>("glow");
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.SetRenderTarget(target1);
            GraphicsDevice.Clear(Color.Black);
 
            spriteBatch.Begin();
            spriteBatch.Draw(clown, position, null, Color.White, 0, Vector2.Zero, spriteEffect, 0);
            spriteBatch.End();
 
             GraphicsDevice.SetRenderTarget(null);
             GraphicsDevice.Clear(Color.Black);
  
             glowEffect.CurrentTechnique = glowEffect.Techniques["Filter"];
             spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
             spriteBatch.Draw(target1, Vector2.Zero, Color.White);
             spriteBatch.End();
 

            base.Draw(gameTime);
        }

Следующий шаг – размытие. Тут существуют различные алгоритмы, я выбрал такой, который дает довольно красивый визуальный эффект, а именно размытие будет происходить по вертикали и по горизонтали.

 

float4 BlurPS(float2 TexCoords : TEXCOORD0) : COLOR0
{
          float step = 2.0;
          float2 deltaX = float2( step / 640, 0);
          float2 deltaY = float2( 0, step / 480);
          
          float4 color = float4(0,0,0,1);
          
          int i = 0;
          for (i = -3; i <= 3; i++)
                    color += tex2D(ColorSampler, TexCoords + i*deltaX);
                    
          for (i = -3; i <= 3; i++)
                    color += tex2D(ColorSampler, TexCoords + i*deltaY);
                    
          color -= tex2D(ColorSampler, TexCoords);
          
          color /= 13;
          
          return color;
}
technique Blur
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 BlurPS();
    }

}

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.SetRenderTarget(target1);
            GraphicsDevice.Clear(Color.Black);
 
            spriteBatch.Begin();
            spriteBatch.Draw(clown, position, null, Color.White, 0, Vector2.Zero, spriteEffect, 0);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(target2);
            GraphicsDevice.Clear(Color.Black);
 
            glowEffect.CurrentTechnique = glowEffect.Techniques["Filter"];
            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
            spriteBatch.Draw(target1, Vector2.Zero, Color.White);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(null);
             GraphicsDevice.Clear(Color.Black);
  
             glowEffect.CurrentTechnique = glowEffect.Techniques["Blur"];
             spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
             spriteBatch.Draw(target2, Vector2.Zero, Color.White);
             spriteBatch.End();
 
 
            base.Draw(gameTime);
        }

Вот что мы получим:

Ну теперь осталось только объединить полученные текстуры.

 
sampler  ColorSampler  : register(s0);
sampler  ColorSampler2  : register(s1);


float4 GlowPS(float2 TexCoords : TEXCOORD0) : COLOR0
{
          float4 color = tex2D(ColorSampler, TexCoords);
          float4 color2 = tex2D(ColorSampler2, TexCoords);
          return color2 + color;
}

technique Glow
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 GlowPS();
    }
}

Текстура будет передаваться через регистр s1, так что не нужно устанавливать параметр шейдера, а нужно установить параметр GraphicsDevice.

 
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.SetRenderTarget(target1);
            GraphicsDevice.Clear(Color.Black);
 
            spriteBatch.Begin();
            spriteBatch.Draw(clown, position, null, Color.White, 0, Vector2.Zero, spriteEffect, 0);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(target2);
            GraphicsDevice.Clear(Color.Black);
 
            glowEffect.CurrentTechnique = glowEffect.Techniques["Filter"];
            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
            spriteBatch.Draw(target1, Vector2.Zero, Color.White);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(target3);
            GraphicsDevice.Clear(Color.Black);
 
            glowEffect.CurrentTechnique = glowEffect.Techniques["Blur"];
            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
            spriteBatch.Draw(target2, Vector2.Zero, Color.White);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(null);
             GraphicsDevice.Clear(Color.Black);
  
             glowEffect.CurrentTechnique = glowEffect.Techniques["Glow"];
  
             GraphicsDevice.Textures[1] = target3;
             spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
             spriteBatch.Draw(target1, Vector2.Zero, Color.White);
             spriteBatch.End();
 
            base.Draw(gameTime);
        }

Вот такой забавный результат получился с использованием достаточно несложного шейдера.

Исходный код:

Glow.fx

sampler  ColorSampler  : register(s0);
sampler  ColorSampler2  : register(s1);
 
float4 FilterPS(float2 TexCoords : TEXCOORD0) : COLOR0
{
          float3 luminance = float3(0.299, 0.587, 0.114);
          float4 color = tex2D(ColorSampler, TexCoords);
          float intensity = dot(color, luminance);
          if (intensity < 0.9)
                    return float4(0,0,0,1);
          else
                    return float4(1,1,1,1);
}
 
float4 BlurPS(float2 TexCoords : TEXCOORD0) : COLOR0
{
          float step = 2.0;
          float2 deltaX = float2( step / 640, 0);
          float2 deltaY = float2( 0, step / 480);
          
          float4 color = float4(0,0,0,1);
          
          int i = 0;
          for (i = -3; i <= 3; i++)
                    color += tex2D(ColorSampler, TexCoords + i*deltaX);
                    
          for (i = -3; i <= 3; i++)
                    color += tex2D(ColorSampler, TexCoords + i*deltaY);
                    
          color -= tex2D(ColorSampler, TexCoords);
          
          color /= 13;
          
          return color;
}
 
float4 GlowPS(float2 TexCoords : TEXCOORD0) : COLOR0
{
          float4 color = tex2D(ColorSampler, TexCoords);
          float4 color2 = tex2D(ColorSampler2, TexCoords);
          return color2 + color;
}
 
 
 
technique Filter
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 FilterPS();
    }
}
technique Blur
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 BlurPS();
    }
}
technique Glow
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 GlowPS();
    }
}

 

Game1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
 
namespace EvilClown
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
 
        Texture2D clown;
        Rectangle position = new Rectangle(200, 100, 384/2, 640/2);
        SpriteEffects spriteEffect;
 
 
        Effect glowEffect;
        RenderTarget2D target1;
        RenderTarget2D target2; 
        RenderTarget2D target3;
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
 
            graphics.PreferredBackBufferWidth = 640;
            graphics.PreferredBackBufferHeight = 480;
        }
 
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            target1 = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
            target2 = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
            target3 = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
            base.Initialize();
        }
 
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            // TODO: use this.Content to load your game content here
            clown = Content.Load<Texture2D>("evil_clown1");
 
 
            glowEffect = Content.Load<Effect>("glow");
        }
 
        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }
 
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
 
            // TODO: Add your update logic here
            KeyboardState kb = Keyboard.GetState();
 
            int speed = (int)(100 * gameTime.ElapsedGameTime.TotalSeconds);
            if (kb.IsKeyDown(Keys.Up))
                position.Offset(0, -speed);
            if (kb.IsKeyDown(Keys.Down))
                position.Offset(0, +speed);
            if (kb.IsKeyDown(Keys.Left))
            {
                position.Offset(-speed, 0);
                spriteEffect = SpriteEffects.FlipHorizontally;
            }
            if (kb.IsKeyDown(Keys.Right))
            {
                position.Offset(+speed, 0);
                spriteEffect = SpriteEffects.None;
            }
 
 
 
            base.Update(gameTime);
        }
 
        /// <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)
        {
            GraphicsDevice.SetRenderTarget(target1);
            GraphicsDevice.Clear(Color.Black);
 
            spriteBatch.Begin();
            spriteBatch.Draw(clown, position, null, Color.White, 0, Vector2.Zero, spriteEffect, 0);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(target2);
            GraphicsDevice.Clear(Color.Black);
 
            glowEffect.CurrentTechnique = glowEffect.Techniques["Filter"];
            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
            spriteBatch.Draw(target1, Vector2.Zero, Color.White);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(target3);
            GraphicsDevice.Clear(Color.Black);
 
            glowEffect.CurrentTechnique = glowEffect.Techniques["Blur"];
            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
            spriteBatch.Draw(target2, Vector2.Zero, Color.White);
            spriteBatch.End();
 
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.Black);
 
            glowEffect.CurrentTechnique = glowEffect.Techniques["Glow"];
 
            GraphicsDevice.Textures[1] = target3;
            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, glowEffect);
            spriteBatch.Draw(target1, Vector2.Zero, Color.White);
            spriteBatch.End();
 
            base.Draw(gameTime);
        }
    }
}

 


					
Реклама
Запись опубликована в рубрике Компьютерная графика с метками , , , , , , , , . Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s