Как именно OpenGL делает перспективно правильную линейную интерполяцию?

Если линейная интерполяция происходит на этапе растеризации в конвейере OpenGL, а вершины уже преобразованы в пространство экрана, откуда берется информация о глубине, используемая для перспективно правильной интерполяции?

может ли кто-нибудь дать подробное описание того, как OpenGL переходит от примитивов экрана к фрагментам с правильно интерполированными значениями?

2 ответов


выход шейдера вершин равен четыре векторная компонента, vec4 gl_Position. Из раздела 13.6 координатные преобразования core GL 4.4 spec:

координирует клип для результата вершины от выполнения шейдера, который дает вершину координата gl_Position.

перспективное деление на координаты клипа дает нормализованные координаты устройства, затем просмотра трансформация (см. раздел 13.6.1) для преобразования этих координат в окно с координатами.

рассмотрим типичную проекционную матрицу с левой, верхней, правой, нижней и ближней плоскостями отсечения ± 1. Это выглядит так:

    [ 1  0  0  0 ]
    [ 0  1  0  0 ]
P = [ *  *  *  * ]
    [ 0  0 -1  0 ]

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

учитывая вершину (x, y, z, 1) в мировом пространстве, вершина шейдер передаст значение

gl_Position = P * vec4(x, y, z, 1) = vec4(x, y, *, -z)

на этапе растеризации. Вот откуда берется перспективная информация: она находится в последнем компоненте gl_Position! Обратите внимание, что вершинный шейдер не несет ответственности за разделение перспективы. Если вы это сделаете, вы получите неправильную интерполяцию.

как вычисляется правильная перспективная интерполяция?

давайте рассмотрим плоскость, проходящую через данный треугольник. Мы даем ему параметризацию (s, t):

x = s*x0 + t*x1 + x2
y = s*y0 + t*y1 + y2
z = s*z0 + t*z1 + z2

пусть u = -x/z, v = -y/z быть координатами устройства. Подставим выражения для x, y,z и решим для (s, t). Обозначим w = -1/z и заменить z, s и t. Если ни один из нас не ошибся, вы должны получить

            (y0*z1 - z0*y1)*u + (z0*x1 - x0*z1)*v + (x0*y1 - y0*x1)
w = -1 * -------------------------------------------------------------- .
          (y0*z1 - z0*y1)*x2 + (z0*x1 - x0*z1)*y2 + (x0*y1 - y0*x1)*z2

мы получили, что обратная глубина (w) аффинна по отношению к координатам устройства (u,v). Таким образом, мы можем вычислить w в вершинах треугольника и интерполировать его линейно внутри.

Далее, мы хотим интерполируйте некоторый произвольный атрибут. Очевидно, что достаточно вычислить параметризацию (s,t) для каждого фрагмента.

решение (x, y) для (s, t) получаем:

     (x - x2)*y1 - x1*(y - y2)
s = ---------------------------
           x0*y1 - x1*y0

     x0*(y - y2) - (x - x2)*y0
t = ---------------------------
           x0*y1 - x1*y0

здесь (x,y) = (u,v)/w непосредственно из определений. Таким образом,мы выражали (s,t) как функцию (u, v), с одним делением на фрагмент и множеством мулов и добавлений.

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

все вместе

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

  1. рассмотрим растеризацию одного треугольника, для которого вершинный шейдер вернул позиции P0, P1, P2 (это их gl_Positions) и некоторые атрибуты A0, A1, A2, которые мы должны правильно интерполировать.

  2. обозначения Qi = (Pi.x, Pi.y, -Pi.w).

  3. вычислить

    (x0, y0, z0) = Q0 - Q2
    (x1, y1, z1) = Q1 - Q2
    (x2, y2, z2) = Q2 .
    

    (это делается один раз в треугольник.)

  4. для каждой вершины, вычислить

    Ui = Pi.x / Pi.w
    Vi = Pi.y / Pi.w
    

    это дает свое положение на экране.

  5. Растрировать в 2D треугольник с vetices (как U0, v0 в), (У1, У1), (У2, В2), производит набор фрагментов позиции (U,в), которые лежат в треугольник.

  6. для каждого фрагмента (u, v), произведенного вышеуказанной растеризацией, выполните следующие действия:

    1. заменить (u,v) в формулах в предыдущем разделе, давая (s,t) для фрагмента.

      • (ы,т) различаются linearily в мировое пространство (с (0,0) на Р2 (1,0) при P0, и (0,1) при цене P1), и перспективно правильной на экране пространство.
    2. вычислить

      A = s*(A0 - A2) + t*(A1 - A2) + A2
      
    3. выполните шейдер фрагмента с вводом A (который правильно интерполирован).


формула, которую вы найдете в папке GL спецификация (посмотрите на стр. 427; ссылка-текущая спецификация 4.4, но она всегда была такой) для интерполяции с поправкой на перспективу значения атрибута в треугольнике:

   a * f_a / w_a   +   b * f_b / w_b   +  c * f_c / w_c
f=-----------------------------------------------------
      a / w_a      +      b / w_b      +     c / w_c

здесь a,b,c обозначим барицентрические координаты точки в треугольнике, для которой мы интерполируем (a,b,c >=0, a+b+c = 1),f_i значение атрибута в вершине i и w_i клип пробел w координат вершина i. Обратите внимание, что барицентрические координаты вычисляются только для 2D-проекции координат пространства окна треугольника (поэтому z игнорируется).

вот к чему сводятся формулы, которые ибунгалоубилл дал в своем прекрасном ответе, в общие case, с произвольной осью проекции. Фактически, последняя строка матрицы проекции определяет только ось проекции, плоскость изображения будет ортогональна, а пространство клипа w компонент только точечное произведение между вершинными координатами и этой осью.

в типичном случае, матрица прогноза -1,0 (0,0), а в последнем ряду, так что transfroms так что w_clip = -z_eye, и это то, что использовал ybungalowbill. Однако, поскольку w это то, что мы фактически сделаем деление (это единственный нелинейный шаг во всей цепочке преобразований), это будет работать для любой оси проекции. Он также будет работать в тривиальном случае ортогональных проекций, где w всегда 1 (или, по крайней мере, постоянно).

  1. обратите внимание на несколько вещей для эффективной реализации этого. Инверсия 1/w_i можно предварительно вычислить на вершину (назовем их q_i в следующем), он не должен быть повторно оценен на фрагмент. И это совершенно бесплатно, так как мы делим на w во всяком случае, при переходе в пространство NDC, поэтому мы можем сохранить это значение. Спецификация GL никогда не описывает, как определенная функция должна быть реализована внутри, но тот факт, что координаты пространства экрана будут доступны в glFragCoord.xyz и gl_FragCoord.w гарантированно дает (lineariliy интерполируется)1/w космический клип координата здесь довольно показательна. Это за-фрагмент 1_w значение фактически является знаменателем приведенной выше формулы.

  2. факторы a/w_a, b/w_b и c/w_c каждый используется два раза в Формуле. И они также постоянны для любого значения атрибута, теперь имеет значение, сколько атрибутов там должны быть интерполированы. Итак, за фрагмент вы можете вычислить a'=q_a * a, b'=q_b * b и c'=q_c и вам

      a' * f_a + b' * f_b + c' * f_c
    f=------------------------------
               a' + b' + c'
    

таким образом, перспективная интерполяция сводится к

  • 3 дополнительных умножений,
  • 2 дополнительных дополнения и
  • 1 дополнительное подразделение

на фрагмент.