Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Shader to paint roads on terrain

Discussion in 'Scripting' started by TomPo, May 19, 2022.

  1. TomPo

    TomPo

    Joined:
    Nov 30, 2013
    Posts:
    86
    Hi.
    Can't find an answer so posting a thread.
    Not sure if scripting is the right place but can't find the Shaders section.
    To the point...

    Do you maybe have a shader so it can be used to paint roads on the terrain?
    As for now, I'm just changing splatmaps but this is very heavy and can't be used at runtime without freezing a game for a split second as tere is no async version for SetAlphamaps void.

    I can only imagine that this should be possible via some shader.
    What I have as input is a list of Vectors where the road should be placed.

    And the other problem is that terrain shader is a built-in solution so probably there must be some way to override it or change it as well.
     
  2. Cameron_SM

    Cameron_SM

    Joined:
    Jun 1, 2009
    Posts:
    915
  3. TomPo

    TomPo

    Joined:
    Nov 30, 2013
    Posts:
    86
    Never mind, the problem was somewhere else not with applying layers.

    But still, some shader to make paths/roads on a terrain sounds like a good idea :)
     
    Last edited: May 21, 2022
  4. StaggartCreations

    StaggartCreations

    Joined:
    Feb 18, 2015
    Posts:
    2,136
    The SetAlphamaps function is indeed painfully slow, since it involves transferring data from the CPU to GPU (and back at some point).

    As of Unity 2018.3 you can use the TerrainPaintUtility API to perform realtime GPU-based height & splatmap modifications. This is basically what the built-in terrain brush tools use as well. But just the API documentation probably won't get you very far. I found the Unity C# source code reference repo to be extremely helpful. For instance, this part involves using a shader to modify terrain splatmaps.

    This is fairly straightforward to use with simply rectangular shapes, but spline points need to be converted to a format a GPU understands.

    For a splines, this is a possible workflow: https://gyazo.com/019ae41ee1f77063af59a58ae99cd994
    - Generate geometry along the points (essentially something like a road)
    - Render the generated mesh into a Render Texture
    - Use the RT as an input for the material used in the Graphics.Blit call (see C# reference). Effectively using it as a brush.

    I realize this doesn't exactly sound super straightforward, it definitely took me a good couple of days to get right, but hopefully this provides a conceptual starting point :)

    An alternative could be to simply paint a whole bunch of individual rectangles along the positional points, which should amount to a path/road in appearance.
     
  5. TomPo

    TomPo

    Joined:
    Nov 30, 2013
    Posts:
    86
    I checked again checking all times my process is taking.
    All of voids are taking like from 0.001 (calculations, etc) to 0.5 ms (pathfinding) and whole proces is about 1ms long but...
    Code (CSharp):
    1.         startTime = Time.realtimeSinceStartup;
    2.         terrain.terrainData.SetAlphamaps(0, 0, Alphamaps);
    3.         Debug.Log($"painting time: {(Time.realtimeSinceStartup - startTime) * 1000} ms");
    This code alone is taking 30-40 ms to complete what means 2-3 frames
    And this is a small terrain like 100x100 units with other settings (sizes) set to default.
    "Funny" is that faster is to instantiate 10 quads prefabs and put on a terrain than paint a tarrain.

    Probably better option would be to not update whole maps from 0,0 but instead just a changed part but can't make it work - I mean this is even slover that to update whole terrain.
    Because here we are updating after each square and not after changing maps for the whole path, so if the path is longer then this is slower and slower than updating whole map at once after whole path maps being changed.

    Code (CSharp):
    1.  
    2.     void PaintPoinTerrain(Vector2Int center, int TextureIndex) {
    3.         int minX = Mathf.Max(0, center.x - Offset);
    4.         int minY = Mathf.Max(0, center.y - Offset);
    5.         float[,,] maps = terrain.terrainData.GetAlphamaps(minX, minY, Offset *2 +2, Offset *2 +2);
    6.         int passX, passY = 0;
    7.         for (int y = center.x - Offset; y <= center.x + Offset; y++) {
    8.             passY++;
    9.             passX = 0;
    10.             for (int x = center.y - Offset; x <= center.y + Offset; x++) {
    11.                 passX++;
    12.                 if (x < 0 || y < 0) continue;
    13.                 for (int i = 0; i < terrainData.terrainLayers.Length; i++) {
    14.                     if (i != TextureIndex) maps[passX, passY, i] = 0;
    15.                     else maps[passX, passY, i] = 1;
    16.                 }
    17.             }
    18.         }
    19.         terrain.terrainData.SetAlphamaps(minX, minY, maps);
    20.     }
    21.  
    So as for now the fastest (bad) solution is to spawn at start like 30 Quad prefabs and put them into Queue pool and then put them on each path position and make visible.
    But this is meh :/
     
    Last edited: May 22, 2022
  6. TomPo

    TomPo

    Joined:
    Nov 30, 2013
    Posts:
    86
    Yeah... faster is to instantiate 10 quads prefabs and put them on terrain than painting a terrain.
    The problem is with SetAlphamaps(0,0,maps) - this took ages to complete even with very small terrain.