Базовая 3D-проекция рендера на 2D-экран с камерой (без OpenGL) - PullRequest
25 голосов
/ 26 декабря 2011

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

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-точку на 2-й экран.

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, положение 3d-точки в пространстве. Как рассчитать эти значения?

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

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

Ответы [ 8 ]

48 голосов
/ 06 января 2012

«Как это делается» - использовать однородные преобразования и координаты. Вы берете точку в пространстве и:

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

Это становится довольно расплывчатым, но я постараюсь охватить важные моменты и оставлю некоторые из них вам. Я полагаю, вы понимаете основы математики математике:).

Гомогенные векторы, точки, преобразования

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

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

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

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

Орфография и перспектива

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

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

Матрица ортографической проекции строится следующим образом:

An orthographic projection matrix, courtesy of Wikipedia.

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

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

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

Если вам трудно с википедией по перспективной проекции, вот код для построения подходящей матрицы, любезно предоставлен 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 : Поле зрения, пи / 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, в результате ps: точка на экране.

Основная информация

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

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

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

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

10 голосов
/ 06 января 2012

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

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

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

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

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

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

Computing a vector orthogonal to the camera

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

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

Projecting Q onto the screen

5 голосов
/ 02 января 2012

После википедии сначала вычислите «d»:

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

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

θ = Camera.angle*

a = SomePointIn3DSpace

с = Camera.x | y | z

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

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

Теперь мы вычисляем «b», 2D-точку:

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

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

ez = 1 / tan (fov / 2)

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

4 голосов
/ 02 января 2012

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

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

3 голосов
/ 06 января 2012

Если предположить, что камера находится в точке (0, 0, 0) и направлена ​​прямо вперед, уравнения будут иметь вид:

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

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

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

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

2 голосов
/ 02 января 2012

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

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

0 голосов
/ 04 января 2012

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

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

И только для ударов LINQ версия .

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

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

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

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

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

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

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

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

удачи!

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

0 голосов
/ 02 января 2012

Вы хотите преобразовать свою сцену с помощью матрицы, похожей на gluLookAt в OpenGL, а затем рассчитать проекцию с использованием матрицы проекции, аналогичной gluPerspective .

в OpenGL.

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

...