Grim-es / material-combiner-addon

Blender addon for material combining, uv bounds fixing
MIT License
450 stars 36 forks source link

Export out multiple atlas's #85

Open Benboesel opened 6 months ago

Benboesel commented 6 months ago

I have a model that contains thousands of 512x512 textures (around 4000 individual textures). Is there a way to use this tool to simplify the number of materials without compressing the data or creating a texture that is larger than 16384x16384?

In my case with 512 textures and 4000 materials, the desired output would be four 16384x16384 textures that each contain a 32x32 grid of 512 textures. When I've tried to use the tool today I can only get one texture where the individual textures get compressed, not multiple atlas textures output.

Thanks so much!

Grim-es commented 6 months ago

If you're working on a Unity project, a potentially simpler approach might involve utilizing Texture Arrays and either finding a program or a GitHub script that can resize all the images within a folder. Within Unity, you'll then need to create your own Texture Array. This process will require a specific or modified shader, which may also need to support SPS-I (or from Unity Docs) to ensure that rendering behaves as expected on various devices, including the Quest. Additionally, creating a texture array necessitates a script for generating it from your texture files, which you can either find or create yourself. You can utilize the second UV map's X coordinate as an index to determine which texture to use.

Furthermore, you can utilize that program or GitHub script to downscale textures and replace the defaults with them if the file names match. However, you might need to press "File > External Data > Unpack all into files" in Blender first, because the combiner uses packed images, but you'll be editing the actual files.

Typically, to resize materials using the combiner, you can change the size settings in the gear options on a per-material basis. However, with 4000 materials, it would be more efficient to create a small Blender script or command to automate this process.

If you encounter issues with textures not being properly atlased, it might be due to unsupported shaders or incorrect node names. The combiner relies on node names to identify which nodes to use. The easiest approach is to use a Diffuse BSDF node, as it has only one input, and check that its "name" property is set to "Diffuse BSDF" without translations. And the Image Texture node has "Image Texture". You can find the name property in the Shading tab when you select the node and press the 'N' key to reveal the right-side panel in the nodes window. Information on supported shaders and the expected node names can be found in this script: utils/materials.py.

Benboesel commented 6 months ago

Hey Grim-es! Thank you so much for your response!

Your tool is actually working great if I select a subset of my mesh, but when I select the whole thing I get an error that says the output would be too large. So far, the only option that works is setting the size to custom, but that causes unwanted compression of the textures into one atlas.

I am hoping to automatically pack the textures up to a limit (16384x16384) after which it would start a new atlas, resulting in an output that contains multiple atlases / materials. Is that possible? I started looking through your code to see if there would be an easy modification that basically separates out the textures into multiple atlases after one reaches the maximum 16384x16384, but have not figured it out yet.

Thanks so much, Ben

Grim-es commented 6 months ago

Implementing atlas separation can be challenging currently, as it uses a basic bin-packing method. Someday it would be great to add more advanced packer(s) that can create multiple bins when the atlas exceeds certain dimensions.

Currently the tool generates one large atlas of unlimited size and then checks to see if it has been exceeded. The maximum allowed size can be changed here. You can then create an atlas (but it has some memory limits and may not work), manually split it into smaller images, and adjust the corresponding UVs. This adjustment involves proportionally scaling the UVs based on the size of the cropped image relative to the overall size of the atlas, and manually adjusting the UVs by calculating the required X and Y coordinates to ensure they are properly centered in the image.

Benboesel commented 6 months ago

Hello again! I found a reasonable workaround for this issue! I wrote a script that iterates through all my meshes combining them in groups of 1024 (which with 512x512 textures on each model, the atlas from each group is a nice 16384x16384 grid). I then toggle on objects each group individually and export out an atlas for each group.

Because I am dealing with massive data sets and each atlas export takes a while, I am wondering if I can add the atlas export to my script? Is there a way call the "save atlas to" function from a script? If so, I could iterate through each of my groups, and save the atlas out!

Here's my script if you are curious:

`import bpy objCount = 0 parentList = []

Group objects into parents with no more than 1024 meshes.

This also removes unwanted empty objects

for obj in bpy.data.objects: if obj.type == 'MESH': if(objCount % 1024) == 0: bpy.ops.object.empty_add(type='PLAIN_AXES') parent = bpy.context.active_object parentList.append(parent) world_loc = obj.matrix_world.to_translation() obj.parent = parent obj.matrix_world.translation = world_loc objCount += 1

bpy.ops.object.select_all(action='DESELECT')

Join all the models together that contain the same group parent

for parent in parentList: for child in parent.children: child.select_set(True) joiner = parent.children[0] bpy.context.view_layer.objects.active = joiner bpy.ops.object.join() bpy.ops.object.select_all(action='DESELECT')`

Grim-es commented 6 months ago

You can use the following Python command within your script to initiate the atlasing process and specify the folder where the atlased images should be saved:

bpy.ops.smc.combiner('EXEC_DEFAULT', directory='C:/save/directory/path', cats=False)

You can iterate through objects in your scene and selectively hide those that should not be atlased during the current iteration. Objects that are hidden will automatically be excluded from the atlasing process.