Про перенос объектов между Blender и UE5

·

Если вам по какой-то причине потребовалось экспортировать из Blender трансформы объектов и распарсить их в Unreal Engine, но в движке всё какое-то кривое и неправильное – эта заметка для вас.

Geometry Nodes в текущем виде – очень крутой инструмент, который позволяет автоматизировать многие процессы в левел-дизайне. Но переносить всё это вручную в Unreal Engine как-то не хочется, поэтому потребовалось автоматизировать процесс через экспорт в JSON и парсинг в движке. И у этого процесса есть некоторые особенности (во многом очевидные, но всё же).

Момент 1: разное направление оси Y

В Blender направление «вперёд» – Y+, а в Unreal Engine – Y-. Поэтому для сохранения положения объектов относительно друг друга нужно инверитровать Y в Location.

Если этого не сделать, объекты поменяются местами при одинаковых координатах. Обратите внимание: если мы будем смотреть на объекты 1 и 2 в проекции спереди в Unreal Engine, объект 2 окажется впереди объекта 1, хотя должно быть ровно наоборот. Поэтому нужно флипать знак у Y.

Момент 2: направление вращения

В Blender вращение по часовой стрелке отрицательное, а в Unreal Engine – положительное. Флипаем знак у поворота по осям Y и Z (X не трогаем), чтобы с этим не было проблем.

Момент 3: юниты

Самый очевидный момент: в Blender дефолтные юниты – метры, а в Unreal Engine — сантиметры. Умножаем все компоненты Location на 100 независимо от выбранных юнитов в сцене.

Момент 4: поворот в радианах

Не забываем, что obj.rotation_euler() это поворот в радианах, а ротаторы в Unreal Engine обычно в градусах, поэтому нужно их либо сразу записывать в градусах, либо конвертировать R2D перед созданием ротатора в Unreal Engine.

Момент 5: проблемы с негативным скейлом

Иногда вместо создания полноценный копии меша мы делаем копию с инвертированным скейлом, чтобы получился «миррор» геометрии. А иногда такая геометрия получается, когда мы создаём инстансы объекта через Geometry Nodes/Particles. Например возьмём вот такой подоконник:

Он «реализован» из инстанса с помощью оператора Make Instances Real. Как можно заметить, у него все скейлы негативные и помимо ротации вокруг оси Z есть ротация вокруг X, для компенсации негативного размера по Z.

Проблема в том, что если мы будем эти инстансы обходить через Despgraph:

Python
evaluated_obj = obj.evaluated_get(depsgraph)
for inst in depsgraph.object_instances:
    if inst.is_instance and inst.parent == evaluated_obj:
        # берём тут данные из инстанса inst.object
   

Мы не сможем взять и просто прочитать трансформы инстанса как готовое поле типа inst.object.rotation_euler – они будут нулевыми. Поэтому нужно брать inst.object.matrix_world и оттуда выдергивать все трансформы, но есть загвоздка, которую можно увидеть на реализованном инстансе:

Python
>>> C.object.rotation_euler
Euler((3.1415927410125732, -0.0, 0.643485426902771), 'XYZ')
>>> C.object.matrix_world.to_euler()
Euler((8.742277657347586e-08, 0.0, -2.4981071949005127), 'XYZ')

Ротация, полученная из matrix_world, не имеет то самое вращение на 180 градусов вокруг оси X, которое нужно для компенсации негативного размера. И именно эту разницу лично я заметил далеко не сразу, долго пытаясь понять, почему часть объектов повернута непонятным образом.

Решается это следующим образом:

Python
from mathutils import Matrix

world_matrix = obj.matrix_world

rot = world_matrix.to_euler()
scale = world_matrix.to_scale()

scale_matrix = Matrix.Diagonal(scale)
rot = (rot.to_matrix() @ scale_matrix).to_euler()

Выделяем скейл и ротацию в отдельные матрицы: Matrix.Diagonal создаёт матрицу масштаба из вектора, а у класса Euler есть метод to_matrix() возвращающий матрицу вращения. Затем комбинируем эти матрицы и получаем корректную ротацию объекта.

Comments

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *