Подтвердить что ты не робот

Перемещение камеры в 3D-сцену

Я ищу алгоритм, который бы соответствовал ограничивающей рамке внутри окна просмотра (в моем случае сцена DirectX). Я знаю об алгоритмах для центрирования ограничивающей сферы в орфографической камере, но для этого нужен и ограничивающий прямоугольник, и перспективная камера. Я не могу просто изменить FOV, потому что это приложение имеет FOV в качестве редактируемой пользователем переменной, поэтому оно должно перемещать камеру.

У меня есть большинство данных:

  • У меня есть верхний вектор для камеры
  • У меня есть центральная точка ограничивающей рамки
  • У меня есть вектор поиска (направление и расстояние) от точки камеры до центра окна.
  • Я проецировал точки на плоскости, перпендикулярной к камере, и получил коэффициенты, описывающие, насколько максимальные/минимальные X и Y-координаты находятся внутри или вне плоскости просмотра.

Проблемы, которые у меня есть:

  • Центр ограничивающей рамки не обязательно находится в центре окна просмотра (т.е. он ограничивает прямоугольник после проецирования).
  • Так как поле зрения "искажает" проекцию (см. http://en.wikipedia.org/wiki/File:Perspective-foreshortening.svg) Я не могу просто использовать коэффициенты как масштабный коэффициент для перемещения камера, потому что она будет превышать/понижать желаемую позицию камеры.

Как найти положение камеры так, чтобы он заполнил область просмотра как можно точнее, как пиксель (исключение составляет, если соотношение сторон далека от 1.0, нужно только заполнить одну из оси экрана)?

Я пробовал другие вещи:

  • Использование ограничивающей сферы и касательной, чтобы найти масштабный коэффициент для перемещения камеры. Это не работает, потому что это не учитывает перспективную проекцию, а во-вторых, сферы - плохие ограничивающие объемы для моего использования, потому что у меня много плоской и длинной геометрии.
  • Итерирование вызовов функции, чтобы получить меньшую и меньшую ошибку в позиции камеры. Это несколько сработало, но иногда я могу столкнуться с странными краевыми случаями, когда положение камеры слишком сильно перерегулируется, а коэффициент ошибки увеличивается. Кроме того, при этом я не возвращал модель, основанную на положении ограничивающего прямоугольника. Я не мог найти надежный и надежный способ сделать это надежно.

Помогите пожалуйста!

4b9b3361

Ответ 1

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

Если вы рассматриваете граничные сферы, одно решение может быть

  • ориентация первого изменения, чтобы посмотреть на центр ограниченной сферы.
  • тогда достаточно достаточно назад (направление негативного взгляда) для ограничивающей сферы, чтобы она соответствовала внутреннему усечению

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

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

Math

Если вы знаете размер ограничения s объекта в мировых координатах (нас не интересуют пиксели или координаты камеры, так как они зависят от вашего расстояния) от ориентации камеры, вы можете вычислить требуемое расстояние d камеры до ограничивающей формы, если вы знаете угол обзора по оси x и y a проекции перспективы.

     frustum      ------            
            ------    *****          -  
       -----          *   *          |
   -===     ) FOV a   *bounding box  | BB size s
camera -----          *   *          |
            ------    *****          -
                  ------

  |-------------------|
        distance d

Итак, math - tan(a/2) = (s/2) / d = > d = (s/2) / tan(a/2) Который даст вам расстояние, которое камера должна быть установлена ​​с ближайшей граничной поверхности.

Ответ 2

Я знаю, что есть несколько отличных ответов выше, но я хотел добавить сложное простое решение, чтобы соответствовать ограничивающей сфере внутри камеры frustrum. Это делает предположение, что вы хотите сохранить вектор Target и Forward камеры одинаковым, и просто отрегулируйте расстояние камеры до цели.

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

введите описание изображения здесь

// Compute camera radius to fit bounding sphere
// Implementation in C#
// 

// Given a bounding box around your scene
BoundingBox bounds = new BoundingBox();

// Compute the centre point of the bounding box
// NOTE: The implementation for this is to take the mid-way point between 
// two opposing corners of the bounding box
Vector3 center = bounds.Center;

// Find the corner of the bounding box which is maximum distance from the 
// centre of the bounding box. Vector3.Distance computes the distance between 
// two vectors. Select is just nice syntactic sugar to loop 
// over Corners and find the max distance.
double boundSphereRadius = bounds.Corners.Select(x => Vector3.Distance(x, bounds.Center)).Max();

// Given the camera Field of View in radians
double fov = Math3D.DegToRad(FieldOfView);

// Compute the distance the camera should be to fit the entire bounding sphere
double camDistance = (boundSphereRadius * 2.0) / Math.Tan(fov / 2.0);

// Now, set camera.Target to bounds.Center
// set camera.Radius to camDistance
// Keep current forward vector the same

Реализация BoundingBox в С# приведена ниже. Важными моментами являются свойства Center и Corners. Vector3 - довольно стандартная реализация трехкомпонентного (X, Y, Z) вектора

public struct BoundingBox
{        
    public Vector3 Vec0;
    public Vector3 Vec1;

    public BoundingBox(Vector3 vec0, Vector3 vec1)
    {
        Vec0 = vec0;
        Vec1 = vec1;
    }

    public Vector3 Center
    {
        get { return (Vec0 + Vec1)*0.5; }
    }

    public IList<Vector3> Corners
    {
        get
        {
            Vector3[] corners = new[]
            {
                new Vector3( Vec0.X, Vec0.Y, Vec0.Z ), 
                new Vector3( Vec1.X, Vec0.Y, Vec0.Z ), 
                new Vector3( Vec0.X, Vec1.Y, Vec0.Z ), 
                new Vector3( Vec0.X, Vec0.Y, Vec1.Z ), 
                new Vector3( Vec1.X, Vec1.Y, Vec0.Z ), 
                new Vector3( Vec1.X, Vec0.Y, Vec1.Z ), 
                new Vector3( Vec0.X, Vec1.Y, Vec1.Z ), 
                new Vector3( Vec1.X, Vec1.Y, Vec1.Z ), 
            };

            return corners;
        }
    } 
}

Ответ 3

Поскольку у вас есть ограничивающая рамка, у вас должна быть основа, описывающая ее ориентацию. Кажется, что вы хотите поместить камеру на линию, совпадающую с базовым вектором, описывающим наименьший размер окна, затем переместите камеру так, чтобы наибольшее измерение было горизонтальным (при условии, что у вас есть OBB, а не AABB). Это предполагает, что соотношение сторон больше 1,0; если нет, вы захотите использовать вертикальный размер.

Что я буду делать:

  • Найдите наименьшее размерность окна.
  • Найти связанный базовый вектор.
  • Масштабируйте базовый вектор на расстоянии от центра коробки, которым должна быть камера. Это расстояние просто boxWidth / (2 * tan(horizontalFov / 2)). Обратите внимание, что boxWidth - это ширина наибольшего размера окна.
  • Поместите камеру в boxCenter + scaledBasis, глядя на boxCenter.
  • При необходимости разверните камеру, чтобы выровнять вектор камеры с соответствующим векторным базовым вектором.

Edit:

Итак, я думаю, что вы получаете то, что у вас есть камера в произвольном месте, где-то где-то, и у вас есть AABB на другой позиции. Не перемещая камеру в сторону коробки, вы хотите:

  • Посмотрите на центр коробки.
  • Переместите фотокамеру вдоль этого вектора так, чтобы поле занимало максимальный объем пространства экрана.

Если это так, у вас будет немного больше работы; вот что я предлагаю:

  • Поверните камеру, чтобы посмотреть на центр ограничивающей рамки.
  • Проецируйте все точки окна в пространство экрана и найдите ограничивающий прямоугольник min/max на экране (у вас уже есть это).
  • Теперь Unproject два противоположных угла ограничивающего прямоугольника экрана в мировое пространство. Для значения Z используйте ближайшую точку пространства мира вашего AABB к камере.
  • Это должно дать вам космическую плоскость мира, обращенную к камере, расположенной в точке на AABB, которая ближе всего к камере.
  • Теперь используйте наш существующий метод боковой ориентации, чтобы переместить камеру в нужное место, рассматривая эту плоскость как часть вашего окна.

Ответ 5

Это скопировано прямо из моего двигателя, оно создает 6 плоскостей, которые представляют каждую из шести сторон frutsum. Надеюсь, это пригодится.

internal class BoundingFrustum
    {
        private readonly float4x4 matrix = new float4x4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        private readonly Plane[] planes;

        internal BoundingFrustum(float4x4 value)
        {
            planes = new Plane[6];
            for (int i = 0; i < 6; i++)
                planes[i] = new Plane();
            Setfloat4x4(value);
        }

        private void Setfloat4x4(float4x4 value)
        {
            planes[2].Normal.X = -value.M14 - value.M11;
            planes[2].Normal.Y = -value.M24 - value.M21;
            planes[2].Normal.Z = -value.M34 - value.M31;
            planes[2].D = -value.M44 - value.M41;
            planes[3].Normal.X = -value.M14 + value.M11;
            planes[3].Normal.Y = -value.M24 + value.M21;
            planes[3].Normal.Z = -value.M34 + value.M31;
            planes[3].D = -value.M44 + value.M41;
            planes[4].Normal.X = -value.M14 + value.M12;
            planes[4].Normal.Y = -value.M24 + value.M22;
            planes[4].Normal.Z = -value.M34 + value.M32;
            planes[4].D = -value.M44 + value.M42;
            planes[5].Normal.X = -value.M14 - value.M12;
            planes[5].Normal.Y = -value.M24 - value.M22;
            planes[5].Normal.Z = -value.M34 - value.M32;
            planes[5].D = -value.M44 - value.M42;
            planes[0].Normal.X = -value.M13;
            planes[0].Normal.Y = -value.M23;
            planes[0].Normal.Z = -value.M33;
            planes[0].D = -value.M43;
            planes[1].Normal.X = -value.M14 + value.M13;
            planes[1].Normal.Y = -value.M24 + value.M23;
            planes[1].Normal.Z = -value.M34 + value.M33;
            planes[1].D = -value.M44 + value.M43;
            for (int i = 0; i < 6; i++)
            {
                float num2 = planes[i].Normal.Length();
                planes[i].Normal = planes[i].Normal / num2;
                planes[i].D /= num2;
            }
        }

        internal Plane Bottom
        {
            get { return planes[5]; }
        }
        internal Plane Far
        {
            get { return planes[1]; }
        }
        internal Plane Left
        {
            get { return planes[2]; }
        }
        internal Plane Near
        {
            get { return planes[0]; }
        }
        internal Plane Right
        {
            get { return planes[3]; }
        }
        internal Plane Top
        {
            get { return planes[4]; }
        }
    }