Skip to content

Vegetation Shaders

Shaders for realistic wind animation on instanced vegetation including grass, trees, and bushes.

import {
grassWindVertexShader,
treeWindVertexShader,
} from '@strata-game-library/shaders';

Wind animation for grass instances:

UniformTypeDescription
uTimefloatAnimation time
uWindSpeedfloatWind animation speed
uWindStrengthfloatWind bend amount
uWindDirectionvec3Wind direction
uWindNoiseScalefloatNoise pattern scale
uWindTurbulencefloatTurbulence intensity
import { grassWindVertexShader } from '@strata-game-library/shaders';
const grassMaterial = new THREE.ShaderMaterial({
vertexShader: grassWindVertexShader,
fragmentShader: `
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`,
uniforms: {
uTime: { value: 0 },
uWindSpeed: { value: 1 },
uWindStrength: { value: 0.3 },
uWindDirection: { value: new THREE.Vector3(1, 0, 0.5).normalize() },
uWindNoiseScale: { value: 0.05 },
uWindTurbulence: { value: 0.2 },
},
});
const grassGeometry = createGrassBladeGeometry();
const grassMesh = new THREE.InstancedMesh(grassGeometry, grassMaterial, 10000);

More complex wind for trees with branch hierarchy:

UniformTypeDescription
uTimefloatAnimation time
uWindSpeedfloatWind animation speed
uWindStrengthfloatWind bend amount
uWindDirectionvec3Wind direction
uTrunkStiffnessfloatTrunk resistance to wind
uBranchStiffnessfloatBranch resistance
uLeafStiffnessfloatLeaf resistance
import { treeWindVertexShader } from '@strata-game-library/shaders';
const treeMaterial = new THREE.ShaderMaterial({
vertexShader: treeWindVertexShader,
fragmentShader: standardFragmentShader,
uniforms: {
uTime: { value: 0 },
uWindSpeed: { value: 0.5 },
uWindStrength: { value: 0.15 },
uWindDirection: { value: new THREE.Vector3(1, 0, 0).normalize() },
uTrunkStiffness: { value: 0.9 },
uBranchStiffness: { value: 0.5 },
uLeafStiffness: { value: 0.2 },
},
});
vec3 grassWind(vec3 position, float time, vec3 windDir, float strength) {
// Height-based influence (more at top)
float heightFactor = position.y;
// Wind noise
float noise = sin(position.x * 0.5 + time) * cos(position.z * 0.5 + time * 0.7);
// Apply wind
vec3 offset = windDir * noise * strength * heightFactor;
return position + offset;
}
vec3 layeredGrassWind(vec3 position, vec3 worldPos, float time) {
float heightFactor = position.y;
// Primary wind wave
float primaryWave = sin(worldPos.x * uWindNoiseScale + time * uWindSpeed);
primaryWave += sin(worldPos.z * uWindNoiseScale * 0.7 + time * uWindSpeed * 0.8);
primaryWave *= 0.5;
// Secondary turbulence
float turbulence = sin(worldPos.x * uWindNoiseScale * 3.0 + time * uWindSpeed * 2.0);
turbulence *= uWindTurbulence;
// Combine
float totalWind = (primaryWave + turbulence) * uWindStrength * heightFactor;
vec3 windOffset = uWindDirection * totalWind;
return position + windOffset;
}
vec3 treeBranchWind(vec3 position, vec3 worldPos, float time, float stiffness) {
// Distance from trunk (using vertex color or UV)
float distanceFromTrunk = length(position.xz);
// Reduce effect near trunk
float influence = smoothstep(0.0, 1.0, distanceFromTrunk) * (1.0 - stiffness);
// Multi-frequency wind
float wind = sin(worldPos.x * 0.1 + time * uWindSpeed) * 0.5;
wind += sin(worldPos.z * 0.15 + time * uWindSpeed * 0.7) * 0.3;
wind += sin(time * uWindSpeed * 2.0 + distanceFromTrunk) * 0.2; // Branch flutter
vec3 offset = uWindDirection * wind * uWindStrength * influence;
return position + offset;
}
vec3 leafFlutter(vec3 position, vec3 normal, float time, float leafStiffness) {
// Random flutter per vertex
float flutter = sin(time * 10.0 + position.x * 50.0) *
cos(time * 8.0 + position.z * 50.0);
flutter *= (1.0 - leafStiffness);
// Flutter along normal
vec3 offset = normal * flutter * 0.02;
return position + offset;
}
// In vertex shader with instanceMatrix
attribute vec3 instanceOffset; // Per-instance world position
void main() {
// Get instance world position
vec3 worldPos = (instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
// Apply wind based on world position
vec3 windedPosition = layeredGrassWind(position, worldPos, uTime);
// Transform
vec4 mvPosition = modelViewMatrix * instanceMatrix * vec4(windedPosition, 1.0);
gl_Position = projectionMatrix * mvPosition;
}

Support for multiple wind sources:

uniform vec3 uWindZones[4]; // Position
uniform float uWindZoneStrength[4]; // Strength
uniform float uWindZoneRadius[4]; // Radius
vec3 calculateWindZones(vec3 worldPos) {
vec3 totalWind = uWindDirection * uWindStrength; // Base wind
for (int i = 0; i < 4; i++) {
float dist = length(worldPos - uWindZones[i]);
float influence = 1.0 - smoothstep(0.0, uWindZoneRadius[i], dist);
vec3 zoneDir = normalize(worldPos - uWindZones[i]);
totalWind += zoneDir * uWindZoneStrength[i] * influence;
}
return totalWind;
}