Search Unity

Least painful way to assign the same material to multiple submeshes.

Discussion in 'Scripting' started by hurleybird, Nov 7, 2019.

  1. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    So, let's say I have a mesh with three sub meshes, each with its own material, and for reasons I won't go into these materials pretty much need to be distinct.

    And I want to add a fourth material to apply to each and every submesh. In this case, a variety of status effects that modify the look of each model.

    And no, I'm not going to add x number of status effects to the same shader. That's a non-starter.

    The problem is that if I add another material to the MeshRenderer it will only apply to the last submesh. This looks to be by design and is a rather... weird and limiting design choice. And I would suggest that having additional materials apply to either all submeshes, or the submesh based on modulo submesh count would make infinitely more sense.

    Or, best yet, the MeshRenderer could expose a more robust materials list. Either an array for each submesh, or a field for each submesh for the main texture + and an array for universally applied materials, or some combination of the two.

    Any way, the least painful way I can think of to work around this bewildering limitation is to make an additional submesh that is just all of the previous submeshes combined into one as the last submesh. Obviously this is far from ideal (to put it lightly). But all the alternatives I can think of are completely impractical from a production standpoint.

    Does anyone have a better solution to this conundrum?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    Submeshes in Unity correspond one-to-one with the Materials array. You can't have two Materials on a given Submesh.

    If you make a fourth Submesh that is the union of all the triangles in the other Submeshes, that geometry is going to render at precisely the same place, causing either Z fighting, or one or the other triangle not showing up.

    One approach is to insert a second colocated camera in your scene that re-renders the character in question with your effects material instead, basically keeping a clone of it but forcing it to render second to the character. If you expect other geometry to obscure your character, this will need to be handled because the effects will go in front of that obscuring geometry. There's probably other shader-y approaches but sounds like that doesn't work for you.
     
  3. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    Yes you can. The limitation is that you can only assign multiple materials to the last submesh.

    Right, that very well could be the case. I suppose one solution would be to increase the size of the last submesh slightly.
     
  4. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    Okay, script was actually pretty easy.

    Code (CSharp):
    1. mesh.subMeshCount = mesh.subMeshCount + 1;
    2. mesh.SetTriangles(mesh.triangles, mesh.subMeshCount - 1);
    I hadn't actually expected that to work, since I was just testing to see what would happen. Oddly enough, no Z-fighting nor any issues with UVs despite not calling Mesh.SetUVs. Triangle count is doubled. Setting the Mesh.subMeshCount = mesh.subMeshCount - 1 changes the Mesh back and correctly halves triangle count.

    It's significantly more involved, but it should even be possible to do all this at runtime to avoid the performance hit of doubling the triangle count when there aren't any status effects applied. But that's for another day.

    There's absolutely no reason I can see that all of this shouldn't be possible with the default MeshRenderer though. If it's possible to set more than one material on the last submesh then the inability to do that on all the others seems like an artificial limitation. There's plenty of use cases for this kind of thing, so it's frustrating that Unity is so rigid. If we can't modify the source (like we can with Unreal Engine), then don't be arbitrarily rigid!
     
    Shin_S likes this.
  5. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
    My understanding of submeshes is the same as Kurt-Dekker's, so I'm confused what you mean when you say that you can assign more than one material to the last submesh.

    Say you have three submeshes, then MeshRenderer.materials[0] should be the one and only material for the first submesh, MeshRenderer.materials[1] for the second, and MeshRenderer.materials[2] for the third. How are you assigning multiple materials to the "last submesh"?
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    He's not. His codelet above is simply producing an additional submesh that is the sum of all the triangles already in the .triangles list, and setting it to be the final mesh.

    That just means he's redrawing the entire set of triangles as a single final Submesh using this fourth material.

    There could be reasons deeper down the API call chain or in the actual C++ engine layer that require this enforcement. Notably the contract is quite explicit in the API:

    https://docs.unity3d.com/ScriptReference/Mesh-subMeshCount.html

    I quote, "Each sub-mesh corresponds to a Material in a Renderer."
     
    alysonlupo likes this.
  7. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    Your and Kurt_Dekker's understanding is incorrect.

    Shouldn't be too confusing. Take a MeshRenderer, add an extra material and assign it. You'll observe that the new material renders on top of the old one. If the new material is transparent you will observe that the original material is still rendered underneath. But if you have more than one submesh the new material only applies to the last submesh.
     
  8. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    What's so special about the last submesh that it can accept multiple materials compared to every other submesh? Obviously you're correct that there could be some meaningful albeit unintuitive reason. And if that's the case I'd prefer to hear it from the horses mouth, but from where I stand it looks more like a design issue.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    I think you are misinterpreting what is happening as "can accept multiple materials."

    By adding a fourth material you have simply supplied a fourth material. Since there were only three meshes, they use materials 0, 1 and 2, and politely ignore your material 3.

    I just ripped out a quick test with a two-submesh object. The presence or absence of the third material has zero impact on what is rendered.

    Screen Shot 2019-11-07 at 3.54.42 PM.png

    Note the little yellow caution sign and absence of blue in the scene. Well, I mean apart from the sky.
     
  10. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    That test does looks like the third material is discarded, but you can read the warning "Multiple materials will be applied to the same submesh." And I've been doing similar tests and I do see multiple materials applied to the same submesh. May have something to do with the shaders involved. Give me a moment to put something together.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    I fiddled some more and it does appear that if I delete the mat2 material reference above, what was formerly mat2 is now rendered with THIRD material.

    I take this to mean Unity just re-blasts all the same geometry of the last submesh using the extra material. It sorta feels like that was just done to accommodate defective FBX file makers that mis-number materials or meshes or something, rather than an intentional use case.

    Personally, I would not rely on it, but if it simplifies your asset creation flow, best of luck!
     
    hurleybird likes this.
  12. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    It is working the way I described, as observed here:

    Vindication.png

    So what I think you're seeing is that Unity renders the materials in reverse order. Eg. the third material is rendered, then the second. But transparent materials are rendered after opaque regardless. That's why it works when you place a material with transparency in the extra slot.

    It's also possible there's something much more convoluted happening, but the above explanation seems likely.

    And yes, I think you may be correct about the geometry. You can observe verts going up in the stats panel. It's basically what I'm doing with my workaround. It's just that the workaround is extremely inconvenient and you need to do a lot of extra work to not pay the performance penalty when that extra material isn't needed (which is 99% of the time in my case). If that's the only thing going on though, then there's absolutely no need to restrict this to only the last submesh and it's just an annoying quirk of MeshRenderers.
     
    Last edited: Nov 8, 2019