
1.CustomTessellation.cginc
// Tessellation programs based on this article by Catlike Coding:
// https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
struct vertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct vertexOutput
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct TessellationFactors
{
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
vertexInput vert(vertexInput v)
{
return v;
}
vertexOutput tessVert(vertexInput v)
{
vertexOutput o;
// Note that the vertex is NOT transformed to clip
// space here; this is done in the grass geometry shader.
o.vertex = v.vertex;
o.normal = v.normal;
o.tangent = v.tangent;
return o;
}
float _TessellationUniform;
TessellationFactors patchConstantFunction (InputPatch<vertexInput, 3> patch)
{
TessellationFactors f;
f.edge[0] = _TessellationUniform;
f.edge[1] = _TessellationUniform;
f.edge[2] = _TessellationUniform;
f.inside = _TessellationUniform;
return f;
}
[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("integer")]
[UNITY_patchconstantfunc("patchConstantFunction")]
vertexInput hull (InputPatch<vertexInput, 3> patch, uint id : SV_OutputControlPointID)
{
return patch[id];
}
[UNITY_domain("tri")]
vertexOutput domain(TessellationFactors factors, OutputPatch<vertexInput, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
{
vertexInput v;
#define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) v.fieldName = \
patch[0].fieldName * barycentricCoordinates.x + \
patch[1].fieldName * barycentricCoordinates.y + \
patch[2].fieldName * barycentricCoordinates.z;
MY_DOMAIN_PROGRAM_INTERPOLATE(vertex)
MY_DOMAIN_PROGRAM_INTERPOLATE(normal)
MY_DOMAIN_PROGRAM_INTERPOLATE(tangent)
return tessVert(v);
}
2.grassGeometryShader
//Multiple features of this shader come from Roystan's grass shader tutorial: https://roystan.net/articles/grass-shader.html
Shader "Geometry/GrassGeometryShader"
{
Properties
{
//颜色
_Color("Color", Color) = (1,1,1,1)
_GradientMap("Gradient map", 2D) = "white" {}
//网格细分
_TessellationUniform ("Tessellation Uniform", Range(1, 64)) = 1
//风
_NoiseTexture("Noise texture", 2D) = "white" {}
_WindTexture("Wind texture", 2D) = "white" {}
_WindStrength("Wind strength", float) = 0
_WindSpeed("Wind speed", float) = 0
[HDR]_WindColor("Wind color", Color) = (1,1,1,1)
//位置和尺寸
_GrassHeight("Grass height", float) = 0
_PositionRandomness("Position randomness", float) = 0
_GrassWidth("Grass width", Range(0.0, 1.0)) = 1.0
//草
_GrassBlades("Grass blades per triangle", float) = 1
_MinimunGrassBlades("Minimum grass blades per triangle", float) = 1
_MaxCameraDistance("Max camera distance", float) = 10
//灯光
[Toggle(IS_LIT)]
_IsLit("Is lit", float) = 0
_RimPower("Rim power", float) = 1
[HDR]_TranslucentColor("Translucent color", Color) = (1,1,1,1)
//互动
_GrassTrample("Grass trample (XYZ -> Position, W -> Radius)", Vector) = (0,0,0,0)
_GrassTrampleOffsetAmount("Grass trample offset amount", Range(0, 1)) = 0.2
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
//Downloaded from Catlike Coding: https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
#include "CustomTessellation.cginc"
#include "Autolight.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2g
{
float4 vertex : POSITION;
};
struct g2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 col : COLOR;
float3 normal : NORMAL;
unityShadowCoord4 _ShadowCoord : TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
fixed4 _Color;
sampler2D _GradientMap;
sampler2D _NoiseTexture;
float4 _NoiseTexture_ST;
sampler2D _WindTexture;
float4 _WindTexture_ST;
float _WindStrength;
float _WindSpeed;
fixed4 _WindColor;
float _GrassHeight;
float _GrassWidth;
float _PositionRandomness;
float _GrassBlades;
float _MaxCameraDistance;
float _MinimunGrassBlades;
float4 _GrassTrample;
float _GrassTrampleOffsetAmount;
g2f GetVertex(float4 pos, float2 uv, fixed4 col, float3 normal) {
g2f o;
o.vertex = UnityObjectToClipPos(pos);
o.uv = uv;
o.viewDir = WorldSpaceViewDir(pos);
o.col = col;
o._ShadowCoord = ComputeScreenPos(o.vertex);
o.normal = UnityObjectToWorldNormal(normal);
#if UNITY_PASS_SHADOWCASTER
o.vertex = UnityApplyLinearShadowBias(o.vertex);
#endif
return o;
}
float random (float2 st) {
return frac(sin(dot(st.xy,
float2(12.9898,78.233)))*
43758.5453123);
}
v2g vert (appdata v)
{
v2g o;
o.vertex = v.vertex;
return o;
}
//3 + 3 * 15 = 48
[maxvertexcount(48)]
void geom(triangle v2g input[3], inout TriangleStream<g2f> triStream)
{
g2f o;
float3 normal = normalize(cross(input[1].vertex - input[0].vertex, input[2].vertex - input[0].vertex));
int grassBlades = ceil(lerp(_GrassBlades, _MinimunGrassBlades, saturate(distance(_WorldSpaceCameraPos, mul(unity_ObjectToWorld, input[0].vertex)) / _MaxCameraDistance)));
for (uint i = 0; i < grassBlades; i++) {
float r1 = random(mul(unity_ObjectToWorld, input[0].vertex).xz * (i + 1));
float r2 = random(mul(unity_ObjectToWorld, input[1].vertex).xz * (i + 1));
//Random barycentric coordinates from https://stackoverflow.com/a/19654424
float4 midpoint = (1 - sqrt(r1)) * input[0].vertex + (sqrt(r1) * (1 - r2)) * input[1].vertex + (sqrt(r1) * r2) * input[2].vertex;
r1 = r1 * 2.0 - 1.0;
r2 = r2 * 2.0 - 1.0;
float4 pointA = midpoint + _GrassWidth * normalize(input[i % 3].vertex - midpoint);
float4 pointB = midpoint - _GrassWidth * normalize(input[i % 3].vertex - midpoint);
float4 worldPos = mul(unity_ObjectToWorld, pointA);
float2 windTex = tex2Dlod(_WindTexture, float4(worldPos.xz * _WindTexture_ST.xy + _Time.y * _WindSpeed, 0.0, 0.0)).xy;
float2 wind = (windTex * 2.0 - 1.0) * _WindStrength;
float noise = tex2Dlod(_NoiseTexture, float4(worldPos.xz * _NoiseTexture_ST.xy, 0.0, 0.0)).x;
float heightFactor = noise * _GrassHeight;
triStream.Append(GetVertex(pointA, float2(0,0), fixed4(0,0,0,1), normal));
float4 newVertexPoint = midpoint + float4(normal, 0.0) * heightFactor + float4(r1, 0.0, r2, 0.0) * _PositionRandomness + float4(wind.x, 0.0, wind.y, 0.0);
float3 trampleDiff = mul(unity_ObjectToWorld, newVertexPoint).xyz - _GrassTrample.xyz;
float4 trampleOffset = float4(float3(normalize(trampleDiff).x, 0, normalize(trampleDiff).z) * (1.0 - saturate(length(trampleDiff) / _GrassTrample.w)) * random(worldPos), 0.0) * noise;
newVertexPoint += trampleOffset * _GrassTrampleOffsetAmount;
float3 bladeNormal = normalize(cross(pointB.xyz - pointA.xyz, midpoint.xyz - newVertexPoint.xyz));
triStream.Append(GetVertex(newVertexPoint, float2(0.5, 1), fixed4(1.0, length(windTex), 1.0, 1.0), bladeNormal));
triStream.Append(GetVertex(pointB, float2(1,0), fixed4(0,0,0,1), normal));
triStream.RestartStrip();
}
for (int i = 0; i < 3; i++) {
triStream.Append(GetVertex(input[i].vertex, float2(0,0), fixed4(0,0,0,1), normal));
}
}
ENDCG
Pass
{
Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" }
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma hull hull
#pragma domain domain
#pragma target 4.6
#pragma multi_compile_fwdbase
#pragma shader_feature IS_LIT
#include "Lighting.cginc"
float _RimPower;
fixed4 _TranslucentColor;
fixed4 frag (g2f i) : SV_Target
{
fixed4 gradientMapCol = tex2D(_GradientMap, float2(i.col.x, 0.0));
fixed4 col = (gradientMapCol + _WindColor * i.col.g) * _Color;
#ifdef IS_LIT
float light = saturate(dot(normalize(_WorldSpaceLightPos0), i.normal)) * 0.5 + 0.5;
fixed4 translucency = _TranslucentColor * saturate(dot(normalize(-_WorldSpaceLightPos0), normalize(i.viewDir)));
half rim = pow(1.0 - saturate(dot(normalize(i.viewDir), i.normal)), _RimPower);
float shadow = SHADOW_ATTENUATION(i);
col *= (light + translucency * rim * i.col.x ) * _LightColor0 * shadow + float4( ShadeSH9(float4(i.normal, 1)), 1.0) ;
#endif
return col;
}
ENDCG
}
Pass
{
Tags {
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment fragShadow
#pragma hull hull
#pragma domain domain
#pragma target 4.6
#pragma multi_compile_shadowcaster
float4 fragShadow(g2f i) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
}
3.源码 http://horse7.cn/download/grassshader.unitypackage