Основные рендеринга 3D перспективной проекции на 2D-экране с камеры (без поддержки OpenGL)


Допустим, у меня есть структура данных, подобная следующей:

Camera {
   double x, y, z

   /** ideally the camera angle is positioned to aim at the 0,0,0 point */
   double angleX, angleY, angleZ;
}

SomePointIn3DSpace {
   double x, y, z
}

ScreenData {
   /** Convert from some point 3d space to 2d space, end up with x, y */
   int x_screenPositionOfPt, y_screenPositionOfPt

   double zFar = 100;

   int width=640, height=480
}

...

Без отсечения экрана или многого другого, как бы я рассчитал положение экрана x, y некоторой точки, заданной некоторой трехмерной точкой в пространстве. Я хочу спроецировать эту 3d-точку на 2d-экран.

Camera.x = 0
Camera.y = 10;
Camera.z = -10;


/** ideally, I want the camera to point at the ground at 3d space 0,0,0 */
Camera.angleX = ???;
Camera.angleY = ????
Camera.angleZ = ????;

SomePointIn3DSpace.x = 5;
SomePointIn3DSpace.y = 5;
SomePointIn3DSpace.z = 5;

ScreenData.x и y-это положение экрана X трехмерной точки в пространстве. Как мне вычислить эти значения?

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

Http://en.wikipedia.org/wiki/3D_projection

8 25

8 ответов:

Способ, которым это делается, заключается в использовании однородных преобразований и координат. Вы берете точку в пространстве и:

    Расположите его относительно камеры, используя матрицу модели.
  • спроецируйте его либо орфографически, либо в перспективе, используя проекционную матрицу.
  • примените видовой экран trnasformation, чтобы разместить его на экране.
Это довольно расплывчато, но я постараюсь осветить важные моменты и оставить некоторые из них вам. Я полагаю, вы понимаете основы матричная математика :).

Однородные Векторы, Точки, Преобразования

В 3D однородная точка будет представлять собой матрицу столбцов вида [x, y, z, 1]. Конечная составляющая-это "w", коэффициент масштабирования, который для векторов равен 0: это приводит к тому, что вы не можете перевести векторы, что математически правильно. Мы не пойдем туда,мы говорим о вещах.

Гомогенные преобразования являются матрицами 4x4, используемыми потому, что они позволяют переводу быть представленным в виде матрицы умножение, а не сложение, что приятно и быстро для вашей видеокарты. Также удобно, потому что мы можем представлять последовательные преобразования, умножая их вместе. Мы применяем преобразования к точкам, выполняя преобразование * point.

Существует 3 первичных однородных преобразования:

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

Орфографические и перспективные

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

  • орфографический, обычно используется для 2D и CAD.
  • Перспектива, хорошо подходит для игр и 3D-сред.

Ортогональная проекционная матрица строится следующим образом:

Ортогональная проекционная матрица, любезно предоставленная Википедией.

Где параметры включают:

  • Top : координата Y верхнего края видимого пространства.
  • Bottom : координата Y нижнего края видимого пространства.
  • левая : координата X левого края видимого пространства.
  • справа : координата X правого края видимого пространства.

Я думаю, что это довольно просто. То, что вы устанавливаете, - это область пространства, которая появится на экране, которую вы можете обрезать. Здесь все просто, потому что видимая область пространства-это прямоугольник. Отсечение в перспективе является более сложным, потому что область, которая появляется на экран или объем просмотра, является фрустрацией .

Если у вас возникли трудности с Википедией по перспективной проекции, вот код для построения подходящей матрицы, любезно предоставленный geeks3D

void BuildPerspProjMat(float *m, float fov, float aspect,
float znear, float zfar)
{
  float xymax = znear * tan(fov * PI_OVER_360);
  float ymin = -xymax;
  float xmin = -xymax;

  float width = xymax - xmin;
  float height = xymax - ymin;

  float depth = zfar - znear;
  float q = -(zfar + znear) / depth;
  float qn = -2 * (zfar * znear) / depth;

  float w = 2 * znear / width;
  w = w / aspect;
  float h = 2 * znear / height;

  m[0]  = w;
  m[1]  = 0;
  m[2]  = 0;
  m[3]  = 0;

  m[4]  = 0;
  m[5]  = h;
  m[6]  = 0;
  m[7]  = 0;

  m[8]  = 0;
  m[9]  = 0;
  m[10] = q;
  m[11] = -1;

  m[12] = 0;
  m[13] = 0;
  m[14] = qn;
  m[15] = 0;
}

Переменные:

  • fov : поле зрения, Pi/4 Радиана-хорошее значение.
  • аспект : отношение высоты к ширине.
  • znear, zfar : используется для вырезки, я их проигнорирую.

И матрица создан крупный колонка, индексируется следующим образом в приведенном выше коде:

0   4   8  12
1   5   9  13
2   6  10  14
3   7  11  15

Преобразование Видового Экрана, Координаты Экрана

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

Таким образом, для точки p мы бы:

  • выполнить преобразование матрицы модели * p, в результате чего получится pm.
  • выполнить проекционная матрица * pm, приводящая к pp.
  • отсечение pp от объема просмотра.
  • выполните преобразование видового экрана матрицы * pp, в результате чего будет ps: точка на экране.

Резюме

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

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

Http://www.songho.ca/opengl/gl_transform.html

Что касается реальной работы, вам нужно будет реализовать класс матриц 4x4 для однородных преобразований, а также однородный точечный класс, который вы можете умножить на него, чтобы применить преобразования (помните, [x, y, z, 1]). Вы будете нужно генерировать преобразования, как описано выше и в ссылках. Это не так уж трудно, если вы понимаете процедуру. Удачи вам:).

@BerlinBrown просто в качестве общего замечания, вы не должны хранить вращение камеры в виде углов X, Y, Z, так как это может привести к неоднозначности.

Например, x=60 градусов - это то же самое, что и -300 градусов. При использовании x, y и z число неоднозначных возможностей очень велико.

Вместо этого попробуйте использовать две точки в трехмерном пространстве: x1, y1,z1 для определения местоположения камеры и x2,y2,z2 для определения "цели"камеры. Углы могут быть вычислены в обратном направлении к/от местоположения/цели, но, по моему мнению, это не так. рекомендуемый. Использование местоположения камеры / цели позволяет построить вектор "LookAt", который является единичным вектором в направлении камеры (v'). Из этого вы также можете построить матрицу LookAt, которая представляет собой матрицу 4x4, используемую для проецирования объектов в 3D-пространстве на пиксели в 2D-пространстве.

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

Задан вектор движения вашей камеры к цели, v = xi, yj, zk
Нормализовать вектор, а V = ХІ, выпускался ЗК / корень(ХІ^2 + ый^2 + ЗК^2)
Пусть U = глобальный мировой вектор u = 0, 0, 1
Тогда мы можем вычислить R = горизонтальный вектор, параллельный направлению обзора камеры R = v ' ^ U,
где ^ - поперечное произведение, заданное
a ^ b = (a2b3 - a3b2) i + (a3b1 - a1b3)j + (a1b2 - a2b1)k

Это даст вам вектор, который выглядит следующим образом.

Вычисление вектора, ортогонального камере

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

В основном все эти проблемы 3D-манипуляции сводятся к преобразованию точки в мировом пространстве в локальное пространство,где локальные оси x,y, z находятся в ориентации с камерой. Есть ли в этом смысл? Итак, если у вас есть точка Q=x, y,z,и вы знаете R и v' (оси камеры), то вы можете спроецировать ее на "экран", используя простые векторные манипуляции. То соответствующие углы могут быть найдены с помощью оператора точечного произведения на векторах.

Проецирование Q на экран

Следуя Википедии, сначала вычислите "d":

Http://upload.wikimedia.org/wikipedia/en/math/6/0/b/60b64ec331ba2493a2b93e8829e864b6.png

Чтобы сделать это, создайте эти матрицы в своем коде. Сопоставления из ваших примеров с их переменными:

Θ = Camera.angle*

A = SomePointIn3DSpace

C = Camera.x | y | z

Или, просто сделайте уравнения отдельно без использования матриц, ваш выбор:

Http://upload.wikimedia.org/wikipedia/en/math/1/c/8/1c89722619b756d05adb4ea38ee6f62b.png

Теперь вычислим "b", двумерную точку:

Http://upload.wikimedia.org/wikipedia/en/math/2/5/6/256a0e12b8e6cc7cd71fa9495c0c3668.png

В этом случае ex и ey-это позиция зрителя, я считаю, что в большинстве графических систем половина размера экрана (0,5) используется для того, чтобы сделать (0, 0) центром экрана по умолчанию, но вы можете использовать любое значение (play вокруг). ez-это то, где поле зрения вступает в игру. Это единственное, чего тебе не хватало. Выберите угол fov и вычислите ez следующим образом:

Ez = 1 / tan (fov / 2)

Наконец, чтобы получить bx и by к реальным пикселям, вы должны масштабировать на коэффициент, связанный с размером экрана. Например, если b отображает от (0, 0) до (1, 1), Вы можете просто масштабировать x на 1920 и y на 1080 для отображения 1920 x 1080. Таким образом, любой размер экрана будет показывать то же самое. Конечно, есть много других факторов, связанных с этим в реальной 3D-графической системе, но это базовая версия.

Преобразование точек в 3D-пространстве в 2D-точку на экране просто производится с помощью матрицы . Используйте матрицу для расчета положения вашей точки на экране, это сэкономит вам много работы.

При работе с камерами вы должны рассмотреть возможность использования look-at-matrix и умножить матрицу look at на вашу проекционную матрицу.

Предполагая, что камера находится в точке (0, 0, 0) и направлена прямо вперед, уравнения будут следующими:

ScreenData.x = SomePointIn3DSpace.x / SomePointIn3DSpace.z * constant;
ScreenData.y = SomePointIn3DSpace.y / SomePointIn3DSpace.z * constant;

Где "константа" - некоторое положительное значение. Установка его на ширину экрана в пикселях обычно дает хорошие результаты. Если вы установите его выше, то сцена будет выглядеть более "увеличенной", и наоборот.

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

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

Вам может быть интересно просто посмотреть, как GLUT делает это за кулисами. Все эти методы имеют схожую документацию, которая показывает математику, которая входит в них.

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

Вы хотите преобразовать свою сцену с матрицей, подобной Матрице Glulookat OpenGL, а затем вычислить проекцию, используя матрицу проекции, подобную матрице gluPerspective OpenGL.

Вы можете попробовать просто вычислить матрицы и сделать умножение в программном обеспечении.

Запустите его через луч трассировщика:

Ray Tracer in C# - Некоторые из объектов, которые у него есть, будут вам знакомы; -)

и просто для удовольствия версия LINQ.

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

Если ваше приложение просто пытается нарисовать всю сцену, это будет быть здорово.

Решение проблемы #1: затемненные точки не будут проецироваться.
решение : хотя я ничего не видел о непрозрачности или прозрачности на странице блога, вы, вероятно, могли бы добавить эти свойства и код для обработки одного луча, который отскочил (как обычно) и один, который продолжил (для "прозрачности").

Решение проблемы #2: проецирование одного пикселя потребует дорогостоящей трассировки всего изображения. пиксели .
Очевидно, если вы просто хотите нарисовать объекты, используйте трассировщик лучей для того, для чего он предназначен! Но если вы хотите найти тысячи пикселей на изображении, из случайных частей случайных объектов (почему?), делая полный луч-след для каждого запроса будет огромная производительность собаки.

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

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

Вы можете добавить код так, чтобы при пересечении с объектами создавались списки, индексированные по пересеченным точкам объектов, причем элемент является текущим 2d пикселем, который отслеживается.

Затем, когда вы хотите спроецировать точку, перейдите к списку этого объекта, найдите ближайшую точку к той, которую вы хотите спроецировать. спроецируйте и посмотрите на 2d-пиксель, который вас интересует. Математика будет гораздо более минимальной, чем уравнения в ваших статьях. К сожалению, используя, например, словарь вашего объекта+отображение структуры точек на 2d-пиксели, я не уверен, как найти ближайшую точку на объекте, не пробегая весь список отображенных точек. Хотя это не самая медленная вещь в мире, и вы, вероятно, могли бы понять это, у меня просто нет времени думать об этом. Кто-нибудь?

Удачи вам!

"Кроме того, я не понимаю в записи wiki, что такое позиция зрителя по отношению к позиции камеры" ... Я на 99% уверен, что это одно и то же.