Для того чтобы отразить объект относительно какой-либо оси (X, Y или Z) в Blender обычно применяется оператор miror. Например, чтобы отзеркалить объект по глобальной оси X, в меню окна 3D вьюпорта нужно выбрать: Object – Miror – X Global. Или можно поступить проще и просто применить к объекту масштаб -1 по нужной оси: s – x – -1. Однако, если на объекте присутствуют кастомные нормали, они не пересчитываются, что в дальнейшем вызывает появление различных артефактов.
Возьмем для примера объект “Сюзанну” и добавим на него кастомные нормали. В рабочей области Properties откроем вкладку Object Data и в разделе Geometry Data нажмем кнопку Add Custom Split Normals Data. Во вкладке Modifiers добавим объекту модификатор NormalEdit и установим произвольные значения. Теперь, если отзеркалить объект стандартным образом (s – x – -1) и применить к нему масштаб (ctrl + a – Scale) визуально станет видно, что нормали “поехали”. Обратите внимание, что в то же время модификатор Mirror зеркалит объект правильно, учитывая кастомные нормали.
Отзеркалить объект относительно оси X с правильной конвертацией кастомных нормалей без использования модификатора Mirror можно при помощи следующего скрипта:
Автор изначального кода Joey (blender.stackexchange).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import bpy me = bpy.context.object.data me.calc_normals_split() me.use_auto_smooth = True mirror_split_normals = [] for iter_poly in me.polygons: reverse_normals_indices = [iter_poly.loop_indices[0]] + list(reversed(iter_poly.loop_indices[1:])) for i in reverse_normals_indices: mirror_split_normals.append((me.loops[i].normal[0] * -1, me.loops[i].normal[1], me.loops[i].normal[2])) for v in me.vertices: v.co = v.co[0] * -1, v.co[1], v.co[2] me.flip_normals() me.normals_split_custom_set(mirror_split_normals) |
В этом скрипте
1 2 |
me.calc_normals_split() me.use_auto_smooth = True |
мы сначала пересчитываем кастомные нормали и включаем режим автосглаживания, который необходим для работы с ними.
Далее для каждого полигона меша мы разворачиваем все лупы в противоположную сторону, оставляя начальную точку неизменной.
1 |
reverse_normals_indices = [iter_poly.loop_indices[0]] + list(reversed(iter_poly.loop_indices[1:])) |
После чего нормаль на каждом лупе отражаем относительно оси X и сохраняем в отдельный список.
1 |
mirror_split_normals.append((me.loops[i].normal[0] * -1, me.loops[i].normal[1], me.loops[i].normal[2])) |
Выполняем собственно отзеркаливание меша относительно оси X:
1 2 |
for v in me.vertices: v.co = v.co[0] * -1, v.co[1], v.co[2] |
Разворачиваем нормали
1 |
me.flip_normals() |
И добавляем пересчитанные кастомные нормали, отраженные относительно оси X.
1 |
me.normals_split_custom_set(mirror_split_normals) |
Если теперь мы к отзеркаленному мешу для проверки кастомных нормалей применим, например, масштаб, мы увидим что они обрабатываются корректно.
Для такого же отзеркаливания относительно остальных осей, в скрипт нужно внести минимальные изменения, чтобы пересчитывать нужную (Y или Z) составляющую нормалей.