понедельник, 15 июня 2009 г.

Effect “Edge Detect”

Последнее время, в форумах, часто упоминается данный эффект. И для тех, кто не знает решил рассказать и приложить рабочий пример на XNA.

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

I. Как вы уже догадались первой техникой мы будем рисовать normal map.

Т.е. в эффекте PositionNormal.fx вместо цвета будем сохранять данные о нормали к данной точке. Но в следствии того, что каждый компонент цвета может хранить число из интервала {0, 1}, а нормали могут иметь отрицательные компоненты, то перед сохранением нормаль упакуем старым добрым способом: 
Out.Normal = mul(normal, 0.5f) + 0.5f;

1) Очищаем экран в цвет Color(0.5f, 0.5f, 0f, 1f), на самом деле это не цвет а так же упокованная нормаль (0f, 0f, -1f, 1f).

2) Рисуем сцену первый раз при помощи PositionNormal.fx
(Текст эффекта)
float4x4 matWorld : WORLD;
float4x4 matWVP : WORLDVIEWPROJECTION; 
struct VS_INPUT 
{
  float4 Position : POSITION0;
  float3 Normal : NORMAL0;
};
struct VS_OUTPUT 
{
  float4 Position : POSITION0;
  float3 Normal : COLOR0;
};
VS_OUTPUT vs_main( VS_INPUT In )
{
  VS_OUTPUT Out = ( VS_OUTPUT ) 0;
  Out.Position = mul( In.Position, matWVP );
  float3 normal = normalize(mul(In.Normal, matWorld)); 
  Out.Normal = mul(normal, 0.5f) + 0.5f;
  return Out;
}
struct PS_INPUT 
{
  float3 Normal : COLOR0;
};

float4 ps_main(PS_INPUT In) : COLOR0
{  
 return float4(In.Normal, 1.0f);
}
technique PositionNormal
{
  pass Pass_0
  {
  VertexShader = compile vs_3_0 vs_main();
  PixelShader = compile ps_3_0 ps_main();
  }
}
все просто, стоит только заострить внимание на двух строчках:
...
Out.Normal = mul(normal, 0.5f) + 0.5f;
...
return float4(In.Normal, 1.0f);
...

3) Нарисованную сцену сохраняем в текстуру

II. Анализируя полученную normal map, выводим контуры объектов сцены.

У меня есть несколько вариаций техник для этого эффекта.
Различаются они сложностью геометрии сцены и подходами сравнения нормалей соседних пикселей.
Своем пример я не стал усложнять и рассмотрел случай, когда фигура на сцене имеет гладкие грани (по всей плоскости грани, нормали имеют одинаковое значение).
Рисуем при помощи EdgeDetect.fx
(Текст эффекта)

float dx;
float dy;
texture ScreenTexture; 
sampler ScreenS = sampler_state
{
 Texture =
};
float4 PS(float2 texCoord: TEXCOORD0) : COLOR
{
 float4 normal = tex2D(ScreenS, texCoord); 
 float4 normalLeft = tex2D(ScreenS, float2(texCoord.x - dx, texCoord.y));
 float4 normalRight = tex2D(ScreenS, float2(texCoord.x + dx, texCoord.y));
 float4 normalUp = tex2D(ScreenS, float2(texCoord.x, texCoord.y - dy));
 float4 normalDown = tex2D(ScreenS, float2(texCoord.x, texCoord.y + dy));
 
 float4 normalSum = mul(normalLeft + normalRight + normalUp + normalDown, 0.25f);
 
 if(normal.x == normalSum.x && normal.y == normalSum.y && normal.z == normalSum.z)
  return normal;
 else
  return float4(1.0f, 1.0f, 1.0f ,1.0f); // цвет контура
}
technique EdgeDetect
{
 pass P0
 {
  PixelShader = compile ps_3_0 PS();
 }
}

где:
dx, dy - задают толщину линии контура.

В этом примере я не стал распаковывать сравниваемые нормали normal и normalSum.
Тут этого не требовалось, и мы не будем перегружать пиксельный шейдер.
Распаковка типа:
normal = (normal - 0.5f) * 2.0f;
Может потребоваться тогда, когда Вам нужно будет анализировать normal map с отображенными объектами, поверхности которых изогнуты.

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

Скачать пример можно тут (для скачивания требуется регистрация на xnadev.ru). 

На реализацию эффекта вдохновило демо, группы ASD.




Комментариев нет:

Отправить комментарий