Files
Emberwild/shaders/water.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

48 lines
1.7 KiB
Plaintext

shader_type spatial;
render_mode blend_mix, cull_disabled, diffuse_burley, specular_schlick_ggx;
// Lake water: two scrolling normal maps for ripples, colour and opacity from
// the depth of water under each pixel (clear shallows, dark deep water), and
// foam where the surface meets the shore.
#include "res://shaders/common.gdshaderinc"
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;
uniform sampler2D normal_tex : hint_normal, filter_linear_mipmap, repeat_enable;
uniform vec3 shallow_col : source_color = vec3(0.22, 0.44, 0.42);
uniform vec3 deep_col : source_color = vec3(0.03, 0.12, 0.17);
uniform float clarity = 2.5;
varying vec3 w_pos;
void vertex() {
w_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
}
void fragment() {
vec2 uv = w_pos.xz;
vec3 n1 = texture(normal_tex, uv * 0.045 + TIME * vec2(0.012, 0.008)).rgb;
vec3 n2 = texture(normal_tex, uv * 0.11 + TIME * vec2(-0.010, 0.017)).rgb;
NORMAL_MAP = mix(n1, n2, 0.5);
NORMAL_MAP_DEPTH = 0.7;
// Thickness of water between the surface and whatever is behind it.
// (Godot 4.3+ uses reversed-Z: raw depth is the NDC z.)
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
vec4 behind = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth, 1.0);
behind.xyz /= behind.w;
float thick = max(VERTEX.z - behind.z, 0.0);
float t = 1.0 - exp(-thick / clarity);
vec3 col = mix(shallow_col, deep_col, t);
float foam_n = nz(uv * 0.6 + TIME * 0.02);
float foam = (1.0 - smoothstep(0.0, 0.25 + foam_n * 0.35, thick)) * 0.8;
col = mix(col, vec3(0.88, 0.93, 0.93), foam);
ALBEDO = col;
ALPHA = clamp(mix(0.3, 0.93, t) + foam, 0.0, 1.0);
ROUGHNESS = 0.04;
SPECULAR = 0.6;
METALLIC = 0.0;
}