Коротко — проблема не в «плохом» SMPL, а в системе отсчёта и в том, что у двух скелетов разные «rest»‑позы и локальные оси. Решение — вычислить относительную ротацию (дельту) между SMPL‑позой и SMPL‑rest и применить эту дельту к rest‑позам вашей игровой модели (с учётом картирования костей и возможных преобразований осей). Ниже — подробный план + формулы и пример для Godot.
1) Подготовка
- Сопоставьте (map) SMPL‑суставы с костями вашей модели (например: smpl: "right_knee" -> game: "thigh.R"). Это ключевой шаг.
- Убедитесь, что у обеих моделей есть rest‑поза (T‑pose или аналог). Если SMPL и игровая модель в разных rest‑позах, нужно учитывать оффсеты (см. далее).
- Определите системы координат (правосторонняя/левосторонняя, направления осей). SMPL обычно RH с +Y вверх (но проверьте вашу реализацию). Godot — правосторонняя, Z‑вперёд, Y‑вверх. Может потребоваться перестановка осей или зеркалирование.
2) Идея метода (delta‑retargeting)
Для каждой пары (smpl_joint, game_bone):
- Вычисляем глобальные трансформы (в мире) для SMPL в rest и в позе:
Gs_rest, Gs_pose (матрицы или кватернионы + позиции).
- Берём глобальный трансформ rest для target bone:
Gt_rest.
- Находим дельту между SMPL‑позой и SMPL‑rest:
Delta = Gs_pose * inverse(Gs_rest)
(это вращение/трансформация, которая переводит SMPL из rest в pose).
- Применяем эту дельту к мировой rest‑трансформации target‑кости:
Gt_pose = Delta * Gt_rest
- Конвертируем мировую Gt_pose в локальную (относительно родителя) для установки в движке:
Lt_pose = inverse(Gt_parent_rest) * Gt_pose
(если вы устанавливаете локальные позы). Если движок умеет устанавливать глобальные позы, можно сразу дать Gt_pose.
Пояснение: мы переносим «что сделала SMPL по отношению к своей rest» и применяем тот же относительный эффект к нашей rest.
3) Учтите корректировки осей и rest‑офсеты
- Если оси суставов/костей отличаются (SMPL локальные оси ориентированы «как мировой»), потребуется матрица смены базиса C: преобразуйте Gs_pose и Gs_rest в ту же систему координат, что и Gt_rest:
Gs_pose' = C * Gs_pose * C^{-1}
Gs_rest' = C * Gs_rest * C^{-1}
Delta = Gs_pose' * inverse(Gs_rest')
- Часто проще заранее вычислить для каждой пары коррекционный кватернион/матрицу R_corr, который вы умножаете:
Gt_pose = R_corr * Delta * Gt_rest
где R_corr вы вычисляете экспериментально как «чтобы rest оси совместились».
4) Работа с древовидностью (последовательность)
- Если устанавливаете локальные ротации по одному костю, делайте это от корня вниз по иерархии (чтобы parent pose использовался корректно).
- Если используете глобальные оверрайды/установку глобального трансформa (например, Godot set_bone_global_pose_override), порядок не так критичен.
5) Позиция root
- Копируйте root‑позицию (перенос/смещение) отдельно: Gt_root_pose.position = transform_position_from_SMPL (учтите масштаб и оси).
- Если SMPL root находится не в том же месте, может понадобиться вычислить глобальный сдвиг и применить к игровому root.
6) Проблемы со скиннингом (веса)
- Если вы просто ставите позы игровых костей и mesh использует те же веса — всё ок.
- Если вы хотите перенести деформированную сетку SMPL напрямую как вертексы — это совершенно другой процесс (нужно переносить веса или переложить деформацию на другую сетку), обычно не делают.
7) Godot: практическая рекомендация / пример (GDScript)
Самый простой путь в Godot — задавать глобальные позы костям через set_bone_global_pose_override, тогда не придётся сами переводить в локальные. Псевдокод:
# Предположим у нас заранее есть:
# - map: соответствие индексов SMPL -> индексы bone в Godot
# - функция get_smpl_global_transform(joint_idx) -> Transform (Basis+origin) в системе Godot
# - массив gt_rest[b] = глобальная rest трансформация кости b (получаем из Skeleton)
for each mapped pair (js, jb):
Gs_pose = get_smpl_global_transform(js) # из SMPL (приведённая к системе Godot)
Gs_rest = smpl_rest_global(js) # если есть
Delta = Gs_pose * Gs_rest.affine_inverse() # матричное умножение
Gt_rest = gt_rest[jb] # глобальный rest target bone
Gt_pose = Delta * Gt_rest
skeleton.set_bone_global_pose_override(jb, Gt_pose, 1.0, true)
Если у вас нет smpl_rest_global или SMPL rotations уже глобальные, Delta можно упростить.
8) Практические советы / отладка
- Визуализируйте оси костей (в Blender/Godot) и проверьте, куда направлена локальная X/Y/Z — часто ошибка в 90° повороте.
- Начните с простых суставов (плечо, локоть), проверьте отдельно.
- Если кость в игре «смотрит» в другую сторону, найдите корректирующую кватернион R_corr = q_target_rest * inverse(q_source_rest) и применяйте его.
- Проверьте масштабы: если SMPL в метрах, а ваша модель в сантиметрах — позиция/длина костей будут отличаться.
- В случае симметрии/зеркалирования (лево/право) учтите знаки осей.
9) Альтернативы / инструменты
- Ретаргетинг в Blender (export SMPL pose -> import into Blender -> retarget with built‑in tools or addons, then export FBX) — удобно для анимаций.
- Для realtime: используйте библиотеку ретаргетинга (например, Autodesk HumanIK, MotionBuilder, or custom retargeter) либо реализуйте delta‑метод выше.
10) Типичные ошибки и их причины
- Кости «крутятся» вокруг другой оси — неверный change‑of‑basis (оси).
- Суставы смещаются в пространстве — не скопирована root‑позиция или различен rest‑поз.
- Деформация некорректная — веса скиннинга отличаются или трансформы задаются в неверной системе.
Если хотите, могу:
- Посмотреть конкретные данные (пример SMPL‑матриц или кватернионов и rest‑трансформ вашей модели) и написать точный код для конвертации;
- Подготовить GDScript, который берет SMPL poses (axis‑angle) и прямо в Godot устанавливает костям глобальные позы с учётом коррекций осей.