Перейти на главную страницу
О реализации некоторых нефотореалистичных эффектов:
Начнем с одного из самых простых эффектов, а именно с Cel модели освещения. Обычно, в нефотореалистичной графике применяется специальная модель освещения, которая лучше передает эффект ручного рисования. В рисованой графике трудно точно отразить оттенки цветов, которые получает материал при освещении, в таком случае, обычно, большие области закрашиваются одним цветом.
Такого эффекта в шейдере можно добиться с помощью специальной маски цветов. Она может выглядеть, например, следующим образом:
(Синяя рамка не входит в маску, а отображается для наглядности)
Мы сможем использовать ее следующим образом:
Освещенность в каждой точке объекта измеряется от 0 до 1. Аналогично, координаты точки в маске принимают значения от 0 до 1 (в шейдере).
Таким образом, можно сопоставить яркости осщения некоторый цвет в текстуре с маской. Так, тусклое освещение соответствует самому черному (слева в маске) цвету, а наиболее яркое – белому (справа в маске).
Сначала разработаем простое приложение, которое будет рисовать чайник, освещенный по модели Фонга, чтобы потом можно было легко сравнить результат нефотореалистичного рендеринга с фотореалистичным.
Исходный код может выглядеть примерно так:
(Только метод Draw, остальной код в конце статьи)
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
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.Parameters["World"].SetValue(world);
effect.Parameters["View"].SetValue(view);
effect.Parameters["Projection"].SetValue(proj);
effect.Parameters["Eye"].SetValue(new Vector3(0, 1, 4));
teapot.Draw(effect);
world = Matrix.CreateRotationY(-(float)gameTime.TotalGameTime.TotalSeconds) * Matrix.CreateTranslation(1.0f, 0, 0);
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));
base.Draw(gameTime);
}
Шейдер:
float4x4 View;
float4x4 Projection;
float4 AmbientColor = float4(0.1, 0.1, 0.1, 1);
float ka = 0;
float kd = 0.7;
float ks = 1;
float SpecularPower = 8;
float3 LightPosition = float3(0,0.5,1);
float3 Eye;
struct VertexShaderInput
{
float4 Position : POSITION0;
// coordinates and vertex colors here.
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float3 Normal : TEXCOORD1;
// 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;
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 PixelShaderFunctionPhong(VertexShaderOutput input) : COLOR0
{
// TODO: add your pixel shader code here.
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;
}
technique Phong
{
pass Pass1
// TODO: set renderstates here.
PixelShader = compile ps_2_0 PixelShaderFunctionPhong();
}
}
Чайники имеют собственный цвет (темно синий). Для лучшего визуального эффекта коэффициент рассеянного света (ka) установлен равным 0, то есть рассеяный свет не используется.
Источник света помещен между чайниками, слегка приподнят и придвинут к наблюдателю.
Теперь перейдем к созданию нефотореалистичного эффекта. Для начала я буду отображать только диффузную составляющую.
Идея в том, чтобы получать цвет из текстуры маски, соответствующий освещенности. В следующем фрагменте кода значение освещенности для диффузной компоненты используется в качестве х-координаты для текстурного сэмплера.
float2 pos= float2(0,0);
pos.x = Diffuse;
float4 dColor = tex2D(LigthMaskSampler, pos) * DiffuseColor;
Исходный код:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
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.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);
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);
base.Draw(gameTime);
}
Шейдер (только новая техника):
{
VertexShaderOutput output;
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 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));
float2 pos= float2(0,0);
pos.x = Diffuse;
float4 dColor = tex2D(LigthMaskSampler, pos) * DiffuseColor;
return Color + Ambient + dColor + sColor ;
}
technique NPR
{
pass Pass1
// TODO: set renderstates here.
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
Для эксперимента можно изменить параметры шейдера, например, таким образом.
float kd = 0.9;
Теперь добавим зеркальную составляющую света:
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
// TODO: add your pixel shader code here.
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);
pos.x = Diffuse;
float4 dColor = tex2D(LigthMaskSampler, pos) * DiffuseColor;
pos.x = Specular;
float4 sColor = tex2D(LigthMaskSampler, pos) * SpecularColor;
return Color + Ambient + dColor + sColor ;
}
Полный исходный код:
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;
{
///
///
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
TeapotPrimitive teapot;
Effect effect;
Texture2D lightMask;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
}
///
/// 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.
///
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
///
/// LoadContent will be called once per game and is the place to load
/// all of your content.
///
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
effect = Content.Load
lightMask = Content.Load
}
///
/// UnloadContent will be called once per game and is the place to unload
/// all content.
///
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
///
///
Provides a snapshot of timing values.
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
this.Exit();
}
///
/// This is called when the game should draw itself.
///
///
Provides a snapshot of timing values.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
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.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);
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);
base.Draw(gameTime);
}
}
}
float4x4 World;
float4x4 View;
float4x4 Projection;
float ka = 0;
float kd = 0.7;
float ks = 1;
float SpecularPower = 8;
float3 LightPosition = float3(0,0.5,1);
float3 Eye;
sampler LigthMaskSampler=sampler_state
{
texture =
struct VertexShaderInput
{
float4 Position : POSITION0;
// coordinates and vertex colors here.
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float3 Normal : TEXCOORD1;
// 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;
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 PixelShaderFunctionPhong(VertexShaderOutput input) : COLOR0
{
// TODO: add your pixel shader code here.
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;
}
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
VertexShaderOutput output;
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 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);
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.
PixelShader = compile ps_2_0 PixelShaderFunctionPhong();
}
}
technique NPR
pass Pass1
{
// TODO: set renderstates here.
PixelShader = compile ps_2_0 PixelShaderFunction();
В прошлый я собирался каким-то образом использовать альфа компоненту цвета, возвращаемого вершинным шейдером
17 12 2014
1 стр.
Нам осталось только каким-то образом объединить эти два изображения. Напишем для этого еще один шейдер постобработки
17 12 2014
1 стр.
Продолжим создание эффекта. У нас уже есть шейдеры для выделения границ одноцветных областей, но теперь нам нужно создать эффект выделения границ (краев) объекта
17 12 2014
1 стр.
Обычно, в нефотореалистичной графике применяется специальная модель освещения, которая лучше передает эффект ручного рисования. В рисованой графике трудно точно отразить оттенки цв
17 12 2014
1 стр.
Андреев Василий Григорьевич, 1900 г р., рядовой, 17 августа 1943 г погиб в бою, захоронен: д. В. Сорочки, Орловская область
16 12 2014
1 стр.
Парк. По аллее навстречу друг другу идут Андреев и Коротков. Оба одеты по-осеннему, у Андреева через плечо сумка для ноутбука. Коротков выглядит жизнерадостным, Андреев бледным и у
12 10 2014
4 стр.
Описать нельзя: бархат! серебро! огонь! Господи Боже мой! Николай Чудотворец, угодник Божий! отчего же у меня нет такой бекеши! Он сшил ее тогда еще, когда Агафия Федосеевна не езд
10 09 2014
4 стр.
Непубликувано интервю на легендарния лидер на вмро иван Михайлов, направено преди 11 години в Рим от скопския журналист Борис Вишински
02 10 2014
1 стр.