Search Unity

MaterialPropertyBlock with Shader Graph?

Discussion in 'General Graphics' started by StuwuStudio, Jun 20, 2019.

  1. StuwuStudio

    StuwuStudio

    Joined:
    Feb 4, 2015
    Posts:
    165
    I don't think I need to give much info for you to understand what I'm trying to achieve so I'm going to keep it simple.

    My goal is to instantiate a large amount of the same mesh but with different texture applied to them and render it as fast as possible. I found out about GPU instancing and decided to try it. To make instancing work, you probably already know that It needs two things, objects using the same mesh and same material.

    To be able to display multiple textures with the same material I created a shader (in shader graph) that takes in some sort of texture array (not unity's texture array, just one texture2D with a bunch of images of the same size next to each other) and a texture index propriety (with the exposed parameter set to false).

    After that, I toggle the GPU instancing checkbox on the material that uses the shader
    https://imgur.com/l3Pqxb1
    https://imgur.com/4k076Tq


    The material is already rendering the mesh it faster but now I need to change the material property that changes the texture used.

    To do that, instead of changing the parameter of a new instance of the same material, I found out that I needed to use a material property block. This is where I'm getting an issue. For some reasons, I can change the material property that changes the texture but for each time I set a new material to use in the property block, the amount of set pass calls increases dramatically to the point of reaching 5000 calls when using 15 different material. This is hard to explain, I'll demonstrate in code:

    This is the code I use to create a new object. Notice the material variable in the set float function of the property block? If it stays at the same value for each object, the set pass calls count is normal. However, if this value is different for multiple objects, the set pass call count increase. The more value this variable can be, the more calls I get. If instead of using material property block, I use a different instance of the same material, I get similar results: a hight set pass call count.

    I just don't get why the material property block seems to create different instance of my material instead of just changing the texture index parameter.

    Code (CSharp):
    1. GameObject buildAssetObject = Object.Instantiate(buildLoadManager.buildAssetPrefab, buildRoot.transform);
    2.         Renderer renderer = buildAssetObject.GetComponent<Renderer>();
    3.  
    4.         renderer.sharedMaterial = buildLoadManager.buildMat_0;
    5.         buildAssetObject.GetComponent<MeshFilter>().sharedMesh = buildLoadManager.meshes[meshIndex];
    6.         buildAssetObject.GetComponent<MeshCollider>().sharedMesh = buildLoadManager.meshes[meshIndex];
    7.  
    8.         renderer.GetPropertyBlock(materialPropertyBlock);
    9.         materialPropertyBlock.SetFloat(buildLoadManager.materialIDProperty, material);
    10.         renderer.SetPropertyBlock(materialPropertyBlock, 0);
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    For you to be able to change a material's property and have instancing continue to work, that property has to be setup to be an instanced property. Unfortunately Shader Graph doesn't (yet) support instanced properties, so there's no way to do this with Shader Graph.

    However if you enable the SRP Batcher you might get similar performance improvements as instancing even with the increased draw calls.
     
  3. StuwuStudio

    StuwuStudio

    Joined:
    Feb 4, 2015
    Posts:
    165
    This option has already been toggled from the beginning.

    Do you know if there's any simple tutorial that could help me set up an instanced material with instanced propriety for LWRP. Once that's setup I could try porting my shader from graph to code
     
    Last edited: Jun 20, 2019
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    You can take a shader graph, and right click on the master node to get the generated vertex fragment shader, this will be the easiest way to get a working shader to start with. After that it should just be a matter of following the directions for adding an instanced property in the official documentation.
    https://docs.unity3d.com/Manual/GPUInstancing.html
     
    Arathorn_J likes this.
  5. StuwuStudio

    StuwuStudio

    Joined:
    Feb 4, 2015
    Posts:
    165
    Sooooo... I tried implementing GPU Instancing for my TextureId parameter. I think It would've worked if it wasn't for this error I got:

    My error:
     Shader error in 'BuildingShader2': unrecognized identifier 'UNITY_INSTANCING_CBUFFER_START' at line 73 (on d3d11) 


    At:
    Code (CSharp):
    1. UNITY_INSTANCING_CBUFFER_START(InstanceProperties)
    2.             UNITY_DEFINE_INSTANCED_PROP(float, Vector1_57CF0780)
    3.         UNITY_INSTANCING_CBUFFER_END
    If I replace CBUFFER_START with BUFFER_START I get this:
     Shader error in 'BuildingShader2': unrecognized identifier 'UNITY_INSTANCING_CBUFFER_END' at line 75 (on d3d11) 


    If I replace CBUFFER_END with BUFFER_END I get this:
     Shader error in 'BuildingShader2': unrecognized identifier 'UNITY_INSTANCING_BUFFER_END' at line 75 (on d3d11) 


    "Unity is easy to learn and blah blah blah" Yeah right
     
  6. StuwuStudio

    StuwuStudio

    Joined:
    Feb 4, 2015
    Posts:
    165
    My bad, had to use

    Code (CSharp):
    1. UNITY_INSTANCING_BUFFER_START(InstanceProperties)
    2.             UNITY_DEFINE_INSTANCED_PROP(float, Vector1_57CF0780)
    3.         UNITY_INSTANCING_BUFFER_END(InstanceProperties)
     
  7. StuwuStudio

    StuwuStudio

    Joined:
    Feb 4, 2015
    Posts:
    165
    Compiled it, it works... Kinda. The set pass calls went from 1900~ to 1000. It's still a big number. Not sure how to lower it.
     
  8. Arathorn_J

    Arathorn_J

    Joined:
    Jan 13, 2018
    Posts:
    51
  9. Turbine

    Turbine

    Joined:
    Nov 9, 2012
    Posts:
    11
    Thanks for posting these tips. I was devastated when my project dropped to 3 FPS because material properties don't work out of the box. I was able to edit the generated shader to go from 5000 draw calls to ~15. Here are some relevant snippets. I am only changing the color of my game objects in this example

    Find all of these blocks of code (there are several copies)
    Code (CSharp):
    1. CBUFFER_START(UnityPerMaterial)
    2. float4 _BaseColor;
    3. CBUFFER_END
    Replace them with this
    Code (CSharp):
    1. CBUFFER_START(UnityPerMaterial)
    2. UNITY_INSTANCING_BUFFER_START(Props)
    3. UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
    4. UNITY_INSTANCING_BUFFER_END(Props)
    5. CBUFFER_END
    Then find every instance of _BaseColor in the shader functions (there are several copies)
    Code (CSharp):
    1.  SurfaceDescription SurfaceDescriptionFunction(SurfaceDescriptionInputs IN) {
    2.         SurfaceDescription surface = (SurfaceDescription)0;
    3.         float4 _Property_A850853A_Out_0 = _BaseColor;
    4.         ....
    Replace them with UNITY_ACCESS_INSTANCED_PROP(Props, _BaseColor)

    Code (CSharp):
    1. SurfaceDescription SurfaceDescriptionFunction(SurfaceDescriptionInputs IN) {
    2.         SurfaceDescription surface = (SurfaceDescription)0;
    3.         float4 _Property_A850853A_Out_0 = UNITY_ACCESS_INSTANCED_PROP(Props, _BaseColor);
    4.         ...
    And lastly don't forget to set the material properties on your game object
    Code (CSharp):
    1. int colorPropId = Shader.PropertyToID("_BaseColor");
    2. var matProps = new MaterialPropertyBlock();
    3. matProps.SetColor(colorPropId, color);
    4. renderer.SetPropertyBlock(matProps);
     
    Last edited: Mar 11, 2020
  10. Arathorn_J

    Arathorn_J

    Joined:
    Jan 13, 2018
    Posts:
    51
    I've done this as well, the problem is every time you make a change to your original graph you have to regenerate and do this all over again which for a workflow is just way too much work (at least for me a solo dev). They are supposed to be adding this support at some point for shader graph but I haven't seen any updates in a very long time.
     
  11. Chimera3D

    Chimera3D

    Joined:
    Jan 27, 2012
    Posts:
    73
    You can alternatively create a custom function and attach an hlsl file in which you declare your instancing buffer properties and in the function either use them directly or spit them out to be used elsewhere. This has some drawbacks as it will "break" the graph editor such that you won't be able to edit the graph even though it will work. In order to continue editing your graph you have to force an error in the function, close and reopen the graph editor, make changes, save, then remove the error. It's a little cumbersome but it's much better than working with the generated code. You can hack a lot of stuff into your shader graph shaders this way.
     
    mdooneymill, potterdai and brainwipe like this.
  12. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Up !

    Any news on this topic ? ;)
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    The update is the SRP Batcher is about 95% as fast as GPU instancing and more flexible, so it's probably low priority for Unity.
     
    Alesk likes this.
  14. Arathorn_J

    Arathorn_J

    Joined:
    Jan 13, 2018
    Posts:
    51
    The latest version of ECS allows you to use shader graph designated material properties. So nothing yet for non-ECS but I've gotten around it using a custom function inside of shader graph to pull whatever instanced material properties I need.
     
  15. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,823
    Hey @bgolus, are you saying it's maybe not worth heading down the Material Property Block path anymore?
    Sorry if I'm right off the mark here :) I find material management pretty confusing.
     
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    For the URP, no. At least not unless they change something. The cost of directly modifying materials is higher on the main thread than using material property blocks for both the built in but URP, but the URP much, much faster on the render thread when using multiple unique materials.
     
  17. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,823
    Thanks for the heads up :)
    I kinda liked the Render Properties because I could tweak the colour of things in the editor without having to create a seperate material instance every time, but it was often tricky in other ways...
    Looks like I'm going to have to re write a bunch of stuff.
     
  18. AustinMclEctro

    AustinMclEctro

    Joined:
    May 3, 2017
    Posts:
    16
    Between how MaterialPropertyBlocks aren't supported when gpu instancing, and how the SRP Batcher apparently does just as good as gpu instancing (or close to it) now, this has become a cluster of confusing information.

    So currently, what is the most performant way to draw meshes together when using a single shader graph + material? Let's use HDRP.
    For a specific example, say we want to use a Texture2DArray per mesh to send information into the shader to give the mesh different colors and other arbitrary properties, say smoothness.

    I have a very high batch count right now doing this. Can I not batch these calls together somehow?
     
  19. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,716
    The idea is that they made batches far less expensive, so you shouldn't care about high batch counts all that much.

    Also, static batching probably still works (although not if you use hybrid renderer), and you could also manually combine more meshes and materials together if that is possible.
     
  20. vanyfilatov

    vanyfilatov

    Joined:
    Dec 2, 2020
    Posts:
    33
    What did you write inside custom function?
     
  21. Arathorn_J

    Arathorn_J

    Joined:
    Jan 13, 2018
    Posts:
    51
    Non need anymore, as long as you designate a property in shadergraph to allow the override and set it to hybrid per instance, it allows you to pass it in a material by setting an array.

    upload_2024-2-19_12-55-35.png