Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / скелетная анимация на кватернионах

скелетная анимация на кватернионах

Поделиться
BernsПользовательwww22 окт. 201720:42#0
доброго времени.
вообще я уже делал скелетную анимацию на матрицах и статичную на тех же кватернионах, но сейчас не могу разобраться с более продвинутым её вариантом.
руководствуюсь этой статьей и исходниками 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);
}

что я конкретно не понимаю, так как это всё объединяется? полагаю, что так: в корневом суставе локальная и глобальная позиции равны, поэтому ничего не меняется. т.к суставы сортированы, просто складываем локальную позицию текущего сустава с уже глобальной родительской. что до кватернионов? они просто комбинируются или еще влияют на позиции дочерних костей?

AndreyПостоялецwww22 окт. 201722:56#1
Berns
>вообще я уже делал скелетную анимацию на матрицах и статичную на тех же кватернионах,
я так и не понял как быть с матрицами Bind Bone Pos если использовать кватернионы.
> они просто комбинируются или еще влияют на позиции дочерних костей?
при иерархии костей, родительская должна влиять на дочерние, не?
BernsПользовательwww22 окт. 201723:08#2
при иерархии костей, родительская должна влиять на дочерние, не?

да. но я про схему и порядок их комбинирования. в примитивной объектной анимации позиции просто складываются. спрошу упрощенно, меняются ли кватернионы всех дочерних объектов при повороте корневой кости или меняется только позиция этих костей?
AndreyПостоялецwww23 окт. 20170:17#3
Berns
>меняются ли кватернионы всех дочерних объектов при повороте корневой кости
да.
BernsПользовательwww23 окт. 201714:56#4
уже ближе, при отключенном оффсете и там и там анимация становится уже более узнаваемой, но всё же где-то ошибка
  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);
  }

  я инвертирую кватернион нулевого кадра и умножаю на кват кадра по анимации, корневой сустав не трогаю, если сустав дочерний, то умножаю его кват на родительский. как более правильно?

AndreyПостоялецwww23 окт. 201715:51#5
Berns
> я инвертирую кватернион нулевого кадра
Это ты его получаешь из матрицы Bind Pose модели?
а что с позицией первого кадра(Bind Pose) делаешь ?
BernsПользовательwww23 окт. 201716:50#6
Это ты его получаешь из матрицы Bind Pose модели?

тут нет матриц, только оффсеты и кватернионы

а что с позицией первого кадра(Bind Pose) делаешь ?

пока что игнорирую, чтобы кватернионы было легче отлаживать. в оригинальном исходнике тоже её отключил, чтобы проводить сравнения
AndreyПостоялецwww23 окт. 201717:04#7
Berns
> тут нет матриц, только оффсеты и кватернионы
т.е. ты на этапе экспорта(подготовки формата анимации) сразу пишешь кватеринион + pos для Bind Pose ?
DeamonПостоялецwww23 окт. 201717:39#8
Andrey
Это свидетель секты "все трансформации на кватернионах, матрицы - не нужны"
BernsПользовательwww23 окт. 201718:05#9
т.е. ты на этапе экспорта(подготовки формата анимации) сразу пишешь кватеринион + 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);
  }

теперь гадаю, каким макаром тут замешивать бинд

Правка: 23 окт. 2017 19:09

BernsПользовательwww24 окт. 201717:36#10
следующее предположение - я забыл сохранить глобальные бинды, они должны высчитываться так же, как а обычная анимация нулевого кадра, а после инвертироваться и умножаться примерно так QUAT = QUAT[FRAME] * INVERSE.QUAT[0]. тут еще баг какой-то не проглядел, из-за которого корневая кость на нулевом кадре гуляет, так что позже проверю
BernsПользовательwww24 окт. 201720:48#11
ладно, как оно на матрицах делается? я устал изучать особенности ООП головного мозга автора этих исходников

вроде правильно, теперь гадаю насчет расчета корневого сустава и оффсета

  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__]));
  }

Правка: 24 окт. 2017 23:10

/ Форум / Программирование игр / Графика

2001—2017 © GameDev.ru — Разработка игр