Вдохновленный презентацией 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 всегда положителен.
К сожалению, мои результаты неверны, и я не знаю, где искать. Я могу различить структуру карты нормалей, так что что-то должно быть правильным.
Если кто-нибудь сообщит мне, в чем может быть моя проблема, или если у них есть опыт в этом, буду очень признателен за любые советы.
texNormal
? т.е. делаюtexture2D(texNormal, vTexCoord).xyz * 2.0 - 1.0
. - person GuyRT   schedule 27.03.2014multQuat
, но только что заметил подозрительную асимметрию — должен ли последний член быть(q1.y * q2.z)
вместо(q2.y * q2.z)
? - person GuyRT   schedule 27.03.2014