Переместите камеру в соответствии с 3D-сценой - PullRequest
21 голосов
/ 19 мая 2010

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

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

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

У меня проблемы:

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

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

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

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

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

Ответы [ 6 ]

33 голосов
/ 19 мая 2010

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

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

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

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

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

Math

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

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

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

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

9 голосов
/ 29 сентября 2015

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

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

enter image description here

// 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 в C # приведена ниже. Важными моментами являются свойства Центра и Углов. Vector3 - довольно стандартная реализация 3-компонентного (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;
        }
    } 
}
6 голосов
/ 19 мая 2010

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

Что я бы попытался:

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

Редактировать:

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

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

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

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

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

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]; }
        }
    }
1 голос
/ 04 августа 2010

В данный момент у меня нет под рукой, но вам нужна книга http://www.amazon.com/Jim-Blinns-Corner-Graphics-Pipeline/dp/1558603875/ref=ntt_at_ep_dpi_1

У него есть целая глава об этом

0 голосов
/ 23 июня 2016

Проверьте эту ссылку https://msdn.microsoft.com/en-us/library/bb197900.aspx

Плавание = сфера.радиус / грех (фов / 2);

float3 eyePoint =phere.centerPoint - расстояние * camera.frontVector;

...