Skip to content

Added One Material per Mesh Func & "GlobalBounding" Splitting Strategy - #99

Open
gojushin wants to merge 5 commits into
OpenDroneMap:masterfrom
gojushin:feat_oneMatPerMesh
Open

Added One Material per Mesh Func & "GlobalBounding" Splitting Strategy#99
gojushin wants to merge 5 commits into
OpenDroneMap:masterfrom
gojushin:feat_oneMatPerMesh

Conversation

@gojushin

@gojushin gojushin commented Jul 28, 2026

Copy link
Copy Markdown

PR Summary

Adds support for --single-material-per-part a new GlobalBounding split strategy (--split-strategy GlobalBounding)

Single-material mode repacks each tile’s used diffuse and normal textures into a shared atlas, remaps UVs, and emits one material per part.

This is especially useful if the source mesh has UDIMs that are vastly scattered across the mesh.

The GlobalBounding split strategy creates a stable, source-derived square grid shared across LODs, with optional Z splitting based on the source bounds.

Both options are non-breaking changes, as they only extend existing functionality by providing additional CLI args.

Related Issue: None

What I did so far

  • Implemented the described functionality.
  • Did a full self-review to the best of my ability.
  • Did a full self-test to the best of my ability.
  • Updated Documentation.
  • Updated Test Cases.

Remarks

  • The Atlas Merge does not take into account tint or opacity/alpha values from the .mtl.
    In theory the other implementations could handle this better as materials are treated individually, but as far as I can tell the implementations mostly disregarded these too, which... assuming the kind of input we are working with... is fair.

  • GlobalBounding Splitting also supports z-splitting, but functionality for it might work counterintuitively.
    Inputs are usually way larger in width and height, then in depth. Using --zsplit would create a longestSide³ Volume, which would almost always encapsulate all parts of one mesh in height and not perform any splits on z.
    To provide meaning to --zsplit, it uses sourceBounds.Min.Z and sourceBounds.Max.Z, splitting across the entire height of the mesh.
    (I documented this in both the code and in the README)

@gojushin gojushin changed the title Added One Material per Mesh Func & "GlobalBounding" Splitting Strategy and Added One Material per Mesh Func & "GlobalBounding" Splitting Strategy Jul 28, 2026
@gojushin

Copy link
Copy Markdown
Author

After Testing, I can certainly say - this needs some more work.
While it generally seems to work, --single-material-per-part is extremely slow at the moment, taking upwards of 16 hours per tile on a high end workstation at LOD1.

image

I will look into it.

@gojushin

Copy link
Copy Markdown
Author

Sooo. The issue is that the current BinPacking algorithm, which I reused for this, can't handle the massively higher amount of charts very well, with its O(N²) - O(N³) speed....

I implemented shelf packing, which under O(N log N), handles packing within seconds but is a clear degradation in quality.

I am currently looking into how I can combine the two... but this could take a bit of time to get right. Please hang tight :)

@gojushin

gojushin commented Jul 30, 2026

Copy link
Copy Markdown
Author

In the long run, the only correct way to do this is probably to re-unwrap & bake the reduced parts. But that is likely out of scope here (and not really the point of the repo anyways).

- Implemented Skyline Packing
- Adjusted MeshT.MergeMaterialsIntoSingleAtlas to use SkylinePacking instead of ShelfPacking
- Implemented various Edge Case Mitigations in MeshT.MergeMaterialsIntoSingleAtlas
- Added Tests for the new logic
@gojushin

gojushin commented Aug 6, 2026

Copy link
Copy Markdown
Author

Well, this was an unexpected deep dive into rectangle packing algorithms.

It turns out shelf packing never achieved what I was looking for. Therefore I made the following changes to finally achieve what I wanted:

  1. I ported the SkylinePacking Algorithm from Jukka Jylänki to C#. While this introduces a new algorithm, it at least is in line with the existing MaxRect's Algo and can be used as a drop in replacement. I did however drop the waste map from the original implementation for simplicities sake. In my testing the Algorithm works well enough like this.
    --single-material-per-part exclusively uses the LevelBottomLeft Heuristic to provide the required speed. In my testing I could not find a degradation in overall chart occupation.

  2. I combined the packing with a binary search to approximate the optimal occupation. This comes with the downside of partially vastly different texel density. This can be counteracted by running with different parameters. (--max-texture-size 0 causes a uniform texel density, but creates textures of different sizes).

Edge cases:

  1. When collapsing edges with the current methodology and then packing all the charts of new parts into one atlas (each) at a "lowish" texture size we end up with a ton of <1px charts per atlas. There is no risk of bleeding for these charts as they fully fit into one pixel. Therefore I decided to drop padding for them, in order to fit a lot more charts, triggering the next fallback way less.

  2. When --max-texture-size is set, it can still happen that, depending on the input quality (i.e. a lot of chart fragmentation), so many charts must be packed that the defined texture size just isn't enough. Instead of hard failing, the atlas grows beyond --max-texture-size to the absolute minimum that can fit all charts at the smallest possible size.

  3. I of course updated tests and the documentation accordingly.

At any point a edge case is reached (which happens quickly), the user gets a WARNING printed to the terminal with the relevant stats. This should indicate a change in input parameters as a lot of quality is lost at that point. For example even .jpg compression actually starts to have a noticeable overall impact, when charts become 1px in size.

In my testing it visually looks..... okay enough, given that charts will be very far away at that point.
tldr: I think that edge cases are handled as neatly as possible.

Also. Noteworthy for a PR review:
This PR does not change any existing logic, just adds new one on top ^^.

There are a couple instances, where I changed minor things, including:

  1. Promoted Padding to a global const in MeshT.cs
  2. Moved some logic around for SplittingStage.cs as its not shared logic anymore with the new GlobaBounding option.
  3. Added a branch to change rootTileTextureSize in Program.cs when --single-material-per-part is set.

This has become quite the big PR at this point... sorry for that😋.

Anyhow. Everything's ready for review.

@gojushin
gojushin marked this pull request as ready for review August 6, 2026 16:51
@HeDo88TH

Copy link
Copy Markdown
Collaborator

Great work! Before merging I need to make sure it doesn't collide with #100

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants