Использование Вершинных Текстур в играх на платформе XNA (перевод)
Catalin ZimaШейдер, отвечающий за рисование снега, теперь для Вас должен быть абсолютно понятен и без объяснений. Мы читаем данные высот из карты смещения в вершинном шейдере. Пиксельный шейдер задает значения цвета на основе данных высоты, серый для “протопанного” снега, белый для обычного. В качестве тренировки можете попытаться добавить нормали и реализовать освещение, так же как мы делали для ландшафта. Шейдер будем писать в файле VTFSnow.fx в папке Shaders.
float4x4 view;
float4x4 proj;
float4x4 world;
texture displacementMap;
sampler displacementSampler = sampler_state
{
Texture = <displacementMap>;
MipFilter = None;
MinFilter = Point;
MagFilter = Point;
AddressU = Clamp;
AddressV = Clamp;
};
struct VS_INPUT
{
float4 position : POSITION;
float4 uv : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 position : POSITION;
float4 pos : TEXCOORD1;
};
float heightModifier = 32;
VS_OUTPUT Transform(VS_INPUT In)
{
VS_OUTPUT Out = (VS_OUTPUT)0;
float4x4 viewProj = mul(view, proj);
float4x4 worldViewProj= mul(world, viewProj);
//читаем данные высоты из карты смещения
float height = tex2Dlod ( displacementSampler, In.uv );
In.position.y = height;
Out.position = mul( In.position , worldViewProj);
Out.pos = In.position;
return Out;
}
float4 PixelShader(in float4 pos:TEXCOORD1) : COLOR
{
return 0.45f + pos.y / heightModifier;
}
technique GridDraw
{
pass P0
{
vertexShader = compile vs_3_0 Transform();
pixelShader = compile ps_3_0 PixelShader();
}
}
Так, это сделано, давайте вернемся обратно к коду класса Game1.cs. Нам нужен экземпляр сетки Grid и Effect для ее отрисовки. Данный кусок кода очень сильно похож на тот, который мы реализовывали на самом первом уроке. (Я знаю, что скучно писать повторяющийся код, но поверьте мне, дальше будет гораздо интересней).
Grid grid;
Effect gridEffect;
public Game1()
{
[...]
grid = new Grid(this);
grid.CellSize = 4;
grid.Dimension = 256;
}
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
[...]
gridEffect = content.Load<Effect>("Shaders\\VTFSnow");
grid.LoadGraphicsContent();
}
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
gridEffect.Parameters["world"].SetValue(Matrix.Identity);
gridEffect.Parameters["view"].SetValue(camera.View);
gridEffect.Parameters["proj"].SetValue(camera.Projection);
gridEffect.Begin();
foreach (EffectPass pass in gridEffect.CurrentTechnique.Passes)
{
pass.Begin();
grid.Draw();
pass.End();
}
gridEffect.End();
[...]
}
Мы ничего не передаем в параметр displacementMap, так как пока что нечего передавать. А вот теперь начинается веселье. Мы добавляем кучу полей в класс Game1.cs и инициализируем их в методе LoadGraphicsContent. Шейдер будет инициализироваться чуть позже, после того, как мы его напишем.
// шейдер отрисовки снега, который содержит все нужные техники
Effect snowEffect;
// шейдер, который отрисовывает модель на карте трансформации
Effect skinnedModelSnowEffect;
// Временная цель визуализации
RenderTarget2D temporaryRT;
//Цель визуализации, содержащая displacementMap для снега
RenderTarget2D snowRT;
//цель визуализация, содеражащая карту для текущего кадра
RenderTarget2D deformationRT;
//буфер глубины используется при обновлении карт дефрмации и смещения
DepthStencilBuffer snowDepth;
// флаг, отвечающий за сбрасывания состояния снега
Boolean isSnowReset = false;
//объект в комбинации с текстурой Noise
//используется для восстановления снежного покрова
Random random = new Random();
Texture2D noiseTexture;
SpriteBatch spriteBatch;
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
[...]
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
noiseTexture = content.Load<Texture2D>("Content\\noise");
}
snowDepth = new DepthStencilBuffer(
graphics.GraphicsDevice,
256,
256,
graphics.GraphicsDevice.DepthStencilBuffer.Format);
snowRT = new RenderTarget2D(
graphics.GraphicsDevice,
256,
256,
1,
SurfaceFormat.Single);
temporaryRT = new RenderTarget2D(
graphics.GraphicsDevice,
256,
256,
1,
SurfaceFormat.Single);
deformationRT = new RenderTarget2D(
graphics.GraphicsDevice,
256,
256,
1,
SurfaceFormat.Single);
isSnowReset = false;
}
Карта деформации
Теперь мы будем формировать карту деформации. Как уже упоминалось ранее, мы будем отрисовывать объекты сцены из положения “снизу”, при помощи ортогональной матрицы. Так как желательно, чтобы деформация соответствовала анимации модели, за основу будет взят шейдер SkinnedModel.fx, но мы модифицируем пиксельный шейдер под наши нужды. В папке Shaders создайте новый файл шейдера с именем SkinnedModelSnow.fx. Я опишу весь эффект и прокомментирую изменённые части.
// Максимальное количество костей которые мы можем использовать
// в шейдерной модели 2.0 за один проход.
// если меняете это значение обновите код класса SkinnedModelProcessor.cs
#define MaxBones 59
float4x4 View;
float4x4 Projection;
float4x4 Bones[MaxBones];
struct VS_INPUT
{
float4 Position : POSITION0;
float4 BoneIndices : BLENDINDICES0;
float4 BoneWeights : BLENDWEIGHT0;
};
struct VS_OUTPUT
{
float4 Position : POSITION0;
float4 worldPos : TEXCOORD1;
};
VS_OUTPUT VertexShader(VS_INPUT input)
{
VS_OUTPUT output;
// Смешивание между весами матриц костей
float4x4 skinTransform = 0;
skinTransform += Bones[input.BoneIndices.x] * input.BoneWeights.x;
skinTransform += Bones[input.BoneIndices.y] * input.BoneWeights.y;
skinTransform += Bones[input.BoneIndices.z] * input.BoneWeights.z;
skinTransform += Bones[input.BoneIndices.w] * input.BoneWeights.w;
float4 position = mul(input.Position, skinTransform);
output.Position = mul(mul(position, View), Projection);
output.worldPos = output.Position;
return output;
}
float4 PixelShader(float4 worldPos : TEXCOORD1) : COLOR0
{
float height = worldPos.y;
return 1.0f - saturate(height / 32.0);
}
technique SkinnedModelTechnique
{
pass SkinnedModelPass
{
VertexShader = compile vs_2_0 VertexShader();
PixelShader = compile ps_2_0 PixelShader();
}
}
Стоит обратить внимания на пиксельный шейдер. Мы ограничиваем высоту снега в 32 единицы, таким образом, всё, что происходит на сцене выше 32f не оказывает никакого влияния на снег. Операцией saturate(height / 32) мы получаем значения в диапазоне от 0 до 1 для значений высоты, у которых изначальное значение между 0 и 32, все, что находится выше, устанавливается в значение 1. Затем мы вычитаем это значение из единицы, в итоге получается, что чем выше высота, тем ниже значение с постепенным уменьшением до 0 (все, что выше 32 будет иметь значение 0). Это основной принцип построения карты трансформации. Все объекты сцены, которые будут оказывать воздействия на снег, должны отрисовываться этим шейдером или отрисовываться подобно статичным объектам. Теперь загружаем шейдер в методе LoadGraphicsContent:
skinnedModelSnowEffect = content.Load<Effect>("Shaders\\SkinnedModelSnow");
Далее нам надо написать метод, который сформирует нам карту деформации. Создадим метод RenderDeformationMap() в классе Game1.cs, в котором подготовим цели визуализации и установим необходимые состояния рендеринга, какое пожелаем.
void RenderDeformationMap()
{
RenderTarget2D oldRT =
graphics.GraphicsDevice.GetRenderTarget(0) as RenderTarget2D;
DepthStencilBuffer oldDS = graphics.GraphicsDevice.DepthStencilBuffer;
graphics.GraphicsDevice.SetRenderTarget(0, deformationRT);
graphics.GraphicsDevice.DepthStencilBuffer = snowDepth;
graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
graphics.GraphicsDevice.RenderState.FillMode = FillMode.Solid;
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
graphics.GraphicsDevice.Clear(Color.Black);
Для матрицы проекции (projection) мы будем использовать ортогональную матрицу проекции. Так как наша сетка состоит из 256 ячеек и размер ячейки – 4, для того чтобы охватить всю поверхность мы должны получить изображение 1024 * 1024. Для матрицы вида, мы будем использовать матрицу которая “смотрит” вдоль положительной оси координат Y.
Matrix orthoMatrix = Matrix.CreateOrthographic(1024,
1024,
1,
500);
Matrix viewMatrix = Matrix.Identity;
viewMatrix.Forward = Vector3.Up;
viewMatrix.Right = Vector3.Right;
viewMatrix.Up = Vector3.Forward;
viewMatrix.Translation = new Vector3(0, 0, 0);
Далее мы передаем параметры в шейдер и отрисовываем анимированную модель, обходя все меши. Так как при отрисовке модели мы используем кастомизированный шейдер, мы не можем использовать метод mesh.Draw() и реализуем отрисовку сами. И в конце подготавливаем цель визуализации для получения из нее данных.
Matrix[] bones = dudeEntity.AnimationPlayer.GetSkinTransforms();
skinnedModelSnowEffect.Parameters["Bones"].SetValue(bones);
skinnedModelSnowEffect.Parameters["View"].SetValue(viewMatrix);
skinnedModelSnowEffect.Parameters["Projection"].SetValue(orthoMatrix);
skinnedModelSnowEffect.Begin();
foreach (EffectPass pass in skinnedModelSnowEffect.CurrentTechnique.Passes)
{
pass.Begin();
foreach (ModelMesh mesh in dudeModel.Meshes)
{
foreach (ModelMeshPart meshpart in mesh.MeshParts)
{
graphics.GraphicsDevice.VertexDeclaration =
meshpart.VertexDeclaration;
graphics.GraphicsDevice.Vertices[0].SetSource(
mesh.VertexBuffer,
meshpart.StreamOffset,
meshpart.VertexStride);
graphics.GraphicsDevice.Indices = mesh.IndexBuffer;
graphics.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList, meshpart.BaseVertex, 0, meshpart.NumVertices,
meshpart.StartIndex, meshpart.PrimitiveCount);
}
}
pass.End();
}
skinnedModelSnowEffect.End();
graphics.GraphicsDevice.ResolveRenderTarget(0);
graphics.GraphicsDevice.SetRenderTarget(0, oldRT);
graphics.GraphicsDevice.DepthStencilBuffer = oldDS;
}
В реальном приложении мы должны были бы обойти все модели на сцене, но в данный момент у нас только одна модель.
Комментариев нет:
Отправить комментарий