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