Я пытаюсь написать простой воксельный 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, они "в основном правильные" (хотя и не очень) - когда выровнены по оси / не повернуты.