Как перенести позу SMPL-модели на игровую 3D‑модель, учитывая различия в скелетах и скиннинге?

Описание проблемы: предполагаю, что ошибка связана с несоответствием локальных осей SMPL-модели и локальных осей костей в Godot. У SMPL локальные оси, по-видимому, совпадают с мировыми осями, тогда как в Godot локальные оси костей ориентированы вдоль самой кости.
  • 10 мая 2026 г. 16:01
Ответы на вопрос 2
Коротко — проблема не в «плохом» 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 устанавливает костям глобальные позы с учётом коррекций осей.
тут важная деталь: theta в SMPL — это локальные вращения, каждый джойнт относительно родителя в кинематической цепочке. только root (j=0) глобальный. имя smplGlobalBasis вводит в заблуждение. <br/> <br/> формула globalRestBasis.Inverse() * smplLocal теоретически ок, но только если rest-ориентации совпадают. а у SMPL T-pose кости в нулевой позе выровнены по мировым осям (identity rotation), у Godot-модели — вдоль направления кости. это и даёт вывернутые локти. <br/> <br/> грубо говоря нужна индивидуальная поправочная матрица для каждой кости, а не одна modelCorrection на всё тело.
Похожие вопросы