Cel Shading 6

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

Посмотрим, что мы имеем на текущий момент:

  1. Изображение чайника, полученное с использованием Cel шейдера
  2. Изображение границ чайника

Нам осталось только каким-то образом объединить эти два изображения. Напишем для этого еще один шейдер постобработки.

Шейдер теперь должен использовать сразу две текстуры (границы и собственно изображение). Зато больше не нужно искать границы в пиксельном шейдере, они уже есть в текстуре.

Ранее я не задавал никаким образом текстурный сэмплер поскольку можно было считать, что он установится самостоятельно. Сейчас нам нужно задать две текстуры, для этого я укажу откуда их брать. Если не сделать операций, описанных далее, шейдер будет работать неверно поскольку мы не сможем воспользоваться обеими текстурами, а сможем использовать только ту текстуру, которая передана в spriteBatch.Draw().

sampler Sampler : register(s0);

Такое определение сэмплера скажет видео-карте о том, что сэмплер Sampler должен соответствовать первому сэмплеру установленному на графическом устройстве, который автоматически устанавливается при вызове spriteBatch.Draw().

А второй сэмплер мы зададим так, как это делается всегда:

texture EdgesMap;

sampler EdgeSampler = sampler_state

{

Texture=<EdgesMap>;

};

То есть нам нужно будет самостоятельно установить текстуру для этого сэмплера из кода Draw() точно таким же образом как мы устанавливаем любой другой параметр.

Код шейдера:

sampler Sampler : register(s0);

texture EdgesMap;

sampler EdgeSampler = sampler_state

{

Texture=<EdgesMap>;

};

float4 PixelShaderFunction(float2 pos : TEXCOORD) : COLOR0

{

float3 luminance = float3(0.299, 0.587, 0.114);

float border = dot(tex2D(EdgeSampler, pos), luminance);


float4 drawColor = tex2D(Sampler, pos) * border;

drawColor.a = 1;

return drawColor;

}

technique EdgeDetection

{

pass Pass1

{

// TODO: set renderstates here.

PixelShader = compile ps_2_0 PixelShaderFunction();

}

}

В методе Draw():

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

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

spriteBatch.End();

postEffect.Begin();

postEffect.Parameters[«EdgesMap»].SetValue(scene1);

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

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

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

spriteBatch.End();

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

postEffect.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;


GeometricPrimitive teapot;


Effect effect;


Effect postEffect;


Texture2D lightMask;


RenderTarget2D target1;


RenderTarget2D target2;


Texture2D scene1;


Texture2D scene2;


int height;


int width;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

width = graphics.PreferredBackBufferWidth = 800;

height = graphics.PreferredBackBufferHeight = 600;

graphics.PreferMultiSampling = true;

}


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

target1 = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth,

graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color, GraphicsDevice.PresentationParameters.MultiSampleType, GraphicsDevice.PresentationParameters.MultiSampleQuality);

target2 = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth,

graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color, GraphicsDevice.PresentationParameters.MultiSampleType, GraphicsDevice.PresentationParameters.MultiSampleQuality);


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>(«postEdge2»);

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, target1);

GraphicsDevice.Clear(Color.White);


// TODO: Add your drawing code here


Vector3 cameraPosition = new
Vector3(0, 0, 2);


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


Matrix view = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);


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

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

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

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

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

effect.Parameters[«LightPosition»].SetValue(cameraPosition);

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

effect.Parameters[«kd»].SetValue(1f);

effect.Parameters[«Color»].SetValue(new
Vector4(0, 0, 0, 1));

effect.Parameters[«AmbientColor»].SetValue(new
Vector4(0, 0, 0, 1));

teapot.Draw(effect);

GraphicsDevice.SetRenderTarget(0, target2);

GraphicsDevice.Clear(new
Color(150, 150, 150));

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

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

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

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

effect.Parameters[«Eye»].SetValue(cameraPosition);

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

effect.Parameters[«Color»].SetValue(new
Vector4(0, 0, 0.3f, 1));

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

effect.Parameters[«AmbientColor»].SetValue(new
Vector4(0.1f, 0.1f, 0.1f, 1));

effect.Parameters[«kd»].SetValue(0.7f);

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

teapot.Draw(effect);

GraphicsDevice.SetRenderTarget(0, null);

GraphicsDevice.Clear(Color.White);


// edges

scene1 = target1.GetTexture();


// scene

scene2 = target2.GetTexture();

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

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

spriteBatch.End();

postEffect.Begin();

postEffect.Parameters[«EdgesMap»].SetValue(scene1);

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

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

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

spriteBatch.End();

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

postEffect.End();


base.Draw(gameTime);

}

}

}

Light.fx

float4x4 World;

float4x4 View;

float4x4 Projection;

float4 AmbientColor = float4(0, 0, 0, 1);

float ka = 0;

float4 DiffuseColor = float4(0,0,1,1);

float kd = 0.7;

float4 SpecularColor = float4(1,0,0,1);

float ks = 1;

float SpecularPower = 8;

float3 LightPosition = float3(0,0.5,1);

float3 Eye;

texture LightMask;

sampler LigthMaskSampler=sampler_state

{

texture = <LightMask>;

};

float4 Color = float4(0,0,0,1);

// TODO: add effect parameters here.

struct VertexShaderInput

{

float4 Position : POSITION0;

float3 Normal : NORMAL;

// TODO: add input channels such as texture

// coordinates and vertex colors here.

};

struct VertexShaderOutput

{

float4 Position : POSITION0;

float3 WorldPosition : TEXCOORD0;

float3 Normal : TEXCOORD1;

// TODO: add vertex shader outputs such as colors and texture

// coordinates here. These values will automatically be interpolated

// over the triangle, and provided as input to your pixel shader.

};

VertexShaderOutput VertexShaderFunctionPhong(VertexShaderInput input)

{

VertexShaderOutput output;


float3 worldNormal = normalize(mul(input.Normal, World));

float4 worldPosition = mul(input.Position, World);

float4 viewPosition = mul(worldPosition, View);

output.Position = mul(viewPosition, Projection);

output.WorldPosition = worldPosition;

output.Normal = worldNormal;

// TODO: add your vertex shader code here.

return output;

}

float4 PixelShaderFunctionPhong(VertexShaderOutput input) : COLOR0

{

// TODO: add your pixel shader code here.

float3 worldPosition = input.WorldPosition;

float3 worldNormal = normalize(input.Normal);

float4 Ambient = ka * AmbientColor;

float3 lightDirection = normalize(LightPosition — worldPosition);

float4 Diffuse = kd * max(0, dot(worldNormal, lightDirection)) * DiffuseColor;

float3 eyeDirection = normalize(Eye — worldPosition);

float3 reflectedLight = normalize(reflect(-lightDirection, worldNormal));

float4 Specular = ks * pow(max(0, dot(eyeDirection, reflectedLight)), SpecularPower) * SpecularColor;

return Color + Ambient + Diffuse + Specular;

}

float4 PixelShaderFunctionPhongDiffuse(VertexShaderOutput input) : COLOR0

{

// TODO: add your pixel shader code here.

float3 worldPosition = input.WorldPosition;

float3 worldNormal = normalize(input.Normal);

float4 Ambient = ka * AmbientColor;

float3 lightDirection = normalize(LightPosition — worldPosition);

float4 Diffuse = kd * max(0, dot(worldNormal, lightDirection)) * DiffuseColor;

float3 luminance = float3(0.299, 0.587, 0.114);

float4 result = float4(0,0,0,1);

if (dot(Color + Ambient + Diffuse, luminance) > 0.2)

{

result = float4(1,1,1,1);

}

return result;

}

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

{

VertexShaderOutput output;

float4 worldPosition = mul(input.Position, World);

float4 viewPosition = mul(worldPosition, View);

float3 worldNormal = normalize(mul(input.Normal, World));

output.Position = mul(viewPosition, Projection);

output.WorldPosition = worldPosition;

output.Normal = worldNormal;

// TODO: add your vertex shader code here.

return output;

}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{

// TODO: add your pixel shader code here.

float3 worldPosition = input.WorldPosition;

float3 worldNormal = normalize(input.Normal);

float4 Ambient = ka * AmbientColor;

float3 lightDirection = normalize(LightPosition — worldPosition);

float Diffuse = kd * max(0, dot(worldNormal, lightDirection));

float3 eyeDirection = normalize(Eye — worldPosition);

float3 reflectedLight = normalize(reflect(-lightDirection, worldNormal));

float Specular = ks * pow(max(0, dot(eyeDirection, reflectedLight)), SpecularPower);

float2 pos= float2(0,0);

pos.x = Diffuse;

float4 dColor = tex2D(LigthMaskSampler, pos) * DiffuseColor;

pos.x = Specular;

float4 sColor = tex2D(LigthMaskSampler, pos) * SpecularColor;

// для экспериметов можно что-нибудь раскомментировать

//dColor = 0;

//Ambient = 0;

//sColor = 0;

return Color + Ambient + dColor + sColor ;

}

technique Phong

{

pass Pass1

{

// TODO: set renderstates here.

VertexShader = compile vs_1_1 VertexShaderFunctionPhong();

PixelShader = compile ps_2_0 PixelShaderFunctionPhong();

}

}

technique PhongDiffuse

{

pass Pass1

{

// TODO: set renderstates here.

VertexShader = compile vs_1_1 VertexShaderFunctionPhong();

PixelShader = compile ps_2_0 PixelShaderFunctionPhongDiffuse();

}

}

technique NPR

{

pass Pass1

{

// TODO: set renderstates here.

VertexShader = compile vs_1_1 VertexShaderFunction();

PixelShader = compile ps_2_0 PixelShaderFunction();

}

}

postEdge2.fx

sampler Sampler : register(s0);

texture EdgesMap;

sampler EdgeSampler = sampler_state

{

Texture=<EdgesMap>;

};

float4 PixelShaderFunction(float2 pos : TEXCOORD) : COLOR0

{

float3 luminance = float3(0.299, 0.587, 0.114);

float border = dot(tex2D(EdgeSampler, pos), luminance);


float4 drawColor = tex2D(Sampler, pos) * border;

drawColor.a = 1;

return drawColor;

}

technique EdgeDetection

{

pass Pass1

{

// TODO: set renderstates here.

PixelShader = compile ps_2_0 PixelShaderFunction();

}

}


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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s