Как рассчитать тангент и бинормаль? - PullRequest
43 голосов
/ 10 марта 2011

Говоря о рельефном отображении, зеркальном выделении и подобных вещах в 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}) (предположим, что зритель находится в центре мира)

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

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

О, да, у меня тоже есть координаты текстуры, если нужно. И поскольку я говорю о 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%.

Ответы [ 3 ]

37 голосов
/ 10 марта 2011

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

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

D = B-A
E = C-A

F = K-H
G = L-H

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

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' вместе с нормалью вершины как часть геометрии модели (как атрибуты вершины), чтобы мы могли использовать их в шейдере для расчетов освещения. Я повторяю: вы не определяете тангенс и бинормаль в шейдере, вы предварительно вычисляете их и сохраняете как часть геометрии модели (точно так же, как и нормалей).

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

16 голосов
/ 10 марта 2011

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

  • Онлайн = прямо в фрагментном шейдереиспользуя производные инструкции.Эти деривации дают вам основание TBN для каждой точки многоугольника.Чтобы получить гладкое, мы должны повторно ортогонализировать его на основе заданной (гладкой) нормали вершины.Эта процедура даже более тяжелая для графического процессора, чем первоначальное извлечение 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);
    
  • Автономно = подготовить касательную как атрибут вершины.Это сложнее получить, потому что это не просто добавит другую атрибуцию вершин, но также потребует перекомпоновки всех других атрибутов.Более того, это не на 100% повысит производительность, поскольку вы получите дополнительную стоимость хранения / передачи / анимации (!) Атрибута вершины vector3.

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

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

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

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

0 голосов
/ 04 июля 2017

Основываясь на ответе 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 );

Распространенным способом ортогонализации любой матрицы является процесс Грама – Шмидта :

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
...