доброго времени.
вообще я уже делал скелетную анимацию на матрицах и статичную на тех же кватернионах, но сейчас не могу разобраться с более продвинутым её вариантом.
руководствуюсь этой статьей и исходниками http://3d-orange.com.ua/skining-in-shader-glsl/
толком ничего не объяснено, комментарий в коде нет.
мне удалось загрузить модель, с весами и индексами проблем нет, пока только процедурная анимация условно работает - локальные координаты пока что вычисляю вручную, а поворот регулирую типа таким:
DirectX::XMFLOAT4(0, 0, sin( s_test / 10), cos( s_test / 10));
. могу таким образом повернуть, например, руку, указав всем дочерним костям позицию и кватернион корневого (плечо) сустава
что самое странное, сумма корневой кости и её наследника не совпадает с глобальной позицией наследника в 3D мах, при этом поворот получается правильным.
поворачиваю вот таким шейдером, немного пределанный оригинал, по идее, он поворачивает по оси из bones_t
float3 VertexTransform(float3 pos, int i) { // restore offset component (vec3) float tx = bones_t[i].x; float ty = bones_t[i].y; float tz = bones_t[i].z; float3 p = pos - float3( tx, ty, tz); float x = bones_q[i].x; float y = bones_q[i].y; float z = bones_q[i].z; float w = bones_q[i].w; // original code from DooM 3 SDK float xxzz = x*x - z*z; float wwyy = w*w - y*y; float xw2 = x*w*2.0; float xy2 = x*y*2.0; float xz2 = x*z*2.0; float yw2 = y*w*2.0; float yz2 = y*z*2.0; float zw2 = z*w*2.0; float3 ret = float3( ( xxzz + wwyy)*p.x + ( xy2 + zw2)*p.y + ( xz2 - yw2)*p.z, ( xy2 - zw2)*p.x + ( y*y + w*w - x*x - z*z)*p.y + ( yz2 + xw2)*p.z, ( xz2 + yw2)*p.x + ( yz2 - xw2)*p.y + ( wwyy - xxzz)*p.z); return ret + float3( tx, ty, tz); }
что я конкретно не понимаю, так как это всё объединяется? полагаю, что так: в корневом суставе локальная и глобальная позиции равны, поэтому ничего не меняется. т.к суставы сортированы, просто складываем локальную позицию текущего сустава с уже глобальной родительской. что до кватернионов? они просто комбинируются или еще влияют на позиции дочерних костей?
Berns
>вообще я уже делал скелетную анимацию на матрицах и статичную на тех же кватернионах,
я так и не понял как быть с матрицами Bind Bone Pos если использовать кватернионы.
> они просто комбинируются или еще влияют на позиции дочерних костей?
при иерархии костей, родительская должна влиять на дочерние, не?
при иерархии костей, родительская должна влиять на дочерние, не?
да. но я про схему и порядок их комбинирования. в примитивной объектной анимации позиции просто складываются. спрошу упрощенно, меняются ли кватернионы всех дочерних объектов при повороте корневой кости или меняется только позиция этих костей?
Berns
>меняются ли кватернионы всех дочерних объектов при повороте корневой кости
да.
уже ближе, при отключенном оффсете и там и там анимация становится уже более узнаваемой, но всё же где-то ошибка
for (int i = 0; i < num_obj; i++) { int parrent__ = ( my_model[0].joint2[i]); int self_id__ = ( my_model[0].joint1[i]); int time = num_obj * 7 * int( rate0); cb1.bones_t[self_id__] = DirectX::XMFLOAT4( my_model[0].qtfo_data[( self_id__ * 7 + time) + 0], my_model[0].qtfo_data[( self_id__ * 7 + time) + 1], my_model[0].qtfo_data[( self_id__ * 7 + time) + 2], 0.0f); DirectX::XMFLOAT4 QUAT_F0 = DirectX::XMFLOAT4( my_model[0].qtfo_data[( self_id__ * 7 ) + 3], my_model[0].qtfo_data[( self_id__ * 7 ) + 4], my_model[0].qtfo_data[( self_id__ * 7 ) + 5], my_model[0].qtfo_data[( self_id__ * 7 ) + 6]); DirectX::XMFLOAT4 QUAT_F1 = DirectX::XMFLOAT4( my_model[0].qtfo_data[( self_id__ * 7 + time) + 3], my_model[0].qtfo_data[( self_id__ * 7 + time) + 4], my_model[0].qtfo_data[( self_id__ * 7 + time) + 5], my_model[0].qtfo_data[( self_id__ * 7 + time) + 6]); QUAT_F0 = inverseQ( QUAT_F0); QUAT_F0 = Quat_Normalize( QUAT_F0); QUAT_F1 = Quat_Normalize( QUAT_F1); cb1.bones_q[self_id__] = Quat_Normalize( mullQ( QUAT_F1, QUAT_F0)); } for ( int i = 0; i < num_obj; i++) { int parrent__ = ( my_model[0].joint2[i]); int self_id__ = ( my_model[0].joint1[i]); if ( parrent__ == -1) { continue; } DirectX::XMFLOAT4 quat_comb = mullQ( DirectX::XMFLOAT4( cb1.bones_q[parrent__].x, cb1.bones_q[parrent__].y, cb1.bones_q[parrent__].z, cb1.bones_q[parrent__].w), DirectX::XMFLOAT4( cb1.bones_q[self_id__].x, cb1.bones_q[self_id__].y, cb1.bones_q[self_id__].z, cb1.bones_q[self_id__].w)); cb1.bones_q[self_id__] = Quat_Normalize( quat_comb); }
я инвертирую кватернион нулевого кадра и умножаю на кват кадра по анимации, корневой сустав не трогаю, если сустав дочерний, то умножаю его кват на родительский. как более правильно?
Berns
> я инвертирую кватернион нулевого кадра
Это ты его получаешь из матрицы Bind Pose модели?
а что с позицией первого кадра(Bind Pose) делаешь ?
Это ты его получаешь из матрицы Bind Pose модели?
тут нет матриц, только оффсеты и кватернионы
а что с позицией первого кадра(Bind Pose) делаешь ?
пока что игнорирую, чтобы кватернионы было легче отлаживать. в оригинальном исходнике тоже её отключил, чтобы проводить сравнения
Berns
> тут нет матриц, только оффсеты и кватернионы
т.е. ты на этапе экспорта(подготовки формата анимации) сразу пишешь кватеринион + pos для Bind Pose ?
Andrey
Это свидетель секты "все трансформации на кватернионах, матрицы - не нужны"
т.е. ты на этапе экспорта(подготовки формата анимации) сразу пишешь кватеринион + pos для Bind Pose ?
пока с экспортом не работаю, использую наработки iOrange, формат моделей аналогичен текстовому SMD из HL2, тут записывается локальная позиция кости + кватернион
продолжаю ломать оригинал для соответствия артефактов, следующая часть моего кода вроде как правильная:
for (int i = 0; i < num_obj; i++) { int self_id__ = ( my_model[0].joint1[i]); int time = num_obj * 7 * int( rate0); cb1.bones_t[self_id__] = DirectX::XMFLOAT4( my_model[0].qtfo_data[( self_id__ * 7 + time) + 0], my_model[0].qtfo_data[( self_id__ * 7 + time) + 1], my_model[0].qtfo_data[( self_id__ * 7 + time) + 2], 0.0f); DirectX::XMFLOAT4 QUAT_F1 = DirectX::XMFLOAT4( my_model[0].qtfo_data[( self_id__ * 7 + time) + 3], my_model[0].qtfo_data[( self_id__ * 7 + time) + 4], my_model[0].qtfo_data[( self_id__ * 7 + time) + 5], my_model[0].qtfo_data[( self_id__ * 7 + time) + 6]); cb1.bones_q[self_id__] = Quat_Normalize( QUAT_F1); } /*AnimateHierarhy*/ for ( int i = 0; i < num_obj; i++) { int parrent__ = ( my_model[0].joint2[i]); if ( parrent__ == -1) { continue; } int self_id__ = ( my_model[0].joint1[i]); DirectX::XMFLOAT4 quat_comb = mullQ( DirectX::XMFLOAT4( cb1.bones_q[parrent__].x, cb1.bones_q[parrent__].y, cb1.bones_q[parrent__].z, cb1.bones_q[parrent__].w), DirectX::XMFLOAT4( cb1.bones_q[self_id__].x, cb1.bones_q[self_id__].y, cb1.bones_q[self_id__].z, cb1.bones_q[self_id__].w)); cb1.bones_q[self_id__] = Quat_Normalize( quat_comb); }
теперь гадаю, каким макаром тут замешивать бинд
следующее предположение - я забыл сохранить глобальные бинды, они должны высчитываться так же, как а обычная анимация нулевого кадра, а после инвертироваться и умножаться примерно так QUAT = QUAT[FRAME] * INVERSE.QUAT[0]. тут еще баг какой-то не проглядел, из-за которого корневая кость на нулевом кадре гуляет, так что позже проверю
ладно, как оно на матрицах делается? я устал изучать особенности ООП головного мозга автора этих исходников
вроде правильно, теперь гадаю насчет расчета корневого сустава и оффсета
for (int i = 0; i < num_obj; i++) { int self_id__ = my_model[0].joint1[i]; int time = num_obj * 7 * int( rate0); XMFLOAT3 OFSET__ = XMFLOAT3 ( my_model[0].qtfo_data[( self_id__ * 7) + time + 0], my_model[0].qtfo_data[( self_id__ * 7) + time + 1], my_model[0].qtfo_data[( self_id__ * 7) + time + 2]); XMVECTOR QUAT__ = XMVectorSet( my_model[0].qtfo_data[( self_id__ * 7) + time + 3], my_model[0].qtfo_data[( self_id__ * 7) + time + 4], my_model[0].qtfo_data[( self_id__ * 7) + time + 5], my_model[0].qtfo_data[( self_id__ * 7) + time + 6]); Bone_Work[self_id__] = XMMatrixIdentity( ); Bone_Work[self_id__] *= XMMatrixTranslation( OFSET__.x, OFSET__.y, OFSET__.z); Bone_Work[self_id__] *= XMMatrixRotationQuaternion( QUAT__); XMFLOAT3 INV_OFSET__ = XMFLOAT3( my_model[0].qtfo_data[( self_id__ * 7) + 0], my_model[0].qtfo_data[( self_id__ * 7) + 1], my_model[0].qtfo_data[( self_id__ * 7) + 2]); XMVECTOR INV_QUAT__ = XMVectorSet( my_model[0].qtfo_data[( self_id__ * 7) + 3], my_model[0].qtfo_data[( self_id__ * 7) + 4], my_model[0].qtfo_data[( self_id__ * 7) + 5], my_model[0].qtfo_data[( self_id__ * 7) + 6]); Bone_Inv[self_id__] = XMMatrixIdentity( ); Bone_Inv[self_id__] *= XMMatrixTranslation( INV_OFSET__.x, INV_OFSET__.y, INV_OFSET__.z); Bone_Inv[self_id__] *= XMMatrixRotationQuaternion( INV_QUAT__); } /*AnimateHierarhy*/ XMVECTOR Inverse_vec; for ( int i = 0; i < num_obj; i++) { int self_id__ = ( my_model[0].joint1[i]); int parrent__ = ( my_model[0].joint2[i]); if ( parrent__ == -1) { cb1.Bone[self_id__] = XMMatrixMultiply( Bone_Work[self_id__], XMMatrixInverse( &Inverse_vec, Bone_Work[self_id__])); continue; } Bone_Work[self_id__] = XMMatrixMultiply( Bone_Work[parrent__], Bone_Work[self_id__]); Bone_Inv[self_id__] = XMMatrixMultiply( Bone_Inv[parrent__], Bone_Inv[self_id__]); cb1.Bone[self_id__] = XMMatrixMultiply( Bone_Work[self_id__], XMMatrixInverse( &Inverse_vec, Bone_Inv[self_id__])); }
Тема в архиве.