netlib.narod.ru< Назад | Оглавление | Далее >

Камеры

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

Класс BaseCamera

В этом разделе вы создадите обобщенный базовый класс для камер, названный BaseCamera. Этот класс будет работать с матрицей вида и матрицей проекции камеры, и с ее объемом визуализации, пирамидой видимого пространства (frustum). Пирамида видимого пространства камеры используется, чтобы определить, какие объекты не находятся внутри визуализируемого камерой объема, и исключить эти объекты из процесса визуализации. Пирамида видимого пространства камеры генерируется на основе матрицы вида и матрицы проекции камеры.

Перспективная проекция камеры

Класс BaseCamera поддерживает только перспективную проекцию. Вы создаете метод SetPerspectiveFov для установки перспективной проекции камеры, и свойство Projection для ее получения. Для создания и обновления матрицы перспективной проекции камеры используется следующий код:

// Параметры перспективной проекции
float fovy;
float aspectRatio;
float nearPlane;
float farPlane;

// Матрицы и флаги
protected bool needUpdateProjection;
protected bool needUpdateFrustum;
protected Matrix projectionMatrix;

// Получение матрицы проекции камеры
public Matrix Projection
{
    get
    {
        if (needUpdateProjection) UpdateProjection();
        return projectionMatrix;
    }
}

// Установка перспективной проекции камеры
public void SetPerspectiveFov(float fovy, float aspectRatio,
                              float nearPlane, float farPlane)
{
    this.fovy        = fovy;
    this.aspectRatio = aspectRatio;
    this.nearPlane   = nearPlane;
    this.farPlane    = farPlane;

    needUpdateProjection = true;
}

// Обновление матрицы перспективной проекции камеры
protected virtual void UpdateProjection()
{
    // Создаем матрицу перспективного поля зрения
    projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
        MathHelper.ToRadians(fovy), aspectRatio, nearPlane, farPlane);
    needUpdateProjection = false;
    needUpdateFrustum    = true;
}

Метод SetPerspectiveFov сохраняет новые параметры перспективной проекции, но не генерирует новую матрицу проекции. Вместо этого он присваивает переменной needUpdateProjection значение true, указывающее, что матрица проекции должна быть обновлена перед ее использованием. Когда перспективная проекция получается через свойство Projection, матрица проекции в случае необходимости будет обновлена. И, наконец, внутри метода UpdateProjection вы генерируете новую матрицу перспективной проекции, используя метод CreatePerspectiveFieldOfView класса Matrix XNA.

Заметьте, что когда обновляется матрица проекции, должна быть обновлена и пирамида видимого пространства камеры.

Вид камеры (местоположение и ориентация)

Матрица вида хранит местоположение и ориентацию камеры в мире. Вы создаете метод SetLookAt для установки матрицы вида камеры и свойство View для ее получения. Чтобы модифицировать и обновлять матрицу вида камеры, используйте следующий код:

// Местоположение и цель
Vector3 position;
Vector3 target;

// Векторы ориентации
Vector3 headingVec;
Vector3 strafeVec;
Vector3 upVec;

// Матрицы и флаги
protected bool needUpdateView;
protected bool needUpdateFrustum;
protected Matrix viewMatrix;

// Получение матрицы вида камеры
public Matrix View
{
    get
    {
        if (needUpdateView) UpdateView();
        return viewMatrix;
    }
}

// Установка вида камеры
public void SetLookAt(Vector3 cameraPos, Vector3 cameraTarget,
                      Vector3 cameraUp)
{
    this.position = cameraPos;
    this.target = cameraTarget;
    this.upVec = cameraUp;

    // Вычисление осей камеры (направления вперед, вверх и вправо)
    headingVec = cameraTarget - cameraPos;
    headingVec.Normalize();
    upVec = cameraUp;
    strafeVec = Vector3.Cross(headingVec, upVec);
    needUpdateView = true;
}

// Обновление вида камеры
protected virtual void UpdateView()
{
    viewMatrix = Matrix.CreateLookAt(position, target, upVec);
    needUpdateView    = false;
    needUpdateFrustum = true;
}

Метод SetLookAt сохраняет новые параметры вида камеры, но, подобно методу SetPerspectiveFov, он оставляет генерацию матрицы вида на будущее, когда эта матрица будет запрошена через свойство View. Этот метод также вычисляет три вектора, образующих систему координат камеры, и используемых для ориентации камеры. Мы более подробно поговорим о том, как вычислить эти векторы, в следующем разделе.

И, наконец, внутри метода UpdateView вы генерируете новую матрицу вида, используя метод CreateLookAt класса Matrix XNA. Заметьте, что когда обновляется матрица вида, должна быть обновлена и пирамида видимого пространства камеры.

Система координат камеры

Каждый раз, когда вы меняете конфигурацию камеры методом SetLookAt, надо вычислить три новых вектора системы координат камеры: передний (heading, ось Z), боковой (strafe, ось X) и верхний (up, ось Y). На рис. 9.1 показана система координат камеры, размещенная в мировой системе координат. Обратите внимание, что образующие систему координат камеры векторы должны быть единичными перпендикулярными векторами. Вы можете использовать для представления направлений единичные векторы, поскольку в данном случае размер вектора не имеет значения. За дополнительными сведениями о системах координат обратитесь к главе 7.


Рис. 9.1. Система координат камеры, размещенная в мировой системе координат

Рис. 9.1. Система координат камеры, размещенная в мировой системе координат. Оси камеры X, Y и Z представляются соответственно боковым, верхним и передним векторами класса BaseCamera


Передний вектор — это направление от местоположения камеры на цель, и вы можете вычислить его, вычтя местоположение камеры из местоположения цели. Верхний вектор определяет, где у камеры будет верх и используется для ориентации камеры. Например, мы можете использовать вектор (0, 1, 0) чтобы направление вверх у камеры совпадало с мировой осью Y. Последний вектор (боковой) вы вычисляете находя вектор, перпендикулярный переднему и верхнему векторам. Векторное произведение — вот операция, вычисляющая вектор одновременно перпендикулярный двум другим векторам. Вы используете векторное произведение переднего и верхнего векторов для вычисления бокового вектора камеры. Для вычисления векторного произведения применяется метод Cross класса Veсtor3 XNA. Заметьте, что векторы, используемые в операции векторного умножения, должны быть единичными, а порядок, в котором они передаются в метод Cross, меняет направление результирующего вектора.

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

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

Пирамида видимого пространства камеры

Вы представляете пирамиду видимого пространства камеры, используя класс XNA BoundingFrustum. В XNA есть несколько классов для представления ограничивающих объемов, такие как BoundingBox (выровненный по осям куб), BoundingSphere и BoundingFrustum. Каждый из этих классов имеет методы обнаружения столкновений, которые можно применять, чтобы проверить наличие пересечений между ними. Итак, используя класс XNA BoundingFrustum, вы получаете готовые методы для проверки пересечений с некоторыми другими объектами.

Вы создаете метод UpdateFrustum для генерации пирамиды видимого пространства камеры и свойство Frustum для ее получения. Вы генерируете пирамиду видимого пространства камеры, комбинируя матрицы проекции и вида камеры и используя результат для конструирования нового объекта BoundingFrustum XNA. Используйте следующий код для построения пирамиды видимого пространства камеры:

public BoundingFrustum Frustum
{
    get
    {
        if (needUpdateProjection)
            UpdateProjection();
        if (needUpdateView)
            UpdateView();
        if (needUpdateFrustum)
            UpdateFrustum();

        return frustum;
    }
}

protected virtual void UpdateFrustum()
{
    frustum = new BoundingFrustum(viewMatrix * projectionMatrix);

    needUpdateFrustum = false;
}

Кроме этого, в классе BaseCamera есть абстрактный метод Update, определяющий как должна обновляться камера. Каждая камера, расширяющая класс BaseCamera, должна реализовать этот метод. Вот сигнатура метода Update:

public abstract void Update(GameTime time);

Камера с видом от третьего лица

В этом разделе вы расширите созданный в предыдущем разделе класс BaseCamera, чтобы создать более специализированный тип камеры: камеру с видом от третьего лица. Для этого типа камеры вы создадите класс с именем ThridPersonCamera, расширяющий класс BaseCamera. Цель камеры с видом от третьего лица — следовать за объектом, пока он перемещается, и расстояние, на котором камера следует за объектом, должно быть переменным. Иначе будет казаться, что объект привязан к камере.

Чтобы сделать камеру, следующую за объектом, например за управляемым игроком персонажем, вам необходимо определить некоторые параметры, такие как местоположение преследуемого (позиция, за которой должна следовать камера); направление преследования (направление, используемое для следования за преследуемой позицией); скорость преследования; минимальное, желаемое и максимальное расстояние между камерой и объектом. На рис. 9.2 показаны некоторые из параметров, которые надо сконфигурировать.


Рис. 9.2. Для камеры с видом от третьего лица квадрат - это преследуемая позиция, а точки - максимальное, желаемое и минимально допустимое местоположение камеры

Рис. 9.2. Для камеры с видом от третьего лица квадрат — это преследуемая позиция, а точки — максимальное, желаемое и минимально допустимое местоположение камеры


Установка параметров преследования

В классе ThridPersonCamera вы создаете метод SetChaseParameters для редко обновляемых параметров преследования: дистанций преследования и скорости. Более часто изменяемые параметры, такие как местоположение преследуемого и направление, обновляются через свойства:

// Параметры преследования
float desiredChaseDistance;
float minChaseDistance;
float maxChaseDistance;
float chaseSpeed;

Vector3 chasePosition;

public Vector3 ChasePosition
{
    get { return chasePosition; }
    set { chasePosition = value; }
}

Vector3 chaseDirection;

public Vector3 ChaseDirection
{
    get { return chaseDirection; }
    set { chaseDirection = value; }
}

public void SetChaseParameters(float chaseSpeed,
                               float desiredChaseDistance,
                               float minChaseDistance,
                               float maxChaseDistance)
{
    this.chaseSpeed           = chaseSpeed;
    this.desiredChaseDistance = desiredChaseDistance;
    this.minChaseDistance     = minChaseDistance;
    this.maxChaseDistance     = maxChaseDistance;
}

Обновление местоположения камеры

При каждом обновлении камеры должно перерасчитываться ее местоположение. Желаемое местоположение камеры равно местоположению преследуемого камерой, минус направление преследования, умноженное на расстояние преследования (которое является расстоянием между камерой и позицией преследуемого), как показано на рис. 9.2. Желаемое местоположение камеры было бы итоговым местоположением камеры, если бы камера располагалась на фиксированном расстоянии от преследуемой позиции. Однако, чтобы обеспечить более плавное передвижение камеры, расстояние между камерой и преследуемой позицией может изменяться между минимальным и максимальным значениями (определенными в атрибутах minChaseDistance и maxChaseDistance). Таким образом, новое местоположение камеры рассчитывается путем линейной интерполяции между ее текущим местоположением и желаемым местоположением. За дополнительной информацией обратитесь к врезке «Линейная интерполяция».

Vector3 targetPosition = chasePosition;
Vector3 desiredCameraPosition = chasePosition -
                   chaseDirection * desiredChaseDistance;

float interpolatedSpeed = MathHelper.Clamp(chaseSpeed *
                   elapsedTimeSeconds, 0.0f, 1.0f);

desiredCameraPosition = Vector3.Lerp(position,
                   desiredCameraPosition, interpolatedSpeed);

Вес, используемый для интерполяции местоположения камеры, рассчитывается на основании времени, прошедшего с момента последнего обновления, и скорости камеры. Однако, поскольку вес интерполяции должен находиться в диапазоне от 0 до 1, вам необходимо ограничить это значение. В классе Vector3 XNA есть метод Lerp, который поможет вам интерполировать векторы.

ЛИНЕЙНАЯ ИНТЕРПОЛЯЦИЯ
Линейная интерполяция — это интерполяция между двумя значениями, которая меняется линейно, согласно заданному весу, где вес обычно является значением с плавающей точкой в диапазоне от 0 до 1. Например, линейная интерполяция между числами 10 и 20, использующая значение веса 0.5 даст в результате значение 15, а линейные интерполяции, использующие веса 0 и 1 дадут в результате значения 10 и 20. Линейная интерполяция между двумя трехмерными векторами линейно интерполирует значение каждой из компонент вектора (X, Y, Z).

Создайте метод UpdateFollowPosition для обновления местоположения камеры. Ниже представлен код для метода UpdateFollowPosition:

private void UpdateFollowPosition(float elapsedTimeSeconds,
                                  bool interpolate)
{
    Vector3 targetPosition = chasePosition;
    Vector3 desiredCameraPosition = chasePosition - chaseDirection *
                                                   desiredChaseDistance;

    if (interpolate)
    {
        float interpolatedSpeed = MathHelper.Clamp(
                          chaseSpeed * elapsedTimeSeconds, 0.0f, 1.0f);
        desiredCameraPosition = Vector3.Lerp(position,
                          desiredCameraPosition, interpolatedSpeed);

        // Сохраняем дистанцию преследования в заданном диапазоне
        Vector3 targetVector = desiredCameraPosition - targetPosition;
        float targetLength = targetVector.Length();
        targetVector /= targetLength;

        if (targetLength < minChaseDistance)
        {
            desiredCameraPosition = targetPosition +
                          targetVector * minChaseDistance;
        }
        else if (targetLength > maxChaseDistance)
        {
             desiredCameraPosition = targetPosition +
                          targetVector * maxChaseDistance;
        }
    }

    // Необходимо заново вычислить передний, боковой и верхний векторы
    SetLookAt(desiredCameraPosition, targetPosition, upVec);
}

У метода UpdateFollowPosition есть параметр interpolate, определяющий, будет ли камера сразу помещена в желаемое местоположение (если значение interpolate равно false), или же будет выполнена плавная интерполяция к желаемому местоположению. Когда камера в первый раз начинает преследование объекта, вы должны установить значение interpolate равным false, заставив камеру начать движение с желаемой позиции.

Когда итоговое местоположение камеры вычисляется путем интерполяции ее текущего местонахождения и желаемой позиции, вам необходимо убедиться, что расстояние от камеры до преследуемой позиции находится в диапазоне между заданными минимальным и максимальным расстоянием преследования, как показано на рис. 9.2. Если расстояние меньше, чем заданный минимум, вы устанавливаете его равным минимально допустимой дистанции. А если расстояние до камеры окажется больше максимально допустимого, вы устанавливаете его равным максимально допустимой дистанции. Эти проверки важны и гарантируют, что камера сможет преследовать объекты, скорость которых больше, чем у камеры.

Вращение камеры вокруг цели

Последняя возможность, которую вы добавляете к камере, — это способность камеры вращаться вокруг ее цели. Для реализации этой возможности вы добавляете к классу ThridPersonCamera два новых атрибута и свойство:

// Максимально допустимый поворот
public static float MAX_ROTATE = 30.0f;

// Текущий угол поворота относительно осей камеры
// (передней, верхней и боковой)
Vector3 eyeRotate;

// Скорость вращения относительно осей камеры
Vector3 eyeRotateVelocity;

public Vector3 EyeRotateVelocity
{
    get { return eyeRotateVelocity; }
    set { eyeRotateVelocity = value; }
}

Допустимый диапазон поворотов камеры располагается от –MAX_ROTATE до MAX_ROTATE, и, если значение поворота камеры выходит за этот диапазон, оно отсекается. Вектор eyeRotate хранит текущие значения поворота камеры, где компоненты вектора X, Y и Z представляют углы поворота относительно боковой, верхней и передней осей камеры. И, наконец, вектор eyeRotateVelocity хранит скорость, с которой происходит обновление углов поворота камеры.

Чтобы вычислить матрицу вида камеры, принимая во внимание поворот камеры, вам надо переопределить метод UpdateView класса BaseCamera. Вспомните, что метод UpdateView вызывается, когда вы получаете матрицу вида камеры через свойство View, и ее необходимо обновить. Ниже показан код метода UpdateView класса ThridPersonCamera:

protected override void UpdateView()
{
    Vector3 newPosition = Position - Target;

    // Вычисляем новое местоположение камеры,
    // вращая ее вокруг осей
    newPosition = Vector3.Transform(newPosition,
                      Matrix.CreateFromAxisAngle(UpVector,
                      MathHelper.ToRadians(eyeRotate.Y)) *
                      Matrix.CreateFromAxisAngle(StrafeVector,
                      MathHelper.ToRadians(eyeRotate.X)) *
                      Matrix.CreateFromAxisAngle(HeadingVector,
                      MathHelper.ToRadians(eyeRotate.Z))
                      );

    viewMatrix = Matrix.CreateLookAt(newPosition + Target,
                      Target, UpVector);

    needUpdateView    = false;
    needUpdateFrustum = true;
}

В переопределенном методе UpdateView вам необходимо вычислить местоположение камеры, учитывая ее вращение. Вращение камеры хранится в атрибуте eyeRotation и задается относительно ее осей. Чтобы повернуть камеру относительно ее осей, вам надо создать матрицу вращения, которая выполняет вращение относительно произвольной оси. Вы можете создать эту матрицу с помощью метода CreateFromAxisAngle класса Matrix XNA. Затем вы можете вычислить итоговую матрицу, используемую для вращения камеры, комбинируя матрицы, вращающие камеру относительно осей Y, X и Z в указанном порядке.

Обновление камеры

Вы должны реализовать финальный метод класса ThridPersonCamera: метод Update. Метод Update является абстрактным методом класса BaseCamera, вызываемым каждый раз, когда камера должна быть обновлена. Внутри метода Update вам надо обновить атрибуты камеры, а также вызвать методы, используемые для обновления камеры. Заметьте, что методы UpdateView и UpdateProjection используют атрибуты камеры для обновления матрицы вида и матрицы проекции камеры. Эти методы вызываются только тогда, матрицы вида и проекции получаются через свойства и нуждаются в обновлении. Вот код метода Update класса ThridPersonCamera:

public override void Update(GameTime time)
{
    float elapsedTimeSeconds =
               (float)time.ElapsedGameTime.TotalSeconds;

    // Обновление позиции преследования
    UpdateFollowPosition(elapsedTimeSeconds, !isFirstTimeChase);

    if (isFirstTimeChase)
    {
        eyeRotate = Vector3.Zero;
        isFirstTimeChase = false;
    }

    // Вычисление нового поворота на основе скорости вращения 
    if (eyeRotateVelocity != Vector3.Zero)
    {
        eyeRotate += eyeRotateVelocity * elapsedTimeSeconds;
        eyeRotate.X = MathHelper.Clamp(eyeRotate.X,
                                    -MAX_ROTATE, MAX_ROTATE);
        eyeRotate.Y = MathHelper.Clamp(eyeRotate.Y,
                                    -MAX_ROTATE, MAX_ROTATE);
        eyeRotate.Z = MathHelper.Clamp(eyeRotate.Z,
                                    -MAX_ROTATE, MAX_ROTATE);

        needUpdateView = true;
    }
}

В методе Update вы сперва обновляете местоположение камеры, используя метод UpdateFollowPosition. Затем вы вычисляете текущие углы поворота камеры, основываясь на ее скорости вращения и времени, прошедшем с момента последнего обновления.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz