Hi all!
I'm facing the following issue:
I have 3d models with multiple material IDs, each with its own texture:
In order to increase efficiency and reduce the number of draw calls ,
I want to unify all of those into a single UV atlas, like so:
However, as you can see, it repeats the same textures over and over across the UV space (highlighted a few with matching colors to exemplify):
I'm trying to use auto Unwrap UVW and UV-packer script in 3ds max.
Is there any way to, while keeping automatic, unwrap the textures by material ID, so that each texture only appears once on the UV space?I'm not restricted to 3ds max solutions, I'm open to any suggestions in different packages.
Thanks!
Replies
i think to do it as part of an auto unwrap you'd have to script it
def shift_uv(uv_layer, faces, u_shift, v_shift):
for face in faces:
for loop_index in face.loop_indices:
loop_uv = uv_layer.data[loop_index].uv
loop_uv.x += u_shift
loop_uv.y += v_shift
def get_udim_shift(material_id):
row = material_id // 4
col = material_id % 4
u_shift = col # Shift right
v_shift = -row # Shift down
return u_shift, v_shift
def get_collective_uv_bounds(objects):
min_u, min_v = float('inf'), float('inf')
max_u, max_v = float('-inf'), float('-inf')
for obj in objects:
if obj.type == 'MESH' and obj.data.uv_layers:
uv_layer = obj.data.uv_layers.active
for uv in uv_layer.data:
min_u = min(min_u, uv.uv.x)
min_v = min(min_v, uv.uv.y)
max_u = max(max_u, uv.uv.x)
max_v = max(max_v, uv.uv.y)
return min_u, min_v, max_u, max_v
def normalize_uv(objects, min_u, min_v, max_u, max_v):
scale_u = 1.0 / (max_u - min_u)
scale_v = 1.0 / (max_v - min_v)
for obj in objects:
if obj.type == 'MESH' and obj.data.uv_layers:
uv_layer = obj.data.uv_layers.active
for uv in uv_layer.data:
uv.uv.x = (uv.uv.x - min_u) * scale_u
uv.uv.y = (uv.uv.y - min_v) * scale_v
def pack_udim_and_normalize_collectively():
selected_objects = bpy.context.selected_objects
# Step 1: Shift UVs based on material ID for each object
for obj in selected_objects:
if obj.type == 'MESH':
mesh = obj.data
if not mesh.uv_layers:
continue
uv_layer = mesh.uv_layers.active
for mat_id, material in enumerate(mesh.materials):
faces = [face for face in mesh.polygons if face.material_index == mat_id]
u_shift, v_shift = get_udim_shift(mat_id)
shift_uv(uv_layer, faces, u_shift, v_shift)
# Step 2: Calculate collective bounds of all UVs across selected objects
min_u, min_v, max_u, max_v = get_collective_uv_bounds(selected_objects)
# Step 3: Normalize all UVs based on collective bounds
normalize_uv(selected_objects, min_u, min_v, max_u, max_v)
# Run the function
pack_udim_and_normalize_collectively()
personally I'd recommend working out how to write the code yourself - at least you'll be able to see where the robot makes mistakes
@poopipe I couldn't make it work with several geometries as in the example.
I'm using Unwrap - Edit Uvs Mapping -Flatten - material ID checked
Instead of overlapping the textures with the same material ID, they are still split into different shells:
@gnoop This is fantastic! It will take me a minute to fully understand it but I'll dive into it this week. Thank you very much for the file and detailed answer!
I might give it a try to the scripting side as well
fwiw. i've usually dealt with this sort of thing by merging everything into the same mesh and breaking it up by ID before packing. you lose your pivot info doing that but there's ways to solve that problem if it's important.
gnoops solution seems like a good shout though - if you don't mind going through blender it's proably the path of least resistance
You detach you mesh by material ID and unwrap each object individually, then combine then e pack the final mesh into a single UV atlas?
@Eric Chadwick Thanks for sharing your method. Could you explain a little bit more about your process?