Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

MaterialPropertyBlock and SRP batcher

Discussion in 'Universal Render Pipeline' started by C-Gabriel, Jan 24, 2020.

  1. C-Gabriel

    C-Gabriel

    Joined:
    Jan 27, 2016
    Posts:
    119
    Hi,

    I am trying to change the color of multiple dynamic identical meshes using the same (Simple Lit) material with GPU instancing enabled without breaking the SRP Batcher/GPU instancing. If I don't change anything, they get batched just fine, however, when a property is set via MaterialPropertyBlock both GPU instancing and SRP Batcher break. The Frame Debugger says "Non-instanced properties set for instanced shader."

    This is how I use it. Am I doing something wrong?

    Code (CSharp):
    1. protected void Start()
    2. {
    3.    materialPropertyBlock.SetColor(
    4.       MaterialUniforms.BaseColor,
    5.       new Color(Random.value, Random.value, Random.value, 1f)
    6.    );
    7.    ballVisuals.SetPropertyBlock(materialPropertyBlock);
    8. }
     
  2. BattleAngelAlita

    BattleAngelAlita

    Joined:
    Nov 20, 2016
    Posts:
    400
    SRP and MaterialPropertyBlock not compatible.
     
  3. C-Gabriel

    C-Gabriel

    Joined:
    Jan 27, 2016
    Posts:
    119
    I think it works as when I set the same color for all of them (eg. Color.red), they get batched. However, the same happens with SRP Batcher disabled. There needs to be a way of changing some material properties of an instanced shader.
     
    Last edited: Jan 26, 2020
  4. JohnKP-Mindshow

    JohnKP-Mindshow

    Joined:
    Oct 25, 2017
    Posts:
    56
    aurelioprovedo likes this.
  5. C-Gabriel

    C-Gabriel

    Joined:
    Jan 27, 2016
    Posts:
    119
    thanks for this @JohnKP-Mindshow . What is UnityPerObject used for? I've never seen it before and can't find anything about it. As for UnityPerMaterial, I already use that, my shaders are 100% SRP Batcher compatible (the inspector says it as well). The problem is that each SRP batch has a lot of draw calls although all objects use the same material with different MaterialPropertyBlock BaseColor. I wonder how this was done . Maybe it's just a pool of predefined materials and each object picks one when initialized.
     
  6. JohnKP-Mindshow

    JohnKP-Mindshow

    Joined:
    Oct 25, 2017
    Posts:
    56

    IIRC, UnityPerObject are like the name says; values that can be set per mesh. I.E. if you have a bunch of different meshes all using the same material, you could have each mesh tinted a different color (still using the same material).

    Whereas if you have UnityPerMaterial, its variables that are specific to a material. So if you have a variety of meshes all using the same material and you would like to tint all of them the same color, thats where the property should live
     
  7. C-Gabriel

    C-Gabriel

    Joined:
    Jan 27, 2016
    Posts:
    119
    That's what I thought, but as soon as I add the UnityPerObject CBUFFER, the shader is no longer SRP batcher compatible
     
  8. ekakiya

    ekakiya

    Joined:
    Jul 25, 2011
    Posts:
    79
    SRP and set many colors with MPB are not compatible.
    Maybe you can instantiate many materials on playing and change color of each.They will be batched by SRP batcher.

    SimpleLit's BaseColor with MPB and GPU instancing are also not compatible.
    The BaseColor must be moved from UnityPerMaterial CBUFFER to UNITY_INSTANCING_BUFFER to support GPU instancing with the colors.

    That UNITY_INSTANCING_BUFFER thing and SRP batcher are not compatible, basically.
    So Unity will automatically Draw them without SRP batcher.

    Draw with SRP batcher and Instancing and many colors must be done manually with BatchRendererGroup or something.
     
    Last edited: Jan 29, 2020
    C-Gabriel likes this.
  9. C-Gabriel

    C-Gabriel

    Joined:
    Jan 27, 2016
    Posts:
    119
    It's worth noting that changing properties of a material through MBP works in the sense that the material updates, but It behaves as if it created a new one and changed that property as it counts as a new drawcall.

    My old approach was to have each object instantiate the original material in the Awake() function, but the results were the same. While the objects were grouped in one SRP Batch, that SRP Batch had as many draw calls as there are objects.

    UNITY_INSTANCING_BUFFER_START() doesn't work with SRP at all. I get an error can't find _BaseColor if I try to use it, even with SRP Batcher disabled.


    I'll set up a small project when I have some time to hopefully illustrate the issue more clearly.
     
  10. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    It would be really great to know more about best practices on this and how, if possible, to reproduce the kind of functionality MPBs afford only using SRP batcher-compatible properties. In my use case, I'm having to animate values on materials which I've been doing with MBPs up 'til now.
     
    Allan-MacDonald and Meatloaf4 like this.
  11. C-Gabriel

    C-Gabriel

    Joined:
    Jan 27, 2016
    Posts:
    119
    I totally agree!

    I've got some time to set up a small repo to emphasize the issue I'm having. either I'm doing something wrong, or the SRP Batcher doesn't work properly.

    repo: https://github.com/GabrielCoriiu/unity-SRP-test.git
    Ignore the top left screen, that's for another issue related to the Opaque Downsampling not working.

    SRP-Batcher.jpg
     
    Deleted User and Brady like this.
  12. C-Gabriel

    C-Gabriel

    Joined:
    Jan 27, 2016
    Posts:
    119
    After some more research, I found out that SRP Batcher doesn't save draw calls, it only makes them faster. So basically it's better to clone the original material and change its properties, as opposed to using MPB. In order for that to work, the GPU Instancing needs to be disabled on the original material.

    // EDIT
    So the problem I was having with my custom shader was that the UnityPerMaterial CBUFFER had too many properties (increasing its size). The Frame Debugger will say that it can't be batched because "Objects have different materials" which is misleading.

    Below is some sample code with the current working CBUFFER properties to make an idea of how big it should be.

    Code (CSharp):
    1.  
    2. CBUFFER_START(UnityPerMaterial)
    3.     half _SphereToQuad;
    4.     half4 _TargetPixelColor;
    5.     half4 _BaseColor;
    6.     half4 _EmissionColor;
    7.     #if defined(_LIGHTMAP)
    8.         TEXTURE2D(_Lightmap);
    9.         SAMPLER(sampler_Lightmap);
    10.     #endif
    11.     TEXTURE2D(_BaseMap);
    12.     SAMPLER(sampler_BaseMap);
    13.     half4 _BaseMap_ST;
    14. CBUFFER_END
    15.  
     
    Last edited: Mar 4, 2020
  13. guycalledfrank

    guycalledfrank

    Joined:
    May 13, 2013
    Posts:
    1,667
    I found you can abuse some of the builtin properties from UnityPerDraw to supply your own data. In my case I never use Enlighten dynamic lighting, so I just set my values to renderer.realtimeLightmapScaleOffset :D
    Note that lightmapIndex or realtimeLightmapIndex must be > 0 (and < 65533), or it won't update scale/offset. Which is okay, but also if you use mixed lighting, your lights won't recognize objects with these values as shadowcasters. But then you can create non-mixed lights and use rendering layers to filter with them instead....
    It's a mess.
     
    forestrf, morepixels, apkdev and 4 others like this.
  14. potterdai

    potterdai

    Joined:
    Apr 23, 2014
    Posts:
    10
    Thank you! This actually works very well!!
     
    guycalledfrank likes this.
  15. goran_okomotive

    goran_okomotive

    Joined:
    Apr 26, 2017
    Posts:
    60
    Can you elaborate a bit on this hack?
    If I understood it right, you can set per frame data via Renderer.realtimeLightmapScaleOffset (Vector4) and than access this data as a float4 in a vert/frag shader without creating a separate material instance and breaking SRP batching, right?
    What is the field / equivalent in Unity for this field, unity_LightmapST?
     
  16. guycalledfrank

    guycalledfrank

    Joined:
    May 13, 2013
    Posts:
    1,667
    Yes, you can set it per-renderer and it won't break SRP batching.
    In the shader you can access it via float4 unity_DynamicLightmapST which can be a part of UnityPerDraw cbuffer.
     
    forestrf likes this.
  17. goran_okomotive

    goran_okomotive

    Joined:
    Apr 26, 2017
    Posts:
    60
    Omg this is genius. I wonder, does Unity apply UnityPerDraw cbuffer built-in properties in a core library automatically somehow? Because it tells me in the inspector that my shader is SRP Batcher compatible even without declaring a UnityPerDraw cbuffer section / property.
     
    guycalledfrank likes this.
  18. PuckLovesGames

    PuckLovesGames

    Joined:
    Mar 4, 2015
    Posts:
    17
    Hi, I'm trying to find all the ways to manually push data in through the existing UnityPerDraw properties, but I'm struggling with the lack of documentation. Have you found any other properties that are available to use for custom perdraw data? I'd love to use unity_ProbeVolumeWorldToObject 4x4, or some of the Spherical Harmonic float4 properties, but I can't figure out how to access them in script.

    I just want to get a single matrix4x4 into each object, but it completely breaks SRP Batching, whether via material property block or material instances (causing a multitude of SRP batches of size 1, which for all intents and purposes is broken). I can't find a way that doesn't kill performance.
     
    guycalledfrank likes this.
  19. guycalledfrank

    guycalledfrank

    Joined:
    May 13, 2013
    Posts:
    1,667
    You can probably declare a global matrix array as a StructuredBuffer (Shader.SetGlobalBuffer) and use unity_DynamicLightmapST.x as an index to it? That'll work, although it'll be an additional indirection.
     
    PuckLovesGames likes this.
  20. DonCornholio

    DonCornholio

    Joined:
    Feb 27, 2017
    Posts:
    92
    Are you using that Hack in Bakery? I wonder if i will be able to use Bakery AND also use this hack to do some per Renderer Shader Tricks.

    It's really frustrating that Unity doesnt provide a way to do stuff like this more easily. I mean they poured so many resources into the Scriptable Renderpipeline to give more control, but failed to offer easier ways for doing something as obviously useful like setting PerRenderer Data while being able to batch draw calls. If Unity ever comes around to making the engine open source (which all the main big competitors do), i'll throw a big party and you're all invited <3 !
     
    guycalledfrank likes this.
  21. guycalledfrank

    guycalledfrank

    Joined:
    May 13, 2013
    Posts:
    1,667
    No, I'm not. I'm using it my custom render pipeline :)

    Objects using regular engine-supported color/direction/mask lightmaps will batch fine, even if baked with Bakery.
    But objects using special RNM/SH lightmaps use MaterialPropertyBlocks unfortunately.
     
    DonCornholio likes this.
  22. EvansNate

    EvansNate

    Joined:
    Sep 2, 2021
    Posts:
    7
    I'm trying to use this trick but if the GameObject is not flagged as ContributeGI static it will not work and unity_DynamicLightmapST will be (0,0,0,0). Is there a way to use this trick without marking the objects as ContributeGI static, maybe by activating some keywords etc?
     
  23. goran_okomotive

    goran_okomotive

    Joined:
    Apr 26, 2017
    Posts:
    60
    https://twitter.com/ekakiya/status/1263347738009169922
     
  24. equalsequals

    equalsequals

    Joined:
    Sep 27, 2010
    Posts:
    154
    Sorry to necro, I am a little curious about this exploit. What are you doing on C# side to get this to update on the GPU properly? If I set Renderer.realtimeLightmapScaleOffset, I still see (1,1,0,0) on the GPU.


    Is it a particular PerObjectData flag I am missing? Something else?

    Thanks in advance.
     
  25. guycalledfrank

    guycalledfrank

    Joined:
    May 13, 2013
    Posts:
    1,667
    mr.realtimeLightmapIndex = 0; // forces "real-time lightmapped" behaviour on the renderer (also not really required on objects using a regular static lightmap)
    mr.realtimeLightmapScaleOffset = custom data
    ...
    drawingSettings.perObjectData = PerObjectData.Lightmaps | otherStuffYouNeed;

    On the GPU you should have a UnityPerDraw cbuffer with float4 unity_DynamicLightmapST in it.
     
    forestrf and equalsequals like this.
  26. equalsequals

    equalsequals

    Joined:
    Sep 27, 2010
    Posts:
    154
    Aha, I was failing to set realtimeLightmapIndex to 0. That was it. Thank you!
     
    guycalledfrank likes this.
  27. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,158
    It's very annoying that unity just force us to abusing things that should work out of the box. And they don't even care to explain anything here

    Still thanks for your discovery
     
  28. sam_stitch

    sam_stitch

    Joined:
    Jun 6, 2023
    Posts:
    6
    Sorry to necro - I am looking for a way to assign a different alpha value per renderer instance of the same material and same shader, to avoiding needing to maintain dozens of functionally identical materials all with different alpha values?

    Is the above described the best way to do this? I did lots of searching but there is lots of argument over what is or isnt the best way to do something performance wise, I want to be able to assign variables on a per renderer basis without killing performance or being forced to manage tons of materials?
     
  29. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,158
  30. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    112
    @sam_stitch Did you find anything? I'm also wondering about something similar, where I have unique color outlines without having multiple materials/render layers for the outline render feature
     
  31. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    339
    I want to necro this again because this is still a feature I dearly miss and I would like to add some important information for everyone attempting to use this.

    Above trick explained by guycalledfrank does still work (2022.3.14) but what it also does is disable the ability to use Light Probes because it is forcing the mesh renderer to behave like it uses Light maps.
    If anyone knows how to get the hack for per object data AND regular light probes to work at the same time I'd be super thankful.

    I tried using

    Code (CSharp):
    1. DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(data.m_ShaderTagIdList, ref renderingData, sortFlags);
    2. drawSettings.perObjectData |= PerObjectData.LightProbe;
    3. drawSettings.perObjectData |= PerObjectData.Lightmaps;

    but that does not change anything.
     
    Radivarig likes this.