Search Unity

Accessing float array in a shader.

Discussion in 'Shaders' started by Coko_97, Feb 15, 2019.

  1. Coko_97

    Coko_97

    Joined:
    Feb 2, 2016
    Posts:
    1
    Hello. I am very new to shader programming within unity and was wondering if there were anyone who could give me pointers or good sources on accessing a float array stored in a script in a shader? I want to manipulate the vertex colour according to the value in the array in that position (x,y,z). The numbers in the array are random between 0 and 1.

    This is my data being created:

    arr = new float[xSize, ySize, zSize];

    for (int x = 0; x < xSize; x++)
    {
    for (int y = 0; y < ySize; y++)
    {
    for (int z = 0; z < zSize; z++)
    {
    float number = Random.Range(0.0f, 1.0f);
    arr[x, y, z] = number;

    }
    }
    }
    Any help would be amazing, I have been trying to search this for days but have had no luck.
    Thanks in advance!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    Generally speaking you don't use multidimensional arrays in HLSL. Some APIs support them, but Unity doesn't provide a way to set them. Instead you'll want to flatten your array out into one long single dimension array, or alternatively you could pack your data into a 3d texture or texture array.

    For a single dimension array you'd want to make an array that is float[xSize * ySize * zSize]. Accessing the data in the array from the shader would then be done using something like:

    valueArray[x * (ySize * zSize) + y * (zSize) + z];
     
    Last edited: Feb 15, 2019
    ImOneIvan likes this.
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    Oh, and because you probably also want to know this, to allow shaders to access arrays, or any value, you have to manually pass that data by setting it on the material being used with one of the Set*() functions, or using the Shader.SetGlobal*() functions.

    For example, for a float array:
    https://docs.unity3d.com/ScriptReference/Material.SetFloatArray.html
    https://docs.unity3d.com/ScriptReference/Shader.SetGlobalFloatArray.html

    You can either have your script have a reference to the material directly, or get the material from a renderer component. Know that directly modifying a material may modify the asset on disk, meaning changes to any values in the Properties list (those that show up in the inspector) will be saved! Using the .material or .materials list on the renderer component will automatically create a copy of the material during play mode, but it's up to you to destroy the material if you destroy the object, otherwise you're potentially creating a memory leak. Using .sharedMaterial will be directly modifying the material just like having a direct reference, as will using .material during edit time.

    A cleaner way is using a MaterialPropertyBlock to set temporary values on a renderer component.
    https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.html
    https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.SetFloatArray.html

    This won't modify the material or create copies that need to be cleaned up.
    Code (csharp):
    1. public Renderer rend;
    2. private MaterialPropertyBlock _matBlock;
    3.  
    4. void Awake()
    5. {
    6.     // create this once and reuse
    7.     _matBlock = new MaterialPropertyBlock();
    8. }
    9.  
    10. void Update()
    11. {
    12.     float[] values = new values[64];
    13.     for (int i=0; i<64; i++)
    14.         values[i] = Random.value;
    15.     _matBlock.SetFloatArray("_Values", values);
    16.     rend.SetPropertyBlock(_matBlock);
    17. }
    Also important to know, regardless of what method you use, the size of the array will be limited to the size it is first set as. So if you first set it as an array that's 4 elements long, then want to change it to be 64 elements, only the first 4 will be available. You may need to restart the editor in some cases to change the size after it's been set! Generally speaking you should be using fixed length arrays that match between your shader and code, but if you need variable length arrays you'll need to use an array that's as big as you think you'll ever need and pass in the current valid length as a separate variable.
     
    esbenrb, joshuacwilde and sundxing like this.
  4. joshuacwilde

    joshuacwilde

    Joined:
    Feb 4, 2018
    Posts:
    726
    Commenting for future wonderers/wanderers. There is currently a bug where you can't use SetFloatArray() on a material (maybe it is only for terrain materials, I am using a terrain material). You MUST use a MaterialPropertyBlock (and SetSplatMaterialPropertyBlock(), in the terrain case). You can't call SetFloatArray() on the material. That won't work.
     
    bgolus likes this.
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    I use arrays on materials frequently, but I'm not surprised it might be broken on something like terrain as that does a lot of "extra" stuff to their materials.