Если вам по какой-то причине потребовалось экспортировать из 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:
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
и оттуда выдергивать все трансформы, но есть загвоздка, которую можно увидеть на реализованном инстансе:
>>> 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, которое нужно для компенсации негативного размера. И именно эту разницу лично я заметил далеко не сразу, долго пытаясь понять, почему часть объектов повернута непонятным образом.
Решается это следующим образом:
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()
возвращающий матрицу вращения. Затем комбинируем эти матрицы и получаем корректную ротацию объекта.
Добавить комментарий