Shader "ProceduralGrass/Grass" { Properties { [Header(Grass Properties)] _BaseMap("Albedo Texture", 2D) = "white" {} _TaperAmount("Taper Tip Amount", Range(0.0, 1.0)) = 0 _TaperInverseAmount("Taper Base Amount", Range(0.0, 1.0)) = 0 _BlendSurfaceNormalDistLower("Normal Blending Distance Min", Float) = 10 _BlendSurfaceNormalDistUpper("Normal Blending Distance Max", Float) = 20 [Header(Wind)] [Toggle(_StaticWind)] _StaticWind("Static Wind", Float) = 0 _WindAmplitude("Wind Amplitude", Float) = 2 _WindSpeed("Wind Speed", Float) = 100 _WindPower("Wind Power", Range(0.0, 1.0)) = 1 _SinOffset("Wind Sin Offset", Range(0.0, 3.0)) = 1 _PushTipOscillationForward("Tip Forward", Float) = 1 [Header(Illumination)] [Toggle(_Unlit)] _Unlit("_Unlit", Float) = 0 _Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5 _Metallic("Metallic", Range(0.0, 1.0)) = 0.0 _CurvedNormalDegrees("Grass Normal Curving Degrees", Float) = 0 _AmbientStrength("Ambient Strength", Float) = 1 _ColorAlbedoStrength("Color Albedo Strength", Range(0.0, 1.0)) = 0.9 [ToggleOff] _SpecularHighlights("Regular Specular Highlights", Float) = 0.0 [Toggle(_WhiteAmbient)] _WhiteAmbient("White Ambient", Float) = 1 [Toggle(_ExtraSpecular)] _ExtraSpecular("Extra Specular", Float) = 1 [Toggle(_ExtraSpecularMonoColor)] _ExtraSpecularMonoColor("Extra Specular Single Color", Float) = 0 _ExtraSpecStrength("Extra Specular Strength", Float) = 3 _ExtraSpecColor("Extra Specular Color", Color) = (1, 1, 1) _ExtraSpecMap("Extra Specular Texture", 2D) = "white" {} _RenderFaces("Face Rendering", Float) = 0.0 // TODO UI [Toggle] _ReceiveShadows("Receive Shadows", Float) = 1.0 // TODO [HideInInspector] _GrassBladesPerPoint ("Grass Blades Per Point", Int) = 1 [HideInInspector] _VerticesPerBlade ("Vertices Per Blade", Int) = 0 [HideInInspector] _Exponent ("Vertices Per Blade", Int) = 0.6 } SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "UniversalMaterialType" = "Lit" "IgnoreProjector" = "True" } Pass { Name "ForwardLit" Tags { "LightMode" = "UniversalForward" } Cull[_RenderFaces] HLSLPROGRAM #pragma vertex vert #pragma fragment frag #define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs #include "UnityIndirect.cginc" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF // ------------------------------------- // Universal Pipeline keywords #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES #pragma shader_feature _Unlit #pragma shader_feature _WhiteAmbient #pragma shader_feature _ExtraSpecular #pragma shader_feature _ExtraSpecularMonoColor #pragma shader_feature _StaticWind struct v2f { float4 pos : SV_POSITION; float4 color : TEXCOORD1; float2 uv : TEXCOORD2; float3 normal : TEXCOORD3; float3 worldPos : TEXCOORD4; }; struct GrassBlade { float3 position; float height; float width; float rotationAngle; float rotationAngleCurrent; float hash; float tilt; float bend; float windStrength; float3 surfaceNorm; float clumpBaseToSecondaryRatio; }; struct GrassInteractorPoint { float3 position; float radius; float force; }; StructuredBuffer _GrassBlades; StructuredBuffer _GrassInteractorPoints; StructuredBuffer _DefaultColors; StructuredBuffer _SecondaryColors; uint _GrassBladesPerPoint; uint _VerticesPerBlade; uint _GrassSegments; float _WindAmplitude; float _WindSpeed; float _WindPower; float _SinOffset; float _PushTipOscillationForward; float _TaperAmount; float _TaperInverseAmount; float _BlendSurfaceNormalDistUpper; float _BlendSurfaceNormalDistLower; float4 _ExtraSpecColor; float _Metallic; float _Smoothness; float _AmbientStrength; float _ExtraSpecStrength; float _CurvedNormalDegrees; float _Exponent; float _ColorAlbedoStrength; float _MaxInteractors; float3 _WSpaceCameraPos; sampler2D _BaseMap; sampler2D _ExtraSpecMap; float3x3 AngleAxis3x3(float angle, float3 axis) //Returns a Rotation matrix of a angle in a axis. { float c, s; sincos(angle, s, c); float t = 1 - c; float x = axis.x; float y = axis.y; float z = axis.z; return float3x3( t * x * x + c, t * x * y - s * z, t * x * z + s * y, t * x * y + s * z, t * y * y + c, t * y * z - s * x, t * x * z - s * y, t * y * z + s * x, t * z * z + c ); } float3 cubicBezier(float3 p0, float3 p1, float3 p2, float3 p3, float t ){ // Returns the position of a point of a bezier curve t (0-1 of the curve) of 4 points. float3 a = lerp(p0, p1, t); float3 b = lerp(p2, p3, t); float3 c = lerp(p1, p2, t); float3 d = lerp(a, c, t); float3 e = lerp(c, b, t); return lerp(d,e,t); } float3 bezierTangent(float3 p0, float3 p1, float3 p2, float3 p3, float t ){ float omt = 1-t; float omt2 = omt*omt; float t2= t*t; float3 tangent = p0* (-omt2) + p1 * (3 * omt2 - 2 *omt) + p2 * (-3 * t2 + 2 * t) + p3 * (t2); return normalize(tangent); } v2f vert(uint svVertexID: SV_VertexID, uint svInstanceID : SV_InstanceID) // asumes only one command. error otherwise. { // Init Blade InitIndirectDrawArgs(0); //this might be useless, was in documentation v2f o; float3 pos; uint bladeIndex = svInstanceID * _GrassBladesPerPoint + (svVertexID/_VerticesPerBlade); uint vertexBladeIndex = svVertexID % _VerticesPerBlade; GrassBlade blade = _GrassBlades[bladeIndex]; // Calculate Exponential vertex height float linearValue = (vertexBladeIndex/2)/(float)(_GrassSegments); float exponentialValue = pow(linearValue, _Exponent); float vertexHeight0_1 = exponentialValue; //Calculate Bezier Points float tilt = blade.tilt; bool continueProcessing = true; // Tilt depending on interactors for (int i = 0; i < _MaxInteractors && continueProcessing; ++i) { GrassInteractorPoint interactor = _GrassInteractorPoints[i]; // Check if the interactor has a non-zero radius if (interactor.radius > 0.0) { float distance = length(blade.position - interactor.position); if (distance <= interactor.radius) { // Calculate a linear falloff factor based on distance float falloffFactor = 1.0 - (distance / interactor.radius); // Apply the adjusted force based on falloff tilt += interactor.force * falloffFactor; } } else { // If radius is 0, set continueProcessing to false to break out of the loop continueProcessing = false; } } if (tilt >= 1) tilt = 1; float height = blade.height; float bend = blade.bend; float p3x = tilt * height; float p3y = height - tilt * height; float3 p3 = float3(p3x, p3y, 0); float2 bladeDir = normalize(p3); float3 bezCtrlOffsetDir = normalize(float3(-bladeDir.y, bladeDir.x,0)); // Direction of bend, based on tilt. float3 p0 = float3(0,0,0); // blade origin float3 p1 = 0.33 * p3; // same direction as p3, smaller values float3 p2 = 0.66 * p3; // same direction as p3, smaller values p1 += bezCtrlOffsetDir * bend; // Might Add Flexibility, so p1 and p2 have different bends. p2 += bezCtrlOffsetDir * bend; float p1Weight = 0.33; float p2Weight = 0.66; float p3Weight = 1; float hash= blade.hash; float mult = 1-bend; float windStrength = blade.windStrength; #ifdef _StaticWind float p1ffset = pow(p1Weight,_WindPower)*(_WindAmplitude/100) * sin((_Time+hash*2*3.1415)*_WindSpeed +p1Weight*2*3.1415*_SinOffset); float p2ffset = pow(p2Weight,_WindPower)*(_WindAmplitude/100) * sin((_Time+hash*2*3.1415)*_WindSpeed +p2Weight*2*3.1415*_SinOffset); float p3ffset = pow(p3Weight,_WindPower)*(_WindAmplitude/100) * sin((_Time+hash*2*3.1415)*_WindSpeed +p3Weight*2*3.1415*_SinOffset); #else float p1ffset = pow(p1Weight,_WindPower)*(_WindAmplitude/100) * sin((_Time+hash*2*3.1415)*_WindSpeed +p1Weight*2*3.1415*_SinOffset) * windStrength; float p2ffset = pow(p2Weight,_WindPower)*(_WindAmplitude/100) * sin((_Time+hash*2*3.1415)*_WindSpeed +p2Weight*2*3.1415*_SinOffset) * windStrength; float p3ffset = pow(p3Weight,_WindPower)*(_WindAmplitude/100) * sin((_Time+hash*2*3.1415)*_WindSpeed +p3Weight*2*3.1415*_SinOffset) * windStrength; #endif p3ffset = (p3ffset) - _PushTipOscillationForward*mult*(pow(p3Weight,_WindPower)*_WindAmplitude/100)/2; p1 += bezCtrlOffsetDir* p1ffset; p2 += bezCtrlOffsetDir* p2ffset; p3 += bezCtrlOffsetDir* p3ffset; float width = (blade.width) * (1-_TaperAmount*vertexHeight0_1); width = width * (1 - _TaperInverseAmount * (1 - vertexHeight0_1)); float3 vertexPos = cubicBezier(p0,p1,p2,p3,vertexHeight0_1); pos = vertexPos; if (vertexBladeIndex % 2 == 0){ pos.z += width/2; }else{ pos.z -= width/2; } o.uv = float2((vertexBladeIndex+1) % 2,vertexHeight0_1); if(vertexBladeIndex == _VerticesPerBlade-1){ pos.z = 0; o.uv.x = 0.5f; } float3x3 rotMatrix = AngleAxis3x3(radians(blade.rotationAngleCurrent), float3(0,1,0)); float3x3 rotMatrixNormalRight = AngleAxis3x3(radians(-blade.rotationAngleCurrent)+_CurvedNormalDegrees, float3(0,1,0)); float3x3 rotMatrixNormalLeft = AngleAxis3x3(radians(-blade.rotationAngleCurrent)-_CurvedNormalDegrees, float3(0,1,0)); pos = mul(rotMatrix, pos); pos += blade.position; o.worldPos = pos; o.pos = mul(UNITY_MATRIX_VP, float4(pos,1)); o.color = lerp(_DefaultColors[vertexBladeIndex/2],_SecondaryColors[vertexBladeIndex/2],blade.clumpBaseToSecondaryRatio); float3 tangent = normalize(bezierTangent(p0, p1,p2,p3, vertexHeight0_1)); float3 curvedNormal = normalize(cross(tangent, float3(0,0,-1))) ; float3 surfaceNorm = blade.surfaceNorm; if (vertexBladeIndex % 2 == 0){ curvedNormal = mul(curvedNormal,rotMatrixNormalRight); }else{ curvedNormal = mul(curvedNormal,rotMatrixNormalLeft); } float distToCam = distance(pos, _WSpaceCameraPos); float surfaceNormalBlendSmoothstep = smoothstep(_BlendSurfaceNormalDistLower,_BlendSurfaceNormalDistUpper, distToCam); o.normal = normalize(lerp(curvedNormal, surfaceNorm,surfaceNormalBlendSmoothstep)); return o; } float4 frag(v2f i, float facing : VFACE) : SV_Target { #ifdef _Unlit return i.color * tex2D(_BaseMap, i.uv); #endif InputData inputData; inputData.positionWS = i.worldPos; inputData.positionCS = i.pos; inputData.normalWS = i.normal; inputData.viewDirectionWS = normalize(_WSpaceCameraPos - i.worldPos); inputData.shadowCoord = TransformWorldToShadowCoord(inputData.positionWS); inputData.fogCoord = 0.0; inputData.vertexLighting = half3(0, 0, 0); inputData.bakedGI = half3(0, 0, 0); inputData.normalizedScreenSpaceUV = float2(0, 0); inputData.shadowMask = half4(0, 0, 0, 0); inputData.tangentToWorld = half3x3(1, 0, 0, 0, 1, 0, 0, 0, 1); SurfaceData surfaceData; surfaceData.albedo = lerp(float4(1, 1, 1, 1), i.color, _ColorAlbedoStrength) * tex2D(_BaseMap, i.uv); surfaceData.specular = float4(1,1,1,1); surfaceData.metallic = _Metallic; surfaceData.smoothness = _Smoothness; surfaceData.normalTS = half3(0, 0, 1); surfaceData.emission = half3(0, 0, 0); surfaceData.occlusion = 0.0; surfaceData.alpha = 1.0; surfaceData.clearCoatMask = 0.0; surfaceData.clearCoatSmoothness = 0.0; if(facing == 0){ inputData.normalWS = -inputData.normalWS; } float4 ambientColor = float4(0,0,0,1); #ifdef _WhiteAmbient ambientColor.rgb = max(unity_SHAr.w, max(unity_SHAg.w,unity_SHAb.w)); #else ambientColor = half4(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w,1); #endif ambientColor = ambientColor * i.color * tex2D(_BaseMap, i.uv) * _AmbientStrength; #ifdef _ExtraSpecular float gloss = tex2D(_ExtraSpecMap, i.uv); AmbientOcclusionFactor aoFactor = CreateAmbientOcclusionFactor(inputData, surfaceData); Light mainLight = GetMainLight(inputData, inputData.shadowMask, aoFactor); float3 l = normalize(mainLight.direction); float3 r = normalize(reflect(-l,i.normal)); float3 r2 = normalize(reflect(-l,-i.normal)); float3 v = normalize(_WSpaceCameraPos - i.worldPos); float shininess = lerp(0, 1, gloss); float spec = saturate(dot(r,v)) * shininess * _ExtraSpecStrength; float4 specColor = float4(0,0,0,1); #ifdef _ExtraSpecularMonoColor specColor = (spec * _ExtraSpecColor * tex2D(_BaseMap, i.uv)); #else specColor = (spec * i.color * tex2D(_BaseMap, i.uv)); #endif half4 finalColor = max(UniversalFragmentPBR(inputData, surfaceData),ambientColor); if (any(finalColor.rgb != ambientColor.rgb)) { finalColor += finalColor * specColor; } return finalColor; #endif return max(UniversalFragmentPBR(inputData, surfaceData),ambientColor); } ENDHLSL } } }