Search Unity

What is a viable substitute for MaterialPropertyBlocks in HDRP?

Discussion in 'High Definition Render Pipeline' started by PuckLovesGames, Oct 18, 2021.

  1. PuckLovesGames

    PuckLovesGames

    Joined:
    Mar 4, 2015
    Posts:
    17
    I have up to a thousand game objects, each with a half dozen materials, and I want to set a single unique int to the renderer of each one, to use as an index in the shaders to access a global vector array. What is the best way to do that in HDRP?

    Normally I'd use a MaterialPropertyBlock, but that isn't supported by the SRP Batcher.

    Creating several thousand material instances works, but is not a viable solution for the scale I need.
    Baking the int into the mesh vertices of each object works better, but still terribly inefficient compared to the simplicity and low overhead of MaterialPropertyBlocks.

    I'm not able to draw the renderers in any other way, such as via Graphics.DrawMeshInstanced due to other project limitations.

    What approach is recommended in HDRP for this situation? What other options are possible.
     
  2. Matjio

    Matjio

    Unity Technologies

    Joined:
    Dec 1, 2014
    Posts:
    108
    Note that you can use MaterialPropertyBlocks with HDRP, it is just that they won't be batched with the SRPBatcher but with the "built-in" batcher.
     
  3. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    What about lightmaps? They are passed through material property blocks, aren't they?
     
  4. PuckLovesGames

    PuckLovesGames

    Joined:
    Mar 4, 2015
    Posts:
    17
    I have tested using MaterialPropertyBlocks with HDRP, but although they work, the performance penalty of skipping the SRP Batcher makes them unusable - for the example I gave it adds over 10ms on all the unbatched passes (gbuffer, shadow casters, motion vectors etc). Unfortunately the existence of the SRP Batcher also disables GPU Instancing, so there's no way to mix and match approaches depending on the situation.

    I have managed to find a workaround, which is limited and not at all ideal, but for this particular situation does work:

    I can set my array index int on each renderer via Renderer.renderingLayerMask and then read it in the shader with asint(unity_ renderingLayer.x) - from https://twitter.com/ekakiya/status/1263347738009169922?s=20

    Apparently it's also possible to set renderer.realtimeLightmapScaleOffset and then read it in the shader via unity_DynamicLightmapST, as discussed here https://forum.unity.com/threads/materialpropertyblock-and-srp-batcher.815499/#post-5875117 - although I wasn't personally able to get this to work for some reason.

    It's wonderful this works, but this is a poor substitute for proper MaterialPropertyBlock support in the SRP Batcher.

    The fact that there are some data channels that can be used for per renderer data, while still preserve SRP Batching, makes me hopeful there could be a way in the future to add proper MaterialPropertyBlock support, or something like it. Even just a few custom data channels that aren't already earmarked by unity would be a huge step up from what is currently available.
     
    Aldeminor likes this.
  5. bac9-flcl

    bac9-flcl

    Joined:
    Dec 5, 2012
    Posts:
    829
    Just to chime in, I think that lack of MaterialPropertyBlock or equivalent per-renderer data support with SRP Batcher is a problem. What is the suggested workflow for something that needs per-instance changes across a huge set of repeating objects? If you have 100 or 1000 objects with the same mesh and material and need to send per-instance data to present instances differently (damage, color, etc.), what's the alternative to MaterialPropertyBlock? Using unique materials doesn't seem like a viable solution, so all that's left is completely bypassing SRP batcher (e.g. with draw commands and structured buffers). I'm not sure SRP batcher is useful for cases outside of uniform environments with immutable objects without support for this. Anything from forests with ability to push unique values to trees to modular environments with ability to display shader driven damage to piles of props with varying coloration won't be easy to do without this.

    To give a practical example, before we migrated to indirect drawing, we heavily leveraged built-in instancing together with material property blocks on Phantom Brigade. Our levels are built out of a small set of modular meshes wrapping a voxel grid and using a single unified material, with instance counts totaling up to 50000 objects. Each instance can have a unique color customization and damage state, allowing us to have a lot of visual variety and beautifully visualize changes to the environment without increasing number of calls as battles progress. On top of that, we have a prop layer containing thousands of objects like vegetation and vehicles sitting on top of base geometry, also leveraging instancing and material property blocks (allowing us to draw groups of objects 50 cars in one go despite each possessing a unique color or damage state). Without material property blocks, we likely would've never managed to get the game into a playable state in the shape we envisioned, it was an absolutely instrumental part of our kit for years - we would've had to completely redesign the way we structure our environments without it. We're now doing things in a more low-level manner not reliant on gameobjects but we continue using material property blocks in additional contexts still built out of gameobjects, and I firmly believe that support for per-renderer data is an absolutely essential part of Unity toolset that must be supported in any instancing/batching solution.

    I'd be totally fine with limitations along the lines of "no support for multi-material renderers" and absolutely certainly fine with "no support for keywords", but total lack of support for per-instance data outside of privileged internal bits like lightmap values is a bit strange.

    Maybe I'm missing something and there is another way to get per-instance data with SRP batcher, just named differently and independent from old concept of material property blocks? I only heard recommendation to simply use materials instead of property blocks, but I don't understand how using unique materials serves to fill the gap here. Do you create a 100 unique materials for every single step of _Damage value animating from 0 to 1 and progress through that mountain of materials over time on instances undergoing destruction animation? That's not smooth, but ok, that's doable. What if there can be a unique per-instance color on top of damage? Do you create material permutations for every possible value? Or do you instantiate arbitrary number of materials on the fly whenever you change any instance, maybe even use .material instead of .sharedMaterial to get a unique material object for every instance? I concede SRP batcher might be fast at combining instances with unique materials with same shader and can reduce SetPass calls you used to have with such an approach, but I struggle to believe that juggling hundreds, if not thousands of material objects frame to frame can be more performant and can really achieve what was easily achievable at scale with material property blocks. That's not even touching on drawbacks like completely losing ability to adjust one unified material to test material changes across whole instanced environment (which is impossible if environment is fragmented into thousands of materials).
     
    Last edited: Oct 21, 2021
  6. pastaluego

    pastaluego

    Joined:
    Mar 30, 2017
    Posts:
    196
    Did they ever end up adding an alternative to materialpropertyblocks for HDRP that plays nice with SRP Batcher since this post?
     
  7. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Place your properties that are per-material into the
    CBUFFER_START(UnityPerMaterial)
    block of an SRP compatible shader file.

    Instead of having a script on each object to set custom material properties, with an SRP compatible shader, all you have to do is access the .material value on a given object and modify its properties and it will still continue to batch, assuming you've set it up like I mentioned above. When you access .material a unique instance is created and assigned behind the scenes for you. You could also re-purpose old scripts that would assign some data to a MaterialPropertyBlock to instead simply assign them directly to the material of the object.

    https://blog.unity.com/technology/srp-batcher-speed-up-your-rendering
     
    Aldeminor likes this.
  8. pastaluego

    pastaluego

    Joined:
    Mar 30, 2017
    Posts:
    196
    Doesn't using MaterialPropertyBlocks to change per-renderer material properties still break SRP batching even if using CBUFFER_START? I also can't use .material because it can't be used in editmode and I want to be able to modify properties per-renderer in both editmode and playmode.
     
  9. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    The point is to not use a MaterialPropertyBlock anymore with that and just set the properties directly.

    For edit mode changes, you would make a new material yes.
    If you've got tons of objects that need unique material values, then you should really be looking into instanced rendering.
     
    Aldeminor likes this.
  10. bac9-flcl

    bac9-flcl

    Joined:
    Dec 5, 2012
    Posts:
    829
    I'm not sure I see how .material is a usable alternative, for reasons I already outlined above (for one, instantiating an arbitrary number of Material objects is an entirely unacceptable side effect). I guess there is still no replacement for what MaterialPropertyBlocks enabled and an entirely custom instanced rendering is the only available path. We already went there and implemented our own, but that's an unfortunate state of affairs for majority of users who might be relying on out of the box renderer features.
     
    Last edited: Feb 21, 2023
  11. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    If you were using so many objects with separate values like that, MPB wouldn't have been a good choice as all the unique MPB values have to be sent to the GPU each frame. With the SRP batcher, those unique instances of materials have their properties stored in a GPU buffer which only updates a given section of it when a value changes on a material.
    It also isn't going to create a new material every-time you re-access
    .material
    on an object, that only happens once. If you decide to change another property on the material, no new data is created, just that value in the GPU constant buffer. Those material instances building up in memory are quite lightweight and aren't like GameObjects that receive messages, they're just data containers.

    So the only real potential downside is the case of still modifying some shared values in
    .sharedMaterial
    and it not updating to materials that were once shared. But a script based workaround for that could be employed to link like-objects with intended changes to their material. But I think it's a pretty rare scenario that someone is needing to do that.

    You can use MPB in URP/HDRP though... Just not with the SRP Batcher, because the SRP Batcher is already making a material property buffer for each material instance of that shader. The usage of MPB and the SRP CBUFFERs would be conflicting.
     
    Aldeminor likes this.
  12. UrbanNuke

    UrbanNuke

    Joined:
    Jun 11, 2019
    Posts:
    21
    Are there any decisions?
     
  13. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    112
    my materials seem to batch together still with targetRenderer.material.SetFloat("_Health", health);
     
  14. Genebris

    Genebris

    Joined:
    Mar 18, 2013
    Posts:
    144
    That is the point of SRP batcher
     
  15. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    112
    Sure, but how insignificant are drawcalls? because the pool would still reduce drawcalls in URP right?
     
  16. Genebris

    Genebris

    Joined:
    Mar 18, 2013
    Posts:
    144
    I haven't experience any drawcalls bottlenecks since introduction of SRP batcher without putting any effort into it. Both on mobile and PC so I assume this is not an issue anymore.
     
  17. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    112
    How many objects are you dealing with on average, and now often are you accessing/updating their material properties? Could just be related to your game/project scale.
     
  18. Genebris

    Genebris

    Joined:
    Mar 18, 2013
    Posts:
    144
    Edit: 3700 materials. We spam a bunch of runtime unique materials for every NPC because we have randomly assigned combinations of clothes and their texture sets. Same for ships' sails.
     
    Last edited: Nov 24, 2023
  19. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    112
    Interesting, and you're just caching the ref to the material and setting it directly?
     
  20. Genebris

    Genebris

    Joined:
    Mar 18, 2013
    Posts:
    144
    Yes