Search Unity

  1. Looking for a job or to hire someone for a project? Check out the re-opened job forums.
    Dismiss Notice
  2. Unity 2020 LTS & Unity 2021.1 have been released.
    Dismiss Notice
  3. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Official Ground shader

Discussion in 'Open Projects' started by cirocontinisio, Jan 19, 2021.

  1. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    Official thread for the Ground Shader (card here) and for the material detector (card).

    It needs to be a toon shader with the ability to mix in different textures from up to 4 different ground types (dirt, sand, grass and cobblestone, potentially others) by painting vertex colour.

    Depending on the convenience, might be good to have it as a shader ["add-on"](https://github.com/UnityTechnologies/open-project-1/wiki/Shader-Add-ons) or, since it doesn't need a lot of the usual shader functionality, a completely new toon shader (reusing only the inmost Subgraphs for toon light calculations).
     
    Last edited: Feb 14, 2021
  2. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    34
    I've been working on a prototype ground shader based on Joyce [Minions Art] patreon tutorials.
    Unity Polybrush and Shaders
    ShaderGraph Conversion - Vertex Colors(Specular/Texture Painting)

    So far I think I have been able to replicate the base functionality but I'm not sure if I've added vertex color support properly, if at all.

    Here is my main graph:
    upload_2021-1-19_13-9-39.png

    And the ground subgraph:
    upload_2021-1-19_13-11-19.png

    And here is the current functionality:


    This is a result of the ground mesh's geometry in the TestingGround scene.
    upload_2021-1-19_13-27-56.png

    Last night I was looking into how to add support for vertex colors for shader graph (or, if I already have vertex color support, to eliminate the warning message in the polybrush window) but I haven't yet found an answer.

    I use the ToonShading subgraph because I receive this error when I use the internal lighting subgraphs:
    upload_2021-1-19_13-32-58.png

    To avoid this error I made the ground shader a shader add-on to use with the ToonShading subgraph. During my process I decided to move the outline subgraph out of the main ToonShading subgraph as the ground shader is not outlined. I made this change in all the shader variants using the outline.

    The ground shader would use the combined diffuse lighting from the ToonLightingModel and AdditionalLightsToon subgraphs but ignore specular light. Ideally I can use the internal subgraphs and avoid unused properties such as Specular Map or Specular Color.
     
    Last edited: Jan 19, 2021
    cirocontinisio and treivize like this.
  3. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    Looks pretty good! I like the idea of using noise to blend them, so it's not just a fade. We could overlap another noise too, of a different scale to make the noise texture less readable.

    I also think that, in the final terrains, we should make it so there are vertices where we want to blend. This way it wouldn't be just a grid of quads as here, which gives away the fact that the textures are blended using vertex colours (it creates those hexagon shaped patches).
     
    daneobyrd likes this.
  4. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    34
    @cirocontinisio
    Here's a small update:


    I made a duplicate test scene and subdivided the ground mesh a few times.

    Edit: Oops! Looks like I had the noise scale set to 0. Here's an image with noise at 0.1.
    upload_2021-1-19_14-56-58.png
     
    Last edited: Jan 19, 2021
    itsLevi0sa likes this.
  5. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    I think it looks really good, but there's a weird ghosting effect, almost like a chromatic aberration, along the contours...?

    upload_2021-1-19_22-14-7.png

    Hehe, of course, the more geometry you have the better it looks. But maybe what we will do in the final geo is not to subdivide everything, but only cut where we need some material jumps, providing the extra edges for the fade.
     
    daneobyrd likes this.
  6. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    34
    I'll look into that chromatic aberration. Right now I'm merging the current project into my fork and then I can make a pull request. I can also work on the ground debug shader.

    Any idea why using the following graph setup returns this error?
    Parse error: syntax error, unexpected $end, expecting TOK_SHADER


    I've also seen this error pop up:
    Error in Graph at Assets/Ground Shader Test/Toon_Ground.shadergraph at node Unlit Master: Graph is generating too many variants. Either delete Keywords, reduce Keyword variants or increase the Shader Variant Limit in Preferences > Shader Graph.
    UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)


    upload_2021-1-19_16-10-59.png
     
  7. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    34
    Here's the debug shader:


    Just added a boolean keyword:
    upload_2021-1-19_18-33-26.png

    Edit: Since one of the requirements of the debug shader was to enable the visualization of individual channels, I could replace this boolean keyword with the W channel of a Vector 4 property I would use as a channel mixer.

    I'd prefer to display this in the inspector as checkboxes (as the codecks card recommends) and I could look into some custom shader GUI to achieve that. Even though the debug feature will be editor-only I'd like to avoid unnecessary booleans. Of course I could also display this as one Boolean with three vector 1 sliders.
    upload_2021-1-19_18-38-4.png upload_2021-1-19_18-35-46.png
     
    Last edited: Jan 21, 2021
  8. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    34

    @cirocontinisio I've removed the ghosting effect that was happening along the contours. I was able to ask Joyce if she had any ideas what might be the cause and she noticed that I was using thevector 4 output from the triplanar's noise texture. Adding a split node to use only one channel fixed this.

    I can put together a pull request but I had thought about making a custom shader GUI to better communicate how to use the vertex color debug function.

    upload_2021-1-20_17-45-20.png
     
    Last edited: Jan 21, 2021
    itsLevi0sa and Smurjo like this.
  9. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    Looks great. I'd say, go ahead with the PR, and then you can add the Inspector later on.

    As far as I know, Booleans in ShaderGraph will be converted to multiplications by 1 or 0 if possible. You should be able to verify this by inspecting the generated code. Basically instead of being a real branch, the values of each branch would be multiplied by 1 or 0 depending on the value of the boolean, and added together. This would simulate a switch as if it was an IF condition, without it being so (which as you know, is not well handled in shaders).
     
  10. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    34
    Pull request is up!
    Demo video:
     
  11. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    Thanks, I pulled it in! Good job!
     
    daneobyrd likes this.
  12. Eddrico

    Eddrico

    Joined:
    Jan 29, 2021
    Posts:
    10
    I didn't found any relevant threads for this, so I'm opening this thread to work on it
    This is the card in Codecks
    https://open.codecks.io/unity-open-project-1/decks/15-code/card/19s-ground-material-detector
    And a reminder that there is another thread for the ground shader which is related to this task
    https://forum.unity.com/threads/ground-shader.1041673/

    So,
    As the card suggests, I was thinking of adding a script for the player to make a raycast to the ground, get the mesh color from the collider and return a value according to a dictionary matching the colors with the material,
    also, we have the case of rocks and other materials that don't depend on the color of the mesh, so for these ones we would be returning the name, the tag, or maybe the material itself could be mapped into the dictionary too
    and for the "run every x frames" requirement I was wondering if an if in the update function with a serialize variable would be enough or if we need to implement something using InvokeRepeating or something like that

    well that my initial thoughts for this task, let me know what you all think of this.
     
    cirocontinisio likes this.
  13. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    86
    Hey! I found this post, I didn't test it or anything and I'm not sure if we can use it because it's using the Terrain component and we aren't, but perhaps you'll find it useful. The idea seems pretty clever, by getting the alpha maps at a specific location, it's then using those values to blend the footstep sounds together according to the textures. I really don't know much about this topic so I have no idea if this is something we can do or if it's specific to the Terrain component, just throwing it out there.

    I think we should use the same animation event we already have to play the footstep sounds and put the ground detection logic there, that way every footstep will have the correct sound, what do you think?
     
  14. Smurjo

    Smurjo

    Joined:
    Dec 25, 2019
    Posts:
    267
    Terrain is entirely different. The alphamaps are basically a 3 dimensional array, one dimension for the different terrain textures and two dimensions for the position (without height). It means you can calculate the index in the array from the player position.

    In our case the terrain is a mesh and the texture information is in the vertex colors. Conveniently if we use Raycast on it's mesh collider, the raycast hit also returns the hit triangle index, which is an array of integers in which the indices of the vertices come in groups of 3.

    Essentially you would get the 3 vertex colors this way:
    Color vertexColor1 = mesh.colors[mesh.triangles[hit.triangleIndex * 3 + 0]];
    Color vertexColor2 = mesh.colors[mesh.triangles[hit.triangleIndex * 3 + 1]];
    Color vertexColor3 = mesh.colors[mesh.triangles[hit.triangleIndex * 3 + 2]];

    Typically you would define local variables to store mesh.colors and mesh.triangles, yet I don't know whether that is efficient concerning memory usage.
     
    Last edited: Feb 6, 2021
    deivsky likes this.
  15. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    86
    Thanks for the explanation, @Smurjo!

    I didn't know about that property in the raycast hit, that is indeed convenient. I don't think memory would be an issue there, we only need to store the info of the mesh we're currently standing on.
     
  16. Eddrico

    Eddrico

    Joined:
    Jan 29, 2021
    Posts:
    10
    ok as an update on this task
    @Smurjo i tried your idea
    Code (CSharp):
    1. Color vertexColor1 = mesh.colors[mesh.triangles[hit.triangleIndex * 3 + 0]];
    2. Color vertexColor2 = mesh.colors[mesh.triangles[hit.triangleIndex * 3 + 1]];
    3. Color vertexColor3 = mesh.colors[mesh.triangles[hit.triangleIndex * 3 + 2]];
    but I'm getting an error

    Not allowed to access colors on mesh 'Combined Mesh (root: scene) 6 Instance' (isReadable is false; Read/Write must be enabled in import settings)
    UnityEngine.Mesh:get_colors()

    I think this is because the mesh is static and we can not get the values that way,
    I'm trying to found a workaround for this, if anyone knows how I could do this I would appreciate the help
     
  17. Smurjo

    Smurjo

    Joined:
    Dec 25, 2019
    Posts:
    267
    That's indeed a show-stopper. I can't see how we can use vertex colors at all if we can't read them.

    We would have to keep a copy of the mesh which we can read. But having closer look at the card it also asks for consideration of rocks as well. I think we might be better off with a 2D texture especially made for the purpose. Here we simply calculate the index from the player position (without height - meaning the same footstep sounds on top of the arch as under the arch of the glade). The texture wouldn't have to have a very high resolution - I guess one pixel per 0.5 m should do. Thinking about it, a "sound texture" is a very interesting idea indeed - we could also code ambient sounds like birds singing, fat sizzling or soup bubbling in it.
     
  18. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    Hmm, interesting issue. Is that coming from the rocks, or from the ground? If it's the ground, we can maybe avoid making it static. Or maybe there's some other trick. For the rocks it's even easier, and we might end up not batching them but just using an instanced shader.
    But as @Smurjo mentioned, making it read/write means you are maintaining two copies of the mesh in memory, which is not the best for performance unless we absolutely need it.

    There's a few issues with that, this way it means that you lose the tridimensionality of the sounds, you basically have them on a flat 2D plane, but you don't know how high they are. So if you climb on rocks you will still hear the sound of something 10 meters below as if it was immediately next to you.

    On the subject, keep an eye on the conversation about the AudioListener!
     
  19. Eddrico

    Eddrico

    Joined:
    Jan 29, 2021
    Posts:
    10
    The issue is coming from all of them, I tried to remove the static and it works for the ground, but palms and rock are still unreadable,
    though I think those don't matter since they don't have a color mesh so for those I was thinking in use their tag to identify if it is a palm, rock, bush, etc, and another one for the ground, so if it's ground we run the color logic to get the right material else we return the tag
     
    Smurjo likes this.
  20. Smurjo

    Smurjo

    Joined:
    Dec 25, 2019
    Posts:
    267
    It's an exception though that the player can be on two very different heights less than 0.5 m apart in the same scene with different sounds. Mostly we use high rocks to separate scenes or the height doesn't matter if we e.g. hear birds twitter in the forest. Neither would it matter if the footstep sound in the arch and on top of the arch (not many would go there) were the same. I doubt it is much of restriction if you can't put sizzling fat (or whatever is making sound on a specific spot) within 0.5 m of a rock, being aware of the systems limitations.
     
  21. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    Yes, definitely, for non-ground objects the plan was always to use the tag. In fact the ground should be tagged as ground too (maybe "Terrain").

    That's not really the case though, since the camera in our current implementation can move from 1.13m to 15m in the air, and this is without the player moving at all. If you climb on top of something, that distance can grow further of course.

    If we want to have tridimensional sounds that are louder or fainter as they become closer, we can't just use a 2D texture to define where they are. I think a 2D texture would be a good idea in a game where the camera couldn't orbit though.
     
  22. murkesz

    murkesz

    Joined:
    Aug 10, 2013
    Posts:
    5
    Hey,
    could someone please link this forum topic to the roadmap card? (https://open.codecks.io/unity-open-project-1/decks/15-code/card/19s-ground-material-detector)

    I've done a little bit of experimenting before I found this topic. I came to the same conclusions @Eddrico reached.

    My script shoots a raycast down from its game object. If the raycast hits something, the script checks whether the found object is ground or not (this check is not implemented yet).
    If the raycast hits the ground, the script searches for the 3 vertices belonging to the found triangle and reads their colors.
    You can see the result here (check the 3 colors in the inspector):


    There are a few problems with this method:

    1. To be able to read the vertex colors, vertices and triangles of the mesh of the ground, I've found that the ground objects need to be dynamic objects. I've tried to expose a generated texture from the ground shader in order to read it from my C# script but I couldn't find any solution. (To be honest I don't have much experience with shaders or game development, but I know that usually it's not an easy task to transfer GPU data to the CPU.)

    2. I'm not really sure how to implement an adequate method that could convert the 3 vertex colors into one "material" information. This could be done by measuring the distance between the hit point and the vertices and choose the color of the closest vertex. Or it could be used another approach that somehow combines the colors together using the distances as weights. A more complex approach could use the neighboring triangles as well.

    3. The algorithm calculates the result based on the vertex colors. If I understand correctly the Toon_Ground shader uses a noise texture to randomize the borders between the other four textures. This means that in order to get a more precise result the algorithm should take this noise texture into account.
     
  23. Eddrico

    Eddrico

    Joined:
    Jan 29, 2021
    Posts:
    10
    @murkesz in order of points
    1. I had the same issue, and I'm being working with a dynamic (non-Static) mesh, that way is easy to get the vertex
    2. yes, it's not easy to calculate it, I was working on an idea that may work, I'll expose it later in this post
    3. the noise is used to blend the textures, I think we want to handle each triangle by its own color, so if sand and dirt are mixed we will play 50% Sand sound and 50% Dirt sound (of course each triangle has 3 vertexes making the calculation more complicated than that)

    Note.- I'm no expert in this, nor have any authority over the direction of the project, so if I'm assuming anything wrong please let me know.

    So, as I was saying I have an idea of what we can do to calculate the material
    We have 4 materials, in RGB form those would be:
    • 0,0,0 = Grass
    • 1,0,0 = Dirt
    • 0,1,0 = Cobblestone
    • 0,0,1 = Sand
    note.- I'm not taking into account the alpha channel, because all vertex has their alpha as 1

    And we want to set a sound according to the material in the triangle we are stood on (just taking into account the "terrain" case, since other objects will be handled by tag), to do this, I was adding the 3 vertex colors to get the mixed color
    (colorVertex1 + colorVertex2 + colorVertex3) / 3
    note.- this is done for each channel on its own.

    I'm taking 3 cases that I was able to look in the current terrain
    case 1.- primary colors based materials are mixed
    suppose we have the next vertex colors
    • 1,0,0
    • 0,1,0
    • 0,0,1
    this is a mix of three of the materials we have, the mix would be
    0.333, 0.333, 0.333 that means 1/3 dirt, 1/3 cobblestone, 1/3 sand
    if we pass this mixed color, we could set the sound using them as percentages

    case 2.- Grass(black) is involved in the mix
    suppose we have the next vertex colors
    • 0,0,0
    • 0,0,0
    • 1,0,0
    the mix would be 0.333, 0, 0, knowing that the max value is 1, we can check if any of the colors are black and add a 0.333 for each one of them, which gives us a 0.666 or a 66% of volume in grass
    we can say that this means 1/3 dirt, 2/3 grass

    case 3.- the dark side is strong in this one (or primary colors are not set to 1)
    3.1.- suppose we have the next vertex color
    • 0,0,0
    • 0.5, 0, 0
    • 0, 0.5, 0
    the mix would be 0.166, 0.166, 0 again we add 0.333 for black values
    let the result side for a moment

    3.2.-
    suppose we have the next vertex colors
    • 0,0,0
    • 1,0,0
    • 0,0.5,0
    the mix would be 0.333, 0.166, 0 again we add 0.333 for black values

    now summarizing, case 3 is a real case though simplify,
    this is the way I was planning in getting the volume for each material in all previous cases
    Results
    we can consider the sum of the values in 3.1 and 3.2 each as 100% meaning:

    3.1.- 0.166 + 0.166 + 0 + 0.333 = 0,665 = 100%
    this would mean
    • Grass% = 0.166 * 100% / 0.665 = 24.962 %
    • Dirt% = 0.166 * 100% / 0.665 = 24.962 %
    • Sand% = 0 * 100% / 0.665 = 0 %
    • Coblestone% = 0.333 * 100% / 0.665 = 50.075 %

    3.2.- 0.333 + 0.166 + 0 + 0.333 = 0,832 = 100%
    the same way
    • Grass% = 0.333 * 100% / 0,832 = 40.024 %
    • Dirt% = 0.166 * 100% / 0,832 = 19.951 %
    • Sand% = 0 * 100% / 0,832 = 0 %
    • Coblestone% = 0.333 * 100% / 0,832 = 40.024 %

    @cirocontinisio
    I've been testing the values and the math, it's a simple spell but quite unbreakable, let me know what you think
     
    Last edited: Feb 13, 2021
    Smurjo likes this.
  24. Smurjo

    Smurjo

    Joined:
    Dec 25, 2019
    Posts:
    267
    In my whiteboxing I have use an additional "rock"-texture instead of the sand (sand you would only find on the beach). I think it would be appropriate if we can associate the ground material with the vertex colors per scene - else I fear the 4 textures are quite limiting. Actually the same information is already in the textures given to the ground shader in the ground material, so maybe you could just return the name of the texture in there.
     
  25. murkesz

    murkesz

    Joined:
    Aug 10, 2013
    Posts:
    5
    Oh, you're right. We can play more than one sound in the same time, so we don't have to narrow down the output to one "material". It definitely worth to check how does that sound.

    I was thinking about giving a sound material component to every object which can interact with a character. So the material detector system would use two different components (scripts): one (sound material detector) for the characters and another (sound material) for every object which can be interacted by the characters.

    Sound Material Component:
    It could have a public method with the following declaration:
    Code (CSharp):
    1. public MySoundEffect GetMySoundEffect(RaycastHit hit);
    This method could be implemented differently in child classes (I'm not sure how does unity handle inheritance in case of game object components) or simply just give back different results based on the actual property values (for example: the name of the used texture) of the Sound Material component.

    Sound Material Detector component:
    It could simply shoot a raycast down. If the ray hit something then the Sound Material Detector asks for the Sound Material component:
    Code (CSharp):
    1. var sm = hit.transform.gameObject.GetComponent<SoundMaterial>();
    2. if (sm != null)
    3. {
    4. var mySoundEffect = sm.GetMySoundEffect();
    5. ...
    6. }
    I don't have a computer on me at the moment, but I can test the solution tonight (GMT +1) and give you further explanation if needed.
     
  26. murkesz

    murkesz

    Joined:
    Aug 10, 2013
    Posts:
    5
    I think I'm done with the above mentioned 2 scripts and an interface.

    The interface is simple:
    Code (CSharp):
    1. /// <summary>
    2. /// Every game object having a component which class implements this interface can be interacted by
    3. /// the <class>SoundMaterialDetector</class> component.
    4. /// </summary>
    5. interface ISoundMaterial
    6. {
    7.     void EmitSound(RaycastHit hit);
    8. }
    9.  
    Then I created a MonoBehaviour script called GroundSoundMaterial. The script implements the ISoundMaterial and can be used as a component on a non-static "ground" game object:
    upload_2021-2-14_1-49-7.png

    As you can see my logic was to give a name to the four different vertex color used by the ground. This way we can reuse the script on ground objects with different textures.

    At the moment the EmitSound method of the component simply prints out four texts every time it is asked to emit a sound by someone.
    Code (CSharp):
    1.         print("Emitting " + _redSoundMaterialName + " sound material. Ratio: " + redRatio);
    2.         print("Emitting " + _greenSoundMaterialName + " sound material. Ratio: " + greenRatio);
    3.         print("Emitting " + _blueSoundMaterialName + " sound material. Ratio: " + blueRatio);
    4.         print("Emitting " + _blackSoundMaterialName + " sound material. Ratio: " + blackRatio);
    5.  
    6.         //TODO: instead of printing the material names and ratios the method should somehow call the sound system
    The ratio is calculated by the algorithm thought out by @Eddrico.


    The second script I created is another MonoBehaviour. This can be used as a component on a game object (mostly on a character). It checks periodically whether a sound material object (a game object with a component which implements the ISoundMaterial interface) is under the game object. If it finds a sound material object then it calls the EmitSound method of it.
    upload_2021-2-14_1-59-51.png

    The Detect Rate property controls the time between two detection cycle. The raycast can hit only one object at a time. (I'm not sure if this is enough.)

    You can see the result here (see console messages). Let me know what you think about it:
    Enu
     
    Last edited: Feb 14, 2021
    cirocontinisio and Smurjo like this.
  27. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    Nah it's not needed, we don't have different footsteps for the cobblestone and for rocks. They share the same set.
    So we can go with 4 results: grass, soil, sand, and hard surfaces (rock / cobblestone).
     
  28. cirocontinisio

    cirocontinisio

    Unity Technologies

    Joined:
    Jun 20, 2016
    Posts:
    742
    That's why we always suggest to post/search first, then give it a go! Glad to see that you two can collaborate on it though. I'll link the thread to the card. Actually, I'll merge with the Ground shader one!
     
unityunity