|
Быть может кто-то из Вас знает игру Sims, к какому жанру она относиться? Жизненная симуляция, ролевая игра, там можно и дома строить..... |
Анимация, эффекты
Эффекты
Простые графические эффекты в XNA можно реализовать при выводе изображений стандартными средствами. Но стандартные эффекты не способны удовлетворить потребности разработчиков. В одной из предыдущих лекций мы уже упоминали о том, что XNA поддерживает использование шейдерных программ, которые позволяют управлять графическими эффектами.
Шейдерные программы или просто шейдеры представлены в виде файлов с расширением FX. Эти файлы можно создавать как с помощью специального ПО для разработки и отладки шейдеров, так и вручную, в редакторе кода Visual Studio. Для описания шейдерных программ существует специальный язык – HLSL – High Level Shader Language – Высокоуровневый язык описания шейдеров.
Шейдерные программы исполняются графическим процессором видеокарты, с их помощью можно создать множество графических эффектов, которыми наполнены современные игры.
Шейдеры принято делить на вершинные (Vertex Shader) и пиксельные (Pixel Shader).
Вершинные работают с вершинами объектов. С их помощью можно осуществить такие операции, как деформацию объектов, анимацию, перемещение и т.д. После того, как вершинный шейдер завершит работу по модификации модели, за дело принимается пиксельный шейдер.
Пиксельные шейдеры применяются к отдельным пикселям изображения. В частности, они отвечают за цвет пикселей, позволяют применять к изображению различные эффекты.
Для того, чтобы создать новый FX-файл в XNA-проект, достаточно выполнить щелчок левой кнопкой мыши по папке Content, выбрать пункт Add
New Item и в появившемся окне выбрать в качестве типа добавляемого файла Effect File. После того, как файл эффектов будет добавлен в проект, он будет содержать некоторые стандартные части, которые представляют собой объявление переменных, необходимых для работы шейдера, объявление точки входа в шейдер и, собственно, шаблоны двух шейдеров – вершинного и пиксельного.
Для того, чтобы разрабатывать шейдеры самостоятельно с помощью XNA-редактора FX-файлов, вам нужно ознакомиться с языком HLSL. Вы можете сделать это, воспользовавшись справочной службой MSDN, в частности, этим разделом: http://msdn2.microsoft.com/en-us/library/bb509561.aspx.
На практике лучше всего пользоваться специализированным ПО для разработки и отладки шейдеров.
Рассмотрим пример работы с шейдерными эффектами – он создан с использованием примера применения шейдеров, приведенного в документации к XNA.
Создадим новый проект P18_2. Нарис. 24.3. вы можете видеть его окно Solution Explorer.
Файл shader.fx содержит код шейдера, файл tex.png используется в качестве текстуры для наложения на модель, созданную программными средствами. Код программы реализован в классе Game1. Рассмотрим его (листинг. 24.2.).
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace P18_2
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Матрицы
Matrix WorldMAtrix, ViewMatrix, ProjectionMatrix;
//Объект для применения эффектов
Effect effect;
//Вершины куба
VertexDeclaration cubeVertexDeclaration;
//Координаты для наложения текстур
VertexPositionTexture[] cubeVertices;
//Вершинный буфер
VertexBuffer vertexBuffer;
//Индексный буфер
IndexBuffer indexBuffer;
//Массив индексов
short[] cubeIndices;
//Номер эффекта в шейдере
int CurPass;
//Множитель для текстуры
float Multiplier;
//Текстура
Texture2D texture;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
protected override void LoadContent()
{
//Мировая матрица
WorldMAtrix = Matrix.Identity;
//Матрица вида
ViewMatrix = Matrix.CreateLookAt(new Vector3(0, 0, 5), Vector3.Zero,
Vector3.Up);
//Матрица проекции
ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
MathHelper .ToRadians (45.0f),
(float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height,
1.0f, 10.0f);
//Текущий эффект имеет номер 0
CurPass = 0;
//Текущий множитель - 1
Multiplier = 1;
//Загрузим текстуру
texture = Content.Load<Texture2D>("tex");
//Загрузим шейдер shader в переменную типа Effect
effect = Content.Load<Effect>("shader");
//Передадим в переменную шейдера UserTexture текстуру
effect.Parameters["UserTexture"].SetValue(texture);
//Установим переменную шейдера Multiplier в значение
//соответствующей игровой переменной
effect.Parameters["Multiplier"].SetValue(Multiplier);
//Создадим куб
InitializeModel();
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Создаем 3D-модель
/// </summary>
public void InitializeModel()
{
// Переменная типа VertexDeclaration
cubeVertexDeclaration = new VertexDeclaration(
graphics.GraphicsDevice, VertexPositionTexture.VertexElements);
//Установка параметров точек, которые будут использованы для рисования фигуры
Vector3 topLeftFront = new Vector3(-1.0f, 1.0f, 1.0f);
Vector3 bottomLeftFront = new Vector3(-1.0f, -1.0f, 1.0f);
Vector3 topRightFront = new Vector3(1.0f, 1.0f, 1.0f);
Vector3 bottomRightFront = new Vector3(1.0f, -1.0f, 1.0f);
Vector3 topLeftBack = new Vector3(-1.0f, 1.0f, -1.0f);
Vector3 topRightBack = new Vector3(1.0f, 1.0f, -1.0f);
Vector3 bottomLeftBack = new Vector3(-1.0f, -1.0f, -1.0f);
Vector3 bottomRightBack = new Vector3(1.0f, -1.0f, -1.0f);
// Координаты текстуры
Vector2 textureTopLeft = new Vector2(0.0f, 0.0f);
Vector2 textureTopRight = new Vector2(1.0f, 0.0f);
Vector2 textureBottomLeft = new Vector2(0.0f, 1.0f);
Vector2 textureBottomRight = new Vector2(1.0f, 1.0f);
// Массив для хранения списка вершин
// Он используется для передачи данных в вершинный буфер
cubeVertices = new VertexPositionTexture[36];
// Передняя часть фигуры
cubeVertices[0] =
new VertexPositionTexture(
topLeftFront, textureTopLeft); // 0
cubeVertices[1] =
new VertexPositionTexture(
bottomLeftFront, textureBottomLeft); // 1
cubeVertices[2] =
new VertexPositionTexture(
topRightFront, textureTopRight); // 2
cubeVertices[3] =
new VertexPositionTexture(
bottomRightFront, textureBottomRight); // 3
// Задняя часть фигуры
cubeVertices[4] =
new VertexPositionTexture(
topLeftBack, textureTopRight); // 4
cubeVertices[5] =
new VertexPositionTexture(
topRightBack, textureTopLeft); // 5
cubeVertices[6] =
new VertexPositionTexture(
bottomLeftBack, textureBottomRight); //6
cubeVertices[7] =
new VertexPositionTexture(
bottomRightBack, textureBottomLeft); // 7
// Верхняя часть фигуры
cubeVertices[8] =
new VertexPositionTexture(
topLeftFront, textureBottomLeft); // 8
cubeVertices[9] =
new VertexPositionTexture(
topRightBack, textureTopRight); // 9
cubeVertices[10] =
new VertexPositionTexture(
topLeftBack, textureTopLeft); // 10
cubeVertices[11] =
new VertexPositionTexture(
topRightFront, textureBottomRight); // 11
// Нижняя часть фигуры
cubeVertices[12] =
new VertexPositionTexture(
bottomLeftFront, textureTopLeft); // 12
cubeVertices[13] =
new VertexPositionTexture(
bottomLeftBack, textureBottomLeft); // 13
cubeVertices[14] =
new VertexPositionTexture(
bottomRightBack, textureBottomRight); // 14
cubeVertices[15] =
new VertexPositionTexture(
bottomRightFront, textureTopRight); // 15
// Левая часть фигуры
cubeVertices[16] =
new VertexPositionTexture(
topLeftFront, textureTopRight); // 16
cubeVertices[17] =
new VertexPositionTexture(
bottomLeftFront, textureBottomRight); // 17
cubeVertices[18] =
new VertexPositionTexture(
topRightFront, textureTopLeft); // 18
cubeVertices[19] =
new VertexPositionTexture(
bottomRightFront, textureBottomLeft); // 19
//Создаем вершинный буфер для хранения информации о вершинах
vertexBuffer = new VertexBuffer(graphics.GraphicsDevice,
VertexPositionTexture.SizeInBytes * cubeVertices.Length,
BufferUsage.None
);
//Добавляем данные в вершинный буфер
vertexBuffer.SetData<VertexPositionTexture>(cubeVertices);
// С помощью этого массива определяем, к каким частям фигуры
//Относятся те или иные компоненты массива cubeVertices
cubeIndices = new short[] {
0, 1, 2, // Передняя плоскость
1, 3, 2,
4, 5, 6, // Задняя плоскость
6, 5, 7,
8, 9, 10, // Верхняя плоскость
8, 11, 9,
12, 13, 14, // Нижняя плоскость
12, 14, 15,
16, 13, 17, // Левая плоскость
10, 13, 16,
18, 19, 14, // Правая плоскость
9, 18, 14 };
//Индексный буфер
indexBuffer = new IndexBuffer(graphics.GraphicsDevice,
sizeof(short) * cubeIndices.Length,
BufferUsage.None,
IndexElementSize.SixteenBits
);
//Добавляем данные в индексный буфер
indexBuffer.SetData<short>(cubeIndices);
}
protected override void Update(GameTime gameTime)
{
//Получим состояние клавиатуры
KeyboardState Key = Keyboard.GetState();
//Если нажата клавиша 1 - установить
//номер прохода эффекта в 0
if (Key.IsKeyDown (Keys.D1))
{
CurPass = 0;
}
//Если нажата клавиша 2 -
//установить номер прохода в 1
if (Key.IsKeyDown(Keys.D2))
{
CurPass = 1;
}
//Если нажата клавиша 3
//установить номер прохода в 2
if (Key.IsKeyDown(Keys.D3))
{
CurPass = 2;
}
//По нажатию клавиш QWERT
//Устанавливать различные множители
//они влияют на размеры текстуры, размещенных
//на гранях куба
//Например, если Multiplier=1 - лишь один экземпляр
//изображения текстуры располагается на грани
//Если Multiplier=2 - на грани размещается уже
//4 текстуры и т.д.
if (Key.IsKeyDown(Keys.Q))
{
Multiplier = 10.0f;
}
if (Key.IsKeyDown(Keys.W))
{
Multiplier = 5.0f;
}
if (Key.IsKeyDown(Keys.E))
{
Multiplier = 2.0f;
}
if (Key.IsKeyDown(Keys.R))
{
Multiplier = 1.0f;
}
if (Key.IsKeyDown(Keys.T))
{
Multiplier = 0.5f;
}
//Модифицируем мировую матрицу таким образом, чтобы модель
//поворачивалась в пространстве с небольшой скоростью
WorldMAtrix = WorldMAtrix * Matrix.CreateRotationX(0.011f) *
Matrix.CreateRotationY(0.012f)*
Matrix.CreateRotationZ (0.013f);
//Установить матрицу, которая является результатом умножения мировой, видовой и
//проекционной матриц в эффекте
effect.Parameters["WorldViewProj"].SetValue(WorldMAtrix *ViewMatrix *ProjectionMatrix);
//Установить переменную Multiplier в эффекте
effect.Parameters["Multiplier"].SetValue(Multiplier);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
//Очистить экран
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
//Выключить режим удаления задних частей треугольников
graphics.GraphicsDevice.RenderState.CullMode =
CullMode.None;
//Установить матрицу вершин
graphics.GraphicsDevice.VertexDeclaration = cubeVertexDeclaration;
//Установить матрицу индексов
graphics.GraphicsDevice.Indices = indexBuffer;
//Установить вершинный буфер
graphics.GraphicsDevice.Vertices[0].SetSource(
vertexBuffer,
0,
VertexPositionTexture.SizeInBytes);
//Начало работы эффекта, используемого для вывода изображения
effect.Begin();
//Использовать проход шейдера, заданный переменной CurPass
effect.CurrentTechnique.Passes[CurPass].Begin();
//Вывести изображение как набор индексированных
//примитивов, используя настройки буферов, сделанные ранее
graphics.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
0,
0,
cubeVertices.Length,
0,
12);
//Завершить текущий проход шейдера
effect.CurrentTechnique.Passes[CurPass].End();
//Завершить вывод изображений
effect.End();
base.Draw(gameTime);
}
}
}
Листинг
24.2.
Код класса Game1
Теперь рассмотрим код шейдера – в листинге 24.3. вы можете найти код файла shader.fx.
//Переменные, устанавливаемые при настройке шейдера
//Матрица - результат перемножений мировой, видовой, проекционной матриц
uniform extern float4x4 WorldViewProj : WORLDVIEWPROJECTION;
//Текстура
uniform extern texture UserTexture;
//Множитель
uniform extern float Multiplier;
struct VS_OUTPUT
{
float4 position : POSITION;
float4 textureCoordinate : TEXCOORD0;
};
sampler textureSampler = sampler_state
{
Texture = <UserTexture>;
mipfilter = LINEAR;
};
//Вывод вершин текстуры
//Вершинный шейдер, который используется
//во всех вариантах эффекта
VS_OUTPUT Transform(
float4 Position : POSITION,
float4 TextureCoordinate : TEXCOORD0 )
{
VS_OUTPUT Out = (VS_OUTPUT)0;
Out.position = mul(Position, WorldViewProj);
Out.textureCoordinate = TextureCoordinate*Multiplier;
return Out;
}
//Вывод текстуры без изменений для P0, пиксельный шейдер
//Возврат того же цвета, который был передан
//шейдеру
float4 ApplyTexture(VS_OUTPUT vsout) : COLOR
{
return tex2D(textureSampler, vsout.textureCoordinate).rgba;
}
//Вывод размытой текстуры для P1, пиксельный шейдер
//Цвет устанавливается исходя из цвета
//соседних пикселей
//Полученная цветовая информация делится на 5
//для сохранения исходной яркости
float4 ApplyTextureBlur(VS_OUTPUT vsout) : COLOR
{
float4 Col;
Col = tex2D(textureSampler, vsout.textureCoordinate);
Col += tex2D(textureSampler, vsout.textureCoordinate + (0.01));
Col += tex2D(textureSampler, vsout.textureCoordinate + (0.02));
Col += tex2D(textureSampler, vsout.textureCoordinate + (0.03));
Col += tex2D(textureSampler, vsout.textureCoordinate + (0.04));
Col = Col / 5;
//Возвращаем найденный цвет пикселя
return Col;
}
//Вывод текстуры в оттенках серого для P2 - пиксельный шейдер
//Интенсивность света всех каналов изображения складывается и
//делится на количество каналов
//Альфа-канал устанавливается равным 1
float4 ApplyTextureGray(VS_OUTPUT vsout) : COLOR
{
float4 Col;
Col = tex2D(textureSampler, vsout.textureCoordinate);
Col.a=1.0f;
Col.rgb=(Col.r+Col.g+Col.b)/3;
return Col;
}
//Техника шейдера
technique TransformAndTexture
{
//Проход P0, имеет индекс 0
pass P0
{
//Вершинный шейдер для данного прохода
vertexShader = compile vs_2_0 Transform();
//Пиксельный шейдер для данного прохода
pixelShader = compile ps_2_0 ApplyTexture();
}
//Проход P1, имеет индекс 1
pass P1
{
vertexShader = compile vs_2_0 Transform();
pixelShader = compile ps_2_0 ApplyTextureBlur();
}
//Проход P2, имеет индекс 2
pass P2
{
vertexShader = compile vs_2_0 Transform();
pixelShader = compile ps_2_0 ApplyTextureGray();
}
}
Листинг
24.3.
Код файла shader.fx
На рис. 24.4., 24.5., 24.6. вы можете видеть игровое окно проекта в различных режимах применения шейдера.



