Realistic Animated Grass Field

Immersive grass field with advanced wind simulation, realistic blade shapes, natural color variation, and interactive camera controls.

What this code does

- Advanced Grass Geometry: 8000+ individual blades with realistic tapered shapes and natural curves created using detailed vertex manipulation.
- Multi-Layer Wind System: complex vertex shader animation with primary wind waves, secondary sway, and micro-movements for authentic motion.
- Natural Color Variation: per-blade HSV color shifting with brightness and hue variation while maintaining realistic green spectrum.
- Performance Optimization: InstancedMesh with custom attributes renders thousands of blades efficiently in a single draw call.
- Interactive Camera: orbit controls with grass-level perspective, smooth damping, and optional auto-rotation for immersive exploration.
- Realistic Physics: grass bends more at tips with subtle vertical compression during wind gusts, mimicking real grass behavior.
- Enhanced Lighting: directional and ambient lighting with subsurface scattering effects for natural grass appearance.

JavaScript (plain)

const scene = new THREE.Scene()
scene.background = new THREE.Color(0x87CEEB)
scene.fog = new THREE.Fog(0x87CEEB, 50, 200)
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
camera.position.set(0, 2, 8)
camera.lookAt(0, 1, 0)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)

renderer.shadowMap.enabled = true
document.querySelector('#app').appendChild(renderer.domElement)

// Orbit controls
const controls = new THREE.OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05
controls.minDistance = 1
controls.maxDistance = 50
controls.maxPolarAngle = Math.PI / 2.05
// Create realistic grass blade geometry
const grassGeometry = new THREE.PlaneGeometry(0.08, 1.5, 1, 8)
grassGeometry.translate(0, 0.75, 0)
// Shader material with wind animation
const grassMaterial = new THREE.ShaderMaterial({
  vertexShader: `
    attribute float phase;
    attribute float amplitude;
    uniform float time;
    uniform float windStrength;
    varying float vHeightRatio;
    void main() {
      vHeightRatio = position.y / 1.5;
      vec3 pos = position;
      float heightPower = vHeightRatio * vHeightRatio;
      float windEffect = sin(time + phase) * windStrength * heightPower;
      pos.x += windEffect * amplitude;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }`,
  fragmentShader: `
    varying float vHeightRatio;
    void main() {
      vec3 grassColor = mix(vec3(0.1, 0.3, 0.1), vec3(0.3, 0.7, 0.2), vHeightRatio);
      gl_FragColor = vec4(grassColor, 1.0);
    }`,
  uniforms: { time: { value: 0 }, windStrength: { value: 2.5 } },
  side: THREE.DoubleSide
})
// Instanced mesh for performance
const instanceCount = 8000
const grassField = new THREE.InstancedMesh(grassGeometry, grassMaterial, instanceCount)
const phases = new Float32Array(instanceCount)
const amplitudes = new Float32Array(instanceCount)
for (let i = 0; i < instanceCount; i++) {
  const matrix = new THREE.Matrix4()
  const x = (Math.random() - 0.5) * 40
  const z = (Math.random() - 0.5) * 40
  const scale = 0.8 + Math.random() * 0.4
  const rotation = Math.random() * Math.PI * 2
  matrix.makeRotationY(rotation)
  matrix.setPosition(x, 0, z)
  matrix.scale(new THREE.Vector3(scale, scale, scale))
  grassField.setMatrixAt(i, matrix)
  phases[i] = Math.random() * Math.PI * 2
  amplitudes[i] = 0.5 + Math.random() * 0.5
}
grassGeometry.setAttribute('phase', new THREE.InstancedBufferAttribute(phases, 1))
grassGeometry.setAttribute('amplitude', new THREE.InstancedBufferAttribute(amplitudes, 1))
scene.add(grassField)

// Lighting
const ambientLight = new THREE.AmbientLight(0x87CEEB, 0.3)
scene.add(ambientLight)

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
directionalLight.position.set(20, 20, 10)
scene.add(directionalLight)

// Ground
const ground = new THREE.Mesh(
  new THREE.PlaneGeometry(100, 100),
  new THREE.MeshLambertMaterial({ color: 0x2d5016 })
)
ground.rotation.x = -Math.PI / 2
scene.add(ground)