subsurface scattering foliage with trampling and wind

set the TramplePos global to your player position

 

subsurface based on: https://godotshaders.com/shader/simple-foliage-subsurface-scattering-shader/

 

quick fix 2/262/2026:

changed height mask to uv space because i forgot im using world vertex coords and changed opacity texture to filter_nearest instead of filter_nearest_mipmap

Shader code
shader_type spatial;
render_mode cull_disabled, world_vertex_coords;

global uniform vec3 TramplePos;

uniform float TrampleFalloff = 2.0;
uniform float TrampleRadius = 1.0;
uniform float TrampleStrength = 1.5;

uniform sampler2D Tex : source_color, filter_nearest;
uniform sampler2D NormalTex : hint_normal, filter_nearest;
uniform sampler2D OpacityTex: filter_nearest;

uniform sampler2D ThicknessTex;
uniform bool UseThicknessMap = false;

uniform float Sigma : hint_range(0.,1.,.01) = .5;
uniform float Power : hint_range(0.,100.,.25) = 5.;
uniform float Scale : hint_range(0.,50.,.1) = 3.3;

uniform bool UseOpacity = true;
uniform float AlphaCutoff : hint_range(0.,1.,.01) = .1;
uniform float AttenuationInfluence : hint_range(0.,1.,.01) = 1.;

uniform float AlbedoInfluence : hint_range(0.,1.,.01) = .7;
uniform float LightColorInfluence : hint_range(0.,1.,.01) = .3;

uniform bool DoVertexSway = true;
uniform float VertexSwayIntensity : hint_range(0.0, 10.0, 0.01) = .3;


uniform float GustScale = 0.05;          // size of gust waves
uniform float GustSpeed = 0.8;           // how fast gust travels
uniform float GustStrength = 1.5;        // intensity multiplier
uniform float GustFrequency = 0.5;       // how often gust pulses
uniform vec2  WindDirection = vec2(1.0, 0.3); // world wind direction


varying vec4 VtxColor;
varying vec3 NormalWs;
varying mat3 Tbn;


float Hash(vec2 P)
{
    P = fract(P * vec2(123.34, 345.45));
    P += dot(P, P + 34.345);
    return fract(P.x * P.y);
}

float Noise(vec2 P)
{
    vec2 I = floor(P);
    vec2 F = fract(P);

    float A = Hash(I);
    float B = Hash(I + vec2(1.0, 0.0));
    float C = Hash(I + vec2(0.0, 1.0));
    float D = Hash(I + vec2(1.0, 1.0));

    vec2 U = F * F * (3.0 - 2.0 * F);

    return mix(A, B, U.x) +
           (C - A) * U.y * (1.0 - U.x) +
           (D - B) * U.x * U.y;
}

float GetSss(vec3 N, vec3 V, vec3 L, float S)
{
    L = normalize(L);
    float Ksss = pow(clamp(dot((-L + N * S), V), 0., 1.), Power);
    return Ksss;
}

void vertex()
{
    VtxColor = COLOR;
    Tbn = mat3(TANGENT, BINORMAL, NORMAL);

    if (DoVertexSway)
    {
        float HeightMask = pow(clamp( 1.0 - UV.y, 0.0, 1.0),1.5);

		vec2 Dir = normalize(WindDirection);

		vec2 NoiseUV = VERTEX.xz * GustScale +
               Dir * TIME * GustSpeed;

		float GustNoise =
    		Noise(NoiseUV) * 0.6 +
    		Noise(NoiseUV * 2.0) * 0.3 +
    		Noise(NoiseUV * 4.0) * 0.1;
		GustNoise = GustNoise * 2.0 - 1.0;

		float Micro = sin(TIME * 2.0 + VERTEX.x * 0.3) * 0.2;

		vec2 WindForce = Dir * GustNoise * GustStrength +
                 vec2(Micro, Micro * 0.5);

		VERTEX.xz += WindForce * HeightMask * VertexSwayIntensity;

        float Dist = distance(VERTEX, TramplePos);

        if (Dist < TrampleRadius)
        {
            float Influence = 1.0 - (Dist / TrampleRadius);
            Influence = pow(Influence, TrampleFalloff);
            Influence *= HeightMask;

           
            
             vec3 Dir = normalize(VERTEX - TramplePos);
             Dir.y = 0.0;
             Dir = normalize(Dir);

             float DownAmount = Influence * TrampleStrength;
             float SideAmount = Influence * TrampleStrength * 0.5;

             VERTEX.y -= DownAmount;
             VERTEX += Dir * SideAmount;
            
        }
    }
}

void fragment()
{
    vec4 C = texture(Tex, UV);
    float O = (UseOpacity) ? texture(OpacityTex, UV).r : C.a;

    if (O < AlphaCutoff)
        discard;

    ALBEDO = C.rgb;

    vec3 N = (1. - texture(NormalTex, UV).rgb) * 2. - 1.;
    N = normalize(Tbn * N);
    NormalWs = N;
}

void light()
{
    vec3 N2 = normalize(vec3(0, 1, 0) + .5 * NormalWs);
    float Kd = max(dot(normalize(LIGHT), N2), 0.0);

    float S = (UseThicknessMap) ? 1. - texture(ThicknessTex, UV).r : Sigma;
    float Ksss = GetSss(NormalWs, VIEW, LIGHT, S);

    vec3 SssColor = AlbedoInfluence * ALBEDO +
                    LightColorInfluence * LIGHT_COLOR;

    DIFFUSE_LIGHT += SssColor *
        (max(mix(1., ATTENUATION, AttenuationInfluence), 0.001) *
        (.3 + max(Kd * Scale, Ksss * Scale)));
}
Live Preview
Tags
foliage, grass
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments