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. Dismiss Notice

Dynamic Grass System in Unity?

Discussion in 'General Graphics' started by themdubs, May 30, 2017.

  1. themdubs

    themdubs

    Joined:
    Jan 12, 2014
    Posts:
    26
    I've been interested in creating an expansive wheat field in a game I'm developing and I noticed this video for an Unreal Engine asset.

    Around 0:19 it shows the character walking through the wheat field and interacting with it, which is exactly what I need. I've looked and found some grass systems but they all seem relatively low to the ground and have very limited interaction.

    To break it down what I'm looking for is:
    • Interactive - Can walk through and make changes to the grass
    • Wind Effects - Waving wheat and adjustable intensity
    • LOD - further away, less detail to save on memory
    I've looked for a breakdown on how some grass systems work but I haven't found much; I don't know whether to use billboards or meshes, if it should just be a replacement for unity's built-in grass shader or volumetric. Potentially I would really love to be able to paint the grass on to a terrain but I could work with whatever.
     
  2. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,886
  3. themdubs

    themdubs

    Joined:
    Jan 12, 2014
    Posts:
    26
    I actually followed the sources and found this reddit post which is what that guy based his grass on. The creator actually made video tutorial series as well!
     
  4. themdubs

    themdubs

    Joined:
    Jan 12, 2014
    Posts:
    26
    I followed along the tutorial and ended up with this as the result:
    This is obviously a little bit overkill and its still pretty basic (e.g. no lighting, no deformation, no LOD, etc.) but I was really impressed by how well it could render such an absolutely ridiculous amount. I definitely learned a lot from the tutorials about geometry shaders, so shout out to World of Zero!
     
  5. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,463
    keep us informed of progress :D
     
  6. themdubs

    themdubs

    Joined:
    Jan 12, 2014
    Posts:
    26
    Got the lighting sort of working:

    So the bottom section is not getting any light (besides ambient) and I think its because the quads making the objects are vertical planes which according to the lighting math shouldn't receive any light. Should I apply some sort of offset to combat this?
    This is my code for calculating lighting so far (I'm not the best at shaders as a warning):
    Code (CSharp):
    1.            
    2.             half4 frag(g2f IN) : COLOR
    3.             {
    4.                 /*
    5.                     g2f contains {pos, norm, uv, LIGHTING_COORDS}
    6.                 */
    7.                 fixed4 c = tex2D(_MainTex, IN.uv);
    8.                 clip(c.a - _Cutoff);
    9.  
    10.                 half3 worldNormal = UnityObjectToWorldNormal(IN.norm);
    11.                 half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
    12.                 fixed3 diff = nl * _LightColor0.rgb;
    13.                 fixed3 ambient = ShadeSH9(half4(worldNormal, 1));
    14.                 fixed attenuation = LIGHT_ATTENUATION(IN);
    15.  
    16.                 fixed3 lighting = diff * attenuation + ambient * 2;
    17.                 c.rgb *= lighting;
    18.                 return c;
    19.             }
    20.  
    Anyone have any pointers to a solution?

    Also I'm doing a pass for the shadow caster and I noticed that, since my object is essentially 6 quads (sense they intersect there has 2 per fin, if there is a better way to do this I would love to know :D), only the last quad to be built by the geometry shader is actually casting shadows (as seen in 1 below). Will I need to do a pass for each of the quads?

    Lastly it seems that my shadows overlay on my objects despite not actually casting on to the object (which is in 2 and 3 below). Is this something to do with how I calculate shadows in my fragment shader above?
     
  7. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,463
    I don't know exactly what procedure you are using, but yeah, the quad must have normal in the direction of the grass upright, not relative to their mesh. That's how it's done for short fur and hair too.

    Beyond that I know nothing about geometry shader and haven't look at the tut you almost linked to.
     
  8. themdubs

    themdubs

    Joined:
    Jan 12, 2014
    Posts:
    26
    Good news, the shadows seem to be working now! I condensed my geometry code so now it only uses 12 vertices and 3 quads, TriangleStream.RestartStrip() was what I was needing. And I also started calculating my normal for each quad which gave me proper lighting however only on half of the area.

    Both areas should be getting about the same amount of light however the left side remains un-shaded. Any clues as to what may be causing this?

    Here is my code for the shader, its a bit rough but if there's any confusion I should be able to clarify.
    Code (CSharp):
    1. Shader "Custom/GrassGeomShader" {
    2.     Properties{
    3.         _MainTex("Albedo (RGB)", 2D) = "white" {}
    4.         _Cutoff("Cutoff", Range(0,1)) = 0.25
    5.         _GrassHeight("Grass Height", Float) = 0.25
    6.         _WindSpeed("Wind Speed", Float) = 100
    7.         _WindStrength("Wind Strength", Float) = 0.5
    8.     }
    9.     SubShader{
    10.         Tags{
    11.             "Queue" = "Geometry"
    12.             "RenderType" = "Opaque"
    13.         }
    14.         LOD 200
    15.         Pass
    16.         {
    17.             Name "ForwardBase"
    18.             Tags { "LightMode" = "ForwardBase"}
    19.             CULL OFF
    20.  
    21.             CGPROGRAM
    22.             #include "UnityCG.cginc"
    23.             #pragma vertex vert
    24.             #pragma fragment frag
    25.             #pragma geometry geom
    26.             #include "Lighting.cginc"
    27.             #pragma multi_compile_fwdbase
    28.             #include "AutoLight.cginc"
    29.  
    30.             #pragma target 5.0
    31.  
    32.             sampler2D _MainTex;
    33.  
    34.             struct v2g
    35.             {
    36.                 float4 pos : SV_POSITION;
    37.                 float3 norm : NORMAL;
    38.                 float2 uv : TEXCOORD0;
    39.                 float3 color : TEXCOORD1;
    40.             };
    41.  
    42.             struct g2f
    43.             {
    44.                 float4 pos : SV_POSITION;
    45.                 float3 norm : NORMAL;
    46.                 float2 uv : TEXCOORD0;
    47.                 LIGHTING_COORDS(1,2)
    48.             };
    49.  
    50.             struct QuadComponents {
    51.                 float3 v0;
    52.                 float3 v1;
    53.                 float3 norm;
    54.                 float3 color;
    55.             };
    56.  
    57.             half _GrassHeight;
    58.             half _Cutoff;
    59.             half _WindStrength;
    60.             half _WindSpeed;
    61.  
    62.             void BuildQuad(QuadComponents quadComp, float3 displacement, inout TriangleStream<g2f> triStream) {
    63.                 g2f OUT;
    64.                 OUT.norm = cross(quadComp.norm, quadComp.v0 + displacement);
    65.              
    66.                 float3 position[4];
    67.                 float2 uv[4];
    68.                 int vertexIndices[4] = { 0,1,2,3 };
    69.                 position[0] = quadComp.v0 - displacement * 0.5 * _GrassHeight;
    70.        uv[0] = float2(0, 0);
    71.                 position[1] = quadComp.v1 - displacement * 0.5 * _GrassHeight; uv[1] = float2(0, 1);
    72.                 position[2] = quadComp.v0 + displacement * 0.5 * _GrassHeight; uv[2] = float2(1, 0);
    73.                 position[3] = quadComp.v1 + displacement * 0.5 * _GrassHeight; uv[3] = float2(1, 1);
    74.  
    75.                 for (int i = 0; i < 4; i++) {
    76.                     OUT.uv = uv[vertexIndices[i]];
    77.                     OUT.pos = UnityObjectToClipPos(position[vertexIndices[i]]);
    78.                     TRANSFER_VERTEX_TO_FRAGMENT(OUT);
    79.                     triStream.Append(OUT);
    80.                 }
    81.  
    82.                 triStream.RestartStrip();
    83.             }
    84.  
    85.             v2g vert(appdata_full v)
    86.             {
    87.                 v2g OUT;
    88.                 OUT.pos = v.vertex;
    89.                 OUT.norm = v.normal;
    90.                 OUT.uv = v.texcoord;
    91.                 OUT.color = tex2Dlod(_MainTex, v.texcoord).rgb;
    92.                 return OUT;
    93.             }
    94.  
    95.             [maxvertexcount(12)]
    96.             void geom(point v2g IN[1], inout TriangleStream<g2f> triStream)
    97.             {
    98.                 QuadComponents quadComp;
    99.                 quadComp.v0 = IN[0].pos.xyz;
    100.                 quadComp.v1 = IN[0].pos.xyz + IN[0].norm * _GrassHeight;
    101.  
    102.                 float3 perpendicularAngle = float3(0, 0, 1);
    103.                 quadComp.norm = cross(perpendicularAngle, IN[0].norm);
    104.                 quadComp.color = (IN[0].color);
    105.  
    106.                 float3 wind = float3(sin(_Time.x  * _WindSpeed + quadComp.v0.x * _WindSpeed) + sin(_Time.x * _WindSpeed + quadComp.v0.z * 2), 0, cos(_Time.x  * _WindSpeed + quadComp.v0.z) + cos(_Time.x  * _WindSpeed + quadComp.v0.x *2));
    107.                 quadComp.v1 += wind * _WindStrength;
    108.  
    109.                 float sin60 = 0.866f;
    110.                 float cos60 = 0.5f;
    111.              
    112.                 //Creates inner section grass mesh - Can be replaced
    113.                 BuildQuad(quadComp, perpendicularAngle, triStream);
    114.                 BuildQuad(quadComp, float3(sin60, 0, cos60), triStream);
    115.                 BuildQuad(quadComp, float3(sin60, 0, -cos60), triStream);
    116.             }
    117.  
    118.             half4 frag(g2f IN) : COLOR
    119.             {
    120.                 /*
    121.                     g2f contains {pos, norm, uv, LIGHTING_COORDS}
    122.                 */
    123.                 fixed4 c = tex2D(_MainTex, IN.uv);
    124.                 clip(c.a - _Cutoff);
    125.  
    126.                 half3 worldNormal = UnityObjectToWorldNormal(IN.norm);
    127.                 half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
    128.                 fixed3 diff = nl * _LightColor0.rgb;
    129.                 fixed3 ambient = ShadeSH9(half4(worldNormal, 1));
    130.                 fixed attenuation = LIGHT_ATTENUATION(IN);
    131.  
    132.                 fixed3 lighting = diff * attenuation + ambient * 2;
    133.                 c.rgb *= lighting;
    134.                 return c;
    135.             }
    136.         ENDCG
    137.         }
    138.     }
    139. }
    140.  
    141.  
     
    neoshaman likes this.
  9. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,886
  10. themdubs

    themdubs

    Joined:
    Jan 12, 2014
    Posts:
    26
    Seems interesting I'll have to look more into it.

    Update on the grass shader: I got lighting working! All that I needed to change was line 64 so that it wasn't adding v0 to displacement (displacement being the angle at which the new quad is to be built). I also added a wind texture lookup rather than doing a calculation to find it so it can be more deliberate rather than random, although its very simple and needs to be rewritten.

    I posted a gif of it in action here
     
    marcatore, neoshaman and Hikiko66 like this.
  11. Renanmgs

    Renanmgs

    Joined:
    May 16, 2016
    Posts:
    31
    Hey man, any update on this? can u share your work so we can improve it? Im trying to reproduce the same experiment for learning purposes, if u can share it would be aweasome!