Перспективное правильное наложение текстур;расчет расстояния z может быть неправильным

StackOverflow https://stackoverflow.com/questions/2068134

  •  20-09-2019
  •  | 
  •  

Вопрос

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

Мой алгоритм состоит в том, чтобы сначала отсортировать координаты для построения графика. y.Это возвращает самую высокую, самую низкую и центральную точку.Затем я прохожу по линиям сканирования, используя дельту:

// ordering by y is put here

order[0] = &a_Triangle.p[v_order[0]];
order[1] = &a_Triangle.p[v_order[1]];
order[2] = &a_Triangle.p[v_order[2]];

float height1, height2, height3;

height1 = (float)((int)(order[2]->y + 1) - (int)(order[0]->y));
height2 = (float)((int)(order[1]->y + 1) - (int)(order[0]->y));
height3 = (float)((int)(order[2]->y + 1) - (int)(order[1]->y));

// x 

float x_start, x_end;
float x[3];
float x_delta[3];

x_delta[0] = (order[2]->x - order[0]->x) / height1;
x_delta[1] = (order[1]->x - order[0]->x) / height2;
x_delta[2] = (order[2]->x - order[1]->x) / height3;

x[0] = order[0]->x;
x[1] = order[0]->x;
x[2] = order[1]->x;

И затем мы рендерим из order[0]->y к order[2]->y, увеличивая x_start и x_end по дельте.При рендеринге верхней части дельта x_delta[0] и x_delta[1].При рендеринге нижней части дельта x_delta[0] и x_delta[2].Затем мы линейно интерполируем между x_start и x_end на нашей строке сканирования.UV-координаты интерполируются таким же образом в порядке y, начиная с начала и конца, к которым на каждом шаге применяются дельты.

Это работает нормально, за исключением тех случаев, когда я пытаюсь выполнить UV-картирование с правильной перспективой.Основной алгоритм – принять UV/z и 1/z для каждой вершины и интерполировать между ними.Для каждого пикселя UV-координата становится UV_current * z_current.Однако вот результат:

alt text

Обратная часть говорит вам, где перевернуты дельты.Как вы можете видеть, кажется, что оба треугольника движутся к разным точкам на горизонте.

Вот что я использую для расчета Z в точке пространства:

float GetZToPoint(Vec3 a_Point)
{
    Vec3 projected = m_Rotation * (a_Point - m_Position);

    // #define FOV_ANGLE 60.f
    // static const float FOCAL_LENGTH = 1 / tanf(_RadToDeg(FOV_ANGLE) / 2);
    // static const float DEPTH = HALFHEIGHT * FOCAL_LENGTH; 
    float zcamera = DEPTH / projected.z;

    return zcamera;
}

Я прав, это проблема с z-буфером?

Это было полезно?

Решение

ZBuffer не имеет к этому никакого отношения.

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

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

Однажды я сделал программный растеризатор с фиксированной точкой (для мобильного телефона), но на моем ноутбуке нет исходников.Итак, позвольте мне сегодня вечером проверить, как я это сделал.В принципе то, что у вас получилось, неплохо!Подобная вещь может быть вызвана очень маленькой ошибкой.

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

РЕДАКТИРОВАТЬ:

пеудокод моего растеризатора (учитываются только U, V и Z...если вы также хотите сделать Гуро, вам также придется сделать все для R G и B аналогично тому, что вы делаете для U, V и Z:

Идея в том, что треугольник можно разбить на две части.Верхняя часть и нижняя часть.Верхняя часть — от y[0] до y[1], а нижняя часть — от y[1] до y[2].Для обоих наборов вам необходимо вычислить переменные шага, с помощью которых вы выполняете интерполяцию.В примере ниже показано, как сделать верхнюю часть.При необходимости могу доставить и нижнюю часть.

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

  • сначала упорядочите координаты(x,y,z,u,v) в таком порядке, чтобы coord[0].y < coord[1].y < coord[2].y
  • Затем проверьте, идентичны ли какие-либо два набора координат (проверьте только x и y).Если да, то не рисуй
  • исключение:у треугольника плоская вершина?если да, то первый наклон будет бесконечным
  • исключение2:есть ли у треугольника плоское дно (да, у треугольников оно тоже может быть ;^)), тогда последний наклон тоже будет бесконечным
  • рассчитать 2 уклона (левую и правую сторону)
    leftDeltaX = (x[1] - x[0]) / (y[1]-y[0]) и rightDeltaX = (x[2] - x[0]) / (y[2]-y[0] )
  • вторая часть треугольника рассчитывается в зависимости от:если левая сторона треугольника теперь действительно находится слева (или ее нужно поменять местами)

фрагмент кода:

 if (leftDeltaX < rightDeltaX)
 {
      leftDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
      rightDeltaX2 = rightDeltaX
      leftDeltaU = (u[1]-u[0]) / (y[1]-y[0]) //for texture mapping
      leftDeltaU2 = (u[2]-u[1]) / (y[2]-y[1])
      leftDeltaV = (v[1]-v[0]) / (y[1]-y[0]) //for texture mapping
      leftDeltaV2 = (v[2]-v[1]) / (y[2]-y[1])
      leftDeltaZ = (z[1]-z[0]) / (y[1]-y[0]) //for texture mapping
      leftDeltaZ2 = (z[2]-z[1]) / (y[2]-y[1])
 }
 else
 {
      swap(leftDeltaX, rightDeltaX);
      leftDeltaX2 = leftDeltaX;
      rightDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
      leftDeltaU = (u[2]-u[0]) / (y[2]-y[0]) //for texture mapping
      leftDeltaU2 = leftDeltaU
      leftDeltaV = (v[2]-v[0]) / (y[2]-y[0]) //for texture mapping
      leftDeltaV2 = leftDeltaV
      leftDeltaZ = (z[2]-z[0]) / (y[2]-y[0]) //for texture mapping
      leftDeltaZ2 = leftDeltaZ
  }
  • установите currentLeftX и currentRightX на x[0]
  • установите currentLeftU на leftDeltaU, currentLeftV на leftDeltaV и currentLeftZ на leftDeltaZ
  • вычислить начальную и конечную точку для первого диапазона Y:startY = ячейка (y[0]);endY = ячейка(y[1])
  • Prestep x, u, v и z для дробной части y для точности субпикселя (я думаю, это также необходимо для поплавков) для моих алгоритмов фиксированной точки. разрешение дисплея)
  • вычислите, где x должен находиться в точке y[1]:Halfwayx = (x [2] -x [0]) * (y [1] -y [0]) / (y [2] -y [0]) + x [0] и то же самое для U и V и z :на полпутиU = (u[2]-u[0]) * (y[1]-y[0]) / (y[2]-y[0]) + u[0]
  • и, используя halfwayX, рассчитаем степпер для U, V и z:if(halfwayX - x[1] == 0){ наклонU=0, наклонV=0, наклонZ=0 } else { наклонU = (halfwayU - U[1]) / (halfwayX - x[1])} //( и то же самое для v и z)
  • сделайте обрезку вершины Y (поэтому рассчитайте, где мы собираемся начать рисовать, если вершина треугольника находится за пределами экрана (или за пределами прямоугольника отсечения))
  • для y=startY;у <конецY;y ++) {
    • Y находится за нижней частью экрана?хватит рендерить!
    • Calc startx и endx для первой горизонтальной линии Leatscurx = ceil (startx);leftCurY = ячейка (конец);
    • обрезать линию, которую нужно нарисовать, до левой горизонтальной границы экрана (или области отсечения)
    • Подготовьте указатель к буферу назначения (выполнять его с помощью индексов массива каждый раз слишком медленно) без знака Int buf = destbuf + (yшаг) + startX;(Unsigned int, если вы делаете 24 -битный или 32 бита рендеринга) также подготовите указатель Zbuffer здесь (если вы используете это)
    • для (х = startX;х <конецX;x ++) {
      • теперь для наложения перспективных текстур (без билинейной интерполяции вы делаете следующее):

фрагмент кода:

         float tv = startV / startZ
         float tu = startU / startZ;
         tv %= texturePitch;  //make sure the texture coordinates stay on the texture if they are too wide/high
         tu %= texturePitch;  //I'm assuming square textures here. With fixed point you could have used &=
         unsigned int *textPtr = textureBuf+tu + (tv*texturePitch);   //in case of fixedpoints one could have shifted the tv. Now we have to multiply everytime. 
         int destColTm = *(textPtr);  //this is the color (if we only use texture mapping)  we'll be needing for the pixel
  • фиктивная линия
    • фиктивная линия
      • фиктивная линия
      • необязательный:проверьте zbuffer, если ранее нанесенный пиксель по этой координате выше или ниже нашего.
      • построить пиксель
      • началоZ += наклонZ;startU+=slopeU;началоV += наклонV;//обновляем все интерполяторы
    • } конец цикла x
    • leftCurX+= leftDeltaX;rightCurX += rightDeltaX;leftCurU+= rightDeltaU;leftCurV += rightDeltaV;leftCurZ += rightDeltaZ;//обновляем интерполяторы Y
  • } конец цикла y

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

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

дайте мне знать, если это поможет вам решить проблему, с которой вы столкнулись!

Другие советы

Не знаю, смогу ли я помочь с вашим вопросом, но одна из лучших книг по программному рендерингу, которую я когда-либо читал, доступна в Интернете. Черная книга графического программирования Майкл Абраш.

Если вы интерполируете 1/z, вам нужно умножить UV/z к z, нет 1/z.Предполагая, что у вас есть это:

UV = UV_current * z_current

и z_current интерполирует 1/z, вам следует изменить его на:

UV = UV_current / z_current

И тогда вы можете переименовать z_current к чему-то вроде one_over_z_current.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top