воскресенье, 21 июня 2009 г.

Terrain Geomorphing in the Vertex Shader

Читал несколько статей на эту тему. Искал с рабочими примерами (желательно на XNA + HLSL), конкретно с пред расчетом и готовым шейдером. Но нашел только много теории и не для XNA и DirectX, а в общем. Написано умно, ничего не понял. Конкретно озадачив себя, просидел как всегда допоздна. На утро, проснулся с готовым представлением о содержимом шейдера. Мне повезло.



Демонстрация:



И так по порядку.
Рассмотрим вот такую картинку:



Здесь изображен процесс перехода между уровнями детализации ландшафта.
черным цветом - контур геометрии предыдущего слоя детализации;
синим цветом – его вершины;
красным цветом – вершины нового уровня детализации.
зеленым цветом – выделен контур новой геометрии с учетом достроенных вершин, в следствии разбиения;
фиолетовым цветом – вершины середин отрезков между вершинами предыдущего слоя детализации;
красные вектора в низ – delta вектора разности между серединами отрезков вершин предыдущего слоя и соответствующими им новыми вершинами. Дальше по тексту разъясню подробнее.

Что из себя представляет «Terrain Geomorphing in the Vertex Shader»?
Если посмотреть более пристально на видео пример к моей предыдущей статье "Procedural Landscape on XNA Game Studio 3.0", где нет геоморфинга,то можно увидеть как «выпрыгивает» (новый научный термин) новая геометрия, при наезде друг на друга слоев с различной детализацией.

Это не есть good!

Легко избежать этого можно следующим образом:
- для каждой достраиваемой вершине в новом, более детализированном слое рассчитываем ее положение на середине отрезка построенного между соседними вершинами старого, менее детализированного слоя. Эта вершина поможет нам скрыть (замаскировать) первоначальную разницу изменения геометрии. И находим вектор разницу между этой вершиной и вершиной с реальным значением высоты. Вектор должен быть именно такого направления как показано на рисунке. Это даст нам возможность при его сложении с реальной вершиной опускать ее до уровня середины отрезка.

Нам понадобится следующее описание формата вершин для хранения выше описанных данных:



namespace gEngine.gProgressiveLandscape
{
public struct gplVertexPositionNormalTexture
{

public Vector3 Position;
public Vector3 PositionGeomorphing;
public Vector3 Normal;
public Vector3 NormalGeomorphing;
public Vector2 TextureCoordinate;
public float IndexDetailedLayer;


public static readonly VertexElement[] VertexElements;

static gplVertexPositionNormalTexture()
{
VertexElements = new VertexElement[6];
short offset = 0;
// Position
VertexElements[0] = new VertexElement(0, offset,
VertexElementFormat.Vector3,
VertexElementMethod.Default,
VertexElementUsage.Position, 0);
offset += (short)Marshal.SizeOf(new Vector3());
// PositionGeomorphing
VertexElements[1] = new VertexElement(0, offset,
VertexElementFormat.Vector3,
VertexElementMethod.Default,
VertexElementUsage.Position, 1); // !
offset += (short)Marshal.SizeOf(new Vector3());
// Normal
VertexElements[2] = new VertexElement(0, offset,
VertexElementFormat.Vector3,
VertexElementMethod.Default,
VertexElementUsage.Normal, 0);
offset += (short)Marshal.SizeOf(new Vector3());
// NormalGeomorphing
VertexElements[3] = new VertexElement(0, offset,
VertexElementFormat.Vector3,
VertexElementMethod.Default,
VertexElementUsage.Normal, 1); // !
offset += (short)Marshal.SizeOf(new Vector3());
// TextureCoordinate
VertexElements[4] = new VertexElement(0, offset,
VertexElementFormat.Vector2,
VertexElementMethod.Default,
VertexElementUsage.TextureCoordinate, 0);
offset += (short)Marshal.SizeOf(new Vector2());
// NamberDetailedLayer
VertexElements[5] = new VertexElement(0, offset,
VertexElementFormat.Single,
VertexElementMethod.Default,
VertexElementUsage.TextureCoordinate, 1); // !
}

public gplVertexPositionNormalTexture( Vector3 position,
Vector3 positionGeomorphing,
Vector3 normal,
Vector3 normalGeomorphing,
Vector2 textureCoordinate,
float indexDetailedLayer)
{
this.Position = position;
this.PositionGeomorphing = positionGeomorphing;
this.Normal = normal;
this.NormalGeomorphing = normalGeomorphing;
this.TextureCoordinate = textureCoordinate;
this.IndexDetailedLayer = indexDetailedLayer;
}

public static int SizeInBytes
{
get
{
return (int)( Marshal.SizeOf(new Vector3()) +
Marshal.SizeOf(new Vector3()) +
Marshal.SizeOf(new Vector3()) +
Marshal.SizeOf(new Vector3()) +
Marshal.SizeOf(new Vector2()) +
sizeof(float));
}
}
}
}


Здесь PositionGeomorphing и есть то поле для хранения вектора смещения.

Нечто похожее делаем и с нормалями.

Все волшебство произойдет в шейдере!

Куда мы передадим дополительную информацию о:
float3 vEyePosition; - положении камеры
float layerRadius[7]; - реальные размеры уровней детализации

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

Сам шейдер выглядит следующим образом:



float4x4 matWorld : WORLD;
float4x4 matWVP : WORLDVIEWPROJECTION;

float3 vLightPosition;
float3 vEyePosition;

float layerRadius[7];

float fogBegin = 200.0f;
float fogEnd = 400.0f;

struct VS_INPUT
{
float3 Position : POSITION0;
float3 PositionGeomorphing : POSITION1;
float3 Normal : NORMAL0;
float3 NormalGeomorphing : NORMAL1;
float2 Texcoord : TEXCOORD0;
float IndexDetailedLayer : TEXCOORD1;
};

struct VS_OUTPUT
{
float4 Position : POSITION0;
float2 Texcoord : TEXCOORD0;
float3 Light : TEXCOORD1;
float3 Normal : TEXCOORD2;
float Fog : TEXCOORD3;
};

VS_OUTPUT VS(VS_INPUT In)
{
VS_OUTPUT Out = ( VS_OUTPUT ) 0;
float Distance = length ( In.Position - vEyePosition );
float K = clamp ((( Distance / layerRadius[In.IndexDetailedLayer] - 0.5f ) * 2.0f ), 0.0f, 1.0f );
Out.Position = mul ( float4(In.Position + In.PositionGeomorphing * K, 1.0f ), matWVP );
Out.Fog = clamp ((( 1.0f - (Distance - fogBegin) / fogEnd) - 0.5f) * 2.0f, 0.0f, 1.0f);
//Out.Fog = 1.0f;
Out.Texcoord = In.Texcoord;
Out.Light = normalize ( mul(vLightPosition, matWorld) );
Out.Normal = normalize ( mul( In.Normal + In.NormalGeomorphing * K, matWorld ) );
return Out;
}

texture tex;
sampler2D sTexture = sampler_state
{
Texture = (tex);
};

float spec = 0.5f;

float4 PS(VS_OUTPUT In) : COLOR0
{
float4 specColor = tex2D( sTexture, In.Texcoord ) * spec;
float4 diffColor = clamp( tex2D( sTexture, In.Texcoord ) * dot( In.Normal, In.Light ), 0.0f, 1.0f );
float4 color = float4(1.0f, 1.0f, 1.0f, 1.0f) * (1.0f - In.Fog) + (diffColor + specColor) * In.Fog;
return color;
}

technique TerrainGeomorphing
{
pass Pass1
{
VertexShader = compile vs_3_0 VS();
PixelShader = compile ps_3_0 PS();
}
}


Да, в шейдере добавлен расчет тумана, его можно выбросить. Он влияет только на текстурирование.

Оригинальный блог

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

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