Feb 14, 2012

Building endless terrain in Unity3D

[EDIT] Find the updated location for Helix Toolkit’s DoubleKeyDictionary.cs here..

[EDIT] Finally, the new, independent script is up here

The first thing we needed in ‘Songlines’ was to create a realistic open world environment is to have no boundaries, where the player can move freely and fly in any direction as far as he wanted, and one thing we noticed very quickly was the terrain in Unity was created as a tile, and each tile is described by a width, height, length, and the most important attribute for us, as we’ll see later, ‘heightmap resolution’. The details of these attributes is described in the Unity – Using Terrains

We start off by placing a single Unity terrain tile, as shown here:

Single Terrain Tile

The parameters to choose for the tile of your terrain depend on a number of factors, including:

1. How high can the player go? If the player can fly really high above the terrain, he can probably notice the edge of the world, or the end of all the tiles. In such a case, we may need to drop additional tiles so the horizon stays invisible, or we use a tile really large. The higher you go, the lower your chances with a super-tile, so i’d suggest stick to a conservative value and drop tiles when required.

2. How fast does the player move across the tile? So this is more of a question of game mechanic. Does the player fly or walk across the terrain? If he walks/runs, how fast does he do that?

3. How far do you anticipate the player would go before he stops, or, you know, gets bored? This is where we had a problem in our game, we found that players, in the tests, would continue to fly endlessly in a straight line, and this meant, virtually endless terrains are a must. In this case, we needed frequent checks to ensure the player doesn’t lose track of where he’s going, or he doesn’t reach the end of the world.

4. Do you need additional functionality in the terrain? Do you need dynamic terrain deformation? Do you need to add details or prefabs at runtime in the game. If the answer to any of these questions is yes, stick to a lower resolution for optimal performance.

The attributes I use for our terrain is :

Terrain Settings

Designing a Terrain Manager

A terrain manager must:

1. Keep track of all terrain tiles currently in the scene.

2. Track the current player position and view vector.

3. Based on the field of vision and the parameters calculated in (2), decide if a terrain tile needs to be placed.

 

Part 1 – Keeping track of all terrain tiles in the scene.

In order to keep track of the terrain tiles placed in the scene, I need a list. A list of all terrains, but this must be easily accessible. The key to such a list is the 2-dimensional index of the terrain tile. Since each tile is evenly sized, it’s easy to calculate the 2D index.

Tile Indices

Now, we need an efficient way of indexing this list. That’s where DoubleKeyDictionary from Helix Toolkit comes to the rescue. It provides a 2 keyed dictionary by creating two dictionaries internally. An understandable overhead for such an essential functionality.

We then create a terrainList to contain all Terrain objects using 2 integer keys (x and y indices) of the terrain tile in question.

private DoubleKeyDictionary<int, int, Terrain> terrainList = 
                         new DoubleKeyDictionary<int, int, Terrain>();

Part 2 – Track the current player position and view vector

Player position = transform.position;
View Vector = Camera.mainCamera.transform.forward;

In order to calculate what tiles need to be placed, we can iterate over the view vector and sample at a couple of locations forward. Also, in order to compensate for the fov, we can transform the view vector 45 degrees to the left and 45 degrees to the right and sample over those vectors too, as shown below.

Part 3 – Decide if a terrain needs to be placed 

First, we sample the terrain under the first of the vectors, under the player, and forward 500 units.

GetTerrainUnder(transform.position + Camera.mainCamera.transform.forward 
                * 500.0f);
GetTerrainUnder(transform.position + (Camera.mainCamera.transform.forward
                + Camera.mainCamera.transform.right) * m * 500.0f);
GetTerrainUnder(transform.position + (Camera.mainCamera.transform.forward
                - Camera.mainCamera.transform.right) * m * 500.0f);

Given the position to sample at, we can transform that to an index by :

index x = Mathf.FloorToInt((x - terrainOriginPosition.x) 
                           / Terrain.activeTerrain.terrainData.size.x)
index y = Mathf.FloorToInt((z - terrainOriginPosition.z ) 
                           / Terrain.activeTerrain.terrainData.size.z )

where terrainOriginPosition is set at the start of the script to the position of the initial tile.

terrainOriginPosition = Terrain.activeTerrain.transform.position;

We then check the list if the terrain exists:

terrainList.ContainsKey(x, y)

and if it doesn’t exist, we can drop another tile:

TerrainData tData = new TerrainData();
tGameObject = Terrain.CreateTerrainGameObject(tData);
tGameObject.transform.position = piecePosition;

where

piecePosition = terrainOriginPosition 
                 + new Vector3(x * tileDimensions.x, 
                               0, 
                               y * tileDimensions.y);

and

Vector2 tileDimensions = 
    new Vector2((Terrain.activeTerrain.terrainData.heightmapWidth - 1) 
                * Terrain.activeTerrain.terrainData.heightmapScale.z,
                (Terrain.activeTerrain.terrainData.heightmapHeight - 1) 
                * Terrain.activeTerrain.terrainData.heightmapScale.x);

Set Neighbors to Match LOD

Once we drop the new tiles, we must set the neighbors on all the surrounding terrain tiles to ensure LODs match up.

public void SetTerrainNeighborsFor(Vector2 tID)
{
  if (GetTerrainAt(tID))
    GetTerrainAt(tID).SetNeighbors(GetTerrainAt(tID + new Vector2(-1, 0)),
                                 GetTerrainAt(tID + new Vector2(0, 1)),
                                 GetTerrainAt(tID + new Vector2(1, 0)),
                                 GetTerrainAt(tID + new Vector2(0, -1)));
}

Conclusions

That’s it. All other settings are optional. For more information, attached is a copy of the completed script. Just add it to the Player object and watch the tiles unfold.

<<TerrainManager>>

Also, and example unitypackage (zipped for wordpress love) here

In the next post I’ll talk about how we can set up dynamic terrain deformation in Unity…

28 Comments

  • Hi,
    great tutorial !
    i miss the asset you want to post..
    I it possible to use it with my own Meshes ?
    like i have my own low poly terrain …
    thx

  • … and can’t get the source c# from Helix
    would be really nice if you could attach the script’s or Unity.package
    thank you :-)

  • Ah, sorry about the confusion. I had to pull the script off the website when my producer warned me of potential publisher issues when posting game code. I’m writing independent code which I shall put online as soon as I have it done.

  • ok … can i get in contact via mail with you ? i_dvd @ me.com
    thank you

  • if you write the Code new… maybe you can use prefabs instead of terrain ? So that it can be used on Mobile Systems …. i have 3 different approaches to get paging running on Mobile… the last one works fine but still a bit slow on iPad1… so i was looking for other solutions… and found your side… but i can’t get it run… the Helix stuff works but the Array won’t…
    So my idea is to create a Manager that use a Spawnpool with created prefabs like planes with some objects on it (Animals, Trees etc) if you than spawn the prefabs a Script with a Random function plants all the Trees, Rocks and places the Animals on that Plane… than maybe use Simple Noise to deform the Plane to a terrain so that it is not flat… or use your own low Poly Mesh terrain… So instead to destroy the Planes i would use Despawn and reset the Plane….With the Spawn Manager or Pool Manager you have the best Performance on Mobile & Desktop… i use it extensively in my Projects for all Bullets, Rockets and Explosions… because instantiate & destroy still suck on Mobile & Desktop (Lag & Perf. impact)… So that’s my idea for a endless universal Terrain Manager but my Script Skills are to bad to do it myself… i would pay for…
    thank you !

    Stefan

  • Unity has a little issue with using terrain prefabs… It saves any edits for the terrain back into the prefab. So if you do edit the terrain, say with a randomization, it would reflect the next time you drop it in game. Also, consequently, that would change the look of all your tiles.

    So, If you have a limited number of terrain tiles, then sure, you can use static prefabs each of them and drop into the game when needed, but for truly random terrain, you’d probably need to drop a plain terrain by code and do all the magic from there.

    The script I use (TerrainManager) functions exactly like a Spawnpool, but it keeps track of every single terrain tile. Now, trees, plants, animals can be spawned as needed.

    However, I do like the idea of despawning and respawing/initiating tiles rather than recreating new ones. I shall try it on this independent script.

  • Super !
    Works great !!!
    Well is it easy possible to change the terrain to planes ?
    terrain cost to much performance on mobile still…
    i have 9 different planes with Rocks and other stuff on it that fit together…
    so you can change them by random and get different results like endless terrain…
    would be a great choice to use your own or build in ….
    anyway many thx for the script & work you put in !
    go on with your blog

    Stefan

    • it would depend wholly on how you’re storing the terrain information. size and replication of terrain data is trivial, but i’m using a secondary store for the terrain information, storing that would require a change, possible a structure for the information you’re storing.

      That, and a couple of tweaks to how the information is saved and retrieved… overall, not too much effort.

      • http://terrymorgan.net/endless_terrain.zip
        12 MB

        It works, but the trees are all black, any way to use terrain toolkit 1.02 or Forst’s advanced terrain shaders v2? Both free on asset store.

        • Thanks for the report. Unfortunately this tool has not been tested with either of those plugins yet. However, I wonder why the trees are black. Shall look into it and let you know.

        • I couldn’t download that file linked in your comment. It seems corrupted. Could you re-upload it?

  • endless terrain
    https://www.dropbox.com/sh/5z5bxkdqkdeymi4/KqiiftQZ3R
    I re-upped a version that works on my machine, ftp not working

  • According to your post, the terrain is supposed to be endless, but mine isn’t. I
    have a TerrainManager with the doublekey dictionary and terrain manager, but my
    heightmap resolution is width 2000, height 600, length 2000, resolution 513,
    detail resolution 1024, res per patch 8, control texture res 512, base texture 1024,
    the defaults, is this why it isn’t endless?

  • K, I made a new project with only your package, the terrain only shows when I press play, how do I edit it?

    • Hi Terry, thanks for trying out the endless terrain script. I shall answer your question in 2 parts:

      1. The terrain is created only on play: This version of the script was created specifically for the purpose of demonstrating a way of creating endless terrains as a blank slate. If you notice the script contains a call to CreateTerrainGameObject that creates an empty TerrainData attachment for it. Now this TerrainData object contains the required details to populate. TerrainData also provides interfaces to load up data for each of these, but it’s a little cumbersome. However, for my game, I found that was the best technique for saving and regenerating the terrain on the fly.
      I could use part of that script to create one for you, but it could take a while, well over a week. Whats your timeframe on this?

      2. Terrain not being endless : If you’ve noticed in the screenshot my terrain sizes are other parameters are a little different, however, you’re right, this shouldn’t really hold back the endless terrain generation. Would it be possible for you to package the part of the code so I can take a look at it?

      Thanks
      sahil

  • http://terrymorgan.net/endless_terrain.unitypackage
    21 Mb
    It uses the generic Unity 3d terrain assets download

    For some reason the unity file ‘kickass_terrain20′ didn’t get included, let me know
    if you need it. There are 2 terrains, 1 is yours, the other is mine, but it has no
    endless qualities, if I delete yours I fall out of the world.

    quote
    I found that was the best technique for saving and regenerating the terrain on the fly.
    unquote

    So the terrain is generated procedurally? I’ve always wanted to make a large
    real-world terrain using DEM files, no idea how to do this though
    http://ariadata.arid.arizona.edu/browse/dem_024k.asp
    quote
    I could use part of that script to create one for you, but it could take a while, well over a week. Whats your timeframe on this?
    unquote

    This is just for fun, no timeframe.

    Here’s a procedurally generated very large terrain(takes about 30 minutes to cross it-wraps around) Only the building locations, number of trees, weeds can be edited, the actual terrain can’t be edited.
    http://terrymorgan.net/dhfaq.htm

    • Could you also provide the scene file if possible? I want to know how things are linked up.

    • I’m getting a 404 on that link. Could you email it to me? sahil upshift.org

  • Thanks for the manager. I have been playing around with it, and it works quite nice. Unfortunately I ran into the problem where the placement of the tiles just takes up 100ms for a single frame, which is very noticeable. This is only due to the culling happening because suddenly some new terrains are placed in front of the camera. Its also evident in your sample package.

    I already added some threading for other things (using Unity Threading Helper) to create a infinite procedural terrain but the culling issue cannot be fixed I am afraid. Unfortunately, it seems the culling is going to end my project for a while. Especially because I expect it to be much worse on mobile devices.

    Or did anyone find a solution for this problem? I highly doubt it, but you never know :-) .

    Cheers,

    Kokosnoot

  • Hi,
    Thanks for the pingback. I would love to look at the threaded solution. The culling issue is something I faced a lot too. Unfortunately, I wasn’t able to locate a way of overriding the culling, but maybe I missed something.

    Reducing the number of tiles visible on screen helps mitigate the issue, but doesn’t solve it. However, in some cases, even that solution isn’t viable.

    Do let me know if you find something on the issue.

    Thanks
    Sahil

  • Hi

    Interesting tutorial. I’m wondering how this would be taken further/the next step and how that would be integrated into this demo. That is, how could each placed terrain have a different texture and height map ether generated based on its neighbours to seamlessly match or (given the issue of returning to a location and it not being the same because the landscape is all generated on the fly) all stored in some sort of database (here I’m not really thinking of endless terrain more massive terrain, a world terrain which eventually gets back to where it started). I’m more interested in the later of these. New to Unity3D and still trying to understand dictionaries so really looking for clues or pseudo code at the moment as to how this might happen.

    Thanks.

    • Hi Garrett,
      Thanks for the comments. Actually, the second part of this post was supposed to address some of the issues you posed. For our project, I had to implement dynamic terrain stitching, which is pretty challenging and resource intensive. I tried a couple of approaches, and was planning to list out the drawbacks and advantages of each in the post, but it’s been in the making for the longest of times.

      However, for the second part of your question, it’s possible but not trivial to apply texture and height maps (the latter especially hard). Storing into a database would be useful here… and would love to work on this in the future. I could provide the basic idea but it’d take a while for me to implement the code since I’m doing this just as a hobby.

      • Hi Sahilramani

        Thanks for the reply.

        yes I saw you mentioned there would be a follow up but haven’t had a chance to get to it yet. I appreciate these things take time and this is no easy feat.

        I’ve made a start to try and figure out how to dynamically add textures and then, once I’ve got that working how everything would be stored for retrieval. Way behind you at the moment as I’m new to Unity3d but I’m a quick learner so ill see where it takes me.

        Garrett

        • I’d love to see your approach Garett. This would certainly be interesting and make the manager a lot more versatile.

          Sahil

  • Hey dude I am working using GEO data to build real world locations so procedural terrains are a no go for me on this project.

    Is it possible to use this method to tile mutiple segments of a very large height map.

    I’m thinking of cutting up a 20km x 20km greyscale elevation into 5km x 5km or maybe even 2km x 2km segments.

    My other option would be to use colliders to load a new scene when I get to the limits of smaller terrains but this would seem to be a much more elegant method.

    • Hi Steev3d,
      This looks like a really interesting application for the generator. As I explained in the other comment, height maps aren’t easy to apply on Unity terrains directly, however, there’s definitely a way to paint them. I have been trying to find such a solution to apply height maps directly but haven’t been able to yet.

      I would also agree that tiling would probably provide a better looking terrain overall. Just cutting down the grid size gives it a higher level of detail in each chunk. I haven’t looked into tiling height maps or even using height maps directly, but I’m sure there’s a way to do it.

      Good luck with the project and I’ll definitely keep you informed on my progress on the same.

      Thanks.

Leave a comment

Archives

Chatroom

LOADING...