If you try searching for information on how to assign textures to Unity terrain through code (i.e. derive a “splatmap” of textures based on the terrain heightmap profile itself), you’ll probably end up being sent to this post on the Unity Answers site. Despite being over 3 years old, it still seems to be pretty much the only helpful demonstration of accessing Unity’s somewhat poorly-documented terrain functions through script.

However, while its a good starting point, the script there suffers from several shortcomings (I’m not blaming the original author – I imagine that at the time he wrote it, he probably didn’t expect it to become the authoritative source on the matter!):

It only works if your terrain’s splatmap has the same dimensions as its heightmap.

It allows for only three textures to be blended.

The y/x axes are inverted.

The method of normalisation is incorrect. ( Vector3.Normalize() sets the magnitude of the vector representing the texture weights to 1 – i.e. two equal textures will each have weight of 0.707. What instead is required is that the component weights should sum to 1 – i.e. each have weight of 0.5).

Attached below is my version of an automated splatmap creation script which attempts to correct some of the issues in that earlier code. It allows for any number of terrain textures to be blended based on the height, normal, steepness, or any other rules you create for each part of the terrain. Just attach the AssignSplatMap C# script below onto any terrain gameobject in the scene (having first assigned the appropriate textures to the terrain) and hit play.

using UnityEngine; using System.Collections; using System.Linq; // used for Sum of array public class AssignSplatMap : MonoBehaviour { void Start () { // Get the attached terrain component Terrain terrain = GetComponent(); // Get a reference to the terrain data TerrainData terrainData = terrain.terrainData; // Splatmap data is stored internally as a 3d array of floats, so declare a new empty array ready for your custom splatmap data: float[, ,] splatmapData = new float[terrainData.alphamapWidth, terrainData.alphamapHeight, terrainData.alphamapLayers]; for (int y = 0; y < terrainData.alphamapHeight; y++) { for (int x = 0; x < terrainData.alphamapWidth; x++) { // Normalise x/y coordinates to range 0-1 float y_01 = (float)y/(float)terrainData.alphamapHeight; float x_01 = (float)x/(float)terrainData.alphamapWidth; // Sample the height at this location (note GetHeight expects int coordinates corresponding to locations in the heightmap array) float height = terrainData.GetHeight(Mathf.RoundToInt(y_01 * terrainData.heightmapHeight),Mathf.RoundToInt(x_01 * terrainData.heightmapWidth) ); // Calculate the normal of the terrain (note this is in normalised coordinates relative to the overall terrain dimensions) Vector3 normal = terrainData.GetInterpolatedNormal(y_01,x_01); // Calculate the steepness of the terrain float steepness = terrainData.GetSteepness(y_01,x_01); // Setup an array to record the mix of texture weights at this point float[] splatWeights = new float[terrainData.alphamapLayers]; // CHANGE THE RULES BELOW TO SET THE WEIGHTS OF EACH TEXTURE ON WHATEVER RULES YOU WANT // Texture[0] has constant influence splatWeights[0] = 0.5f; // Texture[1] is stronger at lower altitudes splatWeights[1] = Mathf.Clamp01((terrainData.heightmapHeight - height)); // Texture[2] stronger on flatter terrain // Note "steepness" is unbounded, so we "normalise" it by dividing by the extent of heightmap height and scale factor // Subtract result from 1.0 to give greater weighting to flat surfaces splatWeights[2] = 1.0f - Mathf.Clamp01(steepness*steepness/(terrainData.heightmapHeight/5.0f)); // Texture[3] increases with height but only on surfaces facing positive Z axis splatWeights[3] = height * Mathf.Clamp01(normal.z); // Sum of all textures weights must add to 1, so calculate normalization factor from sum of weights float z = splatWeights.Sum(); // Loop through each terrain texture for(int i = 0; i<terrainData.alphamapLayers; i++){ // Normalize so that sum of all texture weights = 1 splatWeights[i] /= z; // Assign this point to the splatmap array splatmapData[x, y, i] = splatWeights[i]; } } } // Finally assign the new splatmap to the terrainData: terrainData.SetAlphamaps(0, 0, splatmapData); } }

Here’s some examples of rules you might want to implement for various textures:

Texture weight based on surface normal (useful for e.g. snow accumulating on one side of a mountain, moss growing on north side of a hill) Texture weight based on height (e.g. ice caps at high altitudes, sand near sea-level) Texture weight based on steepness (e.g. grass grows on relatively flat terrain)

Using this script with Unity’s standard terrain textures turns the following default grey terrain:

Into this slightly more attractive scene:

Sure there’s certainly a lot more you could do to improve the mapping, and it doesn’t compare to the output you get from professional world modelling tools such as World Machine. But, then again, it’s free, and it gives you a good start from which to refine further improvements to your terrain.