Использование кватернионов для отображения нормалей касательного пространства — проблемы, с которыми я столкнулся

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

Я попытался реализовать это в своем приложении OpenGL, используя свой самодельный класс кватерниона, и у меня возникли некоторые проблемы. Я знаю, что мой кватернион можно построить из матрицы, умножить кватернион на вектор и получить тот же результат, что и умножение матрицы на вектор — я успешно сделал это на стороне процессора. Однако, как только я начинаю работать с ними в GLSL, все идет наперекосяк.

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

Это кватернионная математика, которую я использую в glsl:

vec4 multQuat(vec4 q1, vec4 q2)
{
    return vec4(
        (q1.w * q2.y) + (q1.y * q2.w) + (q1.x * q2.z) - (q1.z * q2.x),
        (q1.w * q2.z) + (q1.z * q2.w) + (q1.y * q2.x) - (q1.x * q2.y),
        (q1.w * q2.w) - (q1.x * q2.x) - (q1.y * q2.y) - (q1.z * q2.z),
        (q1.w * q2.x) + (q1.x * q2.w) + (q1.z * q2.y) - (q1.y * q2.z)
        );
}

vec3 rotateVector(vec4 quat, vec3 vec)
{
    return vec + 2.0 * cross(quat.xyz, cross(quat.xyz, vec) + (quat.w * vec));
}

Вот как это передается из вершинного шейдера:

vQtangent = multQuat(inQtangent, quatView);

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

Наконец, мы вычисляем выпуклую нормаль во фрагментном шейдере:

vec3 calcBumpedNormal(void)
{
    vec4 qtangent = normalize(vQtangent);
    vec3 normal = texture2D(texNormal, vTexCoord).xyz;
    normal = (normal * 2) - 1;
    return normalize(rotateVector(qtangent, normal));
};

Вот как я вычисляю кватернион из 3 vec3 (как я получаю кватернион из векторов tbn):

inline static quat fromMat3(const vec3& col0, const vec3& col1, const vec3& col2)
{
    /* warning - this only works when the matrix is orthogonal and special orthogonal */

    float w = sqrtf(1.0f + col0.x + col1.y + col2.z) / 2.0f;

    return quat(
        (col1.z - col2.y) / (4.0f * w),
        (col2.x - col0.z) / (4.0f * w),
        (col0.y - col1.x) / (4.0f * w),
        w);
}

А вот как я вычисляю кватернион из мат4 (как я получаю quatView из матрицы вида):

inline static quat fromMat4(const mat4& mat)
{
    /* warning - this only works when the matrix is orthogonal and special orthogonal */

    float w = sqrtf(1.0f + mat.m[0][0] + mat.m[1][1] + mat.m[2][2]) / 2.0f;

    return quat(
        (mat.m[1][2] - mat.m[2][1]) / (4.0f * w),
        (mat.m[2][0] - mat.m[0][2]) / (4.0f * w),
        (mat.m[0][1] - mat.m[1][0]) / (4.0f * w),
        w);
}

Я знаю, что ни один из них не работает с неортогональными матрицами.

Однако в буфере нормалей хранятся только x и y нормали, я реконструирую z в ​​шейдере фрагментов прохода света, используя трюк sqrt. Поскольку эти нормали должны находиться в пространстве обзора, компонент z всегда положителен.

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

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


person Haydn V. Harach    schedule 26.03.2014    source источник
comment
Ваше приложение случайно не будет доступно в общедоступном репозитории? :)   -  person Nobbynob Littlun    schedule 27.03.2014
comment
Боюсь, это не так, но если вы хотите просмотреть весь исходный код проекта, вы можете взять его здесь.   -  person Haydn V. Harach    schedule 27.03.2014
comment
разве вы не должны масштабировать/смещать нормальный выход texNormal? т.е. делаю texture2D(texNormal, vTexCoord).xyz * 2.0 - 1.0.   -  person GuyRT    schedule 27.03.2014
comment
Кроме того: поскольку эти нормали должны находиться в пространстве обзора, компонент z всегда положителен, что неверно. Например, если вы стоите рядом со стеной справа от себя, смотрите вдоль стены, а затем смотрите немного влево, вы все еще можете видеть стену, но ее нормальное значение будет иметь -ve z в вашей системе отсчета.   -  person GuyRT    schedule 27.03.2014
comment
Я добавил смещение (оно было в предыдущем коде карты нормалей, и я забыл переместить его), но все равно получаю неправильные результаты. Если нормали пространства просмотра могут быть отрицательными, как люди обычно хранят нормали в двухкомпонентном буфере?   -  person Haydn V. Harach    schedule 27.03.2014
comment
Некоторые люди реконструируют z так же, как вы, и не замечают (иногда незаметную) ошибку освещения. Здесь есть (немного старое) обсуждение различных методов: aras-p.info/texts/CompactNormalStorage. html   -  person GuyRT    schedule 27.03.2014
comment
Кроме того, я не очень внимательно проверил вашу функцию multQuat, но только что заметил подозрительную асимметрию — должен ли последний член быть (q1.y * q2.z) вместо (q2.y * q2.z)?   -  person GuyRT    schedule 27.03.2014
comment
Спасибо, что уловили это, вы правы. Вероятно, это была опечатка, которую я просто пропустил. Он по-прежнему дает неверные результаты, но теперь они менее случайны.   -  person Haydn V. Harach    schedule 28.03.2014
comment
Я замечаю что-то странное. Всякий раз, когда я не поворачиваю нормаль ни на что, связанное с матрицей вида (т. е. нормали находятся в пространстве модели, которое в моей ситуации удобно оказывается мировым пространством), мои кватернионно-повернутые нормали из текстуры появляются, чтобы быть правильным. Это заставляет меня поверить, что, возможно, я неправильно строю свой кватернион...   -  person Haydn V. Harach    schedule 28.03.2014
comment
Я не вижу здесь ничего плохого в коде (возможно, вы могли бы обновить его с уже внесенными исправлениями). Не могли бы вы также опубликовать код для построения кватерниона представления? Также может быть полезно увидеть несколько скриншотов.   -  person GuyRT    schedule 29.03.2014
comment
Я только что обновил вопрос своими исправлениями и кодом, который я использую для генерации кватернионов.   -  person Haydn V. Harach    schedule 30.03.2014


Ответы (1)


Ваш код работает нормально, если вы используете кватернион для каждой вершины только в вершинном шейдере (путем преобразования векторов света и камеры в тангенциальное пространство)? Если он ломается только тогда, когда вы пытаетесь повернуть нормаль в пиксельном шейдере, то ваша проблема заключается в интерполяции кватернионов (если нет, то я только что потратил 20 минут).

Проблема

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

Теперь ваш fromMat3 всегда производит кватернион с положительным компонентом W. Представьте, как интерполяция проходит по ребру между (0.99,0,0,0.1) и (-0.99,0,0,0.1). Компонент X будет перемещаться по всей своей оси, вызывая у вас всевозможные проблемы с затенением.

Решение

Вы должны убедиться, что любая интерполяция кватернионов (QI) происходит между кватернионами, принадлежащими одному и тому же полушарию, то есть dot(q1,q2) > 0. Легко увидеть, как эта проверка дает сбой для примеров кватернионов, которые я упомянул, и как она работает нормально, если вы умножаете второй кватернион на -1.

Сложность заключается в том, что для обеспечения QI-корректности может потребоваться разделение ребер и добавление новых вершин, поэтому лучше всего это делать на стороне экспортера, а не во время загрузки модели. Взгляните на экспортер сетки KRI код для справки.

Вывод

Я не рекомендую вам идти туда из практических соображений, если только вы не очень настойчивы. Вместо этого вы можете просто использовать кватернионы в вершинном шейдере. Если вам когда-нибудь попадалась книга по GPU Pro 3, вы можете найти мою статью о quaternions, подробно объясняя ту же проблему (и решение).

person kvark    schedule 05.08.2014