Search Unity

Passing Arrays to shaders

Discussion in 'Shaders' started by akutruff, Apr 17, 2013.

  1. akutruff

    akutruff

    Joined:
    Jul 24, 2009
    Posts:
    44
    Why can I not pass an array to a vertex shader? This is driving me nuts, and it is terribly limiting. Aras, please tell me this is for a good reason. Restore my faith in humanity.

    Also, a vertex texture fetch isn't really a solution if you want to update the array per frame. Procedural mesh generation destroys the gpu, and worse, unity does not allow custom vertex attributes.
     
    Last edited: Apr 17, 2013
  2. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    Could you explain to me why vtf isn't an option when updating the array per frame? To what isn't it a solution?
    I'm also unsure what you mean when you say that procedural mesh generation destroys the GPU.

    The project I'm currently working on has used more than a million procedurally generated vertices, and the only problems I ran into were related to the CPU (Preparing complex meshes for use with PhysX, and the limit of vertices per mesh, due to the 32 bit index that's used). If it ends up blowing my GPU in a way I don't expect, I would like to know ;)

    If you really need to feed a lot of custom data to the GPU, that you can't fit in the vertex colors, you could consider using DirectCompute, which allows you to use ComputeBuffers.
     
  3. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    VTF is unsupported on old/mobile GPUs. I believe it's also slower on modern GPUs, although not as bad as the first ones to support it.

    I would like to be able to initialize a static array of colours from script every frame in order to pass a bunch of data into a shader and use it in a fragment shader. I could use separate properties, but that makes changing the "array" length a pain, and you can't write the loop as a loop.
     
  4. akutruff

    akutruff

    Joined:
    Jul 24, 2009
    Posts:
    44
    Thanks for replies.

    RC-1290 - Oh how I wish I had the freedom of DX11! However, we are shipping on iPhone 4 hardware, and we are squeezing every last drop of performance we can. Like many others, dynamic batching doesn't really help. It's like pushing down a lump in the carpet. With dynamic batching off, you get a CPU hit from the draw calls. Turning batching on means the driver does less work, but the same amount of CPU is just shifted over to the batching system. (In our experience, dynamic batching only helps when you have a handful of objects with way less vertices than the cutoff points set by Unity.)

    Having said all that, we hoped to push off some rendering work to the GPU. NVidia layed out a clever way of geometry instancing without specific hardware support: You duplicate your mesh data, but insert an extra attribute per vertex that is an instance identifier. You render that entire mesh, and in your vertex shader you can use that instance identifier as an index into an array of matrices, or, in our case, simply a position vector. The end result is a single draw call, and no CPU time spent in the dynamic batching system. However, the whole thing breaks down if you can't declare array uniforms in a vertex shader.

    I was hoping that we could use bone weights as a halfway measure, but Unity does not give access to the bone weights and indices in the shaders. (Why, I have no idea, and it is really disappointing.)

    I did look into doing vertex texture fetching, and wrote up a solution. In general, I don't like locking GPU resources per frame and a 10,000 texture lookups in the vertex stage gives me pause, especially when arrays are a rather fundamental and officially supported data type in most other shader frameworks. They're there for a purpose.

    Since writing my original post, I discovered a means of actually setting a real array in the vertex shader. Because of this, the instancing technique works, and we went from 44 FPS to 52 FPS 10 minutes after I discovered this maneuver.

    It doesn't require a plugin or anything special. I don't think this is a documented way of handling arrays, and I reverse engineered how to get it done. Before posting the solution publicly and promoting a potentially unintended means of accomplishing shader arrays, I'd like to hear a Unity person weigh in. Otherwise, I'll post the solution later today. (Someone else probably discovered this too, or it's published elsewhere deep in the internet that I failed to find from hours of googling.)

    BTW: Here is a link to the aforementioned instancing technique: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter03.html It's under "Vertex Constants Instancing"
     
    Last edited: Apr 17, 2013
  5. Aras

    Aras

    Unity Technologies

    Joined:
    Nov 7, 2005
    Posts:
    4,770
    It's hard to comment whether your approach is "correct" or "hacked" one without knowing it.

    I can only guess that you're declaring an array in the shader, e.g. float4x4 matrices[10]; and then Unity compiler translates that into 10 individual matrix properties, matrices0, matrices1, ... You set each of them individually from the code, but in the shader it's still an array that you can index fine.

    If so, yeah, the above approach works and would be a workaround. And I agree we should get "real" array support. Someday.
     
    deus0 likes this.
  6. akutruff

    akutruff

    Joined:
    Jul 24, 2009
    Posts:
    44
    Thanks for responding so quickly, Aras.

    Yep, that's exactly what I'm doing. Didn't mean to be all cloak and dagger, and was just trying to not spread things that aren't widely publicized.

    Is this documented anywhere? I've seen a couple threads with people banging their heads on the wall just as I was.

    Would you mind providing some insight as to the design decision, or what challenges their are for adding such a feature? I'm very interested in getting access to custom vertex formats beyond what's locked down by the Mesh class.
     
    deus0 likes this.
  7. Aras

    Aras

    Unity Technologies

    Joined:
    Nov 7, 2005
    Posts:
    4,770
    It's not so much a "design decision", as much as "implementhing anything is actual work" :) Especially once you take into account how arrays are represented/reported differently by the umpteen shader compilers that are used in Unity.

    We have some work underway to to have slightly more data in meshes. First user visible thing will be "can have more than 2 UV sets!", but there's also some internal refactoring work to allow more flexible mesh data, that we'll somehow expose later.
     
    deus0 likes this.
  8. jesta

    jesta

    Joined:
    Jun 19, 2010
    Posts:
    294
    Bumping this thread so that this feature is not forgotten :). Passing arrays to shaders would be extremely useful for a lot of effects (anything with kernels, full-screen effects).

    I guess we can't hope to see this in 4.3 though...
     
  9. reefwirrax

    reefwirrax

    Joined:
    Sep 20, 2013
    Posts:
    137
    Hi jesta, i am a noob so i figure i am confusing things, although i thought that it was ok to pass an array to a shader as a 1d and a 2d texture. certainly the permutation table on scrawkblogs gpu improved perlin is a passed array. something of this style:

    Code (csharp):
    1.     void Start ()
    2.     {
    3.         m_perlin = new ImprovedPerlinNoise(m_seed);
    4.        
    5.         m_perlin.LoadResourcesFor3DNoise();
    6.        
    7.         renderer.material.SetTexture("_PermTable2D", m_perlin.GetPermutationTable2D());
    8.         renderer.material.SetTexture("_Gradient3D", m_perlin.GetGradient3D());
    9.     }
     
  10. brianasu

    brianasu

    Joined:
    Mar 9, 2010
    Posts:
    369
    I don't know if this is really relevant but this is a quick way to "pass" an array in

    1. Setup a new RenderTexture to your array size
    2. Use GL.LoadPixelMatrix to render a bunch of quads (each quad = 1 pixel)
    3. Encode each pixel with color into a float rgba. You might want to do a look up table to make it faster or pre compute on another frame
    3. Call camera.render
    4. This will blit all those quad pixels to that render texture
    5. Now pass that render texture to your device

    It's not the best method but it's backwards compatible and I think a lot faster than setpixel.
     
  11. MartinV

    MartinV

    Joined:
    Nov 12, 2013
    Posts:
    3
    I too would love to pass arrays to shader .. I still cannot get over this not being implemented yet, and I'm scratching my head on how to do things I had working in other engines before.
     
  12. brilliantgames

    brilliantgames

    Joined:
    Jan 7, 2012
    Posts:
    1,937
    +1 please! This is super important and I think its holding back alot of potential!
     
  13. derPuppeteer

    derPuppeteer

    Joined:
    Mar 21, 2014
    Posts:
    23
    Does this "hack" yield any dangers when publishing a project with it? It seems more like a feature than a workaround for me.
    Because some of my shaders heavily rely on that.
     
  14. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    It's kind of hacky because you don't access it as an array on the CPU side. It might not be the easiest code to maintain.
     
  15. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    Well, one could do this to set an array to an "array" in CG. Just something I thought of that might help others:
    Code (csharp):
    1.  
    2. var array : int[];
    3. function Update {
    4. for (var i = 0; i < array.Length; ++i) {
    5. renderer.sharedMaterial.SetInt("array"+i.ToString(), array[i]);
    6. }
    7. }
    Of course, it is easy to use something other than a set of integers for this, and it's as simple as re-declaring the variables as, for example, a set of Texture2Ds or Vector4s (As far as I know, only Vector4s can actually be passed into a shader... :D)
     
    atomicjoe likes this.
  16. bluescrn

    bluescrn

    Joined:
    Feb 25, 2013
    Posts:
    642
    Should this still work in Unity 5? I'm currently on 5.4.0b13, also trying to do a form of instancing, and this naming convention doesn't appear to work to set shader params.

    I've declared an array of matrices in the shader:

    float4x4 _MultiMeshTransform[8];

    Setting "_MultiMeshTransform" appears to set the first entry in the array, but setting "_MultiMeshTransform0" doesn't.

    And in the frame debugger, I only see a single MultiMeshTransform matrix.

    Any ideas?

    edit: Ah, it's changed in 5.4, and we have real array support via MaterialPropertyBlock:
    http://forum.unity3d.com/threads/passing-array-to-shader.392586/

    edit2: Seems to work nicely so far (tested in the editor and on Android)
     
    Last edited: May 4, 2016
  17. Aras

    Aras

    Unity Technologies

    Joined:
    Nov 7, 2005
    Posts:
    4,770
    Yes, Unity 5.4 has actual arrays support.
     
    bluescrn likes this.
  18. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    YESSSS! Time to optimize again... :D There's a good few things of mine that could do with this.
     
  19. hammil

    hammil

    Joined:
    Jun 5, 2013
    Posts:
    56
    I'm sure you're aware of all this, but uniform array support is kinda broken right now, possibly more broken than it was before. The backend changes to materials where it only serializes named properties are great, but there's no way to specify an array property in the shader header (we have the Vector, Float types but no VectorArray, FloatArray etc.) so as such, every time the editor recompiles, you instantiate a material etc. you have to update the properties manually. And since that can happen at any time you basically need to set it every frame. Can we expect a fix for this any time soon?
     
  20. Darkcoder

    Darkcoder

    Joined:
    Apr 13, 2011
    Posts:
    3,413
    Just came across a similar issue myself. I really wish someone at Unity would just sit down for a few days and actually finish the shader property serialization system once and for all. It's pretty annoying how arrays and especially matrices cannot be serialized. In most cases this is fine, but for more complex features this requires writing silly code to set these values when required, which is especially annoying when dealing with shaders that need to work in edit mode too.