Files
Emberwild/shaders/grass.gdshader
Paul Bailey 12f7fd677d M0.5: detailed scenery, mountain/sea-cliff boundary, Godot 4.7
Scenery (all procedural, seeded, no imported assets):
- Terrain shader: grass/dry grass/dirt/sand/rock strata/snow + detail normals
- 5 tree types, faceted mossy rocks, cliff outcrops, sea stacks, bushes,
  logs and stumps, batched as MultiMesh per 48 m cell
- Wind-animated grass and flowers streamed in tiles around the player
- Lake (depth-shaded water with shore foam), ocean to the horizon,
  cloud sky shader, volumetric fog, glow, softer shadows
- Swimming: float at the surface, swim-jump to climb out

World bounds:
- Steep mountain rim with an invisible wall partway up; one side opens to
  a sheer sea cliff with the wall 1 m inside the edge; whole chunk is an
  island so the mesh edge is never visible
- Respawn safety net if the player leaves the walkable area or the map
- Fix: the cliff edge now tracks the wall in the headlands (it used to
  drop away inside the wall, leaving a pit with lake water hanging over it)

Tests: tests/test_boundary.gd (24-direction escape attempt) and
tests/test_terrain_edges.gd (no ground below waterline near the wall).

Upgrades the project to Godot 4.7 (project.godot rewritten by the 4.7
importer, .uid files added) and documents it in the setup guide.
2026-11-03 23:49:39 +00:00

46 lines
1.8 KiB
Plaintext

shader_type spatial;
render_mode cull_disabled, diffuse_burley;
// Grass clumps (MultiMesh). UV.y runs 0 at the root to 1 at the tip and drives
// both wind bend and the root-to-tip colour gradient. Base colour uses the same
// noise lookups as terrain.gdshader so the blades blend into the ground beneath.
#include "res://shaders/common.gdshaderinc"
uniform vec3 grass_lush : source_color = vec3(0.25, 0.40, 0.15);
uniform vec3 grass_dry : source_color = vec3(0.48, 0.50, 0.22);
uniform vec3 tip_tint : source_color = vec3(0.78, 0.88, 0.50);
uniform float wind_strength = 0.2;
uniform vec2 wind_dir = vec2(1.0, 0.35);
varying vec3 inst_pos;
varying float hfac;
void vertex() {
inst_pos = MODEL_MATRIX[3].xyz;
hfac = UV.y;
vec3 wp = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec2 wd = normalize(wind_dir);
float gust = nz(wp.xz * 0.015 - TIME * 0.05 * wd);
float sway = sin(TIME * 1.8 + wp.x * 0.35 + wp.z * 0.25) * 0.35 + gust * 1.3 - 0.4;
float k = wind_strength * hfac * hfac;
vec3 off = vec3(wd.x * sway * k, -abs(sway) * k * 0.3, wd.y * sway * k);
VERTEX += (inverse(MODEL_MATRIX) * vec4(off, 0.0)).xyz;
}
void fragment() {
float macro = nz(inst_pos.xz * 0.0035);
float mid = nz(inst_pos.xz * 0.021 + 0.37);
vec3 base = mix(grass_lush, grass_dry, smoothstep(0.48, 0.78, macro + (mid - 0.5) * 0.35));
base *= 0.82 + 0.36 * nz(inst_pos.xz * 0.17);
vec3 col = base * mix(0.55, 1.0, hfac); // darker at the root
col = mix(col, col * tip_tint * 1.3, smoothstep(0.6, 1.0, hfac) * 0.3);
ALBEDO = col;
ROUGHNESS = 0.9;
SPECULAR = 0.2;
BACKLIGHT = vec3(0.35, 0.4, 0.15) * hfac;
// Light blades as if they faced up (same as the ground), so a field reads as
// one soft surface instead of thousands of flickering cards.
NORMAL = normalize((VIEW_MATRIX * vec4(0.0, 1.0, 0.0, 0.0)).xyz);
}