Search Unity

Sampler2D Instanced shader property ? Matrix[] ?

Discussion in 'Shaders' started by Cripple, Oct 31, 2016.

  1. Cripple

    Cripple

    Joined:
    Aug 16, 2012
    Posts:
    92
    Hi,

    I am currently updating my shaders to implement instancing.
    For that I need to have a CBUFFER property of type : Sampler2D (a texture).
    Each time I try to do this : "UNITY_DEFINE_INSTANCED_PROP(sampler2D, _BoneTransformsTexture)"
    It doesn't work... Is that even possible ?
    Same question with the array types : float4x4[] or float4[], can it be stored in the CBUFFER for instancing ?

    Thanks.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    A cbuffer can hold a texture, but instanced cbuffers cannot. The problem is you can't have an array of textures, and instanced cbuffers work by storing data in an array and then using the instance ID as a look up index.

    What you'd need to do is use an array texture and then store an index value for that as an instanced property.
    https://docs.unity3d.com/ScriptReference/Texture2DArray.html

    Ideally Unity would just support this and automatically pack arrays of textures into an array texture, but there are a lot of "gotchas" with array textures.

    For a similar reason arrays of arrays is a problem. HLSL and GLSL are capable of multi-dimensional arrays, but every element of the array must be a uniform length, and the length must be defined. Technically speaking there's nothing preventing this from working apart from a lack of support built in to Unity's instancing stuff. It's plausible you could define your own UNITY_DEFINE_INSTANCED_PROP macros for arrays and it'd work, but I would guess Unity's internal instancing stuff would just skip instancing for materials with differing arrays.

    However if Unity's code doesn't get in the way something like this might work:

    #if defined(UNITY_SUPPORT_INSTANCING) && defined(INSTANCING_ON)
    UNITY_DEFINE_INSTANCED_PROP_ARRAY(type, name, size) type name[UNITY_INSTANCED_ARRAY_SIZE][size];
    #else
    UNITY_DEFINE_INSTANCED_PROP_ARRAY(type, name, size) type name[size];
    #endif


    Completely untested, but if it works the same UNITY_ACCESS_INSTANCED_PROP macro should work to get the data.
     
  3. Cripple

    Cripple

    Joined:
    Aug 16, 2012
    Posts:
    92
    Thanks for the answer, that explains quite a lot.
    I'll have to try something with Texture2DArrays then.
    I might just end up having to build an atlas though... I was hoping I could avoid that ^^ ...
     
  4. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    So if we can only set Texture2DArray, why the method MaterialPropertyBlock.SetTexture use the input type `Texture` instead of `Texture2DArray`? That method has 2 bad points: #1 It makes me cast from `Texture2DArray` to `Texture` to use it. #2 it make me think it's possible to edit `Texture` through MaterialPropertyBlock, but in the end, the shader doesn't support it. Does anyone know why the method has such misleading input type?

    Edit: correct: not set Texture2DArray but set the index of Texture2DArray. When I wrote the sentence above, I'm thinking about using MaterialPropertyBlock.GetTexture to get `Texture2DArray` so I can get the max index number of Texture2DArray.
     
    Last edited: Sep 19, 2018
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Because the MaterialPropertyBlock predates Unity’s instancing support by nearly a half a decade. Its use for setting instanced properties is it’s secondary purpose. It’s primary purpose is just to allow modifying material settings at runtime without needing to create a new material, and allow you to easily reset to the original material’s settings by clearing or removing the material property block. It also predates texture array support by the same time period. The original use for material property blocks, and one of the ways it’s still used today, is for setting the texture on sprites renderers.
     
    NathanJSmith likes this.
  6. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    Hi, sorry for bothering again. But I see that we can do this:
    Code (CSharp):
    1. MaterialPropertyBlock m_mpb;
    2. public Texture2D[] m_arrSkins;
    3.  
    4. //...
    5.  
    6.  
    7. //Loop this to draw
    8. Graphics.DrawMesh(
    9.                     m_mesh,
    10.                     Vector3.zero,
    11.                     m_rotation,
    12.                     m_crtMaterial,
    13.                     0,
    14.                     m_Camera,
    15.                     0,
    16.                     m_mpb);
    17.  
    18. //...
    19.  
    20. m_mpb.SetTexture("_MainTex",
    21.                 m_arrSkins[0]);
    22.                
    23. //Wait some time
    24.  
    25. m_mpb.SetTexture("_MainTex",
    26.                 m_arrSkins[1]);
    The shader's _MainTex doesn't need to UNITY_ACCESS_INSTANCED_PROP
    Code (CSharp):
    1.     Properties
    2.     {
    3.         _MainTex("Texture", 2D) = "white" {}
    4.     }
    5.     //...
    6.     sampler2D _MainTex;
    7.    
    8.     //...
    9.     void surf(Input IN, inout SurfaceOutput o)
    10.     {
    11.         o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
    12.     }
    And it actually works, but I can't check that if this actually instance the _MainTex (like I expect) or it just creates other material in the background (which waste memory)?
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    This. Or rather you're just modifying the material's one texture property. There's nothing related to instancing here.

    Also, if what you mean by the //Wait some time line is you're waiting until the next frame, then of course this will work as, again, this has nothing to do with instancing. Instancing means drawing multiple copies of a mesh at once. Waiting a frame, or even just calling DrawMesh multiple times, means no instancing is occurring and you're just drawing the mesh multiple times normally.
     
  8. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    Is there a solution to gpu Instancing texture yet?
     
    beatdesign likes this.
  9. beatdesign

    beatdesign

    Joined:
    Apr 3, 2015
    Posts:
    137
    Sorry, I didn't get why texture 2D GPU instancing is so difficult to achieve.
    MaterialPropertyBlock.SetTexture() do exists... so I suppose that it is possible to use it the same way I use MaterialPropertyBlock.SetColor().
    Why this should not work?
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    GPUs do not support arrays of textures.*

    Instancing works by passing an array of values to a shader and using the instance ID the GPU provides to the shader as the index into the array. This works great for numerical values, but does not work with textures since they do not support arrays of textures. A
    Texture2DArray
    is not an arbitrary array of textures, but rather a single 3D texture that uses an index to access the Z instead of UVWs.

    As noted above, the
    MaterialPropertyBlock
    predates Unity supporting instancing by many, many years. And thus it supports things that instancing can’t, since it’s not what it was originally designed for. It was just repurposed for use with instancing after the fact. That has caused a ton of confusion as there are some excellent tutorials out there on how to use
    MaterialPropertyBlock
    s to improve the performance of rendering a lot of objects with different material properties that people understandably assume are about instancing, but are not.

    * At least when using D3D11 or OpenGL/ES. Arrays of textures was something added for D3D12, Vulkan, and Metal in the form of dynamic resource indexing. But AFAIK Unity doesn’t yet support this feature. And this would not work on any GPU that doesn’t support D3D12, and isn’t supported by all Metal or Vulkan devices.
     
    beatdesign likes this.
  11. beatdesign

    beatdesign

    Joined:
    Apr 3, 2015
    Posts:
    137
    Ok, now it is more clear. So.. Unity should remove this:
    https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.SetTexture.html
    ..because all GPU Instancing tutorials are using MaterialPropertyBlock to change only color, but if I see the documentation I immediatly think about this: "oh, there is a setTexture property also! Let's use that to render 1K spheres with diferent textures using GPU Instancing!"
    This is really confusing. Btw, thanks a lot for your clarification!
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    No.

    As noted, the
    MaterialPropertyBlock
    predates instancing, and is still used for it's original purpose.

    Efficiently and non-destructively change the properties of a material.​

    Those old tutorials showing that changing properties on 1000 objects using
    MaterialPropertyBlock
    s instead of modifying the material direction are still valid today. All of Unity's 2D & UI makes heavy usage of them as well, almost exclusively to change the base material's texture. Any of Unity's built in systems that let you animate materials makes use of the
    MaterialPropertyBlock
    , as do most third party tools that efficiently do the same. I personally make heavy use of
    MaterialPropertyBlock.SetTexture()
    as well.

    All because it lets you modify material properties without generating a ton of additional material instances, which are more expensive.

    The reasons why is a
    MaterialPropertyBlock
    is only a list of properties that are changed, and cannot modify the material's render state. Where as a
    Material
    is always a list of every single property, and can modify the keywords and render state. Render state is things like the blend mode (transparent or not), if it renders to the depth buffer, stencil settings, etc. Keywords change the shader variant in use. Notably neither of those kinds of things can change for instancing either. But generally a
    Material
    is a much more expensive and bigger "thing", not to mention they need to be manually garbage collected... which almost no one actually remembers to do.
     
  13. amenonegames

    amenonegames

    Joined:
    Nov 10, 2021
    Posts:
    19
    Hi, there!
    I found the project that enable texture GPU instancing pseudo.

    In this approach,
    First, Generate texture array packed various porpose texture and,
    Second, prepare int property in shader that is instancing buffered,
    Third, Set the texture array to the material,
    Finally, accress buffered int , And render texture using buffered int.

    I hope this information help you.

    Project is here

    https://github.com/XJINE/Unity_VariousTextureInstancing