Перекошено: вращающаяся камера в простом процессоре на основе вокселя raycaster / raytracer - PullRequest
1 голос
/ 25 марта 2012

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

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

Таким образом, у меня есть камера, которую я могу перемещать вверх и вниз, перемещаться влево и вправо и «идти вперед / назад» - пока все выровнены по оси, без поворотов камеры. В этом и заключается моя проблема.

Снимки экрана: (1) вокселей радиопередачи при ... ... (2) камера остается ... ... (3) строго по оси выровнены.

Теперь я уже несколько дней пытаюсь заставить работать ротацию. Теория, лежащая в основе матриц и трехмерных вращений, в теории мне очень понятна. Тем не менее, я только когда-либо достигал «2.5 рендеринга», когда камера вращается ... рыбий глаз, немного похоже на Google Streetview: даже если у меня объемное представление мира, кажется - неважно, что я пытаюсь - как я сначала создаст рендеринг из «вид спереди», а затем повернет этот плоский рендеринг в соответствии с поворотом камеры. Само собой разумеется, я уже знаю, что вращающиеся лучи не особенно необходимы и подвержены ошибкам.

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

камера «повернута вправо на 39 градусов» - обратите внимание, что синяя заштрихованная левая сторона куба с экрана № 2 не видна в этом повороте, но пока что «это действительно должен "!

Теперь, конечно, я знаю об этом: в простой установке с выравниванием по оси, без вращения, как у меня в начале, луч просто маленькими шагами проходит через положительное направление z, расходясь влево или правый и верхний или нижний только в зависимости от положения пикселя и матрицы проекции. Когда я «вращаю камеру вправо или влево» - то есть я вращаю ее вокруг оси Y - эти самые шаги должны быть просто преобразованы с помощью правильной матрицы вращения, верно? Таким образом, для обхода вперед Z-шаг становится тем меньше, чем больше вращается кулачок, компенсируемый «увеличением» X-шага. Тем не менее, для горизонтальной + вертикальной дивергенции, основанной на положении пикселей, увеличивающиеся доли шага x необходимо «добавить» к шагу z. Так или иначе, ни одна из моих многочисленных матриц, с которыми я экспериментировал, ни мои эксперименты с жестко закодированными подробными вычислениями sin / cos без матриц действительно не дают этой части права.

Вот мой основной алгоритм предварительного прохождения луча - синтаксис в Go, но примите его как псевдокод:

  • fx и fy : положения пикселей x и y
  • rayPos : vec3 для начальной позиции луча в мировом пространстве (рассчитывается, как показано ниже)
  • rayDir : vec3 для шагов xyz, добавляемых в rayPos на каждом шаге во время обхода луча
  • rayStep : временный vec3
  • camPos : vec3 для положения камеры в мировом пространстве
  • camRad : vec3 для вращения камеры в радианах
  • pmat : типичная матрица перспективной проекции

Алгоритм / псевдокод:

// 1: rayPos is for now "this pixel, as a vector on the view plane in 3d, at The Origin"
rayPos.X, rayPos.Y, rayPos.Z = ((fx / width) - 0.5), ((fy / height) - 0.5), 0

// 2: rotate around Y axis depending on cam rotation. No prob since view plane still at Origin 0,0,0
rayPos.MultMat(num.NewDmat4RotationY(camRad.Y))

// 3: a temp vec3. planeDist is -0.15 or some such -- fov-based dist of view plane from eye and also the non-normalized, "in axis-aligned world" traversal step size "forward into the screen"
rayStep.X, rayStep.Y, rayStep.Z = 0, 0, planeDist

// 4: rotate this too -- 0,zstep should become some meaningful xzstep,xzstep
rayStep.MultMat(num.NewDmat4RotationY(CamRad.Y))

// set up direction vector from still-origin-based-ray-position-off-rotated-view-plane plus rotated-zstep-vector
rayDir.X, rayDir.Y, rayDir.Z = -rayPos.X - me.rayStep.X, -rayPos.Y, rayPos.Z + rayStep.Z

// perspective projection
rayDir.Normalize()
rayDir.MultMat(pmat)

// before traversal, the ray starting position has to be transformed from origin-relative to campos-relative
rayPos.Add(camPos)

Я пропускаю части прохождения и выборки - согласно экранам с 1 по 3, они "в основном правильные" (хотя и не очень) - когда выровнены по оси / не повернуты.

1 Ответ

4 голосов
/ 29 июня 2012

Намного проще, если вы представляете систему в качестве камеры-обскуры, а не чего-либо еще.Вместо того, чтобы снимать лучи с поверхности прямоугольника, представляющего ваше изображение, снимайте лучи из точки через прямоугольник, который будет плоскостью вашего изображения, в сцену.Все первичные лучи должны иметь одну и ту же точку происхождения, только с немного разными направлениями.Направления определяются с помощью базового триггера, по которому пиксель в плоскости изображения вы хотите, чтобы они прошли.Чтобы сделать простейший пример, давайте представим, что ваша точка находится на камере, а ваша плоскость изображения составляет одну единицу вдоль оси z, а две - высоту и ширину.Таким образом, пиксель в верхнем левом углу хочет перейти от (0,0,0) до (-1, -1, 1).Нормализовать (-1, -1, 1), чтобы получить направление.(На самом деле вам не нужно нормализовать направление только для пересечения лучей, но если вы решите этого не делать, помните, что ваши направления не нормированы, прежде чем пытаться вычислить расстояние, которое прошел луч, или что-то в этом роде.)каждый второй пиксель, вычислите точку на плоскости, которую он хочет пройти, как вы уже делали, разделив размер плоскости на количество пикселей в каждом направлении.

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

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

...