Кватернионы в программировании игр.
Автор: Sergei Shaykin
Существует несколько путей представления вращения объектов. Многие программисты используют для этого матрицы вращения или углы Эйлера. Каждое из этих решений работает замечательно, до тех пор, пока вы не пытаетесь осуществить гладкую интерполяцию между двумя разными положениями объекта. Например, представьте себе объект, который просто свободно вращается в пространстве. Если хранить вращение, как матрицу или в виде углов Эйлера, то гладкая интерполяция окажется довольно дорогостоящей по вычислениям и будет не столь гладкой, как при интерполяции кватернионами. Хотя и можно попытаться наиболее близко расставить временные ключи на стадии создания анимации, однако это влечет за собой хранение большего количества данных для данного вращения и не совсем тривиально ясно, какой выбирать шаг. Понятно, что и в этом случае без интерполяции не обойтись, никогда не известно какой FPS будет у игрока.
Многие игры от третьего лица используют кватернионы для анимации движения камеры. Все игры от третьего лица располагают камеру на некотором расстоянии от персонажа. Так как камера имеет другое движение, отличающееся от движения персонажа, например, при повороте персонажа - камера движется по дуге, то иногда бывает, что это движение выглядит не натурально, скачками. Это одна из проблем, которую можно решить при помощи кватернионов. Кватернионы также удобно использовать в лётных симуляторах, таких как ИЛ-2 Штурмовик. Вместо манипулирования тремя углами (roll, pitch и yaw), представляя вращение вокруг осей x, y и z соответственно, намного проще использовать один кватернион. Да и вообще много игр и приложений трёхмерной графики сохраняют ориентацию объектов в кватернионах. Например, легче добавить угловую скорость к кватерниону, чем к матрице.
Что это такое
Основные операции и свойства кватернионов
Преобразования
Вращение вокруг оси
Преобразование сферических координат в кватернион
Углы Эйлера
Преобразования матрицы поворота в кватернион
Преобразование единичного кватерниона в матрицу поворота
Гладкая интерполяция
Кватернионы в DirectX
Кватернионы в MAX SDK
Ссылки по теме
Что это такое
Кватернионы были введены Гамильтоном в XVIII веке. Кватернионы являются 4-х мерным расширением множества комплексных чисел, другими словами — это гиперкомплексные числа. То есть кватернион q задаётся четвёркой чисел (x, y, z, w):
w + xi + yj + zk
, где i2 = j2 = k2 = –1.
Его можно также записывать в виде:
[w,v]
, где w называют скаляр, а v = (x, y, z) — вектор.
Основные операции и свойства кватернионов
Рассмотрим два кватерниона: q[w,v] и q'[w',v']. Для них справедливо следующее:
q+q' = [w+w',v+v']
qq' = [ww' – v·v', vxv' + wv' + w'v]
, где x - векторное произведение, а · — скалярное.
norm(q) = sqrt(w2+x2+y2+z2) (размер)
q*=[w, –v] (сопряжение)
\(q^{-1}=\frac {q*} {norm(q)^2}\)
norm(q)=1 => q–1=q*
Кватернионы расширяют концепцию вращения в трехмерном пространстве до вращения в четырехмерном. Вращение можно задать единичным кватернионом (norm(q) = 1). Чтобы кватернион привести к единичному виду, или другими словами нормализовать, необходимо вычислить его размер norm(q) и все четыре члена кватерниона разделить на величину полученного размера.
Пространство кватернионов представляет собой 4-х мерное пространство. Множество всех единичных векторов — есть 4-х мерная сфера с радиусом 1. Кватернионы можно рассматривать, как добавление дополнительного угла поворота к сферическим координатам, (сферические координаты - долгота, широта, угол поворота).
Если вращение задано некоторым кватернионом q, то вектор v после вращения будет иметь вид v':
V'=q V q–1
, где V = [o,v], V'=[0,v']
Преобразования
Вращение вокруг оси
Рассмотрим вращение на угол Q вокруг оси, заданной направляющим вектором v. Это вращение можно задать кватернионом:
q=[cos(Q/2),sin(Q/2)v]
При необходимости нужно не забыть нормализовать полученный кватернион.
Преобразование сферических координат в кватернион
struct Quaternion{ float x,y,z; // Вектор float w; // Скаляр }; //Преобразование сферических кординат в кватернион void SphericalToQuaternion(Quaternion * q, float latitude, float longitude, float angle) { float sin_a = sin( angle / 2 ); float cos_a = cos( angle / 2 ); float sin_lat = sin( latitude ); float cos_lat = cos( latitude ); float sin_long = sin( longitude ); float cos_long = cos( longitude ); q->x = sin_a * cos_lat * sin_long; q->y = sin_a * sin_lat; q->z = sin_a * sin_lat * cos_long; q->w = cos_a; }
Углы Эйлера
Вращение задаётся yaw, pitch и roll. Тогда кватернион вычисляется следующим образом:
qroll = [cos (y/2), (sin(y/2), 0, 0)]
qpitch = [cos (q/2), (0, sin(q/2), 0)]
qyaw = [cos (f/2), (0, 0, sin(f/2))]
q = qyaw qpitch qroll
Преобразования матрицы поворота в кватернион
Для задания вращения достаточно матрицы размером 3x3. Однако, поскольку многие 3D API используют для трансформации матрицы размером 4x4, мы тоже будем рассматривать такую размерность матрицы. При этом, чтобы дополнительные данные не влияли на смысл матрицы, дополнительные строка и столбец выставляются в ноль, за исключением их пересечения, которое выставляется в 1. Ниже приведён код преобразования матрицы поворота в кватернион. Ясно, что эта матрица не должна нести в себе другую информацию кроме вращения, например — масштабирование.
// Преобразование матрицы в кватернион void MatrixToQuaternion(Quaternion * quat, float m[4][4]) { float tr, s, q[4]; int i, j, k; int nxt[3] = {1, 2, 0}; tr = m[0][0] + m[1][1] + m[2][2]; if ( tr > 0.0) { s = sqrt ( tr + 1.0); quat->w = s / 2.0; s = 0.5 / s; quat->x = ( m[1][2] - m[2][1]) * s; quat->y = ( m[2][0] - m[0][2]) * s; quat->z = ( m[0][1] - m[1][0]) * s; } else { i = 0; if ( m[1][1] > m[0][0]) i = 1; if ( m[2][2] > m[i][i]) i = 2; j = nxt[i]; k = nxt[j]; s = sqrt ( ( m[i][i] - ( m[j][j] + m[k][k])) + 1.0); q[i] = s * 0.5; if ( s != 0.0) s = 0.5 / s; q[3] = ( m[j][k] - m[k][j]) * s; q[j] = ( m[i][j] + m[j][i]) * s; q[k] = ( m[i][k] + m[k][i]) * s; quat->x = q[0]; quat->y = q[1]; quat->z = q[2]; quat->w = q[3]; } }
Преобразование единичного кватерниона в матрицу поворота
Преобразование единичного кватерниона в матрицу поворота можно записать в виде:
TRot = [ 1-2y2-2z2 2xy-2wz 2xz+2wy
2xy+2wz 1-2x2-2z2 2yz-2wx
2xz-2wy 2yz+2wx 1-2x2-2y2 ]
// Преобразование кватерниона в матрицу void QuaternionToMatrix(float m[4][4], const Quaternion * quat) { float wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; x2 = quat->x + quat->x; y2 = quat->y + quat->y; z2 = quat->z + quat->z; xx = quat->x * x2; xy = quat->x * y2; xz = quat->x * z2; yy = quat->y * y2; yz = quat->y * z2; zz = quat->z * z2; wx = quat->w * x2; wy = quat->w * y2; wz = quat->w * z2; m[0][0]=1.0f-( yy+zz); m[0][1]=xy-wz; m[0][2]=xz+wy; m[1][0]=xy+wz; m[1][1]=1.0f-( xx+zz); m[1][2]=yz-wx; m[2][0]=xz-wy; m[2][1]=yz+wx; m[2][2]=1.0f-( xx+yy); m[0][3] = m[1][3] = m[2][3] = 0; m[3][0] = m[3][1] = m[3][2] = 0; m[3][3] = 1; }
При работе с иерархией объектов и инверсной кинематикой возникает композитное задание вращения. В этом случае использование кватернионов предпочтительнее, чем матриц. То есть, если вращение задано двумя матрицами R1 и R2, то чтобы вычислить матрицу поворота нужно перемножить эти матрицы: R = R1 x R2. Аналогично, если вращение задано двумя кватернионами q1 и q2, то результирующий кватернион поворота будет q = q1 x q2. Ясно, что вычисление произведения кватернионов происходит быстрее, чем матриц. Причем произведение кватернионов можно оптимизировать:
void MulQuaternions(Quaternion *res, const Quaternion *q1, const Quaternion *q2) { float A, B, C, D, E, F, G, H; A = ( q1->w + q1->x) * ( q2->w + q2->x); B = ( q1->z - q1->y) * ( q2->y - q2->z); C = ( q1->x - q1->w) * ( q2->y + q2->z); D = ( q1->y + q1->z) * ( q2->x - q2->w); E = ( q1->x + q1->z) * ( q2->x + q2->y); F = ( q1->x - q1->z) * ( q2->x - q2->y); G = ( q1->w + q1->y) * ( q2->w - q2->z); H = ( q1->w - q1->y) * ( q2->w + q2->z); res->w = B + ( -E - F + G + H) * 0.5; res->x = A - ( E + F + G + H) * 0.5; res->y =-C + ( E - F + G - H) * 0.5; res->z =-D + ( E - F - G + H) * 0.5; }
5 августа 2001 (Обновление: 29 дек 2013)