Лабораторная работа №15. Пространственные преобразования объектов
Эта лабораторная работа посвящена пространственным преобразованиям трехмерных объектов. В частности, здесь мы поговорим о перемещении объектов, об их масштабировании, обсудим настройку перемещения камеры.
Цель работы
Научиться перемещать трехмерные объекты в пространстве
Задачи работы
Освоить технику перемещения объектов
Освоить технику масштабирования объектов
Освоить технику перемещения камеры
Пространственные преобразования объектов
Создадим новый проект P15_1 и на его примере рассмотрим следующие пространственные преобразования объектов
Перемещение
Вращение
Масштабирование
Для выполнения этих операций нам понадобится модификация мировой матрицы. Модификации, соответствующие перемещению объекта мы выполним по клавиатурным командам, остальные модификации будут выполняться автоматически в цикле обновления.
Для этого примера мы создали несколько простых трехмерных моделей в редакторе Blender. В частности – это два разноцветных шара и куб. Вот как выглядит игровой экран проекта P15_1 (рис. 15.1.).
Рис. 15.1. Игровой экран проекта P15_1
Для упрощения работы с несколькими игровыми объектами мы создадим игровой компонент - modCls, который будет отвечать за хранение параметров, соответствующих этим объектам и за их визуализацию. В листинге 15.1. вы можете видеть код этого объекта.
usingSystem;usingSystem.Collections.Generic;usingMicrosoft.Xna.Framework;usingMicrosoft.Xna.Framework.Audio;usingMicrosoft.Xna.Framework.Graphics;usingMicrosoft.Xna.Framework.Input;usingMicrosoft.Xna.Framework.Content;namespace P15_1{publicclass modCls : Microsoft.Xna.Framework.DrawableGameComponent{//Модель Model myModel;//Мировая матрицаpublic Matrix WorldMatrix;//Соотношение сторон экранаpublicfloat aspectRatio;//Для управления графическим устройством GraphicsDeviceManager graphics;//Конструктор получает на вход //игровой класс, модель, объект для управления графическим устройствомpublic modCls(Game game, Model mod, GraphicsDeviceManager grf):base(game){ myModel = mod; graphics = grf; aspectRatio =(float)graphics.GraphicsDevice.Viewport.Width/(float)graphics.GraphicsDevice.Viewport.Height;}publicoverridevoid Update(GameTime gameTime){foreach(ModelMesh mesh in myModel.Meshes){//Для каждого эффекта в сетиforeach(BasicEffect effect in mesh.Effects){//Установить освещение по умолчанию effect.LightingEnabled= true; effect.EnableDefaultLighting();//установить матрицы effect.World= WorldMatrix; effect.View= Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 10.0f), Vector3.Zero, Vector3.Up); effect.Projection= Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 1000.0f);}}base.Update(gameTime);}publicoverridevoid Draw(GameTime gameTime){foreach(ModelMesh mesh in myModel.Meshes){ mesh.Draw();}base.Draw(gameTime);}}}
Управление объектами возложено на основной игровой класс – его метод Update используется для вычисления новых позиций объектов и обновления информации объектов. Код этого класса вы можете видеть в листинге 15.2. Шар, расположенный сверху, автоматически циклически изменяет размер, куб вращается вокруг осей X и Y, шар, расположенный ниже, можно перемещать. Клавиши-стрелки используются для перемещения по осям X и Y (вверх, вниз, влево и вправо), клавиши W и S перемещают объект вперед и назад по оси Z.
usingSystem;usingSystem.Collections.Generic;usingMicrosoft.Xna.Framework;usingMicrosoft.Xna.Framework.Audio;usingMicrosoft.Xna.Framework.Content;usingMicrosoft.Xna.Framework.Graphics;usingMicrosoft.Xna.Framework.Input;namespace P15_1{publicclass Game1 : Microsoft.Xna.Framework.Game{ GraphicsDeviceManager graphics;//Мировая матрица Matrix WorldMatrixM, WorldMatrixM1, WorldMatrixM2;//Направление движения для модели modelCls1 Vector3 directM1;//Модели - //modelCls вращается//modelCls1 перемещается по клавиатурным командам//modelCls2 изменяет размер modCls modelCls, modelCls1, modelCls2;//Переменные для текущего размера и //текущего приращения размера модели modelCls2float size, sizeDelta;public Game1(){ graphics =new GraphicsDeviceManager(this); Content.RootDirectory="Content";}protectedoverridevoid LoadContent(){//загрузка моделей и создание объектов, используемых для //хранения информации о моделях и их вывода на экран modelCls =new modCls(this, Content.Load<Model>("cube"), graphics); modelCls1 =new modCls(this, Content.Load<Model>("ball"), graphics); modelCls2 =new modCls(this, Content.Load<Model>("ball2"), graphics);//Добавляем объекты в коллекцию Components - для их //автоматического вывода на экран Components.Add(modelCls); Components.Add(modelCls1); Components.Add(modelCls2);//Значения для приращения размера и текущего размера sizeDelta = 0.01f; size = 0.5f;//Установка исходного положения для модели modelCls1 directM1 =new Vector3(2.0f, -1.0f, 1.0f);//Установка матриц, которые используются для управления объектами WorldMatrixM = Matrix.Identity; WorldMatrixM1 = Matrix.CreateTranslation(directM1); WorldMatrixM2 = Matrix.CreateScale(size);}protectedoverridevoid Update(GameTime gameTime){ KeyboardState kb = Keyboard.GetState();//По нажатии клавиши-стрелки "вверх" перемещаем модель//modelCls1 вверхif(kb.IsKeyDown(Keys.Up)){ directM1.Y=directM1 .Y+0.1f; WorldMatrixM1 = Matrix.CreateTranslation(directM1);}//Нажатие клавиши "вниз" - перемещение внизif(kb.IsKeyDown(Keys.Down)){ directM1.Y= directM1.Y- 0.1f; WorldMatrixM1 = Matrix.CreateTranslation(directM1);}//Нажатие клавиши "влево" - перемещение влевоif(kb.IsKeyDown(Keys.Left)){ directM1.X= directM1.X- 0.1f; WorldMatrixM1 = Matrix.CreateTranslation(directM1);}//Нажатие клавиши "вправо" - перемещение вправоif(kb.IsKeyDown(Keys.Right)){ directM1.X= directM1.X+ 0.1f; WorldMatrixM1 = Matrix.CreateTranslation(directM1);}//Нажатие клавиши "W" - перемещение впередif(kb.IsKeyDown(Keys.W)){ directM1.Z= directM1.Z- 0.1f; WorldMatrixM1 = Matrix.CreateTranslation(directM1);}//Нажатие клавиши "S" - перемещение назадif(kb.IsKeyDown(Keys.S)){ directM1.Z= directM1.Z+ 0.1f; WorldMatrixM1 = Matrix.CreateTranslation(directM1);}//Если текущий размер меньше 0.5 - //устанавливаем приращение, равное 0.01//Если текущий размер больше 1.5 -//устанавливаем приращение -0.01if(size <0.5) sizeDelta = 0.01f;if(size >1.5) sizeDelta =-0.01f; size += sizeDelta;//Матрица для объекта modelCls1 устанавливается для поворота его вокруг//осей X и Y на один градус WorldMatrixM = WorldMatrixM * Matrix.CreateRotationY(MathHelper.ToRadians(1))* Matrix .CreateRotationX(MathHelper .ToRadians(1));//Установка матрицы для объекта modelCls2 WorldMatrixM2 = Matrix.CreateScale(size)* Matrix.CreateTranslation(new Vector3(-5.0f, 2.0f, -3.0f));//Передаем матрицы объектам modelCls.WorldMatrix= WorldMatrixM; modelCls1.WorldMatrix= WorldMatrixM1; modelCls2.WorldMatrix= WorldMatrixM2;base.Update(gameTime);}protectedoverridevoid Draw(GameTime gameTime){ graphics.GraphicsDevice.Clear(Color.CornflowerBlue);base.Draw(gameTime);}}}
Рассмотрев способы модификации объектов, поговорим об особенностях настройки камеры.
Настройка перемещения камеры: базовые приемы
Камера играет огромную роль в трехмерных играх. В играх от первого лица позиция камеры ассоциируется с позицией игрока. Примерами таких игр игры класса simulator, shooter. В играх от третьего лица (strategy) камера расположена выше игрока и сдвинута назад – играющий может видеть своего персонажа и часть карты.
Рассмотрим простой пример управления камерой, в котором реализовано перемещение камеры, а так же изменение угла зрения. Фактически это – пример работы камеры от первого лица. Изменение угла зрения очень похоже на изменение фокусного расстояния в оптических приборах – увеличение фокусного расстояния, которое приводит к уменьшению угла зрения, позволяет «приближать» далеко расположенные объекты. Уменьшение фокусного расстояния, соответствующее увеличению угла зрения, позволяет охватить взглядом большую территорию.
Создадим новый проект – P15_2. Рассмотрим на его примере вышеописанные операции с камерой.
В листинге 15.3. вы можете найти код класса Game1, реализующего демонстрацию работы с камерой.
usingSystem;usingSystem.Collections.Generic;usingMicrosoft.Xna.Framework;usingMicrosoft.Xna.Framework.Audio;usingMicrosoft.Xna.Framework.Content;usingMicrosoft.Xna.Framework.Graphics;usingMicrosoft.Xna.Framework.Input;namespace P15_2{publicclass Game1 : Microsoft.Xna.Framework.Game{//соотношение сторон окна выводаfloat aspectRatio;//Матрицы Matrix viewMatrix; Matrix projMatrix; Matrix worldMatrix;//Модель Model ball;// Позиция и вращение камеры Vector3 camPos =new Vector3(0, 0, -50);float camRotation;// Направление камеры Vector3 cameraDirection =new Vector3(0, 0, 1);// Установка скорости поворота и передвиженияfloat rotationSpeed = 0.01f;float forwardSpeed = 1.0f;//Угол зрения камерыfloat viewAngle = MathHelper.ToRadians(45.0f); GraphicsDeviceManager graphics;public Game1(){ graphics =new GraphicsDeviceManager(this); Content.RootDirectory="Content";}protectedoverridevoid LoadContent(){//Загрузка модели ball = Content.Load<Model>("ball");//Установка мировой матрицы worldMatrix = Matrix.Identity;//Соотношение сторон aspectRatio =(float)graphics.GraphicsDevice.Viewport.Width/(float)graphics.GraphicsDevice.Viewport.Height;}protectedoverridevoid Update(GameTime gameTime){//Получить состояние клавиатуры KeyboardState keyboardState = Keyboard.GetState();//Поворот влевоif(keyboardState.IsKeyDown(Keys.Left)){ camRotation += rotationSpeed;}//Поворот вправоif(keyboardState.IsKeyDown(Keys.Right)){ camRotation -= rotationSpeed;}//Переедвижение впередif(keyboardState.IsKeyDown(Keys.Up)){//Создаем матрицу поворота Matrix forwardMov = Matrix.CreateRotationY(camRotation);//Создаем новый вектор, содержащий скорость движения Vector3 v =new Vector3(0, 0, forwardSpeed);//Трансформируем матрицу в вектор v = Vector3.Transform(v, forwardMov);//модифицируем позицию таким образом, чтобы//она соответствовала новым данным camPos.Z+= v.Z; camPos.X+= v.X;}//Передвижение назадif(keyboardState.IsKeyDown(Keys.Down)){ Matrix forwardMovement = Matrix.CreateRotationY(camRotation); Vector3 v =new Vector3(0, 0, -forwardSpeed); v = Vector3.Transform(v, forwardMovement); camPos.Z+= v.Z; camPos.X+= v.X;}//Уменьшение угла обзора камерыif(keyboardState.IsKeyDown(Keys.W)){ viewAngle -= MathHelper.ToRadians(1.0f);}//Увеличение угла обзора камерыif(keyboardState.IsKeyDown(Keys.S)){ viewAngle += MathHelper.ToRadians(1.0f);}//Если новый угол обзора вышел за дозволенные пределы//изменяем егоif(viewAngle > MathHelper.ToRadians(180.0f)) viewAngle = MathHelper.ToRadians(179.9f);if(viewAngle < MathHelper.ToRadians(0.0f)) viewAngle = MathHelper.ToRadians(0.1f); Matrix rotationMatrix = Matrix.CreateRotationY(camRotation);// Вектор направления камеры Vector3 cameraTransformed = Vector3.Transform(cameraDirection, rotationMatrix);// Вычисляем положение камеры Vector3 cameraLookat = camPos + cameraTransformed;// Устанавливаем матрицу вида viewMatrix = Matrix.CreateLookAt(camPos, cameraLookat, new Vector3(0.0f, 1.0f, 0.0f));//Устанавливаем проекционную матрицу projMatrix = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, 1.0f, 1000.0f);base.Update(gameTime);}/// /// This is called when the game should draw itself./// /// Provides a snapshot of timing values.protectedoverridevoid Draw(GameTime gameTime){ graphics.GraphicsDevice.Clear(Color.CornflowerBlue);foreach(ModelMesh mesh in ball.Meshes){foreach(BasicEffect effect in mesh.Effects){ effect.LightingEnabled= true; effect.EnableDefaultLighting(); effect.Projection= projMatrix; effect.View= viewMatrix; effect.World= worldMatrix;} mesh.Draw();}base.Draw(gameTime);}}}
На рис. 15.2. вы можете видеть изображение игрового экрана проекта P15_2.
Рис. 15.2. Экран проекта P15_2
Шар, который мы видим в окне, искажен – угол обзора камеры увеличен, а расстояние от камеры до объекта сильно уменьшено.
Настройка перемещения камеры: продвинутые приемы
Рассмотрим пример, иллюстрирующий работу с камерой в режиме от первого лица и в режиме вида от третьего лица. Для подготовки этого примера мы использовали справочные материалы по XNA.
Создадим новый проект P15_3. Наш проект выполняет следующие действия. Он выводит на экран плоскость и набор кубов, среди которых может двигаться шар. Причем, движение может осуществляться в двух режимах. В первом режиме демонстрируется работа камеры при виде от первого лица – шар в таком случае не выводится, сцена выглядит так, как будто мы смотрим на нее с позиции шара. Во втором режиме демонстрируется вид от третьего лица – камера находится выше объекта. В играх часто применяется следующий прием – главный игровой объект неподвижен, а игровой мир перемещается, создавая иллюзию движения этого объекта. В нашем случае все выглядит точно так же – шар неподвижно находится в центре экрана, но за счет перемещения игровой сцены создается иллюзия его движения по экрану и по сцене.
В листинге 15.3. вы можете найти код класса Game1, который содержит пример. Для перемещения используются клавиши-стрелки, для смены состояний камеры – клавиши A и S. Клавиша A переключает программу в режим камеры от первого лица, S – в режим вида от третьего лица. Клавиша R уменьшает поле зрения камеры, клавиша F – увеличивает.
usingSystem;usingSystem.Collections.Generic;usingMicrosoft.Xna.Framework;usingMicrosoft.Xna.Framework.Audio;usingMicrosoft.Xna.Framework.Content;usingMicrosoft.Xna.Framework.Graphics;usingMicrosoft.Xna.Framework.Input;namespace P15_3{publicclass Game1 : Microsoft.Xna.Framework.Game{ GraphicsDeviceManager graphics;//Матрицы Matrix viewMatrix; Matrix projMatrix;//Модели Model ball, cube, plane;// Позиция объекта, поворот Vector3 avatarPosition =new Vector3(0, 0, -50);float avatarlRotation;// Положение камеры Vector3 cameraReference =new Vector3(0, 0, 10); Vector3 thirdPersonReference =new Vector3(0, 200, -200);// Скорости поворота и движенияfloat rotationSpeed = 1f / 60f;float forwardSpeed = 50f / 60f;//Поле зрения камерыfloat viewAngle = MathHelper.ToRadians(45.0f);//Расстояние от камеры до переднего и заднего плана//static float nearClip = 5.0f;float nearClip = 1.0f;float farClip = 2000.0f;// Установка позиции камеры 2 - вид от третьего лица//1 - от первого лицаint cameraState =2;//Соотношение сторон экранаfloat aspectRatio;public Game1(){ graphics =new GraphicsDeviceManager(this); Content.RootDirectory="Content";}protectedoverridevoid LoadContent(){//Загрузка моделей ball = Content.Load<Model>("ball"); cube = Content.Load<Model>("cube"); plane = Content.Load<Model>("plane"); aspectRatio =(float)graphics.GraphicsDevice.Viewport.Width/(float)graphics.GraphicsDevice.Viewport.Height;}protectedoverridevoid Update(GameTime gameTime){//обновить положение объекта UpdateAvatarPosition();base.Update(gameTime);}protectedoverridevoid Draw(GameTime gameTime){ graphics.GraphicsDevice.Clear(Color.CornflowerBlue);//Если состояние камеры 1 - вызвать процедуру//обновлеие камеры от первого лица//если 2 - от третьего лицаswitch(cameraState){case1: UpdateCamera(); break;case2: UpdateCameraThirdPerson(); break;}//вывести объекты сцены DrawScene();//Если выбран вид от третьего лица - вывести объектif(cameraState ==2){ DrawModel(ball, Matrix.CreateRotationY(avatarlRotation)* Matrix.CreateTranslation(avatarPosition));}base.Draw(gameTime);}//Обновляем состояние объектаvoid UpdateAvatarPosition(){ KeyboardState keyboardState = Keyboard.GetState();//Поворот влевоif(keyboardState.IsKeyDown(Keys.Left)){ avatarlRotation += rotationSpeed;}//Поворот вправоif(keyboardState.IsKeyDown(Keys.Right)){ avatarlRotation -= rotationSpeed;}//Движение впередif(keyboardState.IsKeyDown(Keys.Up)){ Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation); Vector3 v =new Vector3(0, 0, forwardSpeed); v = Vector3.Transform(v, forwardMovement); avatarPosition.Z+= v.Z; avatarPosition.X+= v.X;}//Движение назадif(keyboardState.IsKeyDown(Keys.Down)){ Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation); Vector3 v =new Vector3(0, 0, -forwardSpeed); v = Vector3.Transform(v, forwardMovement); avatarPosition.Z+= v.Z; avatarPosition.X+= v.X;}//Переключить на режим камеры от первого лицаif(keyboardState.IsKeyDown(Keys.A)){ cameraState=1;}//Переключить на режим камеры от третьего лицаif(keyboardState.IsKeyDown(Keys.S)){ cameraState =2;}//Уменьшение угла обзора камерыif(keyboardState.IsKeyDown(Keys.R)){ viewAngle -= MathHelper.ToRadians(1.0f);}//Увеличение угла обзора камерыif(keyboardState.IsKeyDown(Keys.F)){ viewAngle += MathHelper.ToRadians(1.0f);}//Если новый угол обзора вышел за дозволенные пределы//изменяем егоif(viewAngle > MathHelper.ToRadians(180.0f)) viewAngle = MathHelper.ToRadians(179.9f);if(viewAngle < MathHelper.ToRadians(0.0f)) viewAngle = MathHelper.ToRadians(0.1f);}//Обновляем состояние камеры при выбранном виде от первого лицаvoid UpdateCamera(){//Текущая позиция камеры Vector3 cameraPosition = avatarPosition;//Поворот Matrix rotationMatrix = Matrix.CreateRotationY(avatarlRotation);// Направление камеры Vector3 transformedReference = Vector3.Transform(cameraReference, rotationMatrix);// Новая позиция камеры Vector3 cameraLookat = cameraPosition + transformedReference;//Матрица вида viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraLookat, new Vector3(0.0f, 1.0f, 0.0f));//проекционная матрица projMatrix = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearClip, farClip);}//Обновляем положение камеры при выбранном виде от третьего лицаvoid UpdateCameraThirdPerson(){//Поворот камеры Matrix rotationMatrix = Matrix.CreateRotationY(avatarlRotation);// Направление камеры Vector3 transformedReference = Vector3.Transform(thirdPersonReference, rotationMatrix);// Позиция камеры Vector3 cameraPosition = transformedReference + avatarPosition;//Матрица вида viewMatrix = Matrix.CreateLookAt(cameraPosition, avatarPosition, new Vector3(0.0f, 1.0f, 0.0f));//Проецкионная матрица projMatrix = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearClip, farClip);}//Вывод объектов сценыvoid DrawScene(){//Вывести кубы, расположенные в пять рядов//по пять штук в трех уровняхfor(int x =0; x <5; x++){for(int y =0; y <3; y++){for(int z =0; z <5; z++){ DrawModel(cube, Matrix.CreateTranslation(x *40, y *3, z *40));}}}//Вывести плоскость - при выводе увеличиваем модель и поворачиваем соответствующим//образом DrawModel(plane, Matrix.CreateScale(100)*Matrix .CreateRotationY(MathHelper .ToRadians(90))* Matrix.CreateRotationZ(MathHelper.ToRadians(90))* Matrix .CreateTranslation(80,-2.5f,80));}//Процедура вывода модели//Принимает мировую матрицу для позиционирования модели//и переменную моделиvoid DrawModel(Model model, Matrix world){foreach(ModelMesh mesh in model.Meshes){foreach(BasicEffect effect in mesh.Effects){ effect.LightingEnabled= true; effect.EnableDefaultLighting(); effect.Projection= projMatrix; effect.View= viewMatrix; effect.World= world;} mesh.Draw();}}}}
На рис. 15.3. вы можете видеть состояние игрового экрана проекта P15_3 в режиме вида от третьего лица.
Рис. 15.3. Экран проекта P15_3, вид от третьего лица
На рис. 15.4. вы можете видеть состояние экрана проекта P15_3 в режиме вида от первого лица.
Рис. 15.4. Экран проекта P15_3, вид от первого лица
На рис. 15.5. вы можете видеть состояние экрана проекта P15_3 в режиме вида от третьего лица с уменьшенным полем видимости камеры.
Рис. 15.3. Экран проекта P15_3, вид от третьего лица, уменьшенное поле видимости
Вопросы
1) Мировая матрица объекта преобразована таким образом: worldMatrix = Matrix.CreateScale(2.0f). Как изменится объект после применения такого преобразования?
a. Уменьшится в два раза
b. Увеличится в два раза
c. Повернется вокруг оси X
d. Переместится в начало координат
2) Мировая матрица объекта преобразована таким образом: worldMatrix = Matrix.CreateTranslation(10.0f, 0.0f, -12.0f). Как изменится объект после применения такого преобразования?
a. Уменьшится в 2 раза
b. Увеличится в 2 раза
c. Переместится в позицию 10,0,-12
d. Переместится в начало координат
3) Мировая матрица объекта преобразована таким образом: worldMatrix = Matrix.CreateRotationY (MathHelper.ToRadians (10)). Как изменится объект после применения такого преобразования?
a. Переместится в позицию 10,10,10
b. Уменьшится в 10 раз
c. Повернется вокруг оси Y на 10 градусов
d. Повернется вокруг оси X на 10 градусов
4) Команда Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 10.0f), Vector3.Zero, Vector3.Up) позволяет создать камеру, которая
a. Находится в точке 0,0,0 и направлена в точку 0,0,10
b. Находится в точке 0,0,10 и направлена на точку 0,0,0
c. Находится в точке 0,0,1 и направлена на точку 0,0,10
d. Находится в точке 0,0,10 и направлена на точку 0,0,1
5) Команда Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 1000.0f) позволяет настроить перспективную проекцию сцены на экран с углом зрения, равным
a. 0,7853 градуса
b. 45 градусов
c. 180 градусов
d. 5 градусов
6) При использовании этого вида проекции объекты, которые расположены дальше от камеры, выглядят меньшими, чем те, которые расположены ближе. Так же объекты подвергаются перспективным искажениям. О каком виде проекции идет речь?
a. Ортогональная
b. Перспективная
c. Аксонометрическая
d. Косоугольная
7) При использовании этого вида проекции объекты, находящиеся на различных расстояниях от камеры, не меняют размеров и не искажаются.