Продвинутое освещение и текстурирование в XNA Часть 2

Продвинутое освещение и текстурирование в XNA Часть 2

Методики Текстурирования

В этой статье я опишу несколько методик наложения текстур:

• Diffuse Mapping

• Normal Mapping

• Parallax Mapping

• Relief Mapping

Relief Mapping — превосходная методика наложения текстур, Parallax Mapping — популярная методика, которая используется сегодняшними играми. Наша игра может переключиться на Normal Mapping, если видеоподсистема вашего компьютера стара и, если Вы покупали свою видеокарту в эпохе Возрождения, то Вы должны использовать Diffuse Mapping. Рисунки 17-20 иллюстрируют эти методики:


Рисунок 17. Relief Mapping


Рисунок 18. Parallax Mapping


Рисунок 19. Normal Mapping


Рисунок 20. Diffuse Mapping

Diffuse Mapping

Diffuse Mapping — самая простая методика текстурирования. Используя уравнения освещения, мы можем вычислить коэффициент рассеивания, зеркальный блеск и окружающий цвет. В конечном шаге мы берем пиксель из карты рассеивания и умножаем его на результат уравнения освещения.

Вот HLSL код вертексного шейдера:

VS_OUTPUT DiffuseMapping_VertexShader(float3 Position : POSITION,

float2 TextureCoordinate : TEXCOORD0,

float3 Normal : NORMAL)

{

VS_OUTPUT OUT;

float3 PositionInWorld = mul(float4(Position, 1.0f), World).xyz;

OUT.Position = mul(float4(Position, 1.0f), WorldViewProjection);

OUT.Normal = Normal;

OUT.TextureCoordinate = TextureCoordinate;

OUT.ViewDirection = normalize(CameraPosition — PositionInWorld);

OUT.LightDirection = -LightDirection;

OUT.Diffuse = MaterialDiffuse * LightDiffuse;

OUT.Specular = MaterialSpecular * LightSpecular;

if(LightType == LIGHT_DIRECTIONAL)

{

OUT.LightDirection = -LightDirection;

}

else if(LightType == LIGHT_POINT)

{

OUT.LightDirection = (LightPosition — PositionInWorld) / LightRange;

}

else if(LightType == LIGHT_SPOT)

{

OUT.LightDirection = (LightPosition — PositionInWorld) / LightRange;

}

return OUT;

}

Мы умножаем конечный рассеянный цвет из уравнения освещения на цвет текстуры в пиксельном шейдере:

float4 DiffuseMapping_PixelShader(VS_OUTPUT IN) : COLOR

{

float3 NormalizedLightDirection = normalize(IN.LightDirection);

float4 FinalColor = GetFinalColor(IN.Normal,

NormalizedLightDirection,

IN.ViewDirection,

IN.Diffuse,

IN.Specular);

FinalColor *= tex2D(DiffuseSampler,

IN.TextureCoordinate);

if(LightType == LIGHT_POINT)

{

float Attenuation = saturate(1.0f — dot(IN.LightDirection,

IN.LightDirection));

FinalColor *= Attenuation;

}

else if(LightType == LIGHT_SPOT)

{

float Attenuation = saturate(1.0f — dot(IN.LightDirection,

IN.LightDirection));

FinalColor *= Attenuation *

GetSpotLightEffect(NormalizedLightDirection,

LightDirection);

}

return FinalColor;

}

Normal Mapping

Normal Mapping, как и Diffuse Mapping, использует основное уравнение освещения. Единственное различие между этими двумя эффектами заключается в том, как мы получаем поверхностную нормаль. Вместо того, чтобы использовать нормаль вершины для целой поверхности (все пиксели), в методике Normal Mapping у нас есть текстура, дополнительная к карте рассеивания, которая называется NormalTexture — она описывает нормаль для каждого пикселя (Как я упоминал в предыдущем разделе, в методике Diffuse Mapping у нас есть вектор нормали для каждой вершины). Используя эту попиксельную методику, наш плоский объект будет выглядеть более детализированным. Это происходит потому что различные вектора нормали выливаются в различное количество поглощающего света, и наш конечный цвет будет выглядеть лучше.

Прежде, чем мы начнем с кода HLSL, давайте освежим наши знания о координатных пространствах. В трехмерных системах мы можем определить много мест. Например в мировых пространственных координатах (также известных как координаты тела человека), мы определяем ось X как направление правой руки, ось Y находится на вершине нашей головы и указывает на небо, и ось Z находится позади нас. Это означает, когда мы смотрим вперед, мы смотрим в направлении -Z.

Теперь давайте определям новую систему координат: координаты касательного пространства. В касательном пространстве, как и в любом другом координатном пространстве, у нас есть 3 оси: касательная (X), бинормаль (Y) и нормаль (Z). Чтобы преобразовать позиции от мировых пространственных координат к касательным пространствам, мы должны умножить вершины на матрицу TBN, составленную из касательных, бинормальных и нормальных векторов:

Рисунок 21 иллюстрирует оси касательного пространства:


Рисунок 21. Координаты касательного пространства

Где P’ является новой позицией вершины в касательном пространстве, P — позиция вершины в мировых пространственных координатах, T — ось касательной, B — ось бинормали, и N — ось нормали.

Ниже приведен код на языке C# для определения неоходимой вершины, у которой есть способность держать вектора касательной и бинормали, являющиеся дополнительными для вектора нормали:

public struct VertexPositionTangentBinormalNormalTexture

{

public Vector3 Position;

public Vector3 Tangent;

public Vector3 Binormal;

public Vector3 Normal;

public Vector2 TextureCoordinate;

public static VertexElement[] VertexElements

{

get

{

return new VertexElement[]

{

new VertexElement(0,

0,

VertexElementFormat.Vector3,

VertexElementMethod.Default,

VertexElementUsage.Position,

0),

new VertexElement(0,

12,

VertexElementFormat.Vector3,

VertexElementMethod.Default,

VertexElementUsage.Tangent,

0),

new VertexElement(0,

24,

VertexElementFormat.Vector3,

VertexElementMethod.Default,

VertexElementUsage.Binormal,

0),

new VertexElement(0,

36,

VertexElementFormat.Vector3,

VertexElementMethod.Default,

VertexElementUsage.Normal,

0),

new VertexElement(0,

48,

VertexElementFormat.Vector2,

VertexElementMethod.Default,

VertexElementUsage.TextureCoordinate,

0)

};

}

}

public static int SizeInBytes

{

get

{

return 12 + 12 + 12 + 12 + 8;

}

}

}

Имея P’ как позицию в касательном пространстве, мы должны преобразовать любые другие параметры освещения, такие как направление света, направление визирования и и др. к касательному пространству. Оставшиеся вычисления — те же самые, как и в diffuse mapping. Здесь приведен необходимый HLSL код, который определяет матрицу TBN и преобразовывает всё к касательному пространству; мы можем получить бинормаль непосредственно из ввода или вычислить её как перекрестный продукт вектора нормали и вектора касательной:

VS_OUTPUT NormalMapping_VertexShader(float3 Position : POSITION,

float2 TextureCoordinate : TEXCOORD0,

float3 Normal : NORMAL,

float4 Tangent : TANGENT)

{

VS_OUTPUT OUT;

float3 n = mul(Normal, (float3x3)WorldIT);

float3 t = mul(Tangent.xyz, (float3x3)WorldIT);

float3 b = cross(n, t) * Tangent.w;

float3x3 TangentBinormalNormal = float3x3(t.x, b.x, n.x,

t.y, b.y, n.y,

t.z, b.z, n.z);

float3 PositionInWorld = mul(float4(Position, 1.0f), World).xyz;

OUT.Position = mul(float4(Position, 1.0f), WorldViewProjection);

OUT.TextureCoordinate = TextureCoordinate;

OUT.ViewDirection = normalize(

mul(CameraPosition — PositionInWorld,

TangentBinormalNormal));

OUT.Diffuse = MaterialDiffuse * LightDiffuse;

OUT.Specular = MaterialSpecular * LightSpecular;

if(LightType == LIGHT_DIRECTIONAL)

{

OUT.SpotDirection = 0;

OUT.LightDirection = mul(-LightDirection, TangentBinormalNormal);

}

else if(LightType == LIGHT_POINT)

{

OUT.SpotDirection = 0;

OUT.LightDirection = mul((LightPosition — PositionInWorld) / LightRange,

TangentBinormalNormal);

}

else if(LightType == LIGHT_SPOT)

{

OUT.LightDirection = mul((LightPosition — PositionInWorld) / LightRange,

TangentBinormalNormal);

OUT.SpotDirection = mul(LightDirection,

TangentBinormalNormal);

}

return OUT;

}

float4 NormalMapping_PixelShader(VS_OUTPUT IN) : COLOR0

{

float3 NormalizedLightDirection = normalize(IN.LightDirection);

float3 Normal = normalize(tex2D(NormalMap,

IN.TextureCoordinate).rgb * 2.0f — 1.0f);

float4 FinalColor = GetFinalColor(Normal,

NormalizedLightDirection,

IN.ViewDirection,

IN.Diffuse,

IN.Specular);

FinalColor *= tex2D(DiffuseSampler, IN.TextureCoordinate);

if(LightType == LIGHT_POINT)

{

float Attenuation = saturate(1.0f — dot(IN.LightDirection,

IN.LightDirection));

FinalColor *= Attenuation;

}

else if(LightType == LIGHT_SPOT)

{

float Attenuation = saturate(1.0f — dot(IN.LightDirection,

IN.LightDirection));

FinalColor *= Attenuation * GetSpotLightEffect(NormalizedLightDirection,

IN.SpotDirection);

}

return FinalColor;

}

Parallax Mapping

Parallax Mapping — попиксельная трассировка лучей для определения высот в касательном пространстве. При использовании parallax mapping объекты будет иметь более очевидную глубину, и таким образом, больший реализм. В parallax mapping у нас есть дополнительная текстура под названием Height texture. Мы читаем информацию о высоте из этой текстуры и заменяем наши позицию выборки карты рассеивания и карты нормали смещением, названным смещением параллакса. Мы вычисляем смещение параллакса, используя этот HLSL код:

float2 GetParallaxCorrectSamplingPosition(float2 texCoords, float3 halfVector)

{

float offset = (tex2D(HeightMap, texCoords).r) * HeightScale + HeightBias;

return texCoords + (offset * halfVector.xy);

}

Где HeightScale и HeightBias, как подразумевают их названия, масштаб и смещение глубины. Рисунок 22 иллюстрирует алгоритм Parallax mapping:


Рисунок 22 — Алгоритм Parallax mapping

Теперь мы изменим наш Normal Mapping шейдер из предыдущего раздела, чтобы использовать новые координаты текстуры; вертексный шейдер — такой же, как и Normal Mapping вертексный шейдер:

VS_OUTPUT ParallaxMapping_VertexShader(float3 Position : POSITION,

float2 TextureCoordinate : TEXCOORD0,

float3 Normal : NORMAL,

float4 Tangent : TANGENT)

{

VS_OUTPUT OUT;

float3 n = mul(Normal, (float3x3)WorldIT);

float3 t = mul(Tangent.xyz, (float3x3)WorldIT);

float3 b = cross(n, t) * Tangent.w;

float3x3 TangentBinormalNormal = float3x3(t.x, b.x, n.x,

t.y, b.y, n.y,

t.z, b.z, n.z);

float3 PositionInWorld = mul(float4(Position, 1.0f), World).xyz;

OUT.Position = mul(float4(Position, 1.0f), WorldViewProjection);

OUT.TextureCoordinate = TextureCoordinate;

OUT.ViewDirection = normalize(mul(CameraPosition — PositionInWorld,

TangentBinormalNormal));

OUT.Diffuse = MaterialDiffuse * LightDiffuse;

OUT.Specular = MaterialSpecular * LightSpecular;

if(LightType == LIGHT_DIRECTIONAL)

{

OUT.SpotDirection = 0;

OUT.LightDirection = mul(-LightDirection, TangentBinormalNormal);

}

else if(LightType == LIGHT_POINT)

{

OUT.SpotDirection = 0;

OUT.LightDirection = mul((LightPosition — PositionInWorld) / LightRange,

TangentBinormalNormal);

}

else if(LightType == LIGHT_SPOT)

{

OUT.LightDirection = mul((LightPosition — PositionInWorld) / LightRange,

TangentBinormalNormal);

OUT.SpotDirection = mul(LightDirection,

TangentBinormalNormal);

}

return OUT;

}


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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s