Мне нужно написать собственный программный 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? Потому что сейчас все работает в любом случае. (нет заметной разницы).
источник
Ответы:
Быстрый ответ: 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. Как и должно быть.
Да, математика потрясающая.
источник