Erich_L I don't suppose you know how to get your wave effect to push in all directions outward from the center?
I tinkered with this some more... just for fun. So here's a basic foam shader that pushes "away" from land tiles.
I needed to take care of your funky tile system (land tiles fusing when adjacent). This complicated the calculation a bit.
The only input is 1-pixel-per-tile texture, telling shader whether a tile is land or water. So just 1 bit of information per tile. I used Godot's simplex noise, thresholded by the shader.
Shader basically calculates a distance field for 4 nearest tiles and then uses the minimal one. All 8 adjacent tiles need to be sampled for each tile's distance field calculation. That's 9*4=36 texelFetch() lookups per pixel. This can be greatly optimized by sending adjacent tile information via the same input texture (so 9 bits of information then in total per tile). In that case only 4 texelFetch() calls are needed. Further optimization is possible if foam zone is confined only to 1 tile.
Calculated distance field is then remapped into animated stripes and composited over a simple water/land mix, using normalized distance itself as the opacity falloff factor. The shader also renders the tile grid.
Tile inset, foam zone width and number of stripes are controlled via uniforms.
Calculated distance field can be used in myriad other ways. The whole thing can of course be displaced by a hires noise texture for more watery look.
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx, unshaded;
uniform sampler2D tile_texture;
uniform float inset: hint_range(0.0, 1.0, .01) = .25;
uniform float foam_width: hint_range(0.0, 1.5, .05) = .6;
uniform float foam_steps: hint_range(0.0, 10.0, .1) = 3.0;
// sample tile texture, return 0.0 for water, 1.0 for land
float is_land(ivec2 tile){
tile = clamp(tile, ivec2(0,0), textureSize(tile_texture,0) - ivec2(1,1) );
return step(.35, texelFetch(tile_texture, tile, 0).r);
}
// distance field of an origin centered box
float box_dist(vec2 halfsize, vec2 p){
return length( max( abs(p)-halfsize, 0.0 ));
}
// calculate distance field for a single tile
float distance_from_land_in_tile(vec2 uv_tile, ivec2 tile){
// distance of the current pixel from sampled tile center
vec2 uv_from_tile_center = uv_tile - vec2(tile) - vec2(.5);
// get land/water status of the tile and 8 adjacent tiles
float land[9];
int a = 0;
for(int j = -1; j <= 1; ++j){
for(int i = -1; i <= 1; ++i){
land[a++] = is_land(tile + ivec2(i,j));
}
}
// get distance of all possible sub-regions of a tile and return the minimum
float tilesize = 1.0-inset;
float dist = foam_width;
dist = mix(dist, min(dist, box_dist(vec2(tilesize)*.5, uv_from_tile_center) ), land[4] );
dist = mix(dist, min(dist, box_dist(vec2(inset)*.5, uv_from_tile_center + vec2(.5)) ), land[0] * land[1] * land[3] * land[4] );
dist = mix(dist, min(dist, box_dist(vec2(inset)*.5, uv_from_tile_center - vec2(.5)) ), land[5] * land[7] * land[8] * land[4] );
dist = mix(dist, min(dist, box_dist(vec2(inset)*.5, uv_from_tile_center + vec2(.5, -.5)) ), land[3] * land[6] * land[7] * land[4] );
dist = mix(dist, min(dist, box_dist(vec2(inset)*.5, uv_from_tile_center - vec2(.5, -.5)) ), land[1] * land[2] * land[5] * land[4] );
dist = mix(dist, min(dist, box_dist(vec2(inset,tilesize)*.5, uv_from_tile_center + vec2(.5, 0.0))), land[3] * land[4] );
dist = mix(dist, min(dist, box_dist(vec2(inset,tilesize)*.5, uv_from_tile_center - vec2(.5, 0.0))), land[5] * land[4] );
dist = mix(dist, min(dist, box_dist(vec2(tilesize, inset)*.5, uv_from_tile_center + vec2(0.0, .5))), land[1] * land[4] );
dist = mix(dist, min(dist, box_dist(vec2(tilesize, inset)*.5, uv_from_tile_center - vec2(0.0, .5))), land[7] * land[4] );
return dist;
}
void fragment() {
vec2 uv_tile = UV*vec2(textureSize(tile_texture,0)); // uv coordinate in tile space
ivec2 tile = ivec2(uv_tile); // tile index
vec2 tile_fract = fract(uv_tile); // tile fraction
// max possible distance
float dist = foam_width;
// get distance field of 4 nearest tiles and use the minimum.
ivec2 base_tile = ivec2(uv_tile - vec2(.5));
for(int j = 0; j <= 1; ++j){
for(int i = 0; i <= 1; ++i){
dist = min(dist, distance_from_land_in_tile(uv_tile, base_tile + ivec2(i, j)));
}
}
// normalize distance into maximal range
float dist_norm = mix(0.0, 1.0, dist/foam_width);
dist_norm = clamp(dist_norm, 0.0, 1.0);
// foam gradients
float foam_gradient = step(.000001,dist_norm)*(1.0-dist_norm);
float foam = step(.5, fract(dist_norm * foam_steps - TIME*1.5));
foam = clamp(foam, 0.0, 1.0);
// land/water mix
ALBEDO = mix(vec3(0.01, .07, .04), vec3(0.0, .2, .37), step(.000001,dist_norm));
// mix in foam
ALBEDO = mix(ALBEDO, vec3(1.0), foam * foam_gradient);
// debug grid
ALBEDO = mix(ALBEDO, ALBEDO*.7, 1.0- vec3(step(.02,tile_fract.x) * step(.02, tile_fract.y)) );
}