unity-geometry-grass-shader
unity-geometry-grass-shader copied to clipboard
I converted the shader to support urp, i hope this helps
Shader "Custom/URP/GeometryGrassShader"
{
Properties
{
_TranslucentGain("Translucent Gain", Range(0,1)) = 0.5
_GroundTexture ("Ground Texture", 2D) = "white" {}
_DisplacementTexture("Displacement Texture", 2D) = "grey" {}
_DisplacementFactor("Displacement Factor", Float) = 2
_GrassMask("Grass Mask", 2D) = "white" {}
_GrassMaskThreshold("Mask Threshold", Range(0,1)) = 0.1
_BendRotationRandom("Bend Rotation Random", Range(0, 1)) = 0.2
_BladeWidth("Blade Width", Float) = 0.05
_BladeWidthRandom("Blade Width Random", Float) = 0.02
_BladeHeight("Blade Height", Float) = 0.5
_BladeHeightRandom("Blade Height Random", Float) = 0.3
_WindDistortionMap("Wind Distortion Map", 2D) = "white" {}
_WindFrequency("Wind Frequency", Vector) = (0.05, 0.05, 0, 0)
_WindStrength("Wind Strength", Float) = 1
_BladeForward("Blade Forward Amount", Float) = 0.38
_BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2
_DisplacementLocation("Displacement Location", Vector) = (0,0,0,0)
_DisplacementSize("Displacement Size", Float) = 1
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Geometry"
"RenderType" = "Opaque"
}
LOD 300
Cull Off
HLSLINCLUDE
#pragma target 4.6
#pragma require geometry
// URP includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
// Common.hlsl provides PI/HALF_PI/TWO_PI (fixes UNITY_PI issues on some versions)
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
// Fallback defines if UNITY_* constants are missing in this URP version
#ifndef UNITY_PI
#define UNITY_PI PI
#endif
#ifndef UNITY_TWO_PI
#define UNITY_TWO_PI TWO_PI
#endif
#ifndef UNITY_HALF_PI
#define UNITY_HALF_PI HALF_PI
#endif
#define BLADE_SEGMENTS 3
// SRP Batcher material CBUFFER
CBUFFER_START(UnityPerMaterial)
float _TranslucentGain;
float _DisplacementFactor;
float4 _DisplacementLocation; // xyz used
float _DisplacementSize;
float _GrassMaskThreshold;
float _BendRotationRandom;
float _BladeWidth;
float _BladeWidthRandom;
float _BladeHeight;
float _BladeHeightRandom;
float2 _WindFrequency;
float _WindStrength;
float _BladeForward;
float _BladeCurve;
CBUFFER_END
TEXTURE2D(_GroundTexture); SAMPLER(sampler_GroundTexture);
TEXTURE2D(_DisplacementTexture); SAMPLER(sampler_DisplacementTexture);
TEXTURE2D(_GrassMask); SAMPLER(sampler_GrassMask);
TEXTURE2D(_WindDistortionMap); SAMPLER(sampler_WindDistortionMap);
float4 _WindDistortionMap_ST;
struct Attributes
{
float3 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float3 normalWS : TEXCOORD0;
float4 uvPack : TEXCOORD1; // x=bladeU, y=bladeV, z=baseUV.x, w=baseUV.y
float3 positionWS : TEXCOORD2;
float3 viewDirWS : TEXCOORD3;
float4 shadowCoord: TEXCOORD4;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
// Simple random
float rand(float3 co)
{
return frac(sin(dot(co, float3(12.9898, 78.233, 45.5432))) * 43758.5453);
}
float3x3 AngleAxis3x3(float angle, float3 axis)
{
float s, c;
sincos(angle, s, c);
float t = 1 - c;
float x = axis.x, y = axis.y, 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
);
}
Varyings Vert(Attributes v)
{
Varyings o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
float3 positionWS = TransformObjectToWorld(v.positionOS);
float3 normalWS = TransformObjectToWorldNormal(v.normalOS);
o.positionCS = TransformWorldToHClip(positionWS);
o.positionWS = positionWS;
o.normalWS = normalWS;
// pack base mesh UV in zw, blade UV will be set in GS
o.uvPack = float4(0, 0, v.uv.xy);
o.viewDirWS = GetWorldSpaceViewDir(positionWS);
o.shadowCoord = TransformWorldToShadowCoord(positionWS);
return o;
}
Varyings MakeVaryings(float3 posWS, float3 nrmWS, float2 bladeUV, float2 baseUV)
{
Varyings o;
o.positionWS = posWS;
o.normalWS = nrmWS;
o.positionCS = TransformWorldToHClip(posWS);
o.uvPack = float4(bladeUV, baseUV);
o.viewDirWS = GetWorldSpaceViewDir(posWS);
o.shadowCoord = TransformWorldToShadowCoord(posWS);
return o;
}
Varyings GenerateGrassVertexWS(
float3 baseWS, float3x3 transformWS,
float width, float height, float forward,
float2 bladeUV, float2 baseUV,
float3 tangentWS, float3 bitangentWS, float3 normalWS)
{
float3 tangentPoint = float3(width, forward, height);
float3 tangentNormal = normalize(float3(0, -1, forward)); // matches original
float3 localNormalWS = mul(transformWS, tangentNormal);
float3 localPosWS = baseWS + mul(transformWS, tangentPoint);
return MakeVaryings(localPosWS, localNormalWS, bladeUV, baseUV);
}
[maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
void Geo(triangle Varyings IN[3], inout TriangleStream<Varyings> triStream)
{
// Use first vertex as seed (same as original)
float3 posWS = IN[0].positionWS;
// Build an orthonormal frame from normal
float3 nWS = normalize(IN[0].normalWS);
float3 tWS = normalize(any(nWS.xyz != 0) ? cross(float3(0,1,0), nWS) : float3(1,0,0));
float3 bWS = normalize(cross(nWS, tWS));
float2 baseUV = IN[0].uvPack.zw;
// Wind
float2 windUV = posWS.xz * _WindDistortionMap_ST.xy + _WindDistortionMap_ST.zw + _WindFrequency * _Time.y;
float2 windSample = (SAMPLE_TEXTURE2D_LOD(_WindDistortionMap, sampler_WindDistortionMap, windUV, 0).xy * 2.0h - 1.0h) * _WindStrength;
float3 windAxis = normalize(float3(windSample.x, windSample.y, 0));
float windAngle = UNITY_PI * length(windSample);
float3x3 windRotation = AngleAxis3x3(windAngle, windAxis);
// Displacement influence
float2 dispUVNorm = (posWS.xz - _DisplacementLocation.xz) / max(_DisplacementSize, 1e-4);
float2 dispFetch = dispUVNorm;
// clamp-mask trick from original
float2 dispMaskUv = max(saturate(dispFetch.xy), saturate(1.0 - dispFetch.xy));
float dispMask = floor(max(dispMaskUv.x, dispMaskUv.y));
float2 dispSample = lerp((SAMPLE_TEXTURE2D_LOD(_DisplacementTexture, sampler_DisplacementTexture, dispFetch.xy, 0).xz - 0.5), float2(0.001, 0.001), dispMask);
float3 dispAxis = normalize(float3(dispSample.x, dispSample.y, 0));
float dispAngle = -_DisplacementFactor * abs(dispSample.x + dispSample.y);
float3x3 dispRotation = AngleAxis3x3(dispAngle, dispAxis);
// Random facing/bend
float3 seedA = float3(posWS.x, posWS.y, posWS.z);
float3x3 facingRotation = AngleAxis3x3(rand(seedA) * UNITY_TWO_PI, float3(0,0,1));
float3x3 bendRotation = AngleAxis3x3(rand(seedA.zyx) * _BendRotationRandom * UNITY_HALF_PI, float3(-1,0,0));
// Tangent-to-local in WS using orthonormal frame
float3x3 TBN = float3x3(tWS, bWS, nWS);
float3x3 baseFacing = mul(TBN, facingRotation);
float3x3 fullXform = mul(mul(mul(TBN, mul(windRotation, dispRotation)), facingRotation), bendRotation);
// Mask sampling to decide blade presence
float mask = SAMPLE_TEXTURE2D_LOD(_GrassMask, sampler_GrassMask, baseUV, 0).r;
float height = ((rand(float3(posWS.z, posWS.y, posWS.x)) * 2 - 1) * _BladeHeightRandom + _BladeHeight) * mask;
float width = ((rand(float3(posWS.x, posWS.z, posWS.y)) * 2 - 1) * _BladeWidthRandom + _BladeWidth) * mask;
float forward = rand(float3(posWS.y, posWS.y, posWS.z)) * _BladeForward;
int segments = mask > _GrassMaskThreshold ? BLADE_SEGMENTS : 0;
[loop]
for (int i = 0; i < segments; i++)
{
float t = (float)i / (float)BLADE_SEGMENTS;
float segH = height * t;
float segW = width * (1 - t);
float segF = pow(t, _BladeCurve) * forward;
float3x3 useXform = (i == 0) ? baseFacing : fullXform;
triStream.Append(GenerateGrassVertexWS(posWS, useXform, +segW, segH, segF, float2(0, t), baseUV, tWS, bWS, nWS));
triStream.Append(GenerateGrassVertexWS(posWS, useXform, -segW, segH, segF, float2(1, t), baseUV, tWS, bWS, nWS));
}
if (segments > 0)
{
triStream.Append(GenerateGrassVertexWS(posWS, fullXform, 0, height, forward, float2(0.5, 1), baseUV, tWS, bWS, nWS));
}
}
float3 EvaluateLighting(float3 nWS, float3 posWS, float4 shadowCoord)
{
// Main light
Light mainLight = GetMainLight(shadowCoord);
float NdotL = saturate(dot(nWS, mainLight.direction));
NdotL = saturate(NdotL + _TranslucentGain);
float shadow = mainLight.shadowAttenuation;
float3 main = NdotL * shadow * mainLight.color;
// Ambient/SH
float3 sh = SampleSH(nWS);
return main + sh;
}
ENDHLSL
// Universal Forward (base)
Pass
{
Name "ForwardBase"
Tags { "LightMode"="UniversalForward" }
Cull Off
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma vertex Vert
#pragma geometry Geo
#pragma fragment Frag
// Lighting variants
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile_fragment _ _SHADOWS_SOFT
#pragma multi_compile_fog
float4 Frag(Varyings i) : SV_Target
{
float3 nWS = normalize(i.normalWS);
float3 lighting = EvaluateLighting(nWS, i.positionWS, i.shadowCoord);
float4 albedo = SAMPLE_TEXTURE2D(_GroundTexture, sampler_GroundTexture, i.uvPack.zw);
float3 col = albedo.rgb * lighting;
return float4(col, albedo.a);
}
ENDHLSL
}
// Forward Add (per-light additive)
Pass
{
Name "ForwardAdd"
Tags { "LightMode"="UniversalForwardAdd" }
Blend One One
Cull Off
ZWrite Off
ZTest LEqual
HLSLPROGRAM
#pragma vertex Vert
#pragma geometry Geo
#pragma fragment FragAdd
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile_fragment _ _SHADOWS_SOFT
float4 FragAdd(Varyings i) : SV_Target
{
float3 nWS = normalize(i.normalWS);
float3 posWS = i.positionWS;
uint lightCount = GetAdditionalLightsCount();
float3 add = 0;
[loop]
for (uint li = 0; li < lightCount; li++)
{
Light l = GetAdditionalLight(li, posWS);
float ndl = saturate(dot(nWS, l.direction));
float3 c = ndl * l.color * l.distanceAttenuation * l.shadowAttenuation;
add += c;
}
float3 albedo = SAMPLE_TEXTURE2D(_GroundTexture, sampler_GroundTexture, i.uvPack.zw).rgb;
return float4(add * albedo, 1);
}
ENDHLSL
}
// ShadowCaster (no external include; depth-only)
Pass
{
Name "ShadowCaster"
Tags { "LightMode"="ShadowCaster" }
Cull Off
ZWrite On
ZTest LEqual
ColorMask 0
HLSLPROGRAM
#pragma vertex Vert
#pragma geometry Geo
#pragma fragment FragShadow
float4 FragShadow(Varyings i) : SV_Target
{
// Depth-only; returning 0 writes depth via SV_Position
return 0;
}
ENDHLSL
}
}
FallBack Off
}
That's sick! Thank you so much. Do you mind if I add this to the repository once I've got time? I'll add a credit link to your GH in the header.
That's sick! Thank you so much. Do you mind if I add this to the repository once I've got time? I'll add a credit link to your GH in the header.
yes sure, that will help