Modify terrain texture at runtime

I have manage to modify the terrain texture at runtime (the alphamap) quite easy but is a killer for frame rate. I use this function in a FixedUpadate to create a road on terrain texture as the npc is moving. The culprit here is the terrain.terrainData.SetAlphamaps(0, 0, splatmapData); who takes 500ms to execute and turn the game in a slide show.

Is there any way to update the terrainData at runtime without the huge cost of SetAlphamaps?

void PaintRoad()
    {
        paintingTerrain = true;
        int x = Convert.ToInt32(((npcPosition.position.x - terrainObj.transform.position.x) / terrain.terrainData.size.x) * terrain.terrainData.heightmapWidth);
        int y = Convert.ToInt32(((npcPosition.position.z - terrainObj.transform.position.z) / terrain.terrainData.size.z) * terrain.terrainData.heightmapHeight);
                    
        splatmapData[y, x, 0] = 0;
        splatmapData[y, x, 1] = 1;

        terrain.terrainData.SetAlphamaps(0, 0, splatmapData);
        //terrain.Flush();
        
    }

Hi scarpelius,

if SetAlphamaps function execution is long use it in coroutine to don’t affect frame rate.

Julien G.

Hmm, i’ve tried but still same lag

 private IEnumerator PaintRoad()
    {
        if (isMoving)
        {
            paintingTerrain = true;
            int x = Convert.ToInt32(((npcPosition.position.x - terrainObj.transform.position.x) / terrain.terrainData.size.x) * terrain.terrainData.heightmapWidth);
            int y = Convert.ToInt32(((npcPosition.position.z - terrainObj.transform.position.z) / terrain.terrainData.size.z) * terrain.terrainData.heightmapHeight);

            
            splatmapData[y, x, 0] = 0;
            splatmapData[y, x, 1] = 1;

            yield return StartCoroutine(UpdateSplatmap());
            
           
        }
    }

    IEnumerator UpdateSplatmap()
    {
        terrain.terrainData.SetAlphamaps(0, 0, splatmapData);
        //terrain.Flush();
        yield return 0;
    }

void FixedUpdate()
{
     ...
     StartCoroutine(PaintRoad());
     ...
}

I’ve tried with only 1 tile at the time but same lag. With and without coroutine.
Still need an advice on this.

Ok, problem solved :smile:

Well, I have the same problem, but that didn’t solve it for me. I still get big drops in the frame rate, even when writing just one point in the splatmap! :frowning:

Since this answer appears at the top of google for this query I will place a solution I came up with here for others that may be interested.

I needed this functionality as I was editing the terrain, ‘lowering’ (digging)/ and raising (adding) at runtime and needed the texture at the location of the action to also update the terrain with a texture of choice.

Here is the code of the function that ‘lowers’ the terrain (got the code to lower terrain from here [ Simple Runtime Terrain Editor ] credit to @WinterboltGames ):

private void LowerTerrain(Vector3 worldPosition, float strength, int brushWidth, int brushHeight)
        {
            var brushPosition = GetBrushPosition(worldPosition, brushWidth, brushHeight);
   
            var brush = GetSafeBrushSize(brushPosition.x, brushPosition.y, brushWidth, brushHeight);
   
            var terrainData = GetTerrainData();
   
            var heights = terrainData.GetHeights(brushPosition.x, brushPosition.y, brush.x, brush.y);
   
            for (var y = 0; y < brush.y; y++)
            {
                for (var x = 0; x < brush.x; x++)
                {
                    heights[y, x] -= strength * Time.deltaTime;
                }
            }
   
            terrainData.SetHeights(brushPosition.x, brushPosition.y, heights);
            UpdateTerrainTexture(worldPosition, 0, brushWidth, brushHeight);
            _targetTerrain.Flush();
        }

And here is the ‘UpdateTerrainTexture’ function which I extended the script with:

private void UpdateTerrainTexture(Vector3 worldPosition, int textureIndex, int textureWidth, int textureHeight)
        {
            // Get the position of the texture brush and the safe size of the brush
            Vector2Int brushPosition = GetBrushPosition(worldPosition, textureWidth, textureHeight);
            Vector2Int brush = GetSafeBrushSize(brushPosition.x, brushPosition.y, textureWidth, textureHeight);
  
            // Get the terrain data at the location
            TerrainData terrainData = GetTerrainData();
  
            // Get the number of textures on the terrain
            int texturesCount = terrainData.alphamapLayers;
  
            // Create a 3D array to hold the new alpha values for each texture on the terrain
            float[,,] textureAlphas = new float[brush.y, brush.x, texturesCount];
  
            // Loop through each pixel in the brush area and set the alpha value for the specified texture index to 1, and 0 for all others
            for (var y = 0; y < brush.y; y++)
            {
                for (var x = 0; x < brush.x; x++)
                {
                    for (var i = 0; i < texturesCount; i++)
                    {
                        // If the current texture index matches the specified texture index, set its alpha value to 1
                        // Otherwise, set its alpha value to 0
                        textureAlphas[y, x, i] = (i == textureIndex) ? 1.0f : 0.0f;
                    }
                }
            }
  
            // Set the alpha map at the specified position to the updated texture alphas
            terrainData.SetAlphamaps(brushPosition.x, brushPosition.y, textureAlphas);
        }

This is awesome effort. Thanks for sharing. If you also Share the sub-functions: GetBrushPosition, GetSafeBrushSize, and GetTerrainData I will give it a try.