From 467edf2bb6a379010bfe0a8aa415a43c1256b347 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 5 Feb 2024 21:08:49 +0000 Subject: [PATCH] WS30: Improved shoreline rendering --- Effects/ws30.eff | 18 ----- Shaders/ws30-ALS-detailed.frag | 66 ++++++++-------- .../ws30-ALS-landclass-search-functions.frag | 23 ++++-- Shaders/ws30-ALS-ultra.frag | 75 +++++++++++++------ 4 files changed, 103 insertions(+), 79 deletions(-) diff --git a/Effects/ws30.eff b/Effects/ws30.eff index be5f2f8bd..03f961512 100644 --- a/Effects/ws30.eff +++ b/Effects/ws30.eff @@ -35,14 +35,6 @@ repeat normalized - - Textures/Terrain/sand6.png - 2d - nearest - repeat - repeat - normalized - Textures/Terrain/snow3.png 2d @@ -859,11 +851,6 @@ coastline sampler-2d 7 - - - sand - sampler-2d - 8 swatch_size @@ -1289,11 +1276,6 @@ sampler-2d 7 - - sand - sampler-2d - 8 - swatch_size int diff --git a/Shaders/ws30-ALS-detailed.frag b/Shaders/ws30-ALS-detailed.frag index 465818b72..cbb4c1ba9 100644 --- a/Shaders/ws30-ALS-detailed.frag +++ b/Shaders/ws30-ALS-detailed.frag @@ -29,17 +29,13 @@ // End of test phase controls ////////////////////////////////////////////////////////////////// - - - - - - - - - - - +// Constants controlling the transition from water +// to terrain depending on the terrain normal +// A normal of 1.0 is completely horizontal. +const float WATER_START = 0.995; // Deeper water. +const float WATER_BEACH_TO_WATER = 0.99; // Transition point between the shoreline and the shallow water +const float WATER_STEEP_TO_BEACH = 0.985; // The transition point between the shoreline and the land +const float WATER_STEEP = 0.98; // Anything with less than this value is considered not water or shoreline // written by Thorsten Renk, Oct 2011, based on default.frag // Ambient term comes in gl_Color.rgb. @@ -101,8 +97,8 @@ uniform vec3 fg_modelOffset; // Coastline texture - generated from VPBTechnique uniform sampler2D coastline; -// Sand texture -uniform sampler2D sand; +// Index into the material definition for shorelines +uniform int fg_shoreAtlasIndex; const float EarthRadius = 5800000.0; const float terminator_width = 200000.0; @@ -321,7 +317,8 @@ float noise_2000m = Noise3D(worldPos.xyz, 2000.0); // Mix factor of base textures for 2 neighbour landclass(es) vec4 mfact; - bool water = false; + bool water_lc = false; + bool mix_water_texel = false; // Partial derivatives of s and t of ground texture coords for this fragment, // with respect to window (screen space) x and y axes. @@ -332,35 +329,37 @@ float noise_2000m = Noise3D(worldPos.xyz, 2000.0); get_landclass_id(tile_coord, dxdy_gc, lc, lc_n, num_unique_neighbors, mfact); get_material(lc, ground_tex_coord, dxdy_gc, mat_shininess, mat_ambient, mat_diffuse, mat_specular, dxdy, st); - vec4 coast = texture2D(coastline, tile_coord); if (fg_photoScenery) { - texel = texture(landclass, vec2(gl_TexCoord[0].s, 1.0 - gl_TexCoord[0].t)); - water = (texture(coastline, vec2(tile_coord.s, tile_coord.t)).r > 0.1); - } else if (coast.g > 0.1) { - texel = lookup_ground_texture_array(0, tile_coord, lc, dxdy); - water = texture(landclass, vec2(tile_coord.s, tile_coord.t)).z > 0.9; + texel = texture(landclass, vec2(gl_TexCoord[0].s, 1.0 - gl_TexCoord[0].t)); + water_lc = (texture(coastline, vec2(tile_coord.s, tile_coord.t)).r > 0.1); } else { // Lookup the base texture texel for this fragment and any neighbors, with mixing texel = get_mixed_texel(0, ground_tex_coord, lc, num_unique_neighbors, lc_n, mfact, dxdy_gc); - water = texture(landclass, vec2(tile_coord.s, tile_coord.t)).z > 0.9; + water_lc = texture(landclass, vec2(tile_coord.s, tile_coord.t)).z > 0.9; } - float steep = 0.9; - float steepToBeach = 0.93; - float beachToWater = 0.95; - float waterStart = 0.97; + float steepness_modifier = texture2D(coastline, tile_coord).g * 0.1; + bool water = water_lc || texture2D(coastline, tile_coord).b > 0.05; - if ((water_shader == 1) && ((coast.b > 0.05) || (water && steepness < (waterStart + 0.02)))) { + if ((water_shader == 1) && (water && (steepness + steepness_modifier)< WATER_START)) { // This is a water fragment, so calculate the fragment color procedurally, but mix in the steep and beach - vec4 steep_texel = lookup_ground_texture_array(2, ground_tex_coord, lc, dxdy_gc); // Uses the same index as the gradient texture, which it is - vec4 beach_texel = texture2D(sand, ground_tex_coord); // Use the dot texture, which is overloaded to be the beach texture - texel = mix(steep_texel, beach_texel, smoothstep(steep, steepToBeach, steepness)); - fragColor = mix(texel, generateWaterTexel(), smoothstep(beachToWater,waterStart,steepness)); + if (water_lc) { + lc = fg_shoreAtlasIndex; + get_material(lc, ground_tex_coord, dxdy_gc, mat_shininess, mat_ambient, mat_diffuse, mat_specular, dxdy, st); + } - fragColor.rgb += getClusteredLightsContribution(eyePos.xyz, n, fragColor.rgb); - } else if ((water_shader == 1) && water) { + vec4 steep_texel = lookup_ground_texture_array(0, ground_tex_coord, lc, dxdy_gc); // Uses the same index as the gradient texture, which it is + vec4 beach_texel = lookup_ground_texture_array(0, ground_tex_coord, fg_shoreAtlasIndex, dxdy_gc); // Use the shore texture + texel = mix(steep_texel, beach_texel, smoothstep(WATER_STEEP, WATER_STEEP_TO_BEACH, steepness + steepness_modifier)); + + // Flag that we need to mix in a water texel later if appropriate. + mix_water_texel = (steepness + steepness_modifier > WATER_BEACH_TO_WATER); + water = false; + } + + if ((water_shader == 1) && water) { fragColor = generateWaterTexel(); fragColor.rgb += getClusteredLightsContribution(eyePos.xyz, n, fragColor.rgb); } else { @@ -602,6 +601,9 @@ float noise_2000m = Noise3D(worldPos.xyz, 2000.0); color = clamp(color, 0.0, 1.0); fragColor = color * texel + specular; + + if (mix_water_texel) { fragColor = mix(fragColor, generateWaterTexel(), smoothstep(WATER_BEACH_TO_WATER, WATER_START, steepness + steepness_modifier)); } + fragColor.rgb += getClusteredLightsContribution(eyePos.xyz, n, texel.rgb); } diff --git a/Shaders/ws30-ALS-landclass-search-functions.frag b/Shaders/ws30-ALS-landclass-search-functions.frag index ed5ee9647..4e14d9500 100644 --- a/Shaders/ws30-ALS-landclass-search-functions.frag +++ b/Shaders/ws30-ALS-landclass-search-functions.frag @@ -528,6 +528,13 @@ int read_landclass_id(in vec2 tile_coord) return lc; } +// Landclass sources: texture or random +ivec2 read_landclass_id_and_water(in vec2 tile_coord) +{ + int lc = (int(texture2D(landclass, tile_coord.st).g * 255.0 + 0.5)); + return ivec2(lc, (texture2D(landclass, tile_coord.st).b > 0.9) ? 1 : 0); +} + int read_landclass_id_non_pixelated(in vec2 tile_coord, const in float landclass_texel_size_m) @@ -991,8 +998,8 @@ if ( (enable_large_scale_transition_search == 1) && for (int i=1;i<=n;i++) { vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[0] > n)) {l[0] = v; mi[0] = i; } + ivec2 v = read_landclass_id_and_water(c); + if ((v[0] != lc) && (v[1] != 1) && (mi[0] > n)) {l[0] = v[0]; mi[0] = i; } } @@ -1001,8 +1008,8 @@ if ( (enable_large_scale_transition_search == 1) && for (int i=1;i<=n;i++) { vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[1] > n)) {l[1] = v; mi[1] = i; } + ivec2 v = read_landclass_id_and_water(c); + if ((v[0] != lc) && (v[1] != 1) && (mi[1] > n)) {l[1] = v[0]; mi[1] = i; } } @@ -1011,8 +1018,8 @@ if ( (enable_large_scale_transition_search == 1) && for (int i=1;i<=n;i++) { vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[2] > n)) {l[2] = v; mi[2] = i; } + ivec2 v = read_landclass_id_and_water(c); + if ((v[0] != lc) && (v[1] != 1) && (mi[2] > n)) {l[2] = v[0]; mi[2] = i; } } @@ -1021,8 +1028,8 @@ if ( (enable_large_scale_transition_search == 1) && for (int i=1;i<=n;i++) { vec2 c = c0+float(i)*dir; - int v = read_landclass_id(c); - if ((v != lc) && (mi[3] > n)) {l[3] = v; mi[3] = i; } + ivec2 v = read_landclass_id_and_water(c); + if ((v[0] != lc) && (v[1] != 1) && (mi[3] > n)) {l[3] = v[0]; mi[3] = i; } } diff --git a/Shaders/ws30-ALS-ultra.frag b/Shaders/ws30-ALS-ultra.frag index aeb0aa04e..41464530e 100644 --- a/Shaders/ws30-ALS-ultra.frag +++ b/Shaders/ws30-ALS-ultra.frag @@ -28,6 +28,15 @@ const int randomise_texture_lookups = 0; // Use built-in water shader. Use for testing impact of ws30-water.frag const int water_shader = 1; + +// Constants controlling the transition from water +// to terrain depending on the terrain normal +// A normal of 1.0 is completely horizontal. +const float WATER_START = 0.995; // Deeper water. +const float WATER_BEACH_TO_WATER = 0.99; // Transition point between the shoreline and the shallow water +const float WATER_STEEP_TO_BEACH = 0.985; // The transition point between the shoreline and the land +const float WATER_STEEP = 0.98; // Anything with less than this value is considered not water or shoreline + // // End of test phase controls ////////////////////////////////////////////////////////////////// @@ -103,6 +112,9 @@ uniform vec4 fg_materialParams1[128]; uniform vec4 fg_materialParams2[128]; uniform vec4 fg_materialParams3[128]; +// Index into the material definition for shorelines +uniform int fg_shoreAtlasIndex; + // Coastline texture - generated from VPBTechnique uniform sampler2D coastline; @@ -417,7 +429,8 @@ void main() // Mix factor of base textures for 2 neighbour landclass(es) vec4 mfact; - bool water = false; + bool water_lc = false; + bool mix_water_texel = false; // Partial derivatives of s and t of ground texture coords for this fragment, // with respect to window (screen space) x and y axes. @@ -428,47 +441,64 @@ void main() get_landclass_id(tile_coord, dxdy_gc, lc, lc_n, num_unique_neighbors, mfact); get_material(lc, ground_tex_coord, dxdy_gc, mat_shininess, mat_ambient, mat_diffuse, mat_specular, dxdy, st); - vec4 coast = texture2D(coastline, tile_coord); if (fg_photoScenery) { // The photoscenery orthophotos are stored in the landclass texture // and use normalised tile coordinates texel = texture(landclass, vec2(tile_coord.s, 1.0 - tile_coord.t)); - water = (texture(coastline, vec2(tile_coord.s, tile_coord.t)).r > 0.1); + water_lc = (texture(coastline, vec2(tile_coord.s, tile_coord.t)).r > 0.1); // Do not attempt any mixing flag = 0; mix_flag = 0; - } else if (coast.g > 0.1) { - texel = lookup_ground_texture_array(0, tile_coord, lc, dxdy); - water = texture(landclass, vec2(tile_coord.s, tile_coord.t)).z > 0.9; } else { // Lookup the base texture texel for this fragment and any neighbors, with mixing texel = get_mixed_texel(0, ground_tex_coord, lc, num_unique_neighbors, lc_n, mfact, dxdy_gc); - water = texture(landclass, vec2(tile_coord.s, tile_coord.t)).z > 0.9; + water_lc = texture(landclass, vec2(tile_coord.s, tile_coord.t)).b > 0.5; } vec4 color = gl_Color * mat_ambient; color.a = 1.0; - + // Testing code: mix with green to show values of variables at each point //vec4 green = vec4(0.0, 0.5, 0.0, 0.0); //texel = mix(texel, green, (mfact[2])); - float steep = 0.9; - float steepToBeach = 0.93; - float beachToWater = 0.95; - float waterStart = 0.97; + // The coastline BLUE channel provides a higher detail level for waters. The coastline G channel provides a + // steepness modified that is used so that rivers and lakes are displayed with water on more angled surfaces. Otherwise + // rivers tend to just be sand, as they flow downhill. + float steepness_modifier = texture2D(coastline, tile_coord).g * 0.1; + bool water = water_lc || texture2D(coastline, tile_coord).b > 0.05; + if (water && (steepness + steepness_modifier < WATER_START)) { + // For water surfaces that are simply too steep to be plausible we look for an adjacent landclass and mix it with + // a possible shoreline + if (water_lc) { + if (lc == lc_n[0]) { + // Default to the shore material definition + lc = fg_shoreAtlasIndex; + } else { + lc = lc_n[0]; + } + } - if ((coast.b > 0.05) || (water && steepness < (waterStart + 0.02))) { - float waterline_min_steepness = fg_materialParams3[lc].y; - float waterline_max_steepness = fg_materialParams3[lc].z; - vec4 steep_texel = lookup_ground_texture_array(2, ground_tex_coord, lc, dxdy_gc); // Uses the same index as the gradient texture, which it is - vec4 beach_texel = texture2D(sand, ground_tex_coord); // Use the dot texture, which is overloaded to be the beach texture - texel = mix(steep_texel, beach_texel, smoothstep(steep, steepToBeach, steepness)); - fragColor = mix(texel, generateWaterTexel(), smoothstep(beachToWater,waterStart,steepness)); - fragColor.rgb += getClusteredLightsContribution(ecPosition.xyz, n, fragColor.rgb); - } else if (water) { + get_material(lc, ground_tex_coord, dxdy_gc, mat_shininess, mat_ambient, mat_diffuse, mat_specular, dxdy, st); + vec4 color = gl_Color * mat_ambient; + color.a = 1.0; + + vec4 steep_texel = lookup_ground_texture_array(0, ground_tex_coord, lc, dxdy); // look up the secondary texture + vec4 beach_texel = lookup_ground_texture_array(0, ground_tex_coord, fg_shoreAtlasIndex, dxdy); // Use the shore texture + texel = mix(steep_texel, beach_texel, smoothstep(WATER_STEEP, WATER_STEEP_TO_BEACH, steepness + steepness_modifier)); + + // Flag that we need to mix in a water texel later if appropriate. + mix_water_texel = (steepness + steepness_modifier > WATER_BEACH_TO_WATER); + water = false; + } + + // Test code to view the coastline rasters in-sim + //texel.r = texture2D(coastline, tile_coord).b; + //water = false; + + if (water) { fragColor = generateWaterTexel(); fragColor.rgb += getClusteredLightsContribution(ecPosition.xyz, n, fragColor.rgb); } else { @@ -833,6 +863,9 @@ void main() color.rgb += secondary_light * light_distance_fading(dist); fragColor = color * texel + specular; + + if (mix_water_texel) { fragColor = mix(fragColor, generateWaterTexel(), smoothstep(WATER_BEACH_TO_WATER, WATER_START, steepness + steepness_modifier)); } + fragColor.rgb += getClusteredLightsContribution(ecPosition.xyz, n, texel.rgb); }