Попытка понять математику за перспективной матрицей в WebGL

все библиотеки матриц для WebGL имеют своего рода perspective функция, которую вы вызываете, чтобы получить матрицу перспективы для сцены.
Например,perspective способ в часть gl-matrix кодируется как такой:

mat4.perspective = function (out, fovy, aspect, near, far) {
    var f = 1.0 / Math.tan(fovy / 2),
        nf = 1 / (near - far);
    out[0] = f / aspect;
    out[1] = 0;
    out[2] = 0;
    out[3] = 0;
    out[4] = 0;
    out[5] = f;
    out[6] = 0;
    out[7] = 0;
    out[8] = 0;
    out[9] = 0;
    out[10] = (far + near) * nf;
    out[11] = -1;
    out[12] = 0;
    out[13] = 0;
    out[14] = (2 * far * near) * nf;
    out[15] = 0;
    return out;
};

я действительно пытаюсь понять, что на самом деле делает вся математика в этом методе, но я спотыкаюсь на нескольких моментах.

для начала, если у нас есть холст следующим образом с аспектом соотношение 4:3, то aspect параметр метода фактически будет 4 / 3, верно?

4:3 aspect ratio

я также заметил, что 45° кажется общим полем зрения. Если это так, то

2 ответов


давайте посмотрим, смогу ли я объяснить это, или, может быть, после прочтения этого вы можете придумать лучший способ объяснить это.

первое, что нужно понять, это то, что WebGL требует координат clipspace. Они идут -1 +1 в x, y и z. Таким образом, перспективная матрица в основном предназначена для размещения пространства внутри усеченного и преобразовать его в clipspace.

если вы посмотрите на эту диаграмму

frustum-side

мы знаем, что тангенс = напротив(y) над смежным (z), поэтому, если мы знаем z, мы можем вычислить y, который будет сидеть на краю разочарования для данного fovY.

tan(fovY / 2) = y / -z

умножьте обе стороны на-z

y = tan(fovY / 2) * -z

если мы определяем

f = 1 / tan(fovY / 2)

мы

y = -z / f

обратите внимание, что мы не сделали преобразование из cameraspace в clipspace. Все, что мы сделали, это вычислить y на краю поля зрения для данного z в cameraspace. На краю поля зрения края clipspace. Поскольку clipspace это просто +1 до -1, мы можем просто разделить cameraspace г по -z / f чтобы получить clipspace.

это имеет смысл? Посмотрите на диаграмму еще раз. Предположим, что синий z был -5 и для некоторого заданного поля зрения до +2.34. Нам нужно преобразовать +2.34 к +1 clipspace. Общая версия этого -

clipY = cameraY * f / -z

глядя на 'makePerspective'

function makePerspective(fieldOfViewInRadians, aspect, near, far) {
  var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
  var rangeInv = 1.0 / (near - far);

  return [
    f / aspect, 0, 0, 0,
    0, f, 0, 0,
    0, 0, (near + far) * rangeInv, -1,
    0, 0, near * far * rangeInv * 2, 0
  ];
};

мы видим, что f в этом случае

tan(Math.PI * 0.5 - 0.5 * fovY)

что на самом деле то же самое, что

1 / tan(fovY / 2)

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

видя, что -1 находится в matrix[11] spot означает, когда мы все сделал

matrix[5]  = tan(Math.PI * 0.5 - 0.5 * fovY)
matrix[11] = -1

clipY = cameraY * matrix[5] / cameraZ * matrix[11]

For clipX мы в основном делаем тот же расчет только увеличить пропорции.

matrix[0]  = tan(Math.PI * 0.5 - 0.5 * fovY) / aspect
matrix[11] = -1

clipX = cameraX * matrix[0] / cameraZ * matrix[11]

наконец, мы должны преобразовать cameraZ в-zNear - zFar диапазон в clipZ в -1 + 1 диапазон.

стандартная перспективная матрица делает это с помощью as взаимные функции чтобы значения z закрывали камеру, получите больше разрешения, чем значения z вдали от камеры. Эта формула is

clipZ = something / cameraZ + constant
давайте s на something и c для постоянного.
clipZ = s / cameraZ + c;

и решить для s и c. В нашем случае мы знаем!--58-->

s / -zNear + c = -1
s / -zFar  + c =  1

Итак, переместите " c " на другую сторону

s / -zNear = -1 - c
s / -zFar  =  1 - c

умножить на-zXXX

s = (-1 - c) * -zNear
s = ( 1 - c) * -zFar

эти 2 вещи равны друг другу так

(-1 - c) * -zNear = (1 - c) * -zFar

расширить количество

(-zNear * -1) - (c * -zNear) = (1 * -zFar) - (c * -zFar)

упростить

zNear + c * zNear = -zFar + c * zFar

переместить zNear справа

c * zNear = -zFar + c * zFar - zNear

движение c * zFar налево

c * zNear - c * zFar = -zFar - zNear

упростить

c * (zNear - zFar) = -(zFar + zNear)

разделить на (zNear - zFar)

c = -(zFar + zNear) / (zNear - zFar)

решить s

s = (1 - -((zFar + zNear) / (zNear - zFar))) * -zFar

упростить

s = (1 + ((zFar + zNear) / (zNear - zFar))) * -zFar

изменить 1 до (zNear - zFar)

s = ((zNear - zFar + zFar + zNear) / (zNear - zFar)) * -zFar

упростить

s = ((2 * zNear) / (zNear - zFar)) * -zFar

упростите еще немного

s = (2 * zNear * zFar) / (zNear - zFar)

dang я хочу, чтобы stackexchange поддерживал математику, как их математический сайт делает : (

Итак, вернемся к началу. Наш форумла был

s / cameraZ + c

и мы знаем!--41--> и c сейчас.

clipZ = (2 * zNear * zFar) / (zNear - zFar) / -cameraZ -
        (zFar + zNear) / (zNear - zFar)

давайте переместим-z снаружи

clipZ = ((2 * zNear * zFar) / zNear - ZFar) +
         (zFar + zNear) / (zNear - zFar) * cameraZ) / -cameraZ

мы можем изменить / (zNear - zFar) to * 1 / (zNear - zFar) так

rangeInv = 1 / (zNear - zFar)
clipZ = ((2 * zNear * zFar) * rangeInv) +
         (zFar + zNear) * rangeInv * cameraZ) / -cameraZ

оглядываясь на makeFrustum мы видим, что это в конечном итоге делает

clipZ = (matrix[10] * cameraZ + matrix[14]) / (cameraZ * matrix[11])

глядя на Формулу выше, которая подходит

rangeInv = 1 / (zNear - zFar)
matrix[10] = (zFar + zNear) * rangeInv
matrix[14] = 2 * zNear * zFar * rangeInv
matrix[11] = -1
clipZ = (matrix[10] * cameraZ + matrix[14]) / (cameraZ * matrix[11])

я надеюсь, что имело смысл. Примечание: большая часть этого-просто мое переписывание в этой статье.


f - Это коэффициент, который масштабирует ось y, так что все точки вдоль верхней плоскости вашего фруста просмотра, пост-перспективного деления, имеют координату y 1, а те, что на нижней плоскости имеют координату y -1. Попробуйте подключить точки вдоль одной из этих плоскостей (примеры:0, 2.41, 1, 2, 7.24, 3) и вы можете видеть, почему это происходит: потому что это заканчивается предварительным делением y, равным однородному w.