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

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!

Use of SplatPrototypes is deprecated. You will want to use the TerrainLayers portion of the Terrain/TerrainData APIs. So something like:

public void AddTerrainLayer( Terrain terrain, TerrainLayer terrainLayer )
{
    // get the current array of TerrainLayers
    TerrainLayer[] oldLayers = terrain.terrainData.terrainLayers;

    // check to see that you are not adding a duplicate TerrainLayer
    for( int i = 0; i < oldLayers.Length; ++i )
    {
        if( oldLayers[ i ] == terrainLayer ) return;
    }

    // NOTE: probably want to track Undo step here before modifying the TerrainData

    TerrainLayer[] newLayers = new TerrainLayer[ oldLayers.Length + 1 ];

    // copy old array into new array
    Array.Copy( oldLayers, 0, newLayers, 0, oldLayers.Length );

    // add new TerrainLayer to the new array
    newLayers[ oldLayers.Length ] = terrainLayer;
    terrain.terrainData.terrainLayers = newLayers;
}

or you can use the PaintContext API in the UnityEngine.Experimental.TerrainAPI namespace.

Here are some examples: GitHub - Unity-Technologies/TerrainToolSamples: Unity has archived the TerrainToolSamples repository. For future development, please use the Terrain Tools package.

2 Likes

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

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:

public void AddTerrainLayer( Terrain terrain, TerrainLayer terrainLayer )
{
    // get the current array of TerrainLayers
    TerrainLayer[] oldLayers = terrain.terrainData.terrainLayers;

    // check to see that you are not adding a duplicate TerrainLayer
    for( int i = 0; i < oldLayers.Length; ++i )
    {
        if( oldLayers[ i ] == terrainLayer ) return;
    }

    // NOTE: probably want to track Undo step here before modifying the TerrainData

    TerrainLayer[] newLayers = new TerrainLayer[ oldLayers.Length + 1 ];

    // copy old array into new array
    Array.Copy( oldLayers, 0, newLayers, 0, oldLayers.Length );

    // add new TerrainLayer to the new array
    newLayers[ oldLayers.Length ] = terrainLayer;
    terrain.terrainData.terrainLayers = newLayers;
}

public void FillTerrainLayer( Terrain terrain, TerrainLayer terrainLayer )
{
    // add the terrain layer to the terrain before filling
    AddTerrainLayer( terrain, terrainLayer );

    // get the weight data for the alphamaps
    int width = terrain.terrainData.alphamapWidth;
    int height = terrain.terrainData.alphamapHeight;
    float[ , , ] alphamaps = terrain.terrainData.GetAlphaMaps( 0, 0, width, height );

    int numAlphamaps = alphamaps.GetCount( 2 );
    for( int i = 0; i < numAlphamaps; ++i )
    {
        // TODO: loop through the alphamaps and set weights for your filled
        //       terrain layer to 1 and all other terrain layers to 0
    }

    // NOTE: normally you'd have to renormalize the weights but since you
    //       are filling the weights of one layer and clearing the rest, you can
    //       skip that step since they will technically be normalized

    // NOTE: probably want to track Undo step here before modifying the TerrainData

    // set the new alphamap weights in the terrain data
    terrainData.SetAlphamaps( 0, 0, alphamaps );
}

Does this better answer your question?

1 Like

Using a PaintContext can be faster as that is done on the GPU

1 Like

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!

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

4121056--361633--CreateTerrainLayerTexture.png

4121056--361636--CreateTerrainLayerTexture.png

3 Likes

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.

Ok nevermind on that, it was CTS out of sync with the terrain.

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?

ah it was simply because I was setting the terrainLayers per an array entry rather than setting the entire array in one go.

Any updates on this one?

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.

I’ll ask you the same thing? Figure it out yet?

@jdoxbotica @FungusMonkey This one worked for me (with the help of Unity C# source):

        /// <summary>
        /// Adds the given texture as an extra layer to the given terrain.
        /// </summary>
        /// <param name="terrainData"><see cref="TerrainData"/> to modify the texture of.</param>
        /// <param name="texture">Texture to be used.</param>
        /// <param name="size">Size of the <see cref="Terrain"/> in meters.</param>
        public static void SetTerrainTexture(TerrainData terrainData, Texture2D texture, float size)
        {
            var newTextureLayer = new TerrainLayer();
            newTextureLayer.diffuseTexture = texture;
            newTextureLayer.tileOffset = Vector2.zero;
            newTextureLayer.tileSize = Vector2.one * size;

            AddTerrainLayer(terrainData, newTextureLayer);
        }

        /// <summary>
        /// Adds new <see cref="TerrainLayer"/> to the given <see cref="TerrainData"/> object.
        /// </summary>
        /// <param name="terrainData"><see cref="TerrainData"/> to add layer to.</param>
        /// <param name="inputLayer"><see cref="TerrainLayer"/> to add.</param>
        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;
        }
1 Like

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.

2 Likes

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.

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?

Hi all,

@FungusMonkey @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. :slight_smile:

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! :slight_smile:

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…

TerrainLayer[] newSplatPrototypes;
        newSplatPrototypes = new TerrainLayer[splatLayers.Count];
        int spindex = 0;
        foreach (SplatLayer sh in splatLayers)
        {
            newSplatPrototypes[spindex] = new TerrainLayer();
            newSplatPrototypes[spindex].diffuseTexture = sh.texture;
            newSplatPrototypes[spindex].tileOffset = sh.tileOffset;
            newSplatPrototypes[spindex].tileSize = sh.tileSize;
            newSplatPrototypes[spindex].diffuseTexture.Apply(true);
            string path = "Assets/" + this.gameObject.name + " TerrainLayer " + spindex + ".terrainlayer";
            AssetDatabase.CreateAsset(newSplatPrototypes[spindex], path);
            spindex++;
            Selection.activeObject = this.gameObject;
        }
        // apply textures back into the terrain data
        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!

1 Like