Глубинная интерполяция для z-буфера со сканирующей линией

10

Мне нужно написать собственный программный 3d растеризатор, и пока я могу проецировать свою 3d модель из треугольников в 2d пространство:

Я вращаю, перемещаю и проецирую свои точки, чтобы получить двухмерное представление каждого треугольника. Затем я беру 3 точки треугольника и реализую алгоритм линии сканирования (используя линейную интерполяцию), чтобы найти все точки [x] [y] по краям (слева и справа) треугольников, чтобы я мог сканировать треугольник по горизонтали, строка за строкой, и заполните его пикселями.

Это работает. Кроме того, я должен также реализовать z-буферизацию. Это означает, что, зная повернутые и переведенные z-координаты 3-х вершин треугольника, я должен интерполировать z-координату для всех остальных точек, которые я найду с помощью моего алгоритма линии сканирования.

Концепция кажется достаточно ясной, я сначала нахожу Za и Zb с этими вычислениями:

var Z_Slope = (bottom_point_z - top_point_z) / (bottom_point_y - top_point_y);
var Za = top_point_z + ((current_point_y - top_point_y) * Z_Slope);

Затем для каждого Zp я делаю ту же интерполяцию по горизонтали:

var Z_Slope = (right_z - left_z) / (right_x - left_x);
var Zp = left_z + ((current_point_x - left_x) * Z_Slope);

введите описание изображения здесь

И если текущий z ближе к зрителю, чем предыдущий z с этим индексом, ТО записывает цвет в буфер цвета И записывает новый z в буфер z. (моя система координат: x: слева -> справа; y: сверху -> снизу; z: ваше лицо -> экран компьютера;)

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

PS: я читал здесь, что вы должны превратить z в их взаимные (смысл z = 1/z), прежде чем интерполировать. Я попробовал это, и кажется, что нет никаких изменений. Что мне не хватает? (кто-нибудь может уточнить, где именно вы должны превратить z в 1 / z и где (если), чтобы повернуть его обратно?)

[РЕДАКТИРОВАТЬ] Вот некоторые данные о том, какие максимальные и минимальные значения z я получаю:

    max z: 1;                  min z: -1;                 //<-- obvious, original z of the vertices of the triangles
    max z: 7.197753398761272;  min z: 3.791703256899924;   //<-- z of the points that were drawn to screen (you know, after rotation, translation), by the scanline with zbuffer, gotten with interpolation but not 1/z.
    max z: 0.2649908532179404; min z: 0.13849507306889008;//<-- same as above except I interpolated 1/z instead of z.
//yes, I am aware that changing z to 1/z means flipping the comparison in the zBuffer check. otherwise nothing gets drawn.

Прежде чем приступить к кропотливой отладке, может ли кто-нибудь подтвердить, что моя концепция до сих пор верна?

[EDIT2]

Я решил z-буферизацию. Как выяснилось, порядок рисования совсем не испортился. Координаты z рассчитывались правильно.

Проблема заключалась в том, что, пытаясь увеличить частоту кадров, я рисовал прямоугольники 4px / 4px, каждый 4-й пиксель, вместо реальных пикселей на экране. Поэтому я рисовал 16 пикселей на пиксель, но проверял буфер z только для одного из них. Я такая сиська.

TL / DR: Вопрос все еще стоит: как / почему / когда вы должны использовать обратную величину Z (как в 1 / z) вместо Z? Потому что сейчас все работает в любом случае. (нет заметной разницы).

Spectraljump
источник
Re: «И, конечно, я добавляю в zBuffer, если текущий z ближе к зрителю, чем предыдущее значение в этом индексе.» Мне не ясно, что вы сформулировали это так, как вы хотели, но хотели убедиться, что то, что вы имели в виду, «если текущий z ближе к зрителю, чем предыдущий z с этим индексом, ТО записывает цвет в буфер цвета И записывает new z to z buffer "Цель буфера z состоит в том, чтобы блокировать запись цвета, если цвет в этом пикселе уже был записан ближе к глазу камеры.
Alturis
Это правильно. Извините, было поздно, когда я сформулировал свой вопрос. Я буду пересматривать.
Spectraljump

Ответы:

5

Быстрый ответ: Z не является линейной функцией (X ', Y'), но 1 / Z есть. Поскольку вы интерполируете линейно, вы получите правильные результаты для 1 / Z, но не для Z.

Вы не замечаете, потому что, если сравнение между Z1 и Z2 является правильным, zbuffer будет делать правильные вещи, даже если оба значения неверны. Вы обязательно заметите, когда добавите наложение текстур (и чтобы ответить на вопрос, который у вас будет тогда: интерполируйте 1 / Z, U / Z и V / Z, и восстановите U и V из этих значений: U = (U / Z) / (1 / Z), V = (V / Z) / (1 / Z). Вы будете благодарить меня позже)

Пример. Получите лист бумаги. Вид сверху вниз, поэтому забудьте координату Y. X - горизонтальная ось, Z - вертикальная ось, камера в положении (0, 0), плоскость проекции - z = 1.

Рассмотрим точки A (-2, 2) и B (2, 4). Средняя точка M отрезка AB равна (0, 3). Все идет нормально.

Вы проецируете A в A ': X' = X / Z = -1, поэтому A 'равно (-1, 1). Аналогично, B 'представляет собой (0,5, 1). Но обратите внимание, что проекция M равна (0, 1), которая НЕ является средней точкой A'B '. Почему? Поскольку правая половина сегмента находится дальше от камеры, чем левая, поэтому она выглядит меньше.

Так что же произойдет, если вы попытаетесь вычислить Z of M ', используя линейную интерполяцию? dx = (0,5 - -1) = 1,5, dz = (4 - 2) = 2, поэтому для M ', где X' = 0, линейно интерполированный Z равен zA + (dz / dx) (x - xA) = 2 + (2 / 1,5) (0 - -1) = 2 + 1,333 = 3,3333 - НЕ 3!

Почему? Потому что для каждого шага в направлении X 'вы не перемещаете одну и ту же величину в направлении Z (или, другими словами, Z не является линейной функцией X'). Почему? Поскольку чем больше вы идете направо, тем дальше сегмент находится от камеры, поэтому один пиксель представляет большее расстояние в пространстве.

Наконец, что произойдет, если вы вместо этого интерполируете 1 / Z? Сначала вы вычисляете 1 / Z для A и B: 0,5 и 0,25 соответственно. Затем вы интерполируете: dx = (0,5 - -1) = 1,5, dz = (0,25 - 0,5) = -0,25, поэтому при X '= 0 вы вычисляете 1 / Z = 0,5 + (-0,25 / 1,5) * (0 - -1) = 0,3333. Но это 1 / Z, поэтому значение Z ... точно, 3. Как и должно быть.

Да, математика потрясающая.

ggambett
источник
1
Да, и относительно «когда»: вычислите значения 1 / Z перед началом растеризации треугольника (например, непосредственно перед вертикальным циклом), чтобы вы получили интерполированное 1 / Z слева и справа от линии сканирования. Интерполируйте их линейно (НЕ делайте 1 / Z снова - интерполированные значения уже равны 1 / Z!) И отмените преобразование непосредственно перед проверкой zbuffer.
ggambett
1
И наконец, почему. Плоскость (в которую встроен треугольник) имеет вид Ax + By + Cz + D = 0. z является явно линейной функцией (x, y). Вы проецируете так x '= x / z и y' = y / z. Оттуда x = x'z и y = y'z. Если вы замените их в исходном уравнении, вы получите Ax'z + By'x + Cz + D = 0. Теперь z = -D / (Ax '+ By' + C), где ясно, что z не является линейной функцией из (х ', у'). Но 1 / z, следовательно, (Ax '+ By' + C) / -D, что является линейной функцией от (x ', y').
ggambett
1
Вы знаете, я прочитал довольно много статей и курсов, и ни одна из них не была настолько ясна, как ваш ответ. Для потомков отмечу, что «Буквами« U »и« V »обозначены оси 2D-текстуры, поскольку« X »,« Y »и« Z »уже используются для обозначения осей 3D-объекта в модели. пространство. Ультрафиолетовое текстурирование позволяет многоугольникам, которые составляют трехмерный объект, окрашиваться цветом из изображения. " - Википедия - УФ-картирование
Spectraljump
Рад слышать. Я преподавал компьютерную графику в прошлой жизни :)
ggambett
Большое спасибо - мне всегда было интересно об этом - и я не знаю, нашел бы я когда-нибудь лучший ответ! +1
Codesmith