Search Unity

Limiting grass shader on spherical object based on angle

Discussion in 'Shaders' started by joesobo, Apr 11, 2020.

  1. joesobo

    joesobo

    Joined:
    Sep 6, 2018
    Posts:
    17
    So I've been following along with this tutorial for making a grass shader, for reference I am relatively new to the shader world: https://roystan.net/articles/grass-shader.html. I've pretty much gotten everything to work and I am starting to work on modifications, so it fits better with my project. The basic idea behind what I want, is I have a geodesic dome as my planet surface and I need grass to grow on the meshes surface. Grass should grow on any of the hexagons that face away from the origin, but not on the side of the raised hexagon platforms.

    In it's current state the grass grows on all faces of the mesh. I've tried limiting when blades can spawn by checking that their normals are above a certain value in the geo function. So far it only limits spawning in certain regions. I'm pretty certain, I'm correct in my understanding of using normals to limit the grass. I just don't know how I should go about achieving it.

    I'm pretty lost on where to go from here, and would love any help.
    Thanks!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Your theory sounds correct, but without seeing an example of what you're seeing go wrong and the code it's hard to say how to fix it.
     
  3. joesobo

    joesobo

    Joined:
    Sep 6, 2018
    Posts:
    17
    Code (CSharp):
    1. [maxvertexcount(BLADE_SEGMENTS * 10)]
    2.     void geo(triangle vertexOutput IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream){
    3.         float3 pos = IN[0].vertex;
    4.  
    5.         float3 vNormal = IN[0].normal;
    6.         if(vNormal.z < _GrassCutoff){
    7.             float4 vTangent = IN[0].tangent;
    8.             float3 vBinormal = cross(vNormal, vTangent) * vTangent.w;
    9.  
    10.             float3x3 tangentToLocal = float3x3(
    11.                 vTangent.x, vBinormal.x, vNormal.x,
    12.                 vTangent.y, vBinormal.y, vNormal.y,
    13.                 vTangent.z, vBinormal.z, vNormal.z
    14.             );
    15.  
    16.             float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI, float3(0, 0, 1));
    17.             float3x3 bendRotationMatrix = AngleAxis3x3(rand(pos.zzx) * _BendRotationRandom * UNITY_PI * 0.5, float3(-1, 0, 0));
    18.  
    19.             float2 uv = pos.xz * _WindDistortionMap_ST.xy + _WindDistortionMap_ST.zw + _WindFrequency * _Time.y;
    20.             float2 windSample = (tex2Dlod(_WindDistortionMap, float4(uv, 0, 0)).xy * 2 - 1) * _WindStrength;
    21.             float3 wind = normalize(float3(windSample.x, windSample.y, 0));
    22.             float3x3 windRotation = AngleAxis3x3(UNITY_PI * windSample, wind);
    23.  
    24.             float3x3 transformationMatrix = mul(mul(mul(tangentToLocal, windRotation), facingRotationMatrix), bendRotationMatrix);
    25.             float3x3 transformationMatrixFacing = mul(tangentToLocal, facingRotationMatrix);
    26.  
    27.             float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
    28.             float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;
    29.             float depth = (rand(pos.yxz) * 2 - 1) * _BladeDepthRandom + _BladeDepth;
    30.  
    31.             //FRONT
    32.             triStream.Append(GenerateGrassVertex(pos, 0, 0, 0, float2(0, 0), transformationMatrixFacing));                    //A
    33.             triStream.Append(GenerateGrassVertex(pos, width, 0, 0, float2(1, 0), transformationMatrixFacing));                //B
    34.             triStream.Append(GenerateGrassVertex(pos, 0, 0, height, float2(0.5, 1), transformationMatrix));                    //C
    35.  
    36.             triStream.Append(GenerateGrassVertex(pos, width, 0, 0, float2(1, 0), transformationMatrixFacing));                //B
    37.             triStream.Append(GenerateGrassVertex(pos, 0, 0, height, float2(0.5, 1), transformationMatrix));                    //C
    38.             triStream.Append(GenerateGrassVertex(pos, width, 0, height, float2(0.5, 1), transformationMatrix));                //D
    39.  
    40.             //TOP  
    41.             triStream.Append(GenerateGrassVertex(pos, 0, 0, height, float2(0.5, 1), transformationMatrix));                    //C
    42.             triStream.Append(GenerateGrassVertex(pos, width, 0, height, float2(0.5, 1), transformationMatrix));                //D
    43.             triStream.Append(GenerateGrassVertex(pos, 0, depth, height, float2(0.5, 1), transformationMatrix));                //G
    44.  
    45.             triStream.Append(GenerateGrassVertex(pos, width, 0, height, float2(0.5, 1), transformationMatrix));                //D
    46.             triStream.Append(GenerateGrassVertex(pos, 0, depth, height, float2(0.5, 1), transformationMatrix));                //G
    47.             triStream.Append(GenerateGrassVertex(pos, width, depth, height, float2(0.5, 1), transformationMatrix));            //H
    48.  
    49.             //BACK  
    50.             triStream.Append(GenerateGrassVertex(pos, 0, depth, height, float2(0.5, 1), transformationMatrix));                //G
    51.             triStream.Append(GenerateGrassVertex(pos, width, depth, height, float2(0.5, 1), transformationMatrix));            //H
    52.             triStream.Append(GenerateGrassVertex(pos, 0, depth, 0, float2(0.5, 1), transformationMatrixFacing));            //E
    53.  
    54.             triStream.Append(GenerateGrassVertex(pos, width, depth, height, float2(0.5, 1), transformationMatrix));            //H
    55.             triStream.Append(GenerateGrassVertex(pos, 0, depth, 0, float2(0.5, 1), transformationMatrixFacing));            //E
    56.             triStream.Append(GenerateGrassVertex(pos, width, depth, 0, float2(0.5, 1), transformationMatrixFacing));        //F
    57.  
    58.             //RIGHT  
    59.             triStream.Append(GenerateGrassVertex(pos, width, depth, height, float2(0.5, 1), transformationMatrix));            //H
    60.             triStream.Append(GenerateGrassVertex(pos, width, depth, 0, float2(0.5, 1), transformationMatrixFacing));        //F
    61.             triStream.Append(GenerateGrassVertex(pos, width, 0, 0, float2(1, 0), transformationMatrixFacing));                //B
    62.  
    63.             triStream.Append(GenerateGrassVertex(pos, width, depth, height, float2(0.5, 1), transformationMatrix));            //H
    64.             triStream.Append(GenerateGrassVertex(pos, width, 0, 0, float2(1, 0), transformationMatrixFacing));                //B
    65.             triStream.Append(GenerateGrassVertex(pos, width, 0, height, float2(0.5, 1), transformationMatrix));                //D
    66.  
    67.             //LEFT  
    68.             triStream.Append(GenerateGrassVertex(pos, 0, depth, height, float2(0.5, 1), transformationMatrix));                //G
    69.             triStream.Append(GenerateGrassVertex(pos, 0, 0, height, float2(0.5, 1), transformationMatrix));                    //C
    70.             triStream.Append(GenerateGrassVertex(pos, 0, depth, 0, float2(0.5, 1), transformationMatrixFacing));            //E
    71.  
    72.             triStream.Append(GenerateGrassVertex(pos, 0, 0, height, float2(0.5, 1), transformationMatrix));                    //C
    73.             triStream.Append(GenerateGrassVertex(pos, 0, depth, 0, float2(0.5, 1), transformationMatrixFacing));            //E
    74.             triStream.Append(GenerateGrassVertex(pos, 0, 0, 0, float2(0, 0), transformationMatrixFacing));                    //A
    75.         }
    76.     }
    Here is the code that I was working on. Hopefully it's readable in this format. Also I realize my triangle creation isn't the most efficient right now. I'm just trying to get it spawning on the correct surfaces first.

    On the 5th line 'if(vNormal.z < _GrassCutoff){' is where i'm checking the normal of the vertice and comparing it to the cutoff value. As I said before it just restricts certain areas, instead of certain faces. I've tried several other varients of this method of restriction, but haven't really got anywhere with it.

    https://imgur.com/KB10gYP
    If you can see the image, it's easy to tell the top is pretty much doing the opposite of what I want with grass coming out of the sides of the platforms.
     
    Last edited: Apr 11, 2020
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I’m assuming you want the grass to be coming out of all sides of your “sphere”, just not the sides of the protruding hexagons, yes? The code looks to be working properly for what you have written.

    The problem you’re having is the vertex normal is relative to the object space not surface or “sphere” space. That is to say if you have a sphere the top is going to have a normal vector of float3(0,1,0) and the bottom is float3(0,-1,0). Presumably you want to find if a surface is facing away from the center of your soccer ball planet. To do that you need to compare the vertex normal with the normalized vertex position, assuming your generated mesh’s pivot is at the center of the planet.

    The other option you have, and one that might be easier to implement, would be to bake data into the mesh to mask where you want grass (or other things) to show. This can be as a vertex color value, or an unused UV channel. Or, even better, generate custom meshes for the grass that simply lack the triangles you don’t want to have grass.
     
    joesobo likes this.
  5. joesobo

    joesobo

    Joined:
    Sep 6, 2018
    Posts:
    17
    Yeah I think you got the idea. Can you explain a little more what you mean by comparing the vertex normal with the normalized vertex pos? Do you mean I should be checking to see if they're equal? or subtracting them from each other and comparing to the grass cutoff?

    Code (CSharp):
    1. geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float depth, float height, float2 uv, float3x3 transformMatrix){
    2.         float3 tangentPoint = float3(width, depth, height);
    3.  
    4.         float3 tangentNormal = float3(0, -1, 0);
    5.         float3 localNormal = mul(transformMatrix, tangentNormal);
    6.  
    7.         float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint);
    8.         return VertexOutput(localPosition, uv, localNormal);
    9.     }
    I also have this code which I was using for setting the rotation of the grass blades relative to the center of the planet. I think the cutoff should be relatively the same?
     
  6. bart_the_13th

    bart_the_13th

    Joined:
    Jan 16, 2012
    Posts:
    498
    is the sphere static? if its static then theres chance that static batching is messing around your mesh normals.
     
    joesobo likes this.
  7. joesobo

    joesobo

    Joined:
    Sep 6, 2018
    Posts:
    17
    No the planet isn't static. Doesn't change anything when I make it static either.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    That's not what that code does. That sets the grass to use the surface normal for the grass direction. The fact it sometimes faces away from the center of the planet is really just a coincidence of the form of the mesh... and is why the grass on the side of the hexagonal platforms stick out the direction they do too. That code just makes sure the grass follows the arbitrary surface orientation of whatever mesh is being used.

    What you want is something like this:
    Code (csharp):
    1. dot(IN[0].normal, normalize(IN[0].vertex.xyz)) > _GrassCutoff
     
    joesobo likes this.
  9. joesobo

    joesobo

    Joined:
    Sep 6, 2018
    Posts:
    17
    Haha! That worked perfectly! Thank you so much
     
    bgolus likes this.