Search Unity

What is Terrain Neighbor for?

Discussion in 'World Building' started by NathanJSmith, Jan 15, 2019.

  1. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    I'm trying to set terrain height via script:

    But as you can see, the terrain edge isn't connected when I set height. I thought Terrain neighbor would help me solve this problem, I use this code to set neighbor
    Code (CSharp):
    1. void SetNeighbour(Terrain terrain, Terrain[] neighbours)
    2.     {
    3.         terrain.SetNeighbors(neighbours[(int)E_DIRECTION.WEST],
    4.             neighbours[(int)E_DIRECTION.NORTH],
    5.             neighbours[(int)E_DIRECTION.EAST],
    6.             neighbours[(int)E_DIRECTION.SOUTH]);
    7.         terrain.Flush();
    8.  
    9.         neighbours[(int)E_DIRECTION.WEST].SetNeighbors(null, null, terrain, null);
    10.         neighbours[(int)E_DIRECTION.NORTH].SetNeighbors(null, null, null, terrain);
    11.         neighbours[(int)E_DIRECTION.EAST].SetNeighbors(terrain, null, null, null);
    12.         neighbours[(int)E_DIRECTION.SOUTH].SetNeighbors(null, terrain, null, null);
    13.         foreach (var item in neighbours)
    14.         {
    15.             item.Flush();
    16.         }
    17.     }
    but it doesn't solve the problem, I see terrain neighbor does nothing. I guess I have to manually sew these terrain edge then. But I'm curious: what is Terrain Neighbor for?
     
  2. craigjwhitmore

    craigjwhitmore

    Joined:
    Apr 15, 2018
    Posts:
    135
    I'm going out on a limb here with an educated guess. If you have a large terrain, you don't want to keep all the terrain in memory at the same time to be processed. So, you load only the terrain neighbours that you want to be processed at any given time to save on cpu/memory.

    This explains some other uses for them also.

    https://blogs.unity3d.com/2018/10/10/2018-3-terrain-update-getting-started/

    Sorry i can't help with your problem, I haven't used them myself yet.
     
  3. crysicle

    crysicle

    Joined:
    Oct 24, 2018
    Posts:
    95
    You shouldn't use terrain.SetHeights() if you want to set heights for neighbouring terrains. Use TerrainPaintUtility.BeginPaintHeightmap() and TerrainPaintUtility.EndPaintHeightmap() methods.
     
    hippocoder likes this.
  4. wyattt_

    wyattt_

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    424
    This would be if you wanted to do the set height operations on GPU. Our Set Height tool still uses terrainData.SetHeights

    ---

    If you want all the neighboring Terrains to also have their heights set to match the selected/active Terrain, you will have to call the same SetHeights function on each TerrainData associated with those neighboring Terrains.

    ---

    It also may end up being the case that you will want to set the heights for all the Terrains with a given "Grouping ID" (found and modifiable in the Terrain's Settings tab). To do this, you can do:


    Code (CSharp):
    1. // using statements at the top of your file
    2. using System.Collections.Generic;
    3. using UnityEngine.Experimental.TerrainAPI;
    4.  
    5. // ...
    6. // somewhere else in your file where you are setting height data...
    7.  
    8. int groupingID = terrain.groupingID; // get the grouping ID from the active/selected Terrain
    9.  
    10. TerrainUtility.TerrainMap map = TerrainUtility.TerrainMap.CreateFromPlacement( terrain, ( t ) => { return t.groupingID == groupingID; } );
    11.  
    12. foreach( KeyValuePair< TerrainUtility.TerrainMap.TileCoord, Terrain > pair in map.m_terrainTiles )
    13. {
    14.     // do your set height operations on each Terrain, which you can access via pair.Value
    15. }
     
    Rowlan and NathanJSmith like this.
  5. wyattt_

    wyattt_

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    424
    Terrain neighbors allows us to track spatial relationships between different Terrain tiles(which, in our case is a 2D grid) so we can create the proper PaintContexts when painting across multiple Terrains.

    We grab the various clipped rectangles that the active Terrain Tool's brush covers, stitch them together to create a single RenderTexture, do the paint operation on that RenderTexture, then copy the subsections back into their respective areas on each Terrain Tile that was painted on.

    Here's an example where 4 clipped rectangles are generated, stitched together for a single RenderTexture, and copied back:

     
    Last edited: Apr 12, 2019
    knxrb, Rowlan and NathanJSmith like this.
  6. Redrag

    Redrag

    Joined:
    Apr 27, 2014
    Posts:
    181
    I'm on Unity 5.6. Could you let me know how terrain neighbours are implemented in this version? This seems to be a very poorly documented area. Is there a guide to how to tile terrains properly for different versions?
     
  7. wyattt_

    wyattt_

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    424
    This feature is available in Unity versions 2018.3 and up
     
    leftshoe18 likes this.
  8. ChrisTchou

    ChrisTchou

    Unity Technologies

    Joined:
    Apr 26, 2017
    Posts:
    74
    In addition to painting across borders, the Terrain neighbors are also used to make sure the LOD matches across tile borders, so you don't get any cracks in the rendering.

    FYI, the neighbors existed (just for rendering LOD purpose) prior to 2018.3, but you had to set them up manually in script; there was no UI to control them. The new addition in 2018.3 is auto-connect based on position and grouping ID.
     
    LittleRegicide and NathanJSmith like this.
  9. Mytino

    Mytino

    Joined:
    Jan 28, 2019
    Posts:
    16
    Hi, I have a related issue. I'm trying to connect terrains with code, but I can't get it to work. The heights on each terrain on its own is correct, I just need to connect them.

    What it looks like:
    upload_2019-6-13_23-56-37.png

    This is at the point where three terrains meet. You can see there's just a gap. I want the gap to be closed. I tried just nudging their positions a bit, but that doesn't solve it as textures and lighting still is disconnected.

    I've tried a lot of different code, including swapping right, left, bottom and top to assure I didn't have those wrong, as well as calling "terrain.flush()" and the static "Terrain.SetConnectivityDirty()" in different orders. Autoconnect is true for all terrains, and their groupingId is 0. Below is my current code.

    I call my terrain GameObjects chunks. This is the code I use to create a new chunk:
    Code (CSharp):
    1. TerrainData terrainData = Instantiate(defaultTerrainData);
    2.             GameObject chunk = Terrain.CreateTerrainGameObject(terrainData);
    3.             chunkDictionary[GetChunkKey(chunkX, chunkY)] = chunk;
    4.             Terrain terrain = chunk.GetComponent<Terrain>();
    5.             activeChunksCount++;
    6.  
    7.             UpdateChunkNeighbors(chunkX, chunkY);
    8.  
    9.             chunk.transform.position = new Vector3(chunkX * chunkSize.x, 0f, chunkY * chunkSize.y);
    where defaultTerrainData is a terrainData object from the assets.

    This is the code I use for updating the chunk neighbors. There's a lot of debug code there at the moment.
    Code (CSharp):
    1. private void UpdateChunkNeighbors(long chunkX, long chunkY)
    2.     {
    3.         GameObject chunk = GetChunk(chunkX, chunkY);
    4.  
    5.         if (chunk == null)
    6.         {
    7.             Debug.LogError("Chunk is null");
    8.         }
    9.  
    10.         Terrain terrain = chunk.GetComponent<Terrain>();
    11.  
    12.         Debug.Log("terrain.rightNeighbor old: " + terrain.rightNeighbor);
    13.         Debug.Log("terrain.leftNeighbor old: " + terrain.leftNeighbor);
    14.         Debug.Log("terrain.topNeighbor old: " + terrain.topNeighbor);
    15.         Debug.Log("terrain.bottomNeighbor old: " + terrain.bottomNeighbor);
    16.  
    17.         GameObject rightChunk = GetChunk(chunkX + 1, chunkY);
    18.         GameObject leftChunk = GetChunk(chunkX - 1, chunkY);
    19.         GameObject topChunk = GetChunk(chunkX, chunkY + 1);
    20.         GameObject bottomChunk = GetChunk(chunkX, chunkY - 1);
    21.  
    22.         if (rightChunk != null)
    23.         {
    24.             Terrain terrain1 = rightChunk.GetComponent<Terrain>();
    25.             terrain.SetNeighbors(terrain.leftNeighbor, terrain.topNeighbor, terrain1, terrain.bottomNeighbor);
    26.             terrain1.SetNeighbors(terrain, terrain1.topNeighbor, terrain1.rightNeighbor, terrain1.bottomNeighbor);
    27.             Terrain.SetConnectivityDirty();
    28.             terrain.Flush();
    29.             terrain1.Flush();
    30.         }
    31.  
    32.         if (leftChunk != null)
    33.         {
    34.             Terrain terrain1 = leftChunk.GetComponent<Terrain>();
    35.             terrain.SetNeighbors(terrain1, terrain.topNeighbor, terrain.rightNeighbor, terrain.bottomNeighbor);
    36.             terrain1.SetNeighbors(terrain1.leftNeighbor, terrain1.topNeighbor, terrain, terrain1.bottomNeighbor);
    37.             Terrain.SetConnectivityDirty();
    38.             terrain.Flush();
    39.             terrain1.Flush();
    40.         }
    41.  
    42.         if (topChunk != null)
    43.         {
    44.             Terrain terrain1 = topChunk.GetComponent<Terrain>();
    45.             terrain.SetNeighbors(terrain.leftNeighbor, terrain1, terrain.rightNeighbor, terrain.bottomNeighbor);
    46.             terrain1.SetNeighbors(terrain1.leftNeighbor, terrain1.topNeighbor, terrain1.rightNeighbor, terrain);
    47.             Terrain.SetConnectivityDirty();
    48.             terrain.Flush();
    49.             terrain1.Flush();
    50.         }
    51.  
    52.         if (bottomChunk != null)
    53.         {
    54.             Terrain terrain1 = bottomChunk.GetComponent<Terrain>();
    55.             terrain.SetNeighbors(terrain.leftNeighbor, terrain.topNeighbor, terrain.rightNeighbor, terrain1);
    56.             terrain1.SetNeighbors(terrain1.leftNeighbor, terrain, terrain1.rightNeighbor, terrain1.bottomNeighbor);
    57.             Terrain.SetConnectivityDirty();
    58.             terrain.Flush();
    59.             terrain1.Flush();
    60.         }
    61.  
    62.         Terrain.SetConnectivityDirty();
    63.  
    64.         Debug.Log("Terrain.activeTerrains: " + Terrain.activeTerrains.Length);
    65.         Debug.Log("terrain.allowAutoConnect: " + terrain.allowAutoConnect);
    66.         Debug.Log("terrain.groupingID: " + terrain.groupingID);
    67.  
    68.         Debug.Log("terrain.rightNeighbor new: " + terrain.rightNeighbor);
    69.         Debug.Log("terrain.leftNeighbor new: " + terrain.leftNeighbor);
    70.         Debug.Log("terrain.topNeighbor new: " + terrain.topNeighbor);
    71.         Debug.Log("terrain.bottomNeighbor new: " + terrain.bottomNeighbor);
    72.     }
    I don't have much time before my project deadline, so I would really appreciate some quick help! Thanks in advance.
     
  10. crysicle

    crysicle

    Joined:
    Oct 24, 2018
    Posts:
    95
    @Mytino The problem in your code looks straightforward. The faulty behaviour comes obviously from the position of the chunks, so the "chunk.transform.position" logic must be faulty. I'm not sure about the actual algorythm that's gonna fix the issue, but here's a few ideas:
    Code (CSharp):
    1.  
    2. // This will give a value of how many vectors does it take to travel 1 heightmap.
    3. Vector2 heightmap_to_vector = new Vector2(terrain_size.x / heightmap_resolution, terrain_size.z / heightmap_resolution);
    4.  
    5. // Possible fixes, if none of these work the logic for the actual fix will most likely be similar nonetheless. You're looking for the correct number to offset each terrain. Right now that number is too big.
    6.  
    7. // Possible solution 1
    8. chunk.transform.position = new Vector3(chunkX * (chunkSize.x - heightmap_to_vector .x), 0f, chunkY * (chunkSize.y - heightmap_to_vector.y));
    9.  
    10. // Possible solution 2
    11. chunk.transform.position = new Vector3(chunkX * (chunkSize.x - chunkX * heightmap_to_vector .x), 0f, chunkY * (chunkSize.y - chunkY * heightmap_to_vector.y));
    12.  
    As for syncing terrain's by grouping, at least in my experience, unity does all of that for you as long as you make terrain positions, heightmap resolution and maximum terrain height uniform.
     
    Last edited: Jun 14, 2019
  11. Mytino

    Mytino

    Joined:
    Jan 28, 2019
    Posts:
    16
    Thank you, but I already tried this. This is what I meant by "I tried just nudging their positions a bit, but that doesn't solve it as textures and lighting still is disconnected."

    I would expect textures as well as geometries to seamlessly connect and they don't connect in any way. Interestingly enough, the scene view makes it seem like they're connected, as it only highlights terrains around the existing terrains when the "Create neighbor terrains" tool is active, as can be seen here:
    upload_2019-6-14_19-23-0.png

    Here I also tried creating a new terrain (lower right), but as you can see, it didn't connect, and it shows an error message which seem to be an Unity-side bug.
     
  12. crysicle

    crysicle

    Joined:
    Oct 24, 2018
    Posts:
    95
    @Mytino this might be a bit of a longer conversation for this type of bug. Check in with discord channels. https://discordapp.com/channels/85338836384628736/85593628650504192 and https://discordapp.com/channels/493510779866316801/493511037421879316 for faster answers to dev questions. Are those terrains painted already in a different program or are they made via some kind of perlin noise? If they're made via perlin noise then i'd suggest to offset the heightmap coordinates similarly to the offset you applied to the terrain positions. Also i've noticed that the edges of some of the terrains are going straight down, which indicate that the first Y and X rows of the heightmap are set to 0.
     
    Mytino likes this.
  13. Mytino

    Mytino

    Joined:
    Jan 28, 2019
    Posts:
    16
    Thank you for the links! The terrains were initially generated with gradient noise, but I've ran each of them through a neural net afterwards, which is why they're so disconnected at the edges. The edges go straight down on two of the four sides only. I just thought it was default when it wasn't connected, so interesting to know it means they're 0, thanks!

    Edit: The links don't seem to lead anywhere?

    Edit 2:
    Clamping the edge where heights were 0 looks much better, and it seems like the lighting/pattern disconnect is actually fixed. The only issue is that the heightmaps don't automatically stitch together, so I will have to go through the neighbors and copy over their edge values if I want to get it perfect. Thanks again!

    upload_2019-6-15_5-42-21.png
     
    Last edited: Jun 15, 2019
  14. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    If we are adding terrain to the scene at runtime, am I correct that we will need to connect the terrains the old fashion way using SetNeighbors?

    I just want to make sure that there is no way to make use of the grouping ids and auto connection magic at runtime (for terrains that don't exist before the scene is loaded).

    And if indeed SetNeighbors needs to be used, is there any issue with connecting terrain that don't have the same Grouping ID? I don't foresee this being an actual issue, but if it is a hard fast rule that SetNeighbors will now only work on terrain with the same Grouping ID, I want to know so I can warn the users of my Asset Store tools.
     
  15. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    I'll answer my own question, SetNeighbors does not seems to work anymore on Unity 2018.4 and up (though probably the issue was introduced with 2018.3 when Terrain Groups were introduced).

    I'm uploading a package that you can import into Unity 2018.4 or above version of Unity to see the issue. There's two scenes, one where AutoConnect is enabled and one where it is disabled and SetNeighbors is called manually. AutoConnect works on the terrain while SetNeighbors manual call does not. It makes no sense to me but it is what it is.

    Edit: Have to tag you wyattt_ and ChrisTchou in the hopes for getting help for this, as I've tried elsewhere on the forums and can't get a nibble from anyone Unity related.

    Edit 2: Here is a thread I started about the issue: https://forum.unity.com/threads/ter...ducible-example-attached.871693/#post-5893550
     

    Attached Files:

  16. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    I think I have finally figured out what is happening. The Auto Connect feature has a method under the hood that performs the magic of setting all the neighbors that utilize the Auto Connect option. This method is seemingly called in one of the first two frames upon entering your game, and then any time a terrain is added or removed from the scene, regardless of whether any terrain in the scene has the auto connection option enabled/disabled.

    Now, this should not be an issue, because the AutoConnect method should exit if it detects no terrain are utilizing the auto connect option. Unfortunately, it doesn't do this. Instead, it proceeds to reset the neighbors of EVERY terrain in the scene, no matter if it is utilizing auto connect.

    So, let's say you have a static scene with some number of terrain, and for whatever reason you are manually setting the neighbors via SetNeighbors. To get around the issue with Auto Connect, all you need to do is wait until Time.frameCount = 3 or more before setting the neighbors, because by this time the magic AutoConnect method will have run and you can safely set the neighbors without fear of them being reset.

    However, if you are in the more likely situation of adding/removing terrain at runtime, you only have the poor option of resetting the neighbors of all terrain in the scene after the Auto Connect method has run and wreaked its havoc. If you have multiple terrain being added/removed in quick succession, I recommend waiting until after the last has finished being added/removed before performing the re-neighboring. Unfortunately, this means there will be a very brief time where the seems between neighbors is visible, however there is no way to get around this, because the only alternative is to call SetNeighbors on all terrain in a single frame (the technique employed by AutoConnect), which is not performant and would defeat the whole purpose of manually setting the neighbors in the first place.

    Unity, please fix this. It is such a simple fix, it would take less than 10 lines of code probably.
     
    Last edited: Sep 10, 2021
  17. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Going to add onto my own posts. After further investigation I hoped to be able to solve the issue by trying to detect when the terrain neighbors are cleared automatically and resetting the neighbors ASAP for terrain that are in view of a working camera.

    This kind of works however there is a strange issue where the terrain flickers occasionally. I'm not sure why that happens, the code for fixing the neighbors executes in the RenderPipelineManager.endFrameRendering event.

    My investigation also found that the AutoConnect is called by Unity during the render step, before culling and rendering terrain in the scene. My guess is Unity checks the Active Terrains in the scene and sees if they have changed, and fires off the Auto Connect method if they have. Since everything is done in this render step, I don't believe there is a way to interject the neighbor resetting code in between the call to AutoConnect and the rendering of the terrain.

    Therefore, I believe the best way to resolve this issue is to modify the Auto Connect code in the way I described in the comment section of this bug report:

    Edit: Submitted a new bug because the old one was removed without comment. Here is the link:
    https://fogbugz.unity3d.com/default.asp?1393860_3031p009t2hitemt
     
    Last edited: Jan 10, 2022
    IsobelShasha, Tymianek and TerraUnity like this.
  18. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Bumping because I have not heard a single word on this issue, which is a shame because the code fix is so simple to implement.
     
  19. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Noisecrime and chadfranklin47 like this.
  20. mcintyrecorey53

    mcintyrecorey53

    Joined:
    Oct 22, 2023
    Posts:
    4
    what you want is to check the box auto connect in the terrain settings, this will solve your issue
     
  21. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Who are you replying to?
     
  22. mcintyrecorey53

    mcintyrecorey53

    Joined:
    Oct 22, 2023
    Posts:
    4
    the poster to the question
     
  23. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Okay, well terrain neighboring was not that users issue.

    Their problem is they thought that so long as they set the neighbors for the terrain, when they called SetHeights on that terrain the other neighboring terrains would have their heights adjusted automatically, which is not the case.

    As the devs commented, setting the neighbors properly (either via SetNeighbors or using Auto Connect) accomplishes two things:
    1. It allows the terrain editor code to know which terrain are next to each other, so that when you perform operations on the edges of the terrain those operations affect all terrain on the edge.
    2. It allows LODs on different terrain to match across tile borders.