Hacking the Gras Shader: adding directional shadows and bump maps

I’m trying to hack the Unity builtin Grass Shader at the moment…

For starters I would like the gras to drop shadows and to be bump mapped… yet as I’m pretty new to the whole shader programming, I’m struggling as to how to achieve this.

I used the example with bump mapped shaders in the documentation as starting point for the bump mapping, now the question is how I can send the bump map to the shader (I know others have achieved this, but I don’t quite understand how… anyone could give me a hint?

EDIT: I found a bug in my code, so now the shader works, but I get a warning telling me “Shader wants tangents, but the mesh doesn’t have them” At runtime… what causes this problem? Is bump mapping not possible for simple Meshes like the Gras one? Or do I need to use a different algorithm to make it work (like what is used for the nature/leaves material shader)?

I’m also not quite sure how I add the the capability to drop directional shadows to the existing Gras Shader… I see that there are special tags for that, but I don’t get how to use these and where to put them.

Here the code I use at the moment EDIT: Working now, with the warning as written above:

Shader "Hidden/TerrainEngine/Details/WavingDoublePass" {
Properties {
	_WavingTint ("Fade Color", Color) = (.7,.6,.5, 0)
	_MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
	_WaveAndDistance ("Wave and distance", Vector) = (12, 3.6, 1, 1)
	_GrasBump0 ("Bumpmap", 2D) = "bump" {}
	_Cutoff ("Cutoff", float) = 0.5
}

SubShader {
	Tags {
		"Queue" = "Geometry+200"
		//"IgnoreProjector"="True"
		"RenderType"="Opaque"
	}
	Cull Off
	LOD 200
	ColorMask RGB
		
CGPROGRAM
#pragma surface surf Lambert vertex:WavingGrassVert addshadow
#include "TerrainEngine.cginc"

sampler2D _MainTex;
sampler2D _GrasBump0;
fixed _Cutoff;

struct Input {
	float2 uv_MainTex;
	float2 uv_BumpMap;
	fixed4 color : COLOR;
};

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * IN.color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;
	clip (o.Alpha - _Cutoff);
	o.Alpha *= IN.color.a;
	o.Normal = UnpackNormal (tex2D (_GrasBump0, IN.uv_BumpMap));
}
ENDCG
}
/*	
	SubShader {
		Tags {
			"Queue" = "Geometry+200"
			//"IgnoreProjector"="True"
			"RenderType"="Opaque"
		}
		Cull Off
		LOD 200
		ColorMask RGB
		
		Pass {
			Tags { "LightMode" = "ForwardAdd" }
			Material {
				Diffuse (1,1,1,1)
				Ambient (1,1,1,1)
			}
			Lighting On
			ColorMaterial AmbientAndDiffuse
			AlphaTest Greater [_Cutoff]
			SetTexture [_MainTex] { combine texture * primary DOUBLE, texture }
		}
		Pass {
			Tags { "LightMode" = "VertexLMRGBM" }
			AlphaTest Greater [_Cutoff]
			BindChannels {
				Bind "Vertex", vertex
				Bind "texcoord1", texcoord0 // lightmap uses 2nd uv
				Bind "texcoord", texcoord1 // main uses 1st uv
			}
			SetTexture [unity_Lightmap] {
				matrix [unity_LightmapMatrix]
				combine texture * texture alpha DOUBLE
			}
			SetTexture [_MainTex] { combine texture * previous QUAD, texture }
		}
	}*/
	
	Fallback Off
}

I commented the subshaders out as it seemed they are not doing anything on my machine at least (and I wanted to reduce the possible “point of failures”)

“Shader wants tangents, but the mesh doesn’t have them”

Normal maps rely on the mesh having tangents as well as normals. Without them, the normal map will be unable to display correctly as there is not enough information for it to correctly alter the normals of the mesh.

It seems that the grass meshes do not have tangents by default.

The good news, though, is that you already know the bitangent: straight up. From that and the normal, you can calculate the tangent even if the grass isn’t in billboard mode.

Okay, that makes sense…
But as the Gras is waving, do we really know that the bitangent is always straight up? Or is the waving created by some postprocessing, meaning the shading doesn’t need to know about it?

I added the following to the #pragma directive: “enable fullforwardshadows” … in the hope this would enable the shadows. I’m pretty sure I use forward rendering.

But now I only see black squares instead of my gras texture, and the waving also stopped working. What did I do wrong?

Also I don’t seem to be able to locate any information about the following on the web:

  • What format is tangent space in? Its a float4, but what values are expected by it? 0 to 1? Degrees (0 to 360)?
  • what is the structure of the SurfaceOutput? Is there a tangentspace var in this structure?

Sorry for all the newb questions…

The tangentspace output should be (I think)

Vector4(normal, tangent, bitangent, handedness).

This starts to drive me crazy…

I tried using (“abusing” :wink: ) multiple of the built in shaders as WavingDoublePass shader, I tried all possible combinations, I tried adding the “LightMode”=“ShadowCaster” Pass… With no results concerning dropping directional shadows.

I have come to the conclusion that most probably its not the fault of the shader, but of the “Mesh” not being able to drop shadows. What exactly could be the problem with the grass? Is it “one sided” somehow and thus not able to drop shadows?

Why is Unity complaining every time I try to add a pixel lit pass (which will be needed anyway if I want to have the shader bump mapped, right?) “uses Unity 2.x style fixed function per-pixel lighting. Per-pixel lighting is not supported without shaders”?

Do I have to write my own ShadowCaster Pass for the Grass?

Most probably I just know to little about shaderLab and Cg, but could someone give me a hint? I like to dig deeper into the whole stuff, but at the moment I just want to get the shader to work as fast as possible.

Another 2 Hours wasted on this and not nearer to any of my targets (well, at least I start to understand the dfferences between Vertex/Fragment Shaders and Surface Shaders :slight_smile: )

I tried to write a shader from the ground up, using what I could gather from the Unity references. This is what I put together until now, but even though I used a shadow collector and caster pass, no luck. Neither is done.

And of course the whole waving of Grass has been dropped, and I have no idea how to integrate the vertex:WavingGrassVert pragma (I assume its a method from the included TerrainEngine.cginc file)…

/*
Renders doubled sides objects without lighting. Useful for
grass, trees or foliage.

This shader renders two passes for all geometry, one
for opaque parts and one with semitransparent details.

This makes it possible to render transparent objects
like grass without them being sorted by depth.
*/

Shader "Hidden/TerrainEngine/Details/WavingDoublePass" {
Properties {
	_Color ("Main Color", Color) = (1, 1, 1, 1)
	_MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
	_Cutoff ("Base Alpha cutoff", Range (0,.9)) = .5
}
 
SubShader {
	//Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout" }
	Material {
       	Diffuse [_Color]
   		Ambient [_Color]
    }
	Lighting On
	Cull Off
	
	// first pass:
	//   render any pixels that are more than [_Cutoff] opaque
    Pass {
		AlphaTest Greater [_Cutoff]

        SetTexture [_MainTex] {
            Combine texture * primary DOUBLE, texture * primary
        }
    }
	// Second pass:
	//   render in the semitransparent details.
	Pass {
		// Dont write to the depth buffer
		ZWrite off
		// Don't write pixels we have already written.
		ZTest Less
		// Only render pixels less or equal to the value
		AlphaTest LEqual [_Cutoff]

		// Set up alpha blending
		Blend SrcAlpha OneMinusSrcAlpha

		SetTexture [_MainTex] {
			combine texture * primary DOUBLE, texture * primary
		}
	}
	
	// Pass to render object as a shadow caster
	Pass {
		Name "Caster"
		Tags { "LightMode" = "ShadowCaster" }
		
		Fog {Mode Off}
		ZWrite On ZTest Less Cull Off
		//Offset [_ShadowBias], [_ShadowBiasSlope]

		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile SHADOWS_NATIVE SHADOWS_CUBE
			#pragma fragmentoption ARB_precision_hint_fastest
			#include "UnityCG.cginc"

			struct v2f { 
				V2F_SHADOW_CASTER;
				float2  uv;
			};

			uniform float4 _MainTex_ST;

			v2f vert( appdata_base v )
			{
				v2f o;
				TRANSFER_SHADOW_CASTER(o)
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

			uniform sampler2D _MainTex;
			uniform float _Cutoff;

			float4 frag( v2f i ) : COLOR
			{
				half4 texcol = tex2D( _MainTex, i.uv );
				clip( texcol.a - _Cutoff );
	
				SHADOW_CASTER_FRAGMENT(i)
			}
		ENDCG

	}
	
	// Pass to render object as a shadow collector
	Pass {
		Name "ShadowCollector"
		Tags { "LightMode" = "ShadowCollector" }
		
		Fog {Mode Off}
		ZWrite On ZTest Less

		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest

			#define SHADOW_COLLECTOR_PASS
			#include "UnityCG.cginc"

			struct v2f {
				V2F_SHADOW_COLLECTOR;
				float2  uv;
			};

			uniform float4 _MainTex_ST;

			v2f vert (appdata_base v)
			{
				v2f o;
				TRANSFER_SHADOW_COLLECTOR(o)
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

			uniform sampler2D _MainTex;
			uniform float _Cutoff;

			half4 frag (v2f i) : COLOR
			{
				half4 texcol = tex2D( _MainTex, i.uv );
				clip( texcol.a - _Cutoff );
	
				SHADOW_COLLECTOR_FRAGMENT(i)
			}
		ENDCG
	}
}
}

Any Idea whats going wrong with the shadows, or how to integrate the waving of the gras back into this shader?

This wont show your current problem. But im sure shadows for individual grass components will hit your fps real hard.
Wouldnt you consider SSAO instead?

Well, at the moment I would consider pretty much everything as long as it makes the Gras look nicer than the Unity built-in one… :slight_smile:

I assume I could take the Tree Generator shaders with ambient occlusion as example, or did you meant something else?

Problem is, im not only using directional shadows, but also a moving sun, if this poses a problem…

I meant an image processing SSAO to be used on your scene.

And, if thats still not suitable for your case, you might consider your own grass system with lod meshes and everything else, and you will have your shadows automatically on the way. That would be easier than messing up with the closed source terrain engine.

Last thing i would like to add is, the current built-in grass is very nice and fast in my opinion, you just need better textures not only for grass but also for the terrain itself.

I will have a look at the SSAO for sure… is there an image effect for that? Or do I have to come up with it?

Well, I considered doing it my own way, but as Im not sure how to prevent the massive overhead of placing a massive amount of grass meshes in a level, I rather stay away from until I have time to dig a little bit deeper into the whole performance optimising stuff.

Well, I’m pretty happy with the trees, since they fixed the wind affecting the trees placed in the tree map (I would still like to have more control, being able to rotate and tilt a tree to get “more” out of a single tree prototype), but the grass just isn’t working for an isometric view… of course my Textures might also not be the most ideal, but still, would be nice if the grass would match the tree generator trees (bump mapping, shadows or ao) in quality… maybe I’m expecting too much… :wink: I didn’t wanted to say the TerrainEngine is bad, actually it works pretty well for First Person Games…

Thanks for the input.

Okay, SSAO did help a lot! I think I’ll leave the shadows for the Grass for the time being and concentrate on making the bump mapping work.

I’m trying to find a shader that does the bump mapping without the unpackNormal as this function seems to be dependend on tangents in the mesh (which the grass mesh doesn’t seem to have for some reason)… anyone have an idea how this might be done?

You place your own “mass” grass just like how unity does. You paint a 1bit map in your texture editor as a plantmap(lets say white pixels with grass, black pixels without grass) and you read the pixels from it. Then you place grass object or chunk of grass objects on your terrain depending on terrainheight proportional to the pixel position.
Then you can have builtin normal mapping and shadows and anything else.

Thing is, its very awkward to have normal mapping on a billboard grass object.

Anyhow, good luck.