G-буферы. Часть 8

G-буферы. Часть 8

Теперь мы рассмотрим совместное применение нескольких G-буферов на примере эффекта выделения внутренних границ объектов.

Внутренними границами объекта будем называть все границы объекта, которые не принадлежат его силуэту.

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

Таким образом, можно просто вычесть из границ, полученных по буферу нормалей, границы, полученные по буферу идентификаторов.

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

Модифицируем код шейдера постобработки. Теперь нам понадобится текстура с нормалями и текстура с идентификаторами (ее мы передадим в шейдер через регистр для удобства).

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

Как обычно, скажу о том, что использование двух текстур нерационально, особенно в случае использования такого шейдера. Если бы текстура хранила сразу и информацию о нормалях и об идентификаторах, можно было бы избежать четырех обращений к текстурному сэмплеру для каждого пикселя. А это достаточно значительный прирост производительности.

sampler IDSampler : register(s0);

Texture InputTexture;

sampler NormalSampler = sampler_state

{

Texture = <InputTexture>;

};

float4 PixelShaderFunction(float2 pos : TEXCOORD) : COLOR0

{

float2 deltaX = float2( 1.0 / 800, 0);

float2 deltaY = float2( 0, 1.0 / 600);


float4 color1 = tex2D(NormalSampler, pos-deltaX-deltaY);

float4 color2 = tex2D(NormalSampler, pos-deltaX+deltaY);

float4 color3 = tex2D(NormalSampler, pos+deltaX-deltaY);

float4 color4 = tex2D(NormalSampler, pos+deltaX+deltaY);


float4 color1ids = tex2D(IDSampler, pos-deltaX-deltaY);

float4 color2ids = tex2D(IDSampler, pos-deltaX+deltaY);

float4 color3ids = tex2D(IDSampler, pos+deltaX-deltaY);

float4 color4ids = tex2D(IDSampler, pos+deltaX+deltaY);

float4 deltaNormals = abs(color1 — color4) + abs(color2 — color3);

float4 deltaEdges = abs(color1ids — color4ids) + abs(color2ids — color3ids);


float normalEdges = dot(deltaNormals.xyz, 1);


float idEdges = dot(deltaEdges.xyz, 1);

float iE = 1;

if (idEdges < 0.1) iE = 0;

float nE = 1;

if (normalEdges < 0.7) nE = 0;

float4 drawColor;

drawColor.rgb = ((float3(1,1,1) * (nE — iE) + float3(1,0,0) * iE));

drawColor.a = 1;


return drawColor;

}

technique EdgeDetection

{


pass Pass1

{


// TODO: set renderstates here.


PixelShader = compile
ps_2_0 PixelShaderFunction();

}

}

Исправим немного код Game1, передав в метод DrawEdges поверхность рисования, содержащую буфер идентификаторов.


private
void DrawEdges(RenderTarget2D target, RenderTarget2D normalNarget)

{

GraphicsDevice.SetRenderTarget(0, targetEdges);

GraphicsDevice.Clear(Color.Black);

effectEdge.Parameters[«InputTexture»].SetValue(normalNarget.GetTexture());

effectEdge.Begin();

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

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

spriteBatch.Draw(target.GetTexture(), Vector2.Zero, Color.White);

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

effectEdge.End();

spriteBatch.End();

}

Текстура с идентификаторами будет, как и раньше, передаваться через регистр, то есть при помощи SpriteBatch. Явно передавать этот параметр не нужно.

Получим такую картину:

Исходный код 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;

using Microsoft.Xna.Framework.Net;

using Microsoft.Xna.Framework.Storage;

using Primitives3D;

namespace XNA_MRT

{


///
<summary>


/// This is the main type for your game


///
</summary>


public
class
Game1 : Microsoft.Xna.Framework.Game

{


GraphicsDeviceManager graphics;


SpriteBatch spriteBatch;


CubePrimitive cube;


TeapotPrimitive teapot;


CylinderPrimitive cylinder;


Effect effectLight;


Effect effectDiffuse;


Effect effectDepth;


Effect effectID;


Effect effectNormals;


Effect effectEdge;


RenderTarget2D targetDiffuse;


RenderTarget2D targetDepth;


RenderTarget2D targetID;


RenderTarget2D targetNormals;


RenderTarget2D targetEdges;


SpriteComponent spriteComp;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

graphics.PreferredBackBufferHeight = 600;

graphics.PreferredBackBufferWidth = 800;

}


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

cube = new
CubePrimitive(GraphicsDevice);

teapot = new
TeapotPrimitive(GraphicsDevice);

cylinder = new
CylinderPrimitive(GraphicsDevice);

spriteComp = new
SpriteComponent(this);


this.Components.Add(spriteComp);


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

effectDepth = Content.Load<Effect>(«drawDepth»);

effectDiffuse = Content.Load<Effect>(«drawDiffuse»);

effectID = Content.Load<Effect>(«drawID»);

effectNormals = Content.Load<Effect>(«drawNormals»);

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

effectLight = Content.Load<Effect>(«Light»);

targetDepth = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

targetDiffuse = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

targetID = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

targetNormals = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

targetEdges = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

}


///
<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.RenderState.AlphaBlendEnable = false;

GraphicsDevice.RenderState.DepthBufferEnable = true;


Matrix world = Matrix.Identity;


Matrix view = Matrix.CreateLookAt(new
Vector3(-3, 0, 2), Vector3.Zero, Vector3.Up);


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

DrawDepth(ref world, ref view, ref projection);

DrawDiffuse(ref world, ref view, ref projection);

DrawNormals(ref world, ref view, ref projection);

DrawIDs(ref world, ref view, ref projection);

DrawEdges(targetID, targetNormals);

DrawScene(ref world, ref view, ref projection);

spriteComp.Clear();

spriteComp.Add(targetDepth.GetTexture());

spriteComp.Add(targetNormals.GetTexture());

spriteComp.Add(targetID.GetTexture());

spriteComp.Add(targetDiffuse.GetTexture());

spriteComp.Add(targetEdges.GetTexture());


base.Draw(gameTime);

}


private
void DrawEdges(RenderTarget2D target, RenderTarget2D normalNarget)

{

GraphicsDevice.SetRenderTarget(0, targetEdges);

GraphicsDevice.Clear(Color.Black);

effectEdge.Parameters[«InputTexture»].SetValue(normalNarget.GetTexture());

effectEdge.Begin();

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

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

spriteBatch.Draw(target.GetTexture(), Vector2.Zero, Color.White);

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

effectEdge.End();

spriteBatch.End();

}


private
void DrawDiffuse(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, targetDiffuse);

GraphicsDevice.Clear(Color.Black);

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

effectDiffuse.Parameters[«Projection»].SetValue(projection);

effectDiffuse.Parameters[«LightPosition»].SetValue(new
Vector3(0, 0.5f, 1));

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

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

teapot.Draw(effectDiffuse);

world = Matrix.CreateTranslation(1.5f, 0, 0);

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

cube.Draw(effectDiffuse);

world = Matrix.CreateTranslation(0, 0, 0);

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

cylinder.Draw(effectDiffuse);

}


private
void DrawDepth(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, targetDepth);

GraphicsDevice.Clear(Color.Black);

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

effectDepth.Parameters[«Projection»].SetValue(projection);

effectDepth.Parameters[«Eye»].SetValue(new
Vector3(-3, 0, 2));

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

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

teapot.Draw(effectDepth);

world = Matrix.CreateTranslation(1.5f, 0, 0);

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

cube.Draw(effectDepth);

world = Matrix.CreateTranslation(0, 0, 0);

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

cylinder.Draw(effectDepth);

}


private
void DrawNormals(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, targetNormals);

GraphicsDevice.Clear(Color.Black);

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

effectNormals.Parameters[«Projection»].SetValue(projection);

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

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

teapot.Draw(effectNormals);

world = Matrix.CreateTranslation(1.5f, 0, 0);

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

cube.Draw(effectNormals);

world = Matrix.CreateTranslation(0, 0, 0);

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

cylinder.Draw(effectNormals);

}


private
void DrawIDs(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, targetID);

GraphicsDevice.Clear(Color.Black);

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

effectID.Parameters[«Projection»].SetValue(projection);

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

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

effectID.Parameters[«ID»].SetValue(new
Vector3(0,1,0));

teapot.Draw(effectID);

world = Matrix.CreateTranslation(1.5f, 0, 0);

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

effectID.Parameters[«ID»].SetValue(new
Vector3(0, 0, 1));

cube.Draw(effectID);

world = Matrix.CreateTranslation(0, 0, 0);

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

effectID.Parameters[«ID»].SetValue(new
Vector3(1, 0, 0));

cylinder.Draw(effectID);

}


private
void DrawScene(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, null);

GraphicsDevice.Clear(Color.Black);

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

effectLight.Parameters[«Projection»].SetValue(projection);

effectLight.Parameters[«Eye»].SetValue(new
Vector3(-3, 0, 2));

effectLight.Parameters[«LightPosition»].SetValue(new
Vector3(0, 0.5f, 1));

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

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

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(1, 0, 0, 1));

teapot.Draw(effectLight);

world = Matrix.CreateTranslation(1.5f, 0, 0);

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

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(0, 1, 0, 1));

cube.Draw(effectLight);

world = Matrix.CreateTranslation(0, 0, 0);

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

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(0, 0, 1, 1));

cylinder.Draw(effectLight);

}

}

}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s