Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Terrain Terrain Layers API. Can you tell me the starting point?

Discussion in 'World Building' started by alussam, Jan 1, 2019.

  1. alussam

    alussam

    Joined:
    May 13, 2018
    Posts:
    15
    Hello! Recently I began work on terrain script wich fills the terrain with textures. I used splatPrototypes class. But now terrain API is fully reworked. I want to be up to date and work with new features. Can you tell me from where to start? Can I find some example of using TerrainLayer instead of SplatPrototypes. Thanks for any help!
     
  2. wyatttt

    wyatttt

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    288
    Use of SplatPrototypes is deprecated. You will want to use the TerrainLayers portion of the Terrain/TerrainData APIs. So something like:


    Code (CSharp):
    1. public void AddTerrainLayer( Terrain terrain, TerrainLayer terrainLayer )
    2. {
    3.     // get the current array of TerrainLayers
    4.     TerrainLayer[] oldLayers = terrain.terrainData.terrainLayers;
    5.  
    6.     // check to see that you are not adding a duplicate TerrainLayer
    7.     for( int i = 0; i < oldLayers.Length; ++i )
    8.     {
    9.         if( oldLayers[ i ] == terrainLayer ) return;
    10.     }
    11.  
    12.     // NOTE: probably want to track Undo step here before modifying the TerrainData
    13.  
    14.     TerrainLayer[] newLayers = new TerrainLayer[ oldLayers.Length + 1 ];
    15.  
    16.     // copy old array into new array
    17.     Array.Copy( oldLayers, 0, newLayers, 0, oldLayers.Length );
    18.  
    19.     // add new TerrainLayer to the new array
    20.     newLayers[ oldLayers.Length ] = terrainLayer;
    21.     terrain.terrainData.terrainLayers = newLayers;
    22. }
    or you can use the PaintContext API in the UnityEngine.Experimental.TerrainAPI namespace.

    Here are some examples: https://github.com/Unity-Technologies/TerrainToolSamples
     
    Rowlan and Claytonious like this.
  3. alussam

    alussam

    Joined:
    May 13, 2018
    Posts:
    15
    Thanks, but can you tell me, how can I apply layers to terrain. I used terrainData.applyAlphaMaps fragment before, but now I can't use this and dont method for layers. Thanks again
     
  4. wyatttt

    wyatttt

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    288
    Ah you mean to actually fill the alphamap with a terrain layer. I guess I only answered part of the question. Sorry about that.

    You'd definitely want to use the PaintContext API (BeginPaintAlphamap and EndPaintAlphamap, specifically) or manually set the splat/alphamap weights using something like this:

    Code (CSharp):
    1.  
    2. public void AddTerrainLayer( Terrain terrain, TerrainLayer terrainLayer )
    3. {
    4.     // get the current array of TerrainLayers
    5.     TerrainLayer[] oldLayers = terrain.terrainData.terrainLayers;
    6.  
    7.     // check to see that you are not adding a duplicate TerrainLayer
    8.     for( int i = 0; i < oldLayers.Length; ++i )
    9.     {
    10.         if( oldLayers[ i ] == terrainLayer ) return;
    11.     }
    12.  
    13.     // NOTE: probably want to track Undo step here before modifying the TerrainData
    14.  
    15.     TerrainLayer[] newLayers = new TerrainLayer[ oldLayers.Length + 1 ];
    16.  
    17.     // copy old array into new array
    18.     Array.Copy( oldLayers, 0, newLayers, 0, oldLayers.Length );
    19.  
    20.     // add new TerrainLayer to the new array
    21.     newLayers[ oldLayers.Length ] = terrainLayer;
    22.     terrain.terrainData.terrainLayers = newLayers;
    23. }
    24.  
    25. public void FillTerrainLayer( Terrain terrain, TerrainLayer terrainLayer )
    26. {
    27.     // add the terrain layer to the terrain before filling
    28.     AddTerrainLayer( terrain, terrainLayer );
    29.  
    30.     // get the weight data for the alphamaps
    31.     int width = terrain.terrainData.alphamapWidth;
    32.     int height = terrain.terrainData.alphamapHeight;
    33.     float[ , , ] alphamaps = terrain.terrainData.GetAlphaMaps( 0, 0, width, height );
    34.  
    35.     int numAlphamaps = alphamaps.GetCount( 2 );
    36.     for( int i = 0; i < numAlphamaps; ++i )
    37.     {
    38.         // TODO: loop through the alphamaps and set weights for your filled
    39.         //       terrain layer to 1 and all other terrain layers to 0
    40.     }
    41.  
    42.     // NOTE: normally you'd have to renormalize the weights but since you
    43.     //       are filling the weights of one layer and clearing the rest, you can
    44.     //       skip that step since they will technically be normalized
    45.  
    46.     // NOTE: probably want to track Undo step here before modifying the TerrainData
    47.  
    48.     // set the new alphamap weights in the terrain data
    49.     terrainData.SetAlphamaps( 0, 0, alphamaps );
    50. }
    Does this better answer your question?
     
    Last edited: Jan 4, 2019
    Rowlan likes this.
  5. wyatttt

    wyatttt

    Unity Technologies

    Joined:
    May 9, 2018
    Posts:
    288
    Using a PaintContext can be faster as that is done on the GPU
     
    Last edited: Jan 4, 2019
    alussam likes this.
  6. alussam

    alussam

    Joined:
    May 13, 2018
    Posts:
    15
    Oh, yes, thats right. I tried this before and in most cases got black terrain. Now i tried to fill with one layer and it works. Seems,that I'm doing something wrong. Thanks a lot for help!
     
  7. ghtx1138

    ghtx1138

    Joined:
    Dec 11, 2017
    Posts:
    113
    Hi @wyatttt

    I used your script to create a new layer but it does not appear correct (probably my script). You can see that it is blue and that is does not appear under the Terrain Layers box when selected -like the other one I made manually.

    Can you please tell me how to add a new layer with a texture? Also where is the layer in the Assets folder? I cannot see it yet (probably because I do not have the New Layer working correctly?)

    Thanks


    CreateTerrainLayerTexture.png

    CreateTerrainLayerTexture.png
     
    jdoxbotica, LMckee and zwcloud like this.
  8. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    2,437
    So first pass on converting out splat tools. Setting the alpha maps it looks like the third dimension is in opposite order as the old api? So dimension 0 actually references the 4th layer? Or maybe some other difference in the api is turning it around somehow.
     
  9. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    2,437
    Ok nevermind on that, it was CTS out of sync with the terrain.
     
  10. jdoxbotica

    jdoxbotica

    Joined:
    Aug 6, 2018
    Posts:
    7
    I have the same problem as @ghtx1138.

    If I set the texture layer in the inspector I have no problem but trying to set in code gives the blue box. Does anyone know how to fix this?
     
  11. jdoxbotica

    jdoxbotica

    Joined:
    Aug 6, 2018
    Posts:
    7
    ah it was simply because I was setting the terrainLayers per an array entry rather than setting the entire array in one go.
     
  12. JGroxz

    JGroxz

    Joined:
    Dec 12, 2017
    Posts:
    9
    Any updates on this one?
     
  13. Darren_Therrien

    Darren_Therrien

    Joined:
    Jun 30, 2016
    Posts:
    33
    To anyone reading this, I have not figured this out yet either but as far as where the terrain layers go once created manually, they go in the folder that is open in your assets. What I did was create a folder called layers and then created the layers. They all went in there. Not sure if this is intentional, an oversight or a bug. But it works.
     
  14. Darren_Therrien

    Darren_Therrien

    Joined:
    Jun 30, 2016
    Posts:
    33
    I'll ask you the same thing? Figure it out yet?
     
  15. JGroxz

    JGroxz

    Joined:
    Dec 12, 2017
    Posts:
    9
    @jdoxbotica @Darren_Therrien This one worked for me (with the help of Unity C# source):

    Code (CSharp):
    1.  
    2.         /// <summary>
    3.         /// Adds the given texture as an extra layer to the given terrain.
    4.         /// </summary>
    5.         /// <param name="terrainData"><see cref="TerrainData"/> to modify the texture of.</param>
    6.         /// <param name="texture">Texture to be used.</param>
    7.         /// <param name="size">Size of the <see cref="Terrain"/> in meters.</param>
    8.         public static void SetTerrainTexture(TerrainData terrainData, Texture2D texture, float size)
    9.         {
    10.             var newTextureLayer = new TerrainLayer();
    11.             newTextureLayer.diffuseTexture = texture;
    12.             newTextureLayer.tileOffset = Vector2.zero;
    13.             newTextureLayer.tileSize = Vector2.one * size;
    14.  
    15.             AddTerrainLayer(terrainData, newTextureLayer);
    16.         }
    17.  
    18.         /// <summary>
    19.         /// Adds new <see cref="TerrainLayer"/> to the given <see cref="TerrainData"/> object.
    20.         /// </summary>
    21.         /// <param name="terrainData"><see cref="TerrainData"/> to add layer to.</param>
    22.         /// <param name="inputLayer"><see cref="TerrainLayer"/> to add.</param>
    23.         public static void AddTerrainLayer(TerrainData terrainData, TerrainLayer inputLayer)
    24.         {
    25.             if (inputLayer == null)
    26.                 return;
    27.  
    28.             var layers = terrainData.terrainLayers;
    29.             for (var idx = 0; idx < layers.Length; ++idx)
    30.             {
    31.                 if (layers[idx] == inputLayer)
    32.                     return;
    33.             }
    34.  
    35.             int newIndex = layers.Length;
    36.             var newarray = new TerrainLayer[newIndex + 1];
    37.             System.Array.Copy(layers, 0, newarray, 0, newIndex);
    38.             newarray[newIndex] = inputLayer;
    39.  
    40.             terrainData.terrainLayers = newarray;
    41.         }
     
    Darren_Therrien likes this.
  16. unikum

    unikum

    Joined:
    Oct 14, 2009
    Posts:
    49
    Has anyone used the PaintContext in runtime to edit the terrain alphamap? Would love to see a simple example, because I'm struggling to figure out how to use it.
     
  17. Darren_Therrien

    Darren_Therrien

    Joined:
    Jun 30, 2016
    Posts:
    33
    I was having the same issue. I was not able to find a solution but I did realize that when you are creating the layers they are temporary. They will not stay because they are not like the old school splat maps. Terrain layers are like a blanket over the terrain and unity actually needs corresponding terrain layers in Assets so, What I did is open my resources folder, manually create the amount of layers I needed (Make sure you open the folder you want the in because unity will place new layers in the currently opened folder) and now all that is needed is to change the textures in code of the layers and all is well.

    Example: textures is a Texture2D array passed to the function containing this code.

    TerrainLayer[] terrainTexture = terrain.terrainData.terrainLayers;

    terrainTexture[0].diffuseTexture = textures[0];
    terrainTexture[0].normalMapTexture = textures[1];
    terrainData.terrainLayers = terrainTexture;

    Not sure if there is a way to create the layer objects in code and place them in a folder but this is how I do it. Works perfectly. Run or save project and layers are still there.
     
  18. Darren_Therrien

    Darren_Therrien

    Joined:
    Jun 30, 2016
    Posts:
    33
    I haven't tried your code because I got it working but I see no where in it where it creates a layer and places it in a directory. Am I missing something?
     
  19. muzboz

    muzboz

    Joined:
    Aug 8, 2012
    Posts:
    62
    Hi all,

    @Darren_Therrien @JGroxz @alussam @wyatttt

    I had a problem where, I'm generating a series of different terrains in code, and applying textures.

    I was finding that only 1 of the terrains would get textures, the others had "blue textures" in their terrain texture slots.

    I found that I needed to create separate assets in the AssetDatabase for each terrain, then it worked!

    Here's some sample code that works for me (there is much more code around actually writing out the materials based on altitude and slope, etc, but the code below handles the creation of assets for each terrain, so should be useful for people. :)

    These lines are the main new bits I needed when going from an older Unity 2018.2 project to Unity 2018.4.9.

    TerrainLayer[] newSplatPrototypes; // excuse my old splatPrototypes variable name! :)

    string path = "Assets/" + this.gameObject.name + " TerrainLayer " + spindex + ".terrainlayer";

    AssetDatabase.CreateAsset(newSplatPrototypes[spindex], path);


    This is a complete block of my code that uses these things...

    Code (CSharp):
    1. TerrainLayer[] newSplatPrototypes;
    2.         newSplatPrototypes = new TerrainLayer[splatLayers.Count];
    3.         int spindex = 0;
    4.         foreach (SplatLayer sh in splatLayers)
    5.         {
    6.             newSplatPrototypes[spindex] = new TerrainLayer();
    7.             newSplatPrototypes[spindex].diffuseTexture = sh.texture;
    8.             newSplatPrototypes[spindex].tileOffset = sh.tileOffset;
    9.             newSplatPrototypes[spindex].tileSize = sh.tileSize;
    10.             newSplatPrototypes[spindex].diffuseTexture.Apply(true);
    11.             string path = "Assets/" + this.gameObject.name + " TerrainLayer " + spindex + ".terrainlayer";
    12.             AssetDatabase.CreateAsset(newSplatPrototypes[spindex], path);
    13.             spindex++;
    14.             Selection.activeObject = this.gameObject;
    15.         }
    16.         // apply textures back into the terrain data
    17.         terrainData.terrainLayers = newSplatPrototypes;
    When I run it with 4 islands being generated (each with 4 splat layers), it creates this set of assets in the Project window... and those were needed before I saw each terrain being assigned it's own texture set.

    Terrain0 TerrainLayer 0
    Terrain0 TerrainLayer 1
    Terrain0 TerrainLayer 2
    Terrain0 TerrainLayer 3
    Terrain1 TerrainLayer 0
    Terrain1 TerrainLayer 1
    Terrain1 TerrainLayer 2
    Terrain1 TerrainLayer 3
    Terrain2 TerrainLayer 0
    Terrain2 TerrainLayer 1
    Terrain2 TerrainLayer 2
    Terrain2 TerrainLayer 3
    Terrain3 TerrainLayer 0
    Terrain3 TerrainLayer 1
    Terrain3 TerrainLayer 2
    Terrain3 TerrainLayer 3

    Not sure if there's another/better way to do it! But it works for me.

    All the best!
     
    Last edited: Oct 14, 2019
    wyatttt likes this.
  20. Darren_Therrien

    Darren_Therrien

    Joined:
    Jun 30, 2016
    Posts:
    33
     
  21. Darren_Therrien

    Darren_Therrien

    Joined:
    Jun 30, 2016
    Posts:
    33
    Hey, that is awesome! Thanks for sharing. I saw this a while back but been busy. Finally got back to it and it works perfectly. For anyone else needing this, just paste the two functions, call the first properly and works like a charm. Easily modifiable for your specific needs.

    Edit. While this works perfectly for creating the layers, once you save or run the project, the layers disappear. They do not show on the terrain and in the inspector you get just a blue box. The reason for this is you need to add the layers as assets to the Assets folder. Anywhere you like. So, my code is this and works perfectly. I create my layers in the resources folder. With five layers. I pass in an array of already predefined textures and they create with the textures already as I want. Now the layers remain as you might expect just as if you created them manually in the editor. Hope this helps. NOTE: Not sure why it is not showing but this line .... layer.diffuseTexture = texture; should have square brackets with i between them obviously for loop iteration. after it.

    public static void SetTerrainTexture(TerrainData terrainData, Texture2D[] texture, float size )
    {


    if (terrain.terrainData.terrainLayers.Length == 0 )
    {
    for (int i = 0; i < 5; i++)
    {
    TerrainLayer layer = new TerrainLayer();
    AssetDatabase.CreateAsset(layer, "Assets/Assets/GWC/Resources/TT/Layers/Layer" + i.ToString() + ".terrainLayer");
    layer.diffuseTexture = texture;
    AddTerrainLayer(terrainData, layer );
    }
    }


    }

    public static void AddTerrainLayer(TerrainData terrainData, TerrainLayer inputLayer)
    {
    if (inputLayer == null)
    return;
    var layers = terrainData.terrainLayers;

    for (var idx = 0; idx < layers.Length; ++idx)
    {
    if (layers[idx] == inputLayer)
    return;
    }

    int newIndex = layers.Length;
    var newarray = new TerrainLayer[newIndex + 1];
    System.Array.Copy(layers, 0, newarray, 0, newIndex);
    newarray[newIndex] = inputLayer;

    terrainData.terrainLayers = newarray;

    }
     
    Last edited: Oct 22, 2019
    muzboz likes this.