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.
This commit is contained in:
Paul Bailey
2026-11-03 23:49:39 +00:00
parent 1ebfce08e4
commit 12f7fd677d
32 changed files with 1922 additions and 233 deletions

45
shaders/grass.gdshader Normal file
View File

@@ -0,0 +1,45 @@
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);
}