Подтвердить что ты не робот

Как рассчитать Тангенс и Бинормаль?

Говоря о отображении рельефа, зеркальной подсветке и таких вещах в OpenGL Shading Language (GLSL)

У меня есть:

  • Массив вершин (например, {0,2,0,5,0,1, 0,2,0,4,0,5,...})
  • Массив нормалей (например, {0,0,0,0,1,0, 0,0,1,0,0,0,...})
  • Положение точечного света в мировом пространстве (например, {0.0,1.0, -5.0})
  • Позиция зрителя в мировом пространстве (например, {0.0,0.0,0.0}) (предположим, что зритель находится в центре мира)

Теперь, как я могу вычислить Binormal и Tangent для каждой вершины? Я имею в виду, какова формула для расчета бинормалов, что я должен использовать на основе этих данных? А о касательной?

Я все равно построю матрицу TBN, поэтому, если вы знаете, что формула для построения матрицы, основанная на этой информации, будет приятной!

О, yeh, у меня есть координаты текстуры, если это необходимо. И поскольку я говорю о GLSL, было бы неплохо для решения на вершину, я имею в виду, который не должен получать доступ к более чем одной информации о вершинах за раз.

---- Обновление -----

Я нашел это решение:

vec3 tangent;
vec3 binormal;

vec3 c1 = cross(a_normal, vec3(0.0, 0.0, 1.0));
vec3 c2 = cross(a_normal, vec3(0.0, 1.0, 0.0));

if (length(c1)>length(c2))
{
    tangent = c1;
}
else
{
    tangent = c2;
}

tangent = normalize(tangent);

binormal = cross(v_nglNormal, tangent);
binormal = normalize(binormal);

Но я не знаю, правильно ли это на 100%.

4b9b3361

Ответ 1

Соответствующими входными данными для вашей проблемы являются координаты текстуры. Тангенциальные и бинормальные являются векторами, локально параллельными поверхности объекта. А в случае нормального отображения они описывают локальную ориентацию нормальной текстуры.

Итак, вам нужно рассчитать направление (в пространстве модели), в котором указывают векторы текстурирования. Скажем, у вас треугольник ABC, с координатами текстуры HKL. Это дает нам векторы:

D = B-A
E = C-A

F = K-H
G = L-H

Теперь мы хотим выразить D и E в терминах касательного пространства T, U, i.e.

D = F.s * T + F.t * U
E = G.s * T + G.t * U

Это система линейных уравнений с 6 неизвестными и 6 уравнениями, ее можно записать в виде

| D.x D.y D.z |   | F.s F.t | | T.x T.y T.z |
|             | = |         | |             |
| E.x E.y E.z |   | G.s G.t | | U.x U.y U.z |

Инвертирование матрицы FG дает

| T.x T.y T.z |           1         |  G.t  -F.t | | D.x D.y D.z |
|             | = ----------------- |            | |             |
| U.x U.y U.z |   F.s G.t - F.t G.s | -G.s   F.s | | E.x E.y E.z |

Вместе с вершиной нормаль T и U образуют базис локального пространства, называемый касательным пространством, описываемый матрицей

| T.x U.x N.x |
| T.y U.y N.y |
| T.z U.z N.z |

Преобразование из касательного пространства в пространство объектов. Для расчета освещения нужно обратное. С небольшим количеством упражнений можно найти:

T' = T - (N·T) N
U' = U - (N·U) N - (T'·U) T'

Нормализуя векторы T 'и U', называя их касательными и бинормальными, получаем матрицу, преобразующуюся из объекта в касательное пространство, где мы делаем освещение:

| T'.x T'.y T'.z |
| U'.x U'.y U'.z |
| N.x  N.y  N.z  |

Мы сохраняем T 'и U' вместе с нормальной вершиной как часть геометрии модели (как атрибуты вершин), так что мы можем использовать их в шейдере для расчета освещения. Повторяю: вы не определяете тангенс и бинормаль в шейдере, вы их предварительно компрометируете и сохраняете как часть геометрии модели (как и нормали).

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

Ответ 2

Как правило, у вас есть два способа генерации матрицы TBN: автономный и он-лайн.

  • Он-лайн= справа в шейдере фрагмента с использованием производных инструкций. Эти выводы дают вам плоскую основу TBN для каждой точки многоугольника. Чтобы получить гладкий, мы должны повторно ортогонализировать его на основе заданной (гладкой) вершинной нормали. Эта процедура еще более тяжела для GPU, чем первоначальная экстракция TBN.

    // compute derivations of the world position
    vec3 p_dx = dFdx(pw_i);
    vec3 p_dy = dFdy(pw_i);
    // compute derivations of the texture coordinate
    vec2 tc_dx = dFdx(tc_i);
    vec2 tc_dy = dFdy(tc_i);
    // compute initial tangent and bi-tangent
    vec3 t = normalize( tc_dy.y * p_dx - tc_dx.y * p_dy );
    vec3 b = normalize( tc_dy.x * p_dx - tc_dx.x * p_dy ); // sign inversion
    // get new tangent from a given mesh normal
    vec3 n = normalize(n_obj_i);
    vec3 x = cross(n, t);
    t = cross(x, n);
    t = normalize(t);
    // get updated bi-tangent
    x = cross(b, n);
    b = cross(n, x);
    b = normalize(b);
    mat3 tbn = mat3(t, b, n);
    
  • Off-line= подготовить тангенс как атрибут вершины. Это сложнее получить, потому что это не просто добавит другую атрибут вершины, но также потребует перекомпоновать все остальные атрибуты. Более того, он не будет на 100% давать вам лучшую производительность, так как вы получите дополнительную стоимость хранения/передачи/анимации (!) Атрибута vertex vector3.

Математика описывается во многих местах (google it), включая сообщение @datenwolf.

Проблема состоит в том, что две вершины могут иметь одну и ту же нормальную и текстурную координаты, но разные касательные. Это означает, что вы не можете просто добавить вершинный атрибут в вершину, вам нужно разбить вершину на 2 и указать разные касательные для клонов.

Лучший способ получить уникальную касательную (и другие атрибуты) на вершину - сделать это как можно раньше = в экспортере. Там, на стадии сортировки чистых вершин по атрибутам, вам просто нужно добавить вектор касательной к сортировочному ключу.

В качестве радикального решения проблемы рассмотрим использование кватернионов. Один кватернион (vec4) может успешно представлять тангенциальное пространство с заранее определенной рукой. Легко сохранить ортонормированный (в том числе переход к шейдеру фрагмента), сохранить и извлечь нормальный, если необходимо. Дополнительная информация о KRI wiki.

Ответ 3

Основываясь на ответе от kvark, я хотел бы добавить больше мыслей.

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

Предположим, что мы имеем нормализованный нормальный вектор n, и мы имеем касательную t и бинормаль b, или мы можем вычислить их из производных следующим образом:

// derivations of the fragment position
vec3 pos_dx = dFdx( fragPos );
vec3 pos_dy = dFdy( fragPos );
// derivations of the texture coordinate
vec2 texC_dx = dFdx( texCoord );
vec2 texC_dy = dFdy( texCoord );
// tangent vector and binormal vector
vec3 t = texC_dy.y * pos_dx - texC_dx.y * pos_dy;
vec3 b = texC_dx.x * pos_dy - texC_dy.x * pos_dx;

Конечно, ортонормированную матрицу касательных пространств можно вычислить с помощью кросс-произведения, но это будет работать только для правых систем. Если матрица была зеркальной (левая система), она превратится в правую систему:

t = cross( cross( n, t ), t ); // orthonormalization of the tangent vector
b = cross( n, t );             // orthonormalization of the binormal vector 
                               //   may invert the binormal vector
mat3 tbn = mat3( normalize(t), normalize(b), n );

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

t = cross( cross( n, t ), t ); // orthonormalization of the tangent vector
b = cross( b, cross( b, n ) ); // orthonormalization of the binormal vectors to the normal vector 
b = cross( cross( t, b ), t ); // orthonormalization of the binormal vectors to the tangent vector
mat3 tbn = mat3( normalize(t), normalize(b), n );

Общим способом ортогонализации любой матрицы является процесс Gram-Schmidt:

t = t - n * dot( t, n ); // orthonormalization ot the tangent vectors
b = b - n * dot( b, n ); // orthonormalization of the binormal vectors to the normal vector 
b = b - t * dot( b, t ); // orthonormalization of the binormal vectors to the tangent vector
mat3 tbn = mat3( normalize(t), normalize(b), n );

Другая возможность - использовать определитель матрицы 2 * 2, который получается из выводов координат текстуры texC_dx, texC_dy, чтобы учесть направление бинормального вектора. Идея состоит в том, что определитель ортогональной матрицы равен 1 и определяется одной из ортогональной зеркальной матрицы -1.

Определитель может быть проинтегрирован с помощью функции GLSL determinant( mat2( texC_dx, texC_dy ) или его можно рассчитать по формуле texC_dx.x * texC_dy.y - texC_dy.x * texC_dx.y.

Для вычисления ортонормированной матрицы касательных пространств бинормальный вектор больше не требуется и вычисление единичного вектора (normalize) бинормального вектора можно уклониться.

float texDet = texC_dx.x * texC_dy.y - texC_dy.x * texC_dx.y;
vec3 t = texC_dy.y * pos_dx - texC_dx.y * pos_dy;
t      = normalize( t - n * dot( t, n ) );
vec3 b = cross( n, t );                      // b is normlized because n and t are orthonormalized unit vectors
mat3 tbn = mat3( t, sign( texDet ) * b, n ); // take in account the direction of the binormal vector