Цель работы: Научиться применять графические эффекты и приемы анимации при работе с трехмерной графикой в XNA
Компьютерные игры наполнены анимированными персонажами. XNA содержит встроенные средства, поддерживающие базовые операции по анимации трехмерных персонажей. В частности, это средства для работы с так называемым скелетом персонажа. Скелет состоит из взаимосвязанных костей, управляя которыми можно анимировать модель. Создание анимированных моделей – это отдельная, достаточно трудоемкая задача, которая требует серьезных познаний в области
XNA содержит лишь базовые средства для управления скелетами объектов. Так, мы можем получить коллекцию костей объекта и проводить с ними какие-либо преобразования, вызывающие движение модели. Однако, такой подход ограничен – с его использованием весьма сложно создать реалистичное движение для сложных моделей. На основе базовых средств XNA разработаны библиотеки кода, которые содержат компоненты, позволяющие анимировать персонажи на более высоком уровне. В частности, одну из таких библиотек – XNAAnimation – можно найти на сайте http://www.codeplex.com/xnanimation.
Здесь мы рассмотрим базовый подход к анимации модели. Он заключается в следующем. Можно менять параметры отдельных костей модели и выводить ее на экран с учетом этих изменений. Мы использовали бесплатную модель Ballpen.fbx, взятую с сайта http://www.turbosquid.com.
Создадим новый проект P18_1. Загрузим в него модель Ballpen.fbx.
Код проекта сосредоточен в классе Game1.cs, его код вы можете найти в листинге 27.1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace P20_1
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модель
Model pen;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
//Загрузка модели
pen = Content.Load<Model>("ballpen");
//Соотношение сторон
float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
//Устанавливаем камеру
viewMatrix = Matrix.CreateLookAt(new Vector3(0, 30, 80), new Vector3(-32, 0, 0), new Vector3(0, 1, 0));
//Устанавливаем проекционную матрицу
projMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 1000.0f);
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Перемещение частей ручки
PenMove();
base.Update(gameTime);
}
void PenMove()
{
//Поворот зажима - он представлен родительской костью сети №3
//Умножим его матрицу трансформации на матрицу поворота по оси X
pen.Meshes[3].ParentBone.Transform = pen.Meshes[3].ParentBone.Transform *
Matrix.CreateRotationX(MathHelper.ToRadians(-1.0f));
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
//Кнопка ручки перемещается при прикосновении к экрану
if (touchLocation.State == TouchLocationState.Pressed)
{
//При прикосновении к левой части экрана
//выходит из ручки
if (touchLocation.Position.X < 400)
{
//Умножим матрицу трансформации родительской костью сети №1
//На матрицу трансляции, перемещающей ее на 2 позиции влево по оси Х
pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform *
Matrix.CreateTranslation(new Vector3(-20, 0, 0));
}
//При прикосновении к правой части экрана - входит в ручку
else
{
//Умножим матрицу трансформации родительской кости сети №1
//На матрицу трансляции, перемещающую ее на 2 позиции по оси X вправо
pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform *
Matrix.CreateTranslation(new Vector3(20, 0, 0));
}
}
}
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
//Новый массив матриц размером, соответствующим количеству
//костей в скелете модели
Matrix[] absoluteTransformations = new Matrix[pen.Bones.Count];
//Скопировать матрицы трансформации костей в массив матриц
pen.CopyAbsoluteBoneTransformsTo(absoluteTransformations);
foreach (ModelMesh mesh in pen.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.LightingEnabled = true;
effect.EnableDefaultLighting();
effect.Projection = projMatrix;
effect.View = viewMatrix;
//Установим новую мировую матрицу для родительской кости текущей сети
//это приводит к перемещению кнопки и к перемещению зажима
//Так же здесь мы уменьшаем модель, применяя коэффициент масштабирования 0,15
effect.World = absoluteTransformations[mesh.ParentBone.Index] * Matrix.CreateScale(0.15f);
}
//Выводим подготовленную сеть
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
На рис. 27.1 вы можете видеть игровой экран проекта.
(рис 27.1) Игровой экран проекта
Ранее для вывода трехмерных изображений мы пользовались шейдером BasicEffect. Он, действительно, очень часто используется и при разработке реальных игр. Однако, XNA имеет еще несколько встроенных шейдеров, в частности, SkinnedEffect, DualTextureEffect, AlphaTestEffect и EnvironmentMapEffect. Последним мы воспользуемся в нашем следующем примере для вывода трехмерного объекта, в котором отражается окружающий мир.
Прежде чем заниматься этим проектом, нам понадобится кубическая карта окружающего мира. Такую карту можно создать самостоятельно. Мы воспользовались готовыми изображениями с сайта http://www.humus.name/index.php?page=Textures. Здесь они представлены в разрешении 2048х2048, на рис. 27.2 приведена развёртка кубической карты, которой мы хотим воспользоваться в проекте.
(рис 27.2) Развертка кубической карты
Для того, чтобы собрать из этих изображений кубическую карту, мы можем воспользоваться различными инструментами, в частности, в нашем случае этим инструментом стал Adobe Photoshop CS с установленным
После установки плагина и подготовки изображений, нужно, в нашем случае, создать новое изображение размером 12288х2048 пикселей. Ширина изображения определяется произведением разрешения исходных изображений (2048) и их количеством. Мы имеем 6 изображений, для каждой из поверхностей кубической карты. Далее, нужно расположить эти изображения в следующем порядке:
Правое, Левое, Верхнее, Нижнее, Переднее, Заднее.
В итоге у нас получилось такое изображение для последующей
(рис 27.3) Изображения кубической карты, подготовленные в Photoshop
Высота наших изображений – 2048, для работы нам понадобится карта, собранная из фрагментов по 512 пикселей в высоту. Изменим размер изображения (Image > Image Size) таким образом, чтобы его высота составила 512 пикселей, ширина, соответственно 3072 пикселя.
Учитывая то, что у нас уже установлены дополнения от Nvidia, выполним команду File > Save as и в появившемся окне выберем формат файла
(рис 27.4) Настройки для сохранения кубической карты
После того, как карта сохранена, мы можем загружать её в проект. Кроме того, в проекте мы используем изображение для создания фона – мы вырезали один из фрагментов изображения, используемого для создания кубической карты. Далее, здесь же мы используем трехмерную модель. В
На рис. 27.5 приведено окно Обозреватель решений для проекта P20_2.
(рис 27.5) Обозреватель решений
Мы собираемся применять при выводе модели шейдер EnvironmentMapEffect, прежде чем заработает код, использующий этот шейдер, нам понадобится указать его в поле Эффект по умолчанию окна настройки свойств трехмерной модели, рис. 27.6.
(рис 27.6) Настройка свойств модели
Кроме того, в проект мы добавили текстуру для модели, однако, текстура обрабатывается средствами файла модели, поэтому нужно отключить для неё параметры Обработчик контента в значение Обработка не требуется и Действие при построении в значение Нет. На самом деле, текстуру можно было бы не включать в состав контента, просто скопировав в папку, в которой хранится файл с моделью в структуре папок нашего проекта.
После вышеописанной подготовки всё готово к реализации примера, его код приведен в листинге 27.2
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace P20_2
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Для вывода двумерных изображений
SpriteBatch spriteBatch;
//Трёхмерная модель
Model model;
//Фон
Texture2D background;
//Кубическая карта окружающей среды
TextureCube cubeMap;
//Показатели, влияющие на наложение карты среды
//Цвет
float amount = 1.0f;
//Коэффициент Френеля
float fresnel = 0.1f;
//Альфа-фактор
float specular = 0.3f;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
//Параметры экрана
graphics.PreferredBackBufferWidth = 480;
graphics.PreferredBackBufferHeight = 800;
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Фон
background = Content.Load<Texture2D>("back");
// Модель
model = Content.Load<Model>("saucer");
//Кубическая карта
cubeMap = Content.Load<TextureCube>("cubemap");
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
// Вывод фонового изображения
spriteBatch.Begin(0, BlendState.Opaque);
spriteBatch.Draw(background, new Rectangle(0, 0, 480, 800), Color.White);
spriteBatch.End();
//// Вычисление матриц для управления камерой
float time = (float)gameTime.TotalGameTime.TotalSeconds;
Matrix rotation = Matrix.CreateRotationX(time * 0.3f) *
Matrix.CreateRotationY(time);
Matrix view = Matrix.CreateLookAt(new Vector3(4500, -400, 0),
new Vector3(0, -400, 0),
Vector3.Up);
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio,
10, 10000);
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
//Настройка параметров графического устройства
GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
//Вывод модели с использованием EnvironmentMapEffect
foreach (ModelMesh mesh in model.Meshes)
{
foreach (EnvironmentMapEffect effect in mesh.Effects)
{
Matrix world = transforms[mesh.ParentBone.Index] * rotation;
//Настройка параметров эффекта
effect.World = world;
effect.View = view;
effect.Projection = projection;
effect.EnvironmentMap = cubeMap;
effect.EnableDefaultLighting();
effect.EnvironmentMapAmount = amount;
effect.FresnelFactor = fresnel * 2;
effect.EnvironmentMapSpecular = new Vector3(1, 1, 0.5f) * specular;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}На рис. 27.7 вы можете видеть игровое окно проекта.
(рис 27.7) Игровое окно проекта
В этой лекции мы рассмотрели приёмы скелетной анимации персонажей, создали кубическую карту окружающей среды и ознакомились с применением EnvironmentMapEffect для создания эффекта зеркального объекта, в котором отражается окружающий мир.
Найдите или создайте скелетную трехмерную модель и анимируйте её в зависимости от особенностей модели. Самостоятельно изучите возможности эффектов SkinnedEffect, DualTextureEffect, AlphaTestEffect.
К данной лабораторной работе прилагается видеоурок
и демонстрационный проект.
Цель работы: Научиться применять графические эффекты и приемы анимации при работе с трехмерной графикой в XNA
Компьютерные игры наполнены анимированными персонажами. XNA содержит встроенные средства, поддерживающие базовые операции по анимации трехмерных персонажей. В частности, это средства для работы с так называемым скелетом персонажа. Скелет состоит из взаимосвязанных костей, управляя которыми можно анимировать модель. Создание анимированных моделей – это отдельная, достаточно трудоемкая задача, которая требует серьезных познаний в области
XNA содержит лишь базовые средства для управления скелетами объектов. Так, мы можем получить коллекцию костей объекта и проводить с ними какие-либо преобразования, вызывающие движение модели. Однако, такой подход ограничен – с его использованием весьма сложно создать реалистичное движение для сложных моделей. На основе базовых средств XNA разработаны библиотеки кода, которые содержат компоненты, позволяющие анимировать персонажи на более высоком уровне. В частности, одну из таких библиотек – XNAAnimation – можно найти на сайте http://www.codeplex.com/xnanimation.
Здесь мы рассмотрим базовый подход к анимации модели. Он заключается в следующем. Можно менять параметры отдельных костей модели и выводить ее на экран с учетом этих изменений. Мы использовали бесплатную модель Ballpen.fbx, взятую с сайта http://www.turbosquid.com.
Создадим новый проект P18_1. Загрузим в него модель Ballpen.fbx.
Код проекта сосредоточен в классе Game1.cs, его код вы можете найти в листинге 27.1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace P20_1
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модель
Model pen;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
//Загрузка модели
pen = Content.Load<Model>("ballpen");
//Соотношение сторон
float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
//Устанавливаем камеру
viewMatrix = Matrix.CreateLookAt(new Vector3(0, 30, 80), new Vector3(-32, 0, 0), new Vector3(0, 1, 0));
//Устанавливаем проекционную матрицу
projMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 1000.0f);
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Перемещение частей ручки
PenMove();
base.Update(gameTime);
}
void PenMove()
{
//Поворот зажима - он представлен родительской костью сети №3
//Умножим его матрицу трансформации на матрицу поворота по оси X
pen.Meshes[3].ParentBone.Transform = pen.Meshes[3].ParentBone.Transform *
Matrix.CreateRotationX(MathHelper.ToRadians(-1.0f));
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
//Кнопка ручки перемещается при прикосновении к экрану
if (touchLocation.State == TouchLocationState.Pressed)
{
//При прикосновении к левой части экрана
//выходит из ручки
if (touchLocation.Position.X < 400)
{
//Умножим матрицу трансформации родительской костью сети №1
//На матрицу трансляции, перемещающей ее на 2 позиции влево по оси Х
pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform *
Matrix.CreateTranslation(new Vector3(-20, 0, 0));
}
//При прикосновении к правой части экрана - входит в ручку
else
{
//Умножим матрицу трансформации родительской кости сети №1
//На матрицу трансляции, перемещающую ее на 2 позиции по оси X вправо
pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform *
Matrix.CreateTranslation(new Vector3(20, 0, 0));
}
}
}
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
//Новый массив матриц размером, соответствующим количеству
//костей в скелете модели
Matrix[] absoluteTransformations = new Matrix[pen.Bones.Count];
//Скопировать матрицы трансформации костей в массив матриц
pen.CopyAbsoluteBoneTransformsTo(absoluteTransformations);
foreach (ModelMesh mesh in pen.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.LightingEnabled = true;
effect.EnableDefaultLighting();
effect.Projection = projMatrix;
effect.View = viewMatrix;
//Установим новую мировую матрицу для родительской кости текущей сети
//это приводит к перемещению кнопки и к перемещению зажима
//Так же здесь мы уменьшаем модель, применяя коэффициент масштабирования 0,15
effect.World = absoluteTransformations[mesh.ParentBone.Index] * Matrix.CreateScale(0.15f);
}
//Выводим подготовленную сеть
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
На рис. 27.1 вы можете видеть игровой экран проекта.
(рис 27.1) Игровой экран проекта
Ранее для вывода трехмерных изображений мы пользовались шейдером BasicEffect. Он, действительно, очень часто используется и при разработке реальных игр. Однако, XNA имеет еще несколько встроенных шейдеров, в частности, SkinnedEffect, DualTextureEffect, AlphaTestEffect и EnvironmentMapEffect. Последним мы воспользуемся в нашем следующем примере для вывода трехмерного объекта, в котором отражается окружающий мир.
Прежде чем заниматься этим проектом, нам понадобится кубическая карта окружающего мира. Такую карту можно создать самостоятельно. Мы воспользовались готовыми изображениями с сайта http://www.humus.name/index.php?page=Textures. Здесь они представлены в разрешении 2048х2048, на рис. 27.2 приведена развёртка кубической карты, которой мы хотим воспользоваться в проекте.
(рис 27.2) Развертка кубической карты
Для того, чтобы собрать из этих изображений кубическую карту, мы можем воспользоваться различными инструментами, в частности, в нашем случае этим инструментом стал Adobe Photoshop CS с установленным
После установки плагина и подготовки изображений, нужно, в нашем случае, создать новое изображение размером 12288х2048 пикселей. Ширина изображения определяется произведением разрешения исходных изображений (2048) и их количеством. Мы имеем 6 изображений, для каждой из поверхностей кубической карты. Далее, нужно расположить эти изображения в следующем порядке:
Правое, Левое, Верхнее, Нижнее, Переднее, Заднее.
В итоге у нас получилось такое изображение для последующей
(рис 27.3) Изображения кубической карты, подготовленные в Photoshop
Высота наших изображений – 2048, для работы нам понадобится карта, собранная из фрагментов по 512 пикселей в высоту. Изменим размер изображения (Image > Image Size) таким образом, чтобы его высота составила 512 пикселей, ширина, соответственно 3072 пикселя.
Учитывая то, что у нас уже установлены дополнения от Nvidia, выполним команду File > Save as и в появившемся окне выберем формат файла
(рис 27.4) Настройки для сохранения кубической карты
После того, как карта сохранена, мы можем загружать её в проект. Кроме того, в проекте мы используем изображение для создания фона – мы вырезали один из фрагментов изображения, используемого для создания кубической карты. Далее, здесь же мы используем трехмерную модель. В
На рис. 27.5 приведено окно Обозреватель решений для проекта P20_2.
(рис 27.5) Обозреватель решений
Мы собираемся применять при выводе модели шейдер EnvironmentMapEffect, прежде чем заработает код, использующий этот шейдер, нам понадобится указать его в поле Эффект по умолчанию окна настройки свойств трехмерной модели, рис. 27.6.
(рис 27.6) Настройка свойств модели
Кроме того, в проект мы добавили текстуру для модели, однако, текстура обрабатывается средствами файла модели, поэтому нужно отключить для неё параметры Обработчик контента в значение Обработка не требуется и Действие при построении в значение Нет. На самом деле, текстуру можно было бы не включать в состав контента, просто скопировав в папку, в которой хранится файл с моделью в структуре папок нашего проекта.
После вышеописанной подготовки всё готово к реализации примера, его код приведен в листинге 27.2
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace P20_2
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Для вывода двумерных изображений
SpriteBatch spriteBatch;
//Трёхмерная модель
Model model;
//Фон
Texture2D background;
//Кубическая карта окружающей среды
TextureCube cubeMap;
//Показатели, влияющие на наложение карты среды
//Цвет
float amount = 1.0f;
//Коэффициент Френеля
float fresnel = 0.1f;
//Альфа-фактор
float specular = 0.3f;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
//Параметры экрана
graphics.PreferredBackBufferWidth = 480;
graphics.PreferredBackBufferHeight = 800;
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Фон
background = Content.Load<Texture2D>("back");
// Модель
model = Content.Load<Model>("saucer");
//Кубическая карта
cubeMap = Content.Load<TextureCube>("cubemap");
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
// Вывод фонового изображения
spriteBatch.Begin(0, BlendState.Opaque);
spriteBatch.Draw(background, new Rectangle(0, 0, 480, 800), Color.White);
spriteBatch.End();
//// Вычисление матриц для управления камерой
float time = (float)gameTime.TotalGameTime.TotalSeconds;
Matrix rotation = Matrix.CreateRotationX(time * 0.3f) *
Matrix.CreateRotationY(time);
Matrix view = Matrix.CreateLookAt(new Vector3(4500, -400, 0),
new Vector3(0, -400, 0),
Vector3.Up);
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio,
10, 10000);
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
//Настройка параметров графического устройства
GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
//Вывод модели с использованием EnvironmentMapEffect
foreach (ModelMesh mesh in model.Meshes)
{
foreach (EnvironmentMapEffect effect in mesh.Effects)
{
Matrix world = transforms[mesh.ParentBone.Index] * rotation;
//Настройка параметров эффекта
effect.World = world;
effect.View = view;
effect.Projection = projection;
effect.EnvironmentMap = cubeMap;
effect.EnableDefaultLighting();
effect.EnvironmentMapAmount = amount;
effect.FresnelFactor = fresnel * 2;
effect.EnvironmentMapSpecular = new Vector3(1, 1, 0.5f) * specular;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}На рис. 27.7 вы можете видеть игровое окно проекта.
(рис 27.7) Игровое окно проекта
В этой лекции мы рассмотрели приёмы скелетной анимации персонажей, создали кубическую карту окружающей среды и ознакомились с применением EnvironmentMapEffect для создания эффекта зеркального объекта, в котором отражается окружающий мир.
Найдите или создайте скелетную трехмерную модель и анимируйте её в зависимости от особенностей модели. Самостоятельно изучите возможности эффектов SkinnedEffect, DualTextureEffect, AlphaTestEffect.
К данной лабораторной работе прилагается видеоурок
и демонстрационный проект.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.