Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

TerrainData API & NativeArray

Discussion in 'World Building' started by isaac-ashdown, Apr 16, 2019.

  1. isaac-ashdown

    isaac-ashdown

    Joined:
    Jan 30, 2019
    Posts:
    69
    Hello,
    We're doing a lot of procedural generation of terrain data at runtime, and are using the c# jobs system + Burst to make that nice and fast. We essentially generate a bunch of 2D arrays in these jobs, which we then push onto the Terrain with functions like `TerrainData.SetHeights`. However, these take 2D c# arrays only, and converting between the native arrays and managed arrays has some overhead.

    I'd like to request versions of the `TerrainData.SetXXX` functions that take a NativeContainer type that can be used in the Jobs system, to avoid this overhead.
    Thanks,
    Isaac
     
  2. LennartJohansen

    LennartJohansen

    Joined:
    Dec 1, 2014
    Posts:
    2,394
    I would like to see a NativeArray access to the internal heighmap like the one you can get from Texture2D. This way we could both read and write to the native data without extra memcopy.
     
  3. CityGen3D

    CityGen3D

    Joined:
    Nov 23, 2012
    Posts:
    680
    I completely agree, that's my experience too.
    I'm glad others have picked up on this as I'd been meaning to bring it up and I wasnt sure if I was missing a trick or something.

    The conversion from NativeArray to 2D array for SetHeights takes up a relatively large chunk of time and its such a shame to hit this bottle-neck after the actual work is done so fast with the Jobs/Burst implementation.

    So it would be great if we didn't have to do this conversion to speed it up even more.
     
    isaac-ashdown and andrew-lukasik like this.
  4. isaac-ashdown

    isaac-ashdown

    Joined:
    Jan 30, 2019
    Posts:
    69
    FYI: To speed up the copying, you can use these unsafe functions:

    Code (CSharp):
    1.         public unsafe void ToArray(T[,] arrOut)
    2.         {
    3.             void* unsafeVals = NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(values);
    4.  
    5.             ulong handle;
    6.             var arrOutPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(arrOut, out handle);
    7.  
    8.             UnsafeUtility.MemCpy(arrOutPtr, unsafeVals, sizeX * sizeY * UnsafeUtility.SizeOf<T>());
    9.  
    10.             UnsafeUtility.ReleaseGCObject(handle);
    11.         }
    12.  
    13.  
    14.         public unsafe void FromArray(T[,] arrIn)
    15.         {
    16.             void* unsafeVals = NativeArrayUnsafeUtility.GetUnsafePtr(values);
    17.  
    18.             ulong handle;
    19.             var arrOutPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(arrIn, out handle);
    20.  
    21.             UnsafeUtility.MemCpy(unsafeVals, arrOutPtr, sizeX * sizeY * UnsafeUtility.SizeOf<T>());
    22.  
    23.             UnsafeUtility.ReleaseGCObject(handle);
    24.         }
    values is a NativeArray<T>. You'll need to turn on the 'unsafe' tickbox in your assembly definition (and you'll need to make one of those if you haven't already for your code).

    But still, it would be good to be able to pass a Native structure directly to the Terrain API, as this method still requires allocating the c# array, which can be very large.
     
    Last edited: Apr 18, 2019
    andrew-lukasik likes this.
  5. isaac-ashdown

    isaac-ashdown

    Joined:
    Jan 30, 2019
    Posts:
    69
    So it turns out you can use the heightTexture RenderTarget as a way of getting data to the Terrain via a NativeArray:

    Code (CSharp):
    1.  
    2. Terrain terrainComp;
    3. var renderTex = terrainComp.terrainData.heightmapTexture;
    4. Texture2D tempTex = new Texture2D(renderTex.width, renderTex.height, UnityEngine.Experimental.Rendering.GraphicsFormat.R16_UNorm, UnityEngine.Experimental.Rendering.TextureCreationFlags.None);
    5.  
    6. NativeArray<ushort> rawData = tempTex.GetRawTextureData<ushort>();
    7.  
    8. // fill your rawData here - this draws a sin wave
    9. // note that for some reason a value of 1 in the heights array corresponds to ushort.MaxValue / 2
    10. for (int i = 0; i < rawData.Length; i++)
    11. {
    12.   int y = i % renderTex.width;
    13.   float alpha = (float)y / (float)renderTex.height;
    14.   rawData[i] = (ushort)Mathf.Lerp(0, (float)ushort.MaxValue * 0.5f, 0.5f + ((Mathf.Sin(alpha * 10) * 0.5f)));
    15. }
    16.  
    17. tempTex.Apply();
    18.  
    19. Graphics.CopyTexture(tempTex, renderTex);
    20.  
    21. terrainComp.terrainData.UpdateDirtyRegion(0, 0, renderTex.width, renderTex.height, true);
    22. terrainComp.ApplyDelayedHeightmapModification(); // not sure why this is also necessary, but otherwise physics doesn't get synced properly it seems
    23.  
    I'm not sure if this would work for detail & alphamap textures though, so a direct NativeArray API would still be useful. But at least for heights this has shown to be a lot faster in my case.

    Note this is with 2018.3, I think the api has changed a bit since.
     
    Last edited: May 8, 2019
  6. isaac-ashdown

    isaac-ashdown

    Joined:
    Jan 30, 2019
    Posts:
    69