vGrassUv = uv; vec4 worldPos = modelMatrix * instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0); float gx = worldPos.x; float gz = worldPos.z; vec2 mainWindDir = normalize(vec2(1.0, 0.6)); float windSpeed = 1.5; float windTime = uTime * windSpeed; vec2 windSamplePos = (worldPos.xz * 0.05) - (mainWindDir * windTime * 0.2); float windBase = fbm(windSamplePos * 0.8) * 0.4 + 0.2; float gustNoise = fbm(windSamplePos * 0.4); float gust = pow(gustNoise, 3.0) * 1.8; float totalWind = windBase + gust; float phase = gx * 0.5 + gz * 0.3; float spring = sin(uTime * 2.0 + phase) * 0.06 + sin(uTime * 4.5 + phase * 1.5) * 0.03; float angleNoise = fbm(windSamplePos * 2.0 + uTime * 0.1) - 0.5; vec2 windDir = normalize(mainWindDir + vec2(-mainWindDir.y, mainWindDir.x) * angleNoise * 0.4); // taper (fade) float taperFactor = uv.y * uv.y * uv.y; float taper = 1.0 - taperFactor * 0.85; transformed.x *= taper; transformed.z *= taper; // curve float curveVal = fbm(vec2(gx, gz) * 0.5); float curveStrength = 1.5 + curveVal * 2.5; float curveAmount = uv.y * uv.y * curveStrength; vec2 curveDir = normalize(vec2(curveVal, fbm(vec2(gz, gx))) - 0.5); transformed.x += curveAmount * curveDir.x * 0.4; transformed.z += curveAmount * curveDir.y * 0.4; // sway float swayAmount = (totalWind + spring) * uv.y * uv.y; transformed.x += swayAmount * windDir.x; transformed.z += swayAmount * windDir.y; transformed.y -= abs(swayAmount) * 0.2; // normal comp vec2 totalBend = curveDir * curveAmount * 0.4 + windDir * swayAmount; float bendMag = length(totalBend); vec3 bentNormal = normalize(vec3(-totalBend.x * 0.5, 1.0, -totalBend.y * 0.5)); // normal mix objectNormal = normalize(mix(vec3(0.0, 1.0, 0.0), bentNormal, uv.y)); vWorldPos = (modelMatrix * instanceMatrix * vec4(transformed, 1.0)).xyz;