Перспективное правильное наложение текстуры; Расчет расстояния z может быть неправильным - PullRequest
6 голосов
/ 15 января 2010

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

Мой алгоритм - сначала отсортировать координаты для построения по 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?

Ответы [ 3 ]

4 голосов
/ 18 января 2010

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

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

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

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

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

EDIT:

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

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

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

  • сначала упорядочьте координаты (x, y, z, u, v) в таком порядке, чтобы координируйте [0] .y
  • следующая проверка, если любые 2 набора координат идентичны (только отметьте 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 = ceil (y [0]); endY = ceil (y [1])
  • предварительный шаг 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: halfwayU = (u [2] -u [0]) * (y [1] -y [0]) / (y [2] -y [0]) + u [0 ]
  • и с помощью halfwayX вычисляем шагер для U и V и z: if (halfwayX - x [1] == 0) {slopeU = 0, slopeV = 0, slopeZ = 0} else {slopeU = (halfwayU - U [1]) / (halfwayX - x [1])} // ( и то же самое для v и z)
  • сделать отсечение для вершины Y (поэтому вычислите, где мы собираемся начать рисовать, если верх треугольника находится за пределами экрана (или за пределами прямоугольника отсечения))
  • для y = startY; y
  • Y находится за нижней частью экрана? прекратить рендеринг!
  • calc startX и endX для первой горизонтальной линии leftCurX = ceil (startx); leftCurY = ceil (endy);
  • обрезать линию, которую нужно провести, к левой горизонтальной границе экрана (или области отсечения)
  • подготовить указатель на буфер назначения (выполнение его с помощью индексов массива каждый раз слишком медленно) без знака int buf = destbuf + (y pitch) + startX; (unsigned int в случае, если вы делаете 24-битный или 32-битный рендеринг) также подготовьте свой указатель ZBuffer здесь (если вы используете это)
  • для (x = startX; 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, если ранее построенный пиксель с этой координатой выше или ниже нашего.
      • построить пиксель
      • startZ + = slopeZ; startU + = slopeU; startV + = slopeV; // обновляем все интерполяторы
    • } конец цикла x
    • leftCurX + = leftDeltaX; rightCurX + = rightDeltaX; leftCurU + = rightDeltaU; leftCurV + = rightDeltaV; leftCurZ + = rightDeltaZ; // обновляем Y-интерполяторы
  • } конец цикла y

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

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

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

0 голосов
/ 20 января 2010

Если вы интерполируете 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.

0 голосов
/ 15 января 2010

Я не знаю, смогу ли я помочь с вашим вопросом, но одна из лучших книг по рендерингу программного обеспечения, которую я читал в то время, доступна онлайн Черное книжное программирование Майкла Абраш. 1003 *

...