Search Unity

Using MaterialPropertyBlocks with ShaderGraph shaders (help wanted)

Discussion in 'Shaders' started by Rienhl, Mar 27, 2019.

  1. Rienhl

    Rienhl

    Joined:
    Nov 14, 2013
    Posts:
    42
    Hello!

    I'm trying to use material instancing with a shader created with ShaderGraph. I don't seem to find the way to set any property to be used in a MaterialPropertyBlock.

    I tried compiling the shader's code, adding the [PerRenderData] attribute before the property declaration but t doesn't work. Also, it looks like it can't be saved at all since when I open the code again, my [PerRenderData] attribute gets deleted.

    How should I approach this?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    This doesn’t do anything. It’s singular purpose is to hide the value in the inspector. It’s a legacy setting related to the Sprite Renderer and even there is unnecessary. There’s nothing you need to do to make material properties work with material property blocks, they all work by default.

    The one thing with Shader Graph is the name of the material property variable isn’t obvious as the name you set is just the display name, the actual variable gets an auto generated random string like _Vector_H264BBQUSB. Expand the property in the blackboard and change the reference name from the aforementioned gobblygook to something sensible, like _MyVector.
     
    yashgargcreations and Jamez0r like this.
  3. Rienhl

    Rienhl

    Joined:
    Nov 14, 2013
    Posts:
    42
    Thanks for the answer man.

    So, in this post the author says
    . If this is the case then I don't see the point of using the MaterialPropertybLock here since I'm going for the GPU Instancing stuff.

    Also, in this other post the author says
    I'm confused about what can actually be done and how. I can't find any consistent documentation. Have I skipped a section of the Unity Manual?

    When wanting to replace textures while using GPU instancing, what's your recommended way?
    Should I use a Texture asset or a RenderTexture on the MaterialPropertyBlock?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Yep, I'm familiar with that post. It is wrong.

    To the best of my knowledge it was wrong when the author originally posted it too. If you follow the steps he describes and simply omitting the [PerRenderData] step, it produces the exact same result. I can only guess the author never actually tested with out using [PerRenderData], or when they did had some bug in their code. It's unfortunate because so much of the information is useful, and it comes up often. It also predates Unity's GPU instancing support by about 3 months! It's often sited by people when taking about instancing, but it isn't what that post is doing!

    This post is correct. You cannot change the texture and still instance. Well, to be more precise, two objects with different textures cannot be instanced together. This is a GPU & graphics API limitation, not something Unity is imposing themselves.

    The way instancing works is each instance of a mesh gets rendered by the GPU with a different instance id that's passed to the shader. Every single instance is in fact using the exact same "material" with the exact same settings. The bit of magic is instanced properties are put into an array of values that's set on the material rather than a singular value. So when an instance is rendered, and your shader has the _Color value setup as an instanced property, then instead of doing:
    fixed4 col = _Color;

    it's doing
    fixed4 col = _Color[instanceID];

    The problem is you can't have dynamic arrays of textures. It's simply not supported by any graphics API. You can only have arrays of floats, ints, or bools.

    But that's why the Texture2DArray exists. It's a type of texture that is itself an array with multiple slices. Unity supports these, but offers no in editor tools for creating or managing them. So you have to do that yourself with custom scripts. Then you set the single Texture2DArray texture object on your material, and for each renderer you can set which index to use when reading from the array texture.


    But, there's one really big problem...
    Unity doesn't yet appear to support defining instanced properties in Shader Graph!!! The ShaderGraph materials themselves do support instancing, and multiple objects using the same material and mesh will be instanced, there's no way to set properties to be instanced properties, so this is all kind of moot. There's still good reasons to use material property blocks over setting values on a material directly, but not related for allowing instancing. :(
     
  5. Rienhl

    Rienhl

    Joined:
    Nov 14, 2013
    Posts:
    42
    Wow, you really went over the top with this answer! I appreciate it a lot!
    Thank you so much!
     
    Meatloaf4 and amisner2k like this.
  6. amisner2k

    amisner2k

    Joined:
    Jan 9, 2017
    Posts:
    43
    That racing snail really knows his stuff. :D
     
    Meatloaf4 likes this.
  7. Gekigengar

    Gekigengar

    Joined:
    Jan 20, 2013
    Posts:
    738
    Is this supported by Shader Graph yet?
     
    Carrotpie likes this.
  8. StuwuStudio

    StuwuStudio

    Joined:
    Feb 4, 2015
    Posts:
    165
    It's not supported yet to my knowledge but I did manage to get the generated code and make it material property block compatible but I can't remember how
     
  9. lloydv

    lloydv

    Joined:
    Sep 15, 2015
    Posts:
    55
    Just want to add a little quirk I encountered today for anyone else who stumbles on this thread if they come across the same issue when working with shader graph and material property blocks. My issue was I was using a material property block in a tween on a shader graph material value and the initial + default value set in the shader/material for alpha was 1, but the tween always started at 0.

    It seems that MaterialPropertyBlock.GetFloat doesn't return the correct value until it has been set once via SetFloat. Maybe this is expected behaviour? Not sure.

    In any case, I was able to get the correct value via good 'ol Material.GetFloat, and then used the material property block for all subsequent gets/sets.

    Edit: material.GetFloat instantiates the material so I switched to sharedMaterial instead. Not exactly an ideal solution.
     
    Last edited: Aug 21, 2019
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    The MaterialPropertyBlock is just an arbitrary set of material properties in no way connected to a specific material. If you’ve not set data for a property on the block, then it’ll just return a default value (usually zero). If you want the value the material has by default, then you need to get it from that material.

    It would be nice to be able to know from code what the actual value a material is using is, with a property block applied, but that’s not possible. Internally material property blocks have a “has property” method, it’s not exposed so there’s no way to know if a material property block doesn’t have a value or if the value is black. To that end if there’s some value I need to track, or if I need the ability to reset a property back to using the material’s own value, I usually have a separate data structure which I keep track of all the values and update the property block from and never bother using the property block’s get methods.
     
    Rienhl, dev1vrtron and lloydv like this.
  11. noio

    noio

    Joined:
    Dec 17, 2013
    Posts:
    230
    Does this even work at all though? When I set a MaterialPropertyBlock on a bunch of renderers, the ShaderGraph shader actually breaks (pink!). The same shader _without_ the MaterialPropertyBlock does render correctly.

    Code (CSharp):
    1. var propBlock = new MaterialPropertyBlock();
    2. newRenderer.GetPropertyBlock(propBlock);
    3. propBlock.SetFloat("_WindOffset", variant / 17f);
    4. newRenderer.SetPropertyBlock(propBlock);


    (ShaderGraph 7.1.2, Unity 2019.3.0b12)
     
  12. noio

    noio

    Joined:
    Dec 17, 2013
    Posts:
    230

    Edit: Updating to ShaderGraph 7.1.5 seems to have fixed this.

    However, batching no longer happens (as you said), with the FrameDebugger reporting:

     
    Last edited: Nov 26, 2019
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    I think Unity is expecting everyone to use the the SRP Batcher instead of Dynamic Batching or instancing, so I can see bugs falling through the cracks if you still have dynamic batching enabled. I'm kind of impressed it managed to totally break something though.
     
    Rienhl and chrismarch like this.
  14. Gekigengar

    Gekigengar

    Joined:
    Jan 20, 2013
    Posts:
    738
    So, almost a year passed.
    What's the current status of MaterialPropertyBlock for ShaderGraph support?
    What's the current best way to efficiently do it?

    EDIT : Tested it, and it is already working on the newest ShaderGraph!
     
    Last edited: Dec 24, 2019
    Rienhl, Carrotpie and JotaRata like this.
  15. najati

    najati

    Joined:
    Oct 23, 2017
    Posts:
    42
    @Gekigengar

    I'm currently wrestling with this - could you define your test in broad terms so I can see if I can reproduce it?

    Cheers!
     
    Carrotpie likes this.
  16. Gekigengar

    Gekigengar

    Joined:
    Jan 20, 2013
    Posts:
    738
    Code (CSharp):
    1. public class MaterialBlockTest : MonoBehaviour
    2. {
    3.     private MaterialPropertyBlock materialBlock;
    4.     private MeshRenderer meshRenderer;
    5.  
    6.     void Start()
    7.     {
    8.         materialBlock = new MaterialPropertyBlock();
    9.         meshRenderer = GetComponent<MeshRenderer>();
    10.     }
    11.  
    12.     private void ChangePropertyBlock(float amount)
    13.     {
    14.         materialBlock.SetFloat("_SomeFloatProperty", amount);
    15.         meshRenderer.SetPropertyBlock(materialBlock);
    16.     }
    17. }
    Here is a sample, apparently you don't have to do anything special to your property in ShaderGraph to make this work.
     
  17. najati

    najati

    Joined:
    Oct 23, 2017
    Posts:
    42
    Thanks for the update! Batching also seems to be working for me without using a property block, just when setting props on the material (and letting Unity create an instance of it). Did you have to use a property block to get it to work?

    NB: The batching only works when rendering opaques, not the shadowmap. About to post a thread about that.
     
  18. Gekigengar

    Gekigengar

    Joined:
    Jan 20, 2013
    Posts:
    738
    I don't get what you meant,
    Batching will work with or without material property block.

    Its just that having material instances increases draw call because its considered a different material, which is bad for performance for property values that requires unique values for animation/color change/etc.
     
    Last edited: Jan 30, 2020
  19. najati

    najati

    Joined:
    Oct 23, 2017
    Posts:
    42
    Hey @Gekigengar - thanks again for the response.

    I understand that different material instances are treated like different materials but the batcher seems to be able to batch them together for the opaques render pass. See my post here for a clear example:

    https://forum.unity.com/threads/bug...ap-or-am-i-misunderstanding-something.806544/

    When I change that example to use property blocks, it actually breaks the batching for the opaque pass. Perhaps I'm not using them correctly.
     
    Gekigengar likes this.
  20. Gekigengar

    Gekigengar

    Joined:
    Jan 20, 2013
    Posts:
    738
    You're right, my bad, the moment I saw the materials aren't instanced, I prematurely assumed they're already properly batched. But after checking frame debugger, turns out they aren't batched. It seems like I misunderstood.

    Perhaps shader graph isn't working with material property block yet, despite the label saying material property block are used to change values in the inspector.

    Will need Unity to clarify when this is actually coming.
     
    Last edited: Jan 30, 2020
  21. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    "Working with material property blocks" and "working with instanced properties" are different things.

    Shader Graph supports material property blocks, and apart from a short period of time, always have.

    Shader Graph does not support custom instanced properties that would allow material property blocks to be set on multiple renderer components and still render as a single instanced draw, and there's no ETA on when that will be added, if ever.
     
    mgsvevo and hyunBonfire like this.
  22. StuwuStudio

    StuwuStudio

    Joined:
    Feb 4, 2015
    Posts:
    165
    Setting up instancing ourselves from the outputted shader is a pain. I don't see why it would never be added. What issue would it bring?
     
    Opeth001 likes this.
  23. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    No issues, it just seems like it’s a low priority for Unity. The impression I’ve gotten from their responses has been “the SRP Batcher should replace the need for traditional instancing.”
     
    Opeth001 likes this.
  24. An3Apps

    An3Apps

    Joined:
    Nov 22, 2017
    Posts:
    8
    I don't know if this is the same thing, but I achieved instanced shader graphs for my game by using animations and keyframing the values. So for example, when my enemies die, I spawn a ragdoll and animate the alpha clip value from shader graph to slowly dissolve. If I kill multiple enemies they independently dissolve.
     
  25. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Nothing you just described would be rendered using GPU instancing. Unity's built in skinned meshes have never used instancing, so even if you were using an instanced shader it wouldn't be rendered using instancing. This is just going to be rendering the objects normally.
     
  26. castor76

    castor76

    Joined:
    Dec 5, 2011
    Posts:
    2,517
    This isn't exactly tailored question for this thread, but I could not find anything yet.. so does anyone know how batching works for URP + 2Drenderer ? I was hoping for SRP batching to work, but it all seems to be broken now..and have no info what so ever, on what kind of set up I need to to.. even shaders made using Shader Graph for 2DRenderer does not seem to batch at all....
     
  27. diesoftgames

    diesoftgames

    Joined:
    Nov 27, 2018
    Posts:
    122
    Edit: Removing this post because I was mistaken and these steps don't work
     
    Last edited: Aug 4, 2021
  28. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    2D renders are usually a bit different. I haven't used the 2D URP, but all of the built in 2D renderers (sprites, UI, tiles) do mesh merging. Multiple sprites using the same material & sprite atlas, and are consecutively drawn will be merged into a single mesh and drawn as a single draw. This isn't instancing, this isn't SRP Batcher, this isn't even really "dynamic batching", this is the 2D rendering systems generating unique merged meshes every frame. Last I saw the SRP Batcher isn't even supported for 2D URP, though that could have changed in the last few years. Early SRP Batcher discussions have the Unity devs saying they didn't have plans for supporting 2D rendering at all. But again, I've not used the 2D URP to know if things have changed.

    I know sprites separately support instancing, at least in the BIRP. But I don't know if the 2D URP handles these differently than the BIRP. Part of the problem might be that Sprite Renderers explicitly rely on material property blocks, which disables the SRP Batcher on those renderers.

    Shader Graph shaders will happily work with material property blocks, but the SRP Batcher will not, and Shader Graph doesn't (yet) support instancing via material property blocks. That's still "Under Consideration" on the Shader Graph road map.

    So basically as I understand it you're stuck with the traditional mesh merging approaches when using the 2D URP.

    Also note "Hybrid Instancing" is something completely different and separate from all this, and is for to the DOTS Hybrid Renderer. If you don't already know what that is, don't worry about it, it doesn't affect you.
     
    diesoftgames likes this.
  29. diesoftgames

    diesoftgames

    Joined:
    Nov 27, 2018
    Posts:
    122
    Good to know, I didn't realize I was in quite so uncharted waters with this. It looks like you're probably replying to post above me, but for what it's worth, what I laid out above can take what would be multiple "Draw Dynamic" calls in "RenderLoop.Draw" due to "Objects have different MaterialPropertyBlock set" reason, and will combine them into a single "Draw Dynamic" so it does indeed seem to be just simply working as you'd expect.
     
    bgolus likes this.
  30. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Awesome! I was trying to answer both, but misread yours!
     
    diesoftgames likes this.
  31. diesoftgames

    diesoftgames

    Joined:
    Nov 27, 2018
    Posts:
    122
    Hmmm, I'm trying to discern why, but in an earlier scenario I proved out that I could combine draw calls with different material property blocks if the property that differed had "per material" override, but in a new case that's no longer working and I get distinct draw calls when changing those properties. I'm not sure exactly what's different that is the source of the problem, but heads up that it might not be working as cleanly as I had hoped from my first tests.

    Edit: I must have simply done something wrong and accidentally somehow applied the same MaterialPropertyBlock to different instances and not realized because a) I can't reproduce my original success and b) I'm realizing the steps above wouldn't make sense anyways because it's "PerDraw" that I want them to be overriden as, not "PerMaterial", but PerDraw is not an option (which fits as it is not yet supported). Sorry for the confusion.
     
    Last edited: Aug 4, 2021
    bgolus likes this.
  32. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342