Однажды у меня была задача, состоявшая в перезапекании большого количества моделей самих на себя, но в другую UV развёртку.
Однако в процессе Я столкнулся с тем, что в запеченных текстурах получалось большое количество артефактов. Мелких, но неприятных. Замазюкивание косяков вручную подошло бы для 1-2 раз, но в случае постановки этого на конвейер пришлось придумывать что-то ещё.
Причины
Причиной было то, что при запекании проецирующие «лучи» начинают свой путь на небольшом расстоянии от поверхности. И если между желаемым местом пересечения и реальным местом начала луча что-то есть — увы, на проекцию попадёт именно оно.
Решение
Решением стала «разноска» отдельных составляющих (фрагментов) модели на расстояние друг от друга. Это должно обеспечить достаточное расстояние между фрагментами модели для того, что бы лучи запущенные в один фрагмент, не попадали части других фрагментов.
Сделал я это дополнив плагин BakeLab2 дополнительной опцией разноса модели перед запеканием.
Для удобства чтения — вынес этот функционал в отдельный скрипт, который можно запустить напрямую в блендере (проверялось на 4.2 LTS)
Скрипт создаёт копию выделенного ассета, и применяет к нему разбиение на самостоятельные куски. После этого каждый отдельный кусок сдвигается вперёд так, что бы не пересекаться с предыдущими. В конце это всё обратно объединяется в единый, но «разнесённый» меш для удобства запекания.
import bpy
def get_world_scale(o):
return o.matrix_world.to_scale()
def SelectObject(obj):
bpy.ops.object.select_all(action = 'DESELECT')
if obj:
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
obj_selected = bpy.context.selected_objects[0] #saving selected object ref
obj_selected_name = obj_selected.name
obj_selected_data_name = obj_selected.data.name
obj_selected.name= obj_selected_name+"." + str(998)
obj_selected.data.name = obj_selected_data_name + "." + str(998)
#duplicating object
bpy.ops.object.duplicate(linked=False)
obj = bpy.context.selected_objects[0]
SelectObject(obj)
bpy.ops.mesh.separate(type='LOOSE')
# shifting parts
shft = 0
shft1 = 0
for nr, obj in enumerate(bpy.context.selected_objects):
shft = shft + max(obj.dimensions)*(1/get_world_scale(obj)[2])
if shft1 == 0:
shft1 = shft
obj.location = (obj.location[0], obj.location[1], obj.location[2] + shft)
shft = shft + max(obj.dimensions) * (1 / get_world_scale(obj)[2])
#clearing custom normals
bpy.context.view_layer.objects.active = obj
bpy.ops.mesh.customdata_custom_splitnormals_clear()
# rejoin
bpy.context.view_layer.objects.active = bpy.context.selected_objects[0]
bpy.ops.object.join()
Собственно дальше плагин создаёт копию разнесённого меша и запекает один на другой.
Отредактированный мною плагин ещё с тех пор лежит на Гитхабе. Но Я так и не дошел до того, что бы отредактировать README, дописав туда добавленный мною функционал. Если однажды Я доведу репозиторий до ума — обязательно выложу ссылку.
Заключение
В заключение хочу заметить, что это решение не является абсолютным. Если в меше самопересекается цельная геометрия, то такой подход сам по себе не поможет. В таком случае нужно предаврительно отделить самопересекающиеся части в отдельные фрагменты, уже после чего делать всё то, о чем я писал выше.