Свободная камера

Свободная камера

В рамках этой статьи мы создадим простейшую камеру, которая позже также немного сэкономит нам времени.

Мы будем создавать «свободную камеру», то есть такую камеру, которая не привязана к какому-либо объекту, может свободно перемещать в трехмерном пространстве, а также поворачиваться на произвольный угол вокруг каждой координатной оси.


Камеру мы будет реализовывать в виде игрового компонента, но посколько нам не нужно никаким образом рисовать камеру, мы будем наследовать ее от GameComponent.

Основными параметрами, которые нам нужно будет получать от камеры, являются позиция камеры (она необходима для некоторых моделей освещения) и матрица вида, которую мы будем использовать везде в наших дальнейших приложениях.

Можно также было переместить матрицу проекции в класс камеры, но я не буду так делать, поскольку считаю это не совсем логичным (хотя я могу ошибаться).

Класс камеры будет выглядеть следующим образом:


class
FreeCamera : GameComponent

{


public
Matrix View;


public
Vector3 Position;


Vector3 angle;


float speed = 1;


float turnSpeed = 1;


public FreeCamera(Game game, Vector3 position, Vector3 lookAt) : base(game)

{


this.Position = position;

View = Matrix.CreateLookAt(position, lookAt, Vector3.Up);

}


public
override
void Update(GameTime gameTime)

{


int centerX = Game.GraphicsDevice.Viewport.Width / 2;


int centerY = Game.GraphicsDevice.Viewport.Height / 2;


MouseState mouse = Mouse.GetState();


Mouse.SetPosition(centerX, centerY);


float seconds = (float)(gameTime.ElapsedGameTime.TotalSeconds);


float yaw = MathHelper.ToRadians((mouse.X — centerX) * turnSpeed * seconds);


float pitch = MathHelper.ToRadians((mouse.Y — centerY) * turnSpeed * seconds);

angle = new
Vector3(angle.X + pitch, angle.Y + yaw, angle.Z);


Vector3 forward = —Vector3.Normalize(new
Vector3(

(float)Math.Sin(-angle.Y) * (float)Math.Cos(angle.X),

(float)Math.Sin(angle.X),

(float)Math.Cos(-angle.Y) * (float)Math.Cos(angle.X))

);


Vector3 left = —Vector3.Normalize(new
Vector3(

(float)Math.Cos(angle.Y),

0f,

(float)Math.Sin(angle.Y))

);


KeyboardState state = Keyboard.GetState();


if (state.IsKeyDown(Keys.Up))

Position += forward * speed * seconds;


if (state.IsKeyDown(Keys.Down))

Position -= forward * speed * seconds;


if (state.IsKeyDown(Keys.Left))

Position += left * speed * seconds;


if (state.IsKeyDown(Keys.Right))

Position -= left * speed * seconds;

View = Matrix.CreateTranslation(-Position) *


Matrix.CreateRotationZ(angle.Z) *


Matrix.CreateRotationY(angle.Y) *


Matrix.CreateRotationX(angle.X);


base.Update(gameTime);

}

}

Подробно реализация такого класса описана в моем курсе «XNA для начинающих», здесь отметим лишь некоторые основные моменты:

Задаются две переменные, отвечающие за скорость перемещения и поворота камеры – speed и turnSpeed соответственно.

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

Зачет вычисляется матрица вида.

Теперь можно подключить новый компонент в игре и использовать матрицу вида, которая содержится в камере для рендеринга сцены.

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


FreeCamera camera;


protected
override
void Initialize()

{


// TODO: Add your initialization logic here

Components.Add(new
FPSCounter(this));

light = new
Light(this);

Components.Add(light);

camera = new
FreeCamera(this, new
Vector3(0, 0, 3), Vector3.Zero);

Components.Add(camera);

basicEffect = new
BasicEffect(GraphicsDevice, null);

teapot = new
TeapotPrimitive(GraphicsDevice);


base.Initialize();

}


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


Matrix view = camera.View;


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);


// TODO: Add your drawing code here

basicEffect.World = Matrix.Identity;

basicEffect.View = view;

basicEffect.Projection = proj;

basicEffect.AmbientLightColor = new
Vector3(0.1f, 0.1f, 0.1f);

basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;

basicEffect.DirectionalLight0.DiffuseColor = new
Vector3(0.6f, 0, 0.4f);

basicEffect.LightingEnabled = true;

basicEffect.DirectionalLight0.Enabled = true;


for (int i = -2; i < 3; i++)

{


for (int j = -2; j < 3; j++)

{


for (int k = -2; k < 3; k++)

{


Vector3 pos = new
Vector3(i,j,k) * 2;

basicEffect.World = Matrix.CreateTranslation(pos);

basicEffect.DirectionalLight0.Direction = pos — light.Position;

teapot.Draw(basicEffect);

}

}

}

light.Draw(view, proj);


base.Draw(gameTime);

}

Обратите внимание на то, что, поскольку источник света находится на расстоянии 2 от начала координат, источник света находится ближе к началу координат, чем некоторые чайники. Хотя обычно считается, что направленный источник света (а пока мы будем рассматривать именно направленный источник света) бесконечно удален от сцены. В принципе, в этом нет ничего страшного, но, если хочется исправить этот недочет, то можно просто изменить начальную позициб источника света на большую (например на Vector3(0,0,100)).

Также стоит отметить то, что при наших начальных условиях камера по умолчанию находится внутри одного из чайников.

Исходный код:

FreeCamera.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Input;

namespace MyGame

{


class
FreeCamera : GameComponent

{


public
Matrix View;


public
Vector3 Position;


Vector3 angle;


float speed = 1;


float turnSpeed = 1;


public FreeCamera(Game game, Vector3 position, Vector3 lookAt) : base(game)

{


this.Position = position;

View = Matrix.CreateLookAt(position, lookAt, Vector3.Up);

}


public
override
void Update(GameTime gameTime)

{


int centerX = Game.GraphicsDevice.Viewport.Width / 2;


int centerY = Game.GraphicsDevice.Viewport.Height / 2;


MouseState mouse = Mouse.GetState();


Mouse.SetPosition(centerX, centerY);


float seconds = (float)(gameTime.ElapsedGameTime.TotalSeconds);


float yaw = MathHelper.ToRadians((mouse.X — centerX) * turnSpeed * seconds);


float pitch = MathHelper.ToRadians((mouse.Y — centerY) * turnSpeed * seconds);

angle = new
Vector3(angle.X + pitch, angle.Y + yaw, angle.Z);


Vector3 forward = —Vector3.Normalize(new
Vector3(

(float)Math.Sin(-angle.Y) * (float)Math.Cos(angle.X),

(float)Math.Sin(angle.X),

(float)Math.Cos(-angle.Y) * (float)Math.Cos(angle.X))

);


Vector3 left = —Vector3.Normalize(new
Vector3(

(float)Math.Cos(angle.Y),

0f,

(float)Math.Sin(angle.Y))

);


KeyboardState state = Keyboard.GetState();


if (state.IsKeyDown(Keys.Up))

Position += forward * speed * seconds;


if (state.IsKeyDown(Keys.Down))

Position -= forward * speed * seconds;


if (state.IsKeyDown(Keys.Left))

Position += left * speed * seconds;


if (state.IsKeyDown(Keys.Right))

Position -= left * speed * seconds;

View = Matrix.CreateTranslation(-Position) *


Matrix.CreateRotationZ(angle.Z) *


Matrix.CreateRotationY(angle.Y) *


Matrix.CreateRotationX(angle.X);


base.Update(gameTime);

}

}

}

Game1.cs

using System;

using System.Collections.Generic;

using System.Linq;

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.Media;

using Microsoft.Xna.Framework.Net;

using Microsoft.Xna.Framework.Storage;

using Primitives3D;

namespace MyGame

{


///
<summary>


/// This is the main type for your game


///
</summary>


public
class
Game1 : Microsoft.Xna.Framework.Game

{


GraphicsDeviceManager graphics;


SpriteBatch spriteBatch;


Light light;


BasicEffect basicEffect;


TeapotPrimitive teapot;


FreeCamera camera;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

graphics.SynchronizeWithVerticalRetrace = false;

IsFixedTimeStep = false;

}


///
<summary>


/// Allows the game to perform any initialization it needs to before starting to run.


/// This is where it can query for any required services and load any non-graphic


/// related content. Calling base.Initialize will enumerate through any components


/// and initialize them as well.


///
</summary>


protected
override
void Initialize()

{


// TODO: Add your initialization logic here

Components.Add(new
FPSCounter(this));

light = new
Light(this);

Components.Add(light);

camera = new
FreeCamera(this, new
Vector3(0, 0, 3), Vector3.Zero);

Components.Add(camera);

basicEffect = new
BasicEffect(GraphicsDevice, null);

teapot = new
TeapotPrimitive(GraphicsDevice);


base.Initialize();

}


///
<summary>


/// LoadContent will be called once per game and is the place to load


/// all of your content.


///
</summary>


protected
override
void LoadContent()

{


// Create a new SpriteBatch, which can be used to draw textures.

spriteBatch = new
SpriteBatch(GraphicsDevice);


// TODO: use this.Content to load your game content here

}


///
<summary>


/// UnloadContent will be called once per game and is the place to unload


/// all content.


///
</summary>


protected
override
void UnloadContent()

{


// TODO: Unload any non ContentManager content here

}


///
<summary>


/// Allows the game to run logic such as updating the world,


/// checking for collisions, gathering input, and playing audio.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Update(GameTime gameTime)

{


// Allows the game to exit


if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)


this.Exit();


// TODO: Add your update logic here


base.Update(gameTime);

}


///
<summary>


/// This is called when the game should draw itself.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


Matrix view = camera.View;


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);


// TODO: Add your drawing code here

basicEffect.World = Matrix.Identity;

basicEffect.View = view;

basicEffect.Projection = proj;

basicEffect.AmbientLightColor = new
Vector3(0.1f, 0.1f, 0.1f);

basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;

basicEffect.DirectionalLight0.DiffuseColor = new
Vector3(0.6f, 0, 0.4f);

basicEffect.LightingEnabled = true;

basicEffect.DirectionalLight0.Enabled = true;


for (int i = -2; i < 3; i++)

{


for (int j = -2; j < 3; j++)

{


for (int k = -2; k < 3; k++)

{


Vector3 pos = new
Vector3(i,j,k) * 2;

basicEffect.World = Matrix.CreateTranslation(pos);

basicEffect.DirectionalLight0.Direction = pos — light.Position;

teapot.Draw(basicEffect);

}

}

}

light.Draw(view, proj);


base.Draw(gameTime);

}

}

}

Запись опубликована в рубрике Uncategorized с метками , , , , . Добавьте в закладки постоянную ссылку.

30 отзывов на “Свободная камера

  1. Макс:

    Здраствуйте Иван! Я создал 3D модель и загрузил ее в XNA, вы не моглибы мне подсказать как реализовать столкновения камеры со стенами?

    • Я так понял, что на xnadev уже ответили…
      В этом вопросе все зависит от самой игры. Если стен не много, и каждая описывается отдельным объектом, то можно просто проверять пересечение BoundingSphere, в которой находится камера с каждым BoundingBox, в котором содержится стена.

      • Макс:

        У вас такого простого примерчика не будед где проверяется сфера и бох……..

    • Вообще я так делал когда-то, но простого примера нет…
      А что не понятно в таком способе?
      Алгоритм такой:
      1. Сделать BoundingSphere для камеры
      2. Сделать BoundingBox для каждой стены
      3. Проверить пересечения сферы с каждым боксом

  2. Макс:

    мне интересно как в данном свособе все меши загнать в BoundingBox ?

  3. Макс:

    BoundingSphere s1 = ball.Meshes[0].BoundingSphere;
    s1.Center = avatarPosition;
    s1.Radius = 60.0f;

    BoundingBox s2 = new BoundingBox(
    new Vector3(modelPos.X, modelPos.Y, -50),
    new Vector3(modelPos.X , modelPos.Y, 50));

    if (s1.Intersects(s2))
    {

    this.Window.Title = «Collision!»;
    return true;
    }

    • Хм.. получается, что я не могу нормально посмотреть проект, у меня сейчас есть только XNA 4.0

      По коду не понял некоторых вещей:
      1. У Вас весь уровень в одном большой модели? Просто если так, то такой способ не сработает. В этом способе для каждой стены строится бокс (нужно переносить его в соответствующее место)
      2. Там есть проверка на пересечение с какой-то непонятной сферой, а надо сферу строить вокруг камеры

  4. Макс:

    Рещил воспользоваться библиотеокой все получилось Ziggyware.Utility.dll…. вы ни когода не пользовались?…..и еще меня интересует какие параметры имеет базовый источник света

  5. Макс:

    Не могли бы вы посмотреть мой проект и оценить его……..
    http://depositfiles.com/files/qnqqxkf5h
    В проекте использую меню из вашего видео урока….

  6. Идея прикольная, мне в целом понравилось
    Только у меня почему-то текстуры криво наложились на стены…

  7. Макс:

    Думаю создать дополнительный пункт в меню…где можно менять разрешение экрана….и хотел еще реализовать таймер….т.е назначить определенное время и что бы оно шло в обратном порядке…вы мне не подскажите как это можно лучше сделать….и если сможите пришлите мне скрин где текстуры криво лежат, а то может что то я пропустил…..

  8. Таймер — ну просто переменная float (сколько секунд осталось)
    в Update кажый раз нужно вычитать из нее gameTime.ElapsedTime.TotalSeconds. При выводе на экран приводить к int для округления

    баг: http://savepic.ru/2461195.png
    Текстуры криво лежат почти везде…
    У меня Radeon HD5650

  9. Макс:

    Подработал код……вот посмотрите:
    http://depositfiles.com/files/g0qhtjgmt
    освещение такое нормально?

  10. Текстуры починились.
    А с освещением есть баги, например вот этот этот (из первой комнаты налево)

  11. Алексей:

    Добрый день Иван а не подскажете как правильно стоить матрицу допустим у меня есть некая сфера большого радиуса ю Надо расположить камеру на сфере и врашать ее в зависимости от того где она находииться тее чтобы камера какбы двигалась по поверхности сферы и низ камеры был на сфере постоянно .

  12. Алексей:

    немного не то у меня есть модель земли тее рандомно генерируемая радиусом так этак 100кF передвежение свободной камеры разобрался как делать но как привязаться к земле чтобы у меня низ камыры всегда был обращен к земле тее псебда эмуляция что я постоянно иду по земле но пофагту могу находиться верх ногами по отношению к оси

  13. Алексей:

    Да известна. но может меняться тераформом . Но сохраняеться в бд. просто не совсем понятно как камеру переворачивать в зависимости от того гда неходишся тее пускать Ray от позиции камеры до центра как вариант посмотреть где сопрекаснулся с точкой сферы но как дальше перевернуть? И еще вопросик есть ли в XNA какой либо инструмет позволяющий расчитывать динамически Индексы для вершин? а то както накладно выходить для 64 милионов вершин 384 милиона индексов держать и расчитывать зону видимости и выводить нужные триугольники

    • Можно попробовать сначала повернуть камеру, пока она не передвинута на поверхность сферы. Тогда нужен только угол относительно Y. Потом уже переносить.

      Второй вопрос не понял. Вообще что-то похожее было на xnadev.ru

  14. алексей:

    Приеду домой попробую. По второму вопросу вот как можно создовать индексы для треугольников если известны только вершины. Можно ли их создать на лету или как таковое не возможно т.к не известно правильный обход точек

  15. алексей:

    Спасибо за помощь буду думать

  16. Иван:

    А можно спросить как создать 2ю камеру и поместить ее чуть выше 1й и выводить в ней то что она видит в прямоугольнике, в правом верхнему углу, на подобие карты пытаюсь сделать. Но нигде неописано как вывод камеры в прямоугольник запихнуть особенно если она нужна как карта в 2д играх стратегии

    • Да, можно поставить камеру сверху и рендерить кадр с нее на отдельную плоскость.
      Получится примерно как вот в этой статье:
      https://wordpress.com/post/iandreev.wordpress.com/889

      То есть в Draw задаете матрицы View (тут можно камеру просто над сценой), Projection (для миникарты тут лучше использовать ортогональную проекцию), ставите другой рендер таргет GraphicsDevice.SetRenderTarget() и рисуете.
      После этого в рендертаргете будет миникарта. Ну точнее что-то похожее )

Ответить на Макс Отменить ответ