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