Суть в следующем. Рендеринг выполняется в два прохода, потому для этого нам понадобятся два файла эффектов 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.
Комментариев нет:
Отправить комментарий