Cel Shader 2

О реализации некоторых нефотореалистичных эффектов. Часть 2

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

Для этого выполним ряд шагов. Будем рисовать всю сцену не сразу на экран, а на специальную «поверхность» RenderTarget (в нашем случае RenderTarget2D), с котором позднее можно будет получить изображение, выполнить над ним нужные преобразования, а затем нарисовать на экране.

Первый этап – это создание RenderTarget2D. Этап достаточно сложный поскольку конструктор RenderTarget принимает множество параметров, а при неправильном задании (параметры не поддерживаются видео-картой) уже во время выполнения мы получим непонятное исключение, которое будет давать слишком мало информации для решения проблемы.

Собственно, мне подходят следующие параметры


RenderTarget2D target;


protected
override
void Initialize()

{


// TODO: Add your initialization logic here

target = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth,

graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);


base.Initialize();

}

RenderTarget имеет размеры экрана (что подходит для нашего случая), а самый важный параметр тут последний (формат поверхности), именно указанный формат может не поддержиться вашей видео-картой. Я обычно указываю SurfaceFormat.Color, хотя, на самом деле, можно просто получить тукущие параметры адаптера.

Для рисования сцены на дополнительную поверхность нужно сначала установить ее как основную и (желательно) очистить.


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.SetRenderTarget(0, target);

GraphicsDevice.Clear(Color.Black);

// рисуем сцену как обычно

После выполнения таких операций изображение можно получить вызвав метод target.GetTexture();

Установка экрана в качестве поверхности для вывода осуществляется следующим образом:

GraphicsDevice.SetRenderTarget(0, null);

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

Texture2D scene;

protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.SetRenderTarget(0, target);

GraphicsDevice.Clear(Color.Black);


// Рисуем сцену как обычно

GraphicsDevice.SetRenderTarget(0, null);

scene = target.GetTexture();


// Теперь в переменной scene хранится наша сцена,


// а в качестве поверхности для рисования установлен экран.


// Сейчас можно сделать нужные преобразования над полученной

// сценой и наривать что-нибудь на экране.


base.Draw(gameTime);

}

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

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

Будем рисовать всю сцена в два этапа, сначала левую половину, затем правую.

Код может выглядеть следующим образом:

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

spriteBatch.Draw(scene, new
Rectangle(0, 0, width / 2, height), new
Rectangle(0, 0, width / 2, height), Color.White);

spriteBatch.End();

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

spriteBatch.Draw(scene, new
Rectangle(width / 2, 0, width / 2, height), new
Rectangle(width / 2, 0, width / 2, height), Color.White);

spriteBatch.End();

Теперь дело за малым, нужно применить какой-нибудь шейдер ко второму вызову SpriteBatch.Draw().

Я реализую для наглядности шейдер, который делает негатив изображения.

Пиксельный шейдер будет иметь следующий вид:

float4 PixelShaderFunction(float2 pos : TEXCOORD) : COLOR0

{

float4 color = 1-tex2D(Sampler, pos);

color.a = 1;

return color;

}

Главное тут не забыть установить значение альфа компоненты цвета, у нас ведь включен альфа-блендинг.

И теперь применим этот шейдер ко второму правой половине экрана.

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

spriteBatch.Draw(scene, new
Rectangle(0, 0, width / 2, height), new
Rectangle(0, 0, width / 2, height), Color.White);

spriteBatch.End();

postEffect.Begin();

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

postEffect.CurrentTechnique.Passes[0].Begin();

spriteBatch.Draw(scene, new
Rectangle(width / 2, 0, width / 2, height), new
Rectangle(width / 2, 0, width / 2, height), Color.White);

postEffect.CurrentTechnique.Passes[0].End();

postEffect.End();

spriteBatch.End();

В результате получается следующее изображение:

Теперь можно переходить к реализовации нужных нам пост-эффектов.

Весь код выглядит следующим образом.

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;

using Microsoft.Xna.Framework.Net;

using Microsoft.Xna.Framework.Storage;

using Primitives3D;

namespace NPR_1

{


///
<summary>


/// This is the main type for your game


///
</summary>


public
class
Game1 : Microsoft.Xna.Framework.Game

{


GraphicsDeviceManager graphics;


SpriteBatch spriteBatch;


TeapotPrimitive teapot;


Effect effect;


Effect postEffect;


Texture2D lightMask;


RenderTarget2D target;


Texture2D scene;


int height;


int width;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

width = graphics.PreferredBackBufferWidth = 800;

height = graphics.PreferredBackBufferHeight = 600;

}


///
<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

teapot = new
TeapotPrimitive(GraphicsDevice);

target = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth,

graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);


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

effect = Content.Load<Effect>(«light»);

postEffect = Content.Load<Effect>(«postEdge»);

lightMask = Content.Load<Texture2D>(«lightMask»);

}


///
<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


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(0, target);

GraphicsDevice.Clear(Color.Black);


// TODO: Add your drawing code here


Matrix world = Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalSeconds) * Matrix.CreateTranslation(-1.0f, 0, 0);


Matrix view = Matrix.CreateLookAt(new
Vector3(0, 1, 4), Vector3.Zero, Vector3.Up);


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10);

effect.CurrentTechnique = effect.Techniques[«Phong»];

effect.Parameters[«World»].SetValue(world);

effect.Parameters[«View»].SetValue(view);

effect.Parameters[«Projection»].SetValue(proj);

effect.Parameters[«Eye»].SetValue(new
Vector3(0, 1, 4));

effect.Parameters[«LightMask»].SetValue(lightMask);

teapot.Draw(effect);

world = Matrix.CreateRotationY(-(float)gameTime.TotalGameTime.TotalSeconds) * Matrix.CreateTranslation(1.0f, 0, 0);

effect.CurrentTechnique = effect.Techniques[«NPR»];

effect.Parameters[«World»].SetValue(world);

effect.Parameters[«View»].SetValue(view);

effect.Parameters[«Projection»].SetValue(proj);

effect.Parameters[«Eye»].SetValue(new
Vector3(0, 1, 4));

effect.Parameters[«LightMask»].SetValue(lightMask);

teapot.Draw(effect);

GraphicsDevice.SetRenderTarget(0, null);

scene = target.GetTexture();

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

spriteBatch.Draw(scene, new
Rectangle(0, 0, width / 2, height), new
Rectangle(0, 0, width / 2, height), Color.White);

spriteBatch.End();

postEffect.Begin();

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

postEffect.CurrentTechnique.Passes[0].Begin();

spriteBatch.Draw(scene, new
Rectangle(width / 2, 0, width / 2, height), new
Rectangle(width / 2, 0, width / 2, height), Color.White);

postEffect.CurrentTechnique.Passes[0].End();

postEffect.End();

spriteBatch.End();


base.Draw(gameTime);

}

}

}

Шейдер:

sampler Sampler;

float4 PixelShaderFunction(float2 pos : TEXCOORD) : COLOR0

{

float4 color = 1-tex2D(Sampler, pos);

color.a = 1;

return color;

}

technique EdgeDetection

{

pass Pass1

{

// TODO: set renderstates here.

PixelShader = compile ps_1_1 PixelShaderFunction();

}

}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s