Войти
ПрограммированиеФорумОбщее

Магия кватернионов в юнити

Страницы: 1 2 Следующая »
#0

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

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

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

Смотрел старый сорс юнити, там вроде бы все тупо. Преобразование кватерниона в матрицу 3х3 и извлечение из нее эйлеров с учетом сингулярности.

Есть догадки что там еще за магия?

9 янв. 2019, 10:44

#1

Спроси Араса Пранкевичуса

9 янв. 2019, 13:57

#2

Mira
> Есть догадки что там еще за магия?

Нет никакой магии.
Любые углы Эйлера можно сконвертировать в кватернион.
Но не любой кватернион можно сконвертировать в углы Эйлера.
Поэтому если ты работаешь над кватернионами полученными из тех же Эйлеров в которые их пытаешься обратно превратить, то проблемы не будет.
Как я в другой теме уже говорил еще важно в каком порядке применяются повороты по осям углов Эйлера, gimbal lock закладывается поворотом на 90 градусов на втором шаге.
Соответственно проблемы могут возникнуть, если ты из одних углов эйлера сконвертировал в кватернион, а потом пытаешься перевести в другие углы эйлера.
Чувак в той теме на которую ты приводил ссылку просто поменял порядок осей в своём коде и сразу же формулы юнити и данные из юнити всё ему стали прекрасно делать.

9 янв. 2019, 14:30 (Правка: 14:33)

#3

А из векипедии формулы не подходят?

9 янв. 2019, 15:10

#4

=A=L=X=
я нашел у юнитеков второй вариант функции

Vector3f QuaternionToEuler (const Quaternionf& quat)
{
  Matrix3x3f m;
  Vector3f rot;
  QuaternionToMatrix (quat, m);
  MatrixToEuler (m, rot);
  return rot;
}
переписал ее на паскаль, работает все так же. до определенного поворота углы те же самые из которых получен кватернион, потом внезапно оси выворачиваются.
тоесть дело не в порядке, потому что вначале они выдают те же углы.

еще нашел странную внутреннюю функцию выдающую сразу весь набор эйлер вариков)))
std::vector<Vector3f> GetEquivalentEulerAngles (const Quaternionf& quat)

{
  Matrix3x3f m;
  Vector3f rot;

  std::vector<Vector3f> euler_triples;

  QuaternionToMatrix (quat, m);
  MatrixToEuler (m, rot);
  
  euler_triples.push_back(rot);
    
  euler_triples.push_back(Vector3f(rot.x + 180.0f, -rot.y, rot.z + 180.0f));  
  euler_triples.push_back(Vector3f(rot.x - 180.0f, -rot.y, rot.z - 180.0f));  
  euler_triples.push_back(Vector3f(-rot.x, rot.y + 180.0f, -rot.z));
  euler_triples.push_back(Vector3f(-rot.x, rot.y - 180.0f, -rot.z));
  
  return euler_triples;
}

к сожелению ToEulerAngles на сегодняшний день deprecated и используется ToEuler кода которой нет

9 янв. 2019, 15:16

#5

foxes
эту пробовал, работает, но скрючивается в сингулярности на 90 градусах)))

9 янв. 2019, 15:18

#6

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

10 янв. 2019, 16:21

#7

Mira
> Clamp обрезать движение. способы ограничить движение кватерниона тоже конечно
> же есть, но все какие я находил, требуют слишком массивных вычислений.
Поподробней, что конкретно требуется ограничить? Допустим угол в определенной плоскости или ряд плоскостей перемещения поставить (полигон) ?

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

Берем локальную плоскость и делаем из нее кватернион так чтобы разворот давал координаты xy для любого вектора, как проекция вектора на плоскость. По этим двум координатам можно делать ограничения по углам.

Технически можно взять кватернион между двумя векторами, нормалью плоскости и Vector.forward. Но в таком случае положение границ будет не определено. Поэтому роль углов ограничения буду играть другие вектора поставленные в положение границы, после умножения на полученный кватернион они дадут тебе углы ограничения в плоскости по тем же xy.

В конечном итоге тут не будет ни чего кроме одного умножения на кватернион и возможного умножения на кватернион восстанавливающий положение в границы лимита.

10 янв. 2019, 19:28 (Правка: 20:03)

#8

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

10 янв. 2019, 20:30

#9

Mira
> ограничить вращение джойнта по осям.
Ну вот и все, выбираешь ось и вектора ограничивающие вращение (достаточно точно), дальше как я описал, ни каких углов эйлера.

Приблизительно, надо тестить

// Vector3 invector - контрольный вектор
// Vector3 axis- ось вращения
// Vector3 minvector - минимальное положение вектора
// Vector3 maxvector - максимальное положение вектора
Quaternion qaxis = Quaternion.FromToRotation(axis,Vector3.forvard);
Vector2 inv = qaxis * invector;
Vector2 min = qaxis * minvector;
Vector2 max = qaxis * maxvector;

float angle = Vector2.Angle(min,max);
float anglev = Vector2.Angle(min,inv);
if (min.x*max.y - min.y*max.x<0)
  angle = 360.0f-angle;
if (min.x*inv.y - min.y*inv.x<0)
  anglev = 360.0f-anglev;

angle = angle*0.5f;
anglev = anglev - angle;
if (anglev>180.0)
  anglev = anglev-360.0f;

// проверяем лимит
Quaternion correction = Quaternion.identity;
if (anglev<-angle)
  correction = Quaternion.AngleAxis(axis, -angle-anglev);
if (anglev>angle)
  correction = Quaternion.AngleAxis(axis, angle-anglev);

// поворачиваем обратно
invector = correction * invector;

10 янв. 2019, 21:31 (Правка: 22:10)

#10

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

для трех осей придется множить кватернионы еще, а они страшно умножаются, не векторизированно

10 янв. 2019, 23:13 (Правка: 23:15)

#11

Mira
> а они страшно умножаются, не векторизированно
Вот эту часть можно спрятать за скобкой так как по факту это константа для заданного лимита

Vector2 min = qaxis * minvector;
Vector2 max = qaxis * maxvector;
float angle = Vector2.Angle(min,max);
if (min.x*max.y - min.y*max.x<0)
  angle = 360.0f-angle;
Останется min и angle.

А это спрятать за условие

if (anglev<-angle || anglev>angle)
  invector = correction * invector;
Тогда останутся только три умножения на кватернион для трех осей.

По моим тестам на C++ минимальное количество тактов на операции могут быть такими:
Quaternion to matrux - 45 тактов
Matrix mul vector - 15 тактов
Quaternion mul vector - 28 тактов
Это с максимальной оптимизацией, а без нее
Quaternion to matrux - 97 тактов
Matrix mul vector - 20 тактов
Quaternion mul vector - 44 тактов

Кватернион против матрицы разница в два раза. Если к матрице прицепить конвертацию из кватерниона то наоборот.

11 янв. 2019, 0:05 (Правка: 0:48)

#12

Попробую попозже, сначала решил доделать  импорт файлов анимации. Решил попробовать написать свой нативный аниматор, чтоб матрицы не гонять туда сюда. Не факт что будет лучше, однако попробую.

Ну и держать на сцене шаблоны из сотни геймобьектов для перестройки аватара напрягает. Пересоздавать их тем более.

11 янв. 2019, 8:57 (Правка: 9:00)

#13

попробовал смешать анимации, сделал Slerp для кватернионов костей
появились какие то страшные артефакты периодами.
ктонеть может понять по видео что происходит?

PS артефакты это не желтые кубики и не асинхронная смесь )

+ Показать

пробовал разные реализации Slerp и менять на Lerp выглядит все примерно также

смешиваю так (но пробовал и по другому)

procedure THumanAnimator._MixAnimations;
var
 n,i:integer;
 PSB:PSceletonBone;
 WSumm,Weight,NextWeight:single;
 QW:TD3DQuaternion;
begin
 for n:=0 to length(FBMapIndices)-1 do begin
   PSB:=@FBMapIndices[n];
   if PSB.MixCount = 0 then begin  //no rotations
     PSB.currentpose.LocalRotation:=TD3DQuaternion.IdentityQuaternion;
   end else if PSB.MixCount = 1 then begin //one rotation, use non blended
     PSB.currentpose.LocalRotation:=PSB.MixQuats[0];
   end else begin //do mix
     WSumm:= PSB.MixWeights[0];
     QW:=    PSB.MixQuats[0];
     for i:= 0 to PSB.MixCount-2 do begin
       WSumm:=WSumm + PSB.MixWeights[i+1];
       Weight:=1-PSB.MixWeights[i+1]/WSumm;
       QW:= TD3DQuaternion.QuaternionSlerp(PSB.MixQuats[i+1],QW,Weight);
     end;
     PSB.currentpose.LocalRotation:=QW;
   end;
 end;
end;

13 янв. 2019, 18:08 (Правка: 18:10)

#14

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

13 янв. 2019, 21:23 (Правка: 21:24)

Страницы: 1 2 Следующая »
ПрограммированиеФорумОбщее