Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

What is Terrain Neighbor for?

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

  1. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    51
    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:
    17
    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:
    72
    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. wyatttt

    wyatttt

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    271
    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. wyatttt

    wyatttt

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    271
    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
    Rowlan and NathanJSmith like this.
  6. Redrag

    Redrag

    Joined:
    Apr 27, 2014
    Posts:
    127
    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. wyatttt

    wyatttt

    Unity Technologies

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

    ChrisTchou

    Unity Technologies

    Joined:
    Apr 26, 2017
    Posts:
    65
    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.
     
    NathanJSmith likes this.
  9. Mytino

    Mytino

    Joined:
    Jan 28, 2019
    Posts:
    5
    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:
    72
    @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:
    5
    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:
    72
    @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:
    5
    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