Search Unity

Documentation for TerrainData.GetPatchMinMaxHeights()

Discussion in 'World Building' started by ferretnt, Nov 17, 2021.

  1. ferretnt

    ferretnt

    Joined:
    Apr 10, 2012
    Posts:
    379
    Is there any documentation describing the XZ extents of the patches returned by TerrainData.GetPatchMinMaxHeights() (and corresponding override method)? The array returned doesn't seem to be an n*n array given a square terrain - is it hierarchical in some way, and if so is there a way to find out how?

    There seems to be no Google hit for Get/Override PatchMinMaxHeights(), nor any use of it in any public source code, including the Unity Terrain Utilities package. As far as I can see, the number of patches returned does not line up with any possible nxn representation of the terrain data.

    Specifically, we have a terrain with nodata values (which are masked out in the alpha channel), but height values within that nodata extent are causing Unity's terrain rendering to generate far more triangles than necessary (presumably because it's using patchMinMaxHeight to compute the possible triangle density required, when in fact the visible vertices have much smaller extent.)

    (Thanks to @richardkettlewell for politely pointing out that I should probably have posted in this forum, not graphics, after I had thrice posted here: https://forum.unity.com/threads/doc...a-getpatchminmaxheights.1153991/#post-7657018)
     
    richardkettlewell likes this.
  2. SteveTheodore

    SteveTheodore

    Unity Technologies

    Joined:
    Oct 7, 2020
    Posts:
    4
    Do you have the `Draw Instanced` flag set in your terrain? Does the problem persist regardless of that setting?
     
  3. ferretnt

    ferretnt

    Joined:
    Apr 10, 2012
    Posts:
    379
    Thanks Steve - Yes I’m using draw instanced, although I think the question is the same with or without DrawInstanced. The problem is that GetMinMaxHeights returns an array of vertical extents (min/max Y-values), each entry corresponding to… err…. some XZ extent, i.e. some subset of pixels in the heightmap, but there is no way to know what subset.

    The same issue exists if you want to call OverrideMinMaxHeights - you can pass an array of desired minmax heights, but it's not at all clear how to construct that array.
     
  4. SteveTheodore

    SteveTheodore

    Unity Technologies

    Joined:
    Oct 7, 2020
    Posts:
    4
    Sorry for the holiday-induced delay in getting back to you. Here's the way the C++ code calculates the indices of a given patch


    /// Calculates the index of the patch given it's level and x, y index
    int Heightmap::GetPatchIndex(int x, int y, int level) const
    {
    int index = 0;
    int size = 1 << m_Levels;
    for (int i = 0; i < level; ++i)
    {
    index += size * size;
    size = size >> 1;
    }
    return index + size * y + x;
    }


    The call to GetMinMaxHeights() returns the whole data structure:


    dynamic_array<PatchExtents> Heightmap::GetMinMaxPatchHeights()
    {
    dynamic_array<PatchExtents> returnedData;
    PatchExtents* begin = reinterpret_cast<PatchExtents*>(m_MinMaxPatchHeights.data());
    PatchExtents* end = begin + m_MinMaxPatchHeights.size() / 2;
    returnedData.assign_external(begin, end);
    return returnedData;
    }


    So the packed data implied by the first function for a particular patch is coming back en-bloc for the whole terrain, which is why it's hard to parse. Your guess about it being hierarchical is correct.

    From a (non-expert) look at the way bounds are calculated (in
    Heightmaps.cpp
    ) it looks like the at a given mip level the patch index is calculated using getPatchIndex and that it is then used to cerate the height of the AABB around the patch. That in turn gets passed to a quadtree (in
    TerrainRenderer.cpp
    ) which recursively picks lods based on the pixelError value (which you set on the terrain object).

    If you need to customize this, I think you need to copy the data you're getting back from
    GetMinMaxPatchHeights()
    , replacing the height values in the problematic quads, and then pass it back into
     OverrideMinMaxPatchHeights()
    since they are both keyed to the same hierarchical arrangement
     
    ferretnt likes this.
  5. ferretnt

    ferretnt

    Joined:
    Apr 10, 2012
    Posts:
    379
    Fantastic, thank you!

    So a 33x33 terrain (the smallest size supported) always returns an array of size 5 (the whole terrain's minmax, and a 2x2 array of sub-minmaxes)
    65x65 returns 21 (the 5 above, plus 4x4)
    129x129 returns 85 (the 21, plus 8x8)
    257x257 returns 341 (the 85 above, plus 16x16)
    513x512 returns 1365 (the 341 above, plus 32x32)

    and so on. In fairness, I probably should have spotted the pattern, but it was obvious after your code post.

    This gives us everything we need, and also the fact these are hierarchical implies how the patch LoD is chosen at runtime so I expect we can force it by overriding these values in the right place in the flattened tree structure.

    Perfect. Thanks!
     
    richardkettlewell likes this.
unityunity