Спонсор: Microsoft
Опубликован: 10.04.2009 | Уровень: специалист | Доступ: платный
Самостоятельная работа 18:

Анимация, эффекты

Эффекты

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

Шейдерные программы или просто шейдеры представлены в виде файлов с расширением FX. Эти файлы можно создавать как с помощью специального ПО для разработки и отладки шейдеров, так и вручную, в редакторе кода Visual Studio. Для описания шейдерных программ существует специальный язык – HLSL – High Level Shader Language – Высокоуровневый язык описания шейдеров.

Шейдерные программы исполняются графическим процессором видеокарты, с их помощью можно создать множество графических эффектов, которыми наполнены современные игры.

Шейдеры принято делить на вершинные (Vertex Shader) и пиксельные (Pixel Shader).

Вершинные работают с вершинами объектов. С их помощью можно осуществить такие операции, как деформацию объектов, анимацию, перемещение и т.д. После того, как вершинный шейдер завершит работу по модификации модели, за дело принимается пиксельный шейдер.

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

Для того, чтобы создать новый FX-файл в XNA-проект, достаточно выполнить щелчок левой кнопкой мыши по папке Content, выбрать пункт Add \Rightarrow New Item и в появившемся окне выбрать в качестве типа добавляемого файла Effect File. После того, как файл эффектов будет добавлен в проект, он будет содержать некоторые стандартные части, которые представляют собой объявление переменных, необходимых для работы шейдера, объявление точки входа в шейдер и, собственно, шаблоны двух шейдеров – вершинного и пиксельного.

Для того, чтобы разрабатывать шейдеры самостоятельно с помощью XNA-редактора FX-файлов, вам нужно ознакомиться с языком HLSL. Вы можете сделать это, воспользовавшись справочной службой MSDN, в частности, этим разделом: http://msdn2.microsoft.com/en-us/library/bb509561.aspx.

На практике лучше всего пользоваться специализированным ПО для разработки и отладки шейдеров.

Рассмотрим пример работы с шейдерными эффектами – он создан с использованием примера применения шейдеров, приведенного в документации к XNA.

Создадим новый проект P18_2. Нарис. 24.3. вы можете видеть его окно Solution Explorer.

Окно Solution Explorer для проекта P18_2

Рис. 24.3. Окно Solution Explorer для проекта P18_2

Файл 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. вы можете видеть игровое окно проекта в различных режимах применения шейдера.

Наложение текстуры без изменения цвета, с множителем 2

увеличить изображение
Рис. 24.4. Наложение текстуры без изменения цвета, с множителем 2
Наложение текстуры с размытием, множитель 1

увеличить изображение
Рис. 24.5. Наложение текстуры с размытием, множитель 1
Текстура в оттенках серого, множитель 1

увеличить изображение
Рис. 24.6. Текстура в оттенках серого, множитель 1
Alina Lasskaja
Alina Lasskaja

Быть может кто-то из Вас знает игру Sims, к какому жанру она относиться? Жизненная симуляция, ролевая игра, там можно и дома строить.....

Дмитрий Кацман
Дмитрий Кацман
Израиль
Андрей Веденин
Андрей Веденин
Россия, Белгород