Я пытаюсь реализовать простой рендерер OpenGL, имитирующий модель камеры-обскуры.
Стандартная матрица перспективной проекции уже реализует модель камеры-обскуры.То, что вы делаете здесь, это просто большее количество вычислений для каждой вершины, которые могут быть предварительно рассчитаны на ЦП и помещены в одну матрицу.
Единственное отличие - диапазон z
.Но у «камеры-обскуры» нет диапазона z
, все точки проецируются на плоскость изображения.Здесь вам нужна модель камеры-обскуры для x
и y
, а также линейное отображение для z
.
Однако ваша реализация неверна.Графический процессор будет интерполировать z
линейно в пространстве окна .Это означает, что он будет вычислять барицентрические координаты каждого фрагмента относительно 2D-проекции треугольника окна.Однако, когда используется перспективная проекция, и когда треугольник не расположен параллельно плоскости изображения, эти барицентрические координаты не будут такими, как у соответствующей трехмерной точки относительно фактического трехмерного примитива допроекция.
Хитрость в том, что поскольку в экранном пространстве у нас обычно есть x/z
и y/z
в качестве координат вершины, и когда мы интерполируем линейно между ними, мы также должны интерполировать 1/z
дляглубина.Однако в действительности мы делим не z
, а w
(и пусть матрица проекции задает для нас w_clip = [+/-]z_eye
).После деления на w_clip
мы получаем гиперболическое отображение значения z
, но с хорошим свойством, что оно может быть линейно интерполировано в пространстве окна.
Что это значитв том, что при использовании линейного отображения z
ваши примитивы теперь должны были бы быть bend вдоль измерения z
, чтобы получить правильный результат.Посмотрите на следующий сверху вниз вид ситуации.«Линии» представляют собой плоские треугольники, если смотреть прямо сверху:
В глазном пространстве все лучи вида будут проходить от источника через каждый пиксель(мы могли бы представить, например, растр 2D-пикселей на ближней плоскости).В НДЦ мы преобразовали это в ортогональную проекцию .Пиксели все еще можно представить в ближней плоскости, но все лучи обзора теперь параллельны .
В стандартном гиперболическом отображении точка в середине усеченного конуса сильно сжимается кконец.Тем не менее, трэнгл все еще плоский.
Если вместо этого вы используете линейное отображение, ваш треугольник больше не должен быть плоским.Посмотрите, например, на точку пересечения между двумя traingles.Для правильного результата он должен иметь ту же x
(и y
) координату, что и в гиперболическом случае.
Однако вы преобразуете вершины только в соответствии с линейным значением z, графический процессор все равно будетлинейно интерполировать результат, так что в вашем случае вы получите прямые связи между вашими преобразованными точками, ваша точка пересечения между двумя треугольниками будет перемещена, а все значения глубины будут неправильными, за исключением самих фактических точек вершины.
Если вы хотите использовать буфер линейной глубины, вы должны скорректировать глубину каждого фрагмента в шейдере фрагментов, чтобы реализовать необходимую нелинейную интерполяцию самостоятельно.Это может нарушить многие хитрые оптимизации тестирования глубины, которые делают графические процессоры, особенно ранние Z и иерархические Z, поэтому, хотя это возможно, вы потеряете некоторую производительность.
Гораздо лучшее решение: просто используйте стандартное значение гиперболической глубины.Просто линеаризуйте значения глубины после того, как вы прочитаете их обратно.Также не делайте z
деление в вершинном шейдере.Вы не только нарушаете z
таким образом, вы также нарушаете интерполяцию вариаций с поправкой на перспективу, поэтому ваше затенение также будет неправильным.Позвольте GPU выполнить деление, просто перетасуйте правильное значение в gl_Position.w
.Внутри GPU будет не только делить, интерполяция с коррекцией перспективы также зависит от w
.