From fe1dd80ca6eb6d024e046f1ab804e5be707e483e Mon Sep 17 00:00:00 2001 From: hop311 Date: Mon, 15 Jan 2024 00:30:02 +0000 Subject: Improved province colouring and terrain texture tiling --- game/src/Game/GameSession/MapView.gd | 14 ++-- game/src/Game/GameSession/TerrainMap.gdshader | 116 +++++++++++++++----------- 2 files changed, 75 insertions(+), 55 deletions(-) (limited to 'game') diff --git a/game/src/Game/GameSession/MapView.gd b/game/src/Game/GameSession/MapView.gd index 499f745..838a19a 100644 --- a/game/src/Game/GameSession/MapView.gd +++ b/game/src/Game/GameSession/MapView.gd @@ -66,12 +66,7 @@ func _ready() -> void: return _map_shader_material = map_material - if not _map_mesh_instance.mesh is MapMesh: - push_error("Invalid map mesh class: ", _map_mesh_instance.mesh.get_class(), "(expected MapMesh)") - return - _map_mesh = _map_mesh_instance.mesh - - const pixels_per_terrain_tile : float = 20.0 + const pixels_per_terrain_tile : float = 16.0 _map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_terrain_tile_factor, float(GameSingleton.get_map_height()) / pixels_per_terrain_tile) @@ -79,6 +74,13 @@ func _ready() -> void: _map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_stripe_tile_factor, float(GameSingleton.get_map_height()) / pixels_per_stripe_tile) + if not _map_mesh_instance.mesh is MapMesh: + push_error("Invalid map mesh class: ", _map_mesh_instance.mesh.get_class(), "(expected MapMesh)") + return + _map_mesh = _map_mesh_instance.mesh + + _map_mesh.set_aspect_ratio(GameSingleton.get_map_aspect_ratio()) + # Get map mesh bounds var map_mesh_aabb : AABB = _map_mesh.get_core_aabb() * _map_mesh_instance.transform _map_mesh_corner = Vector2( diff --git a/game/src/Game/GameSession/TerrainMap.gdshader b/game/src/Game/GameSession/TerrainMap.gdshader index 97b20e4..487401f 100644 --- a/game/src/Game/GameSession/TerrainMap.gdshader +++ b/game/src/Game/GameSession/TerrainMap.gdshader @@ -4,102 +4,120 @@ render_mode unshaded; #include "res://src/Game/GameSession/ProvinceIndexSampler.gdshaderinc" +// The samplers below do not have the source_color hint because we do not want them +// to be converted from sRGB to linear colour space, we do that manually at the end. + // Province colour texture -uniform sampler2D province_colour_tex: source_color, repeat_enable, filter_nearest; +uniform sampler2D province_colour_tex: repeat_enable, filter_nearest; // Index of the mouse over the map mesh uniform uint hover_index; // Index of the currently selected province uniform uint selected_index; // Cosmetic terrain textures -uniform sampler2DArray terrain_tex: source_color, repeat_enable, filter_linear; +uniform sampler2DArray terrain_tex: repeat_enable, filter_linear; // The number of times the terrain textures should tile vertically uniform float terrain_tile_factor; // Map stripe mask texture -uniform sampler2D stripe_tex: source_color, repeat_enable, filter_linear; +uniform sampler2D stripe_tex: repeat_enable, filter_linear; // The number of times the stripe texture should tile vertically uniform float stripe_tile_factor; // Land map tint -uniform sampler2D colormap_land_tex: source_color, repeat_enable, filter_linear; +uniform sampler2D colormap_land_tex: repeat_enable, filter_linear; // Water map tint -uniform sampler2D colormap_water_tex: source_color, repeat_enable, filter_linear; +uniform sampler2D colormap_water_tex: repeat_enable, filter_linear; const vec3 highlight_colour = vec3(1.0); - -vec3 get_terrain_colour( - vec2 uv, vec2 corner, vec2 half_pixel_size, // Components for calculating province sampling UV - float stripe_mask, // Stripe mask value - between 0 (base) and 1 (stripe) - vec2 terrain_uv, // UV coordinates scaled for terrain texture tiling - vec3 land_tint_colour, vec3 water_tint_colour // Colours for tinting terrain -) { - - uvec3 province_data = read_uvec3(fma(corner, half_pixel_size, uv)); +// Rec.709 luma coefficients +const vec3 luma_weights = vec3(0.2126, 0.7152, 0.0722); + +struct args_t { + vec2 uv, half_pixel_size; // Components for calculating province sampling UV + float stripe_mask; // Stripe mask value - between 0 (base) and 1 (stripe) + vec2 terrain_uv; // UV coordinates scaled for terrain texture tiling + vec3 land_tint_colour, water_tint_colour; // Colours for tinting terrain +}; + +// TODO - add parchment overlay, borders, coastlines, rivers, fog of war +// (when borders are added we can move province colour calculations out of get_terrain_colour +// and into get_map_colour, as we won't need to blend along the borders between provinces) + +// This calculates the terrain and base-stripe province colours, blends them together, and then returns +// either the blended colour or just the terrain colour depending on whether the province colour's alpha +// value was set. It also highlights the result if the province is currently selected and/or hovered over. +vec3 get_terrain_colour(const args_t args, const vec2 corner) { + // Find the province and terrain indices at the specified corner of a pixel centred on uv + uvec3 province_data = read_uvec3(fma(corner, args.half_pixel_size, args.uv)); uint province_index = uvec2_to_uint(province_data.rg); uint terrain_index = province_data.b; + // Get the tinted terrain colour at the current position + vec3 terrain_colour = texture(terrain_tex, vec3(args.terrain_uv, float(terrain_index))).rgb; + vec3 tint_colour = mix(args.land_tint_colour, args.water_tint_colour, float(terrain_index == 0u)); + vec3 tinted_terrain_colour = mix(terrain_colour, tint_colour, 0.3); + float terrain_luma = dot(tinted_terrain_colour, luma_weights); + + // Get base and stripe colours for province at the current position province_data.r *= 2u; // Double "x coordinate" as colours come in (base, stripe) pairs vec4 province_base_colour = texelFetch(province_colour_tex, ivec2(province_data.rg), 0); province_data.r += 1u; // Add 1 to "x coordinate" to move from base to strip colour vec4 province_stripe_colour = texelFetch(province_colour_tex, ivec2(province_data.rg), 0); - vec4 province_colour = mix(province_base_colour, province_stripe_colour, stripe_mask); + // Blend the base and stripe colours according to the current position's stripe mask value + vec4 province_colour = mix(province_base_colour, province_stripe_colour, args.stripe_mask); - vec3 terrain_colour = texture(terrain_tex, vec3(terrain_uv, float(terrain_index))).rgb; - vec3 tint_colour = mix(land_tint_colour, water_tint_colour, float(terrain_index == 0u)); - vec3 tinted_terrain_colour = mix(terrain_colour, tint_colour, 0.3); - vec3 mixed_colour = mix(tinted_terrain_colour, province_colour.rgb, province_colour.a); + // Darken the province colour, use it to tint the monochrome terrain colour, and re-lighten the result + province_colour.rgb = mix(vec3(terrain_luma), province_colour.rgb - 0.7, 0.3) * 1.5; - float highlight_mix_val = 0.4 * (float(province_index == hover_index) + float(province_index == selected_index)); + // Show the terrain colour if the province colour has no alpha, otherwise show the province colour + vec3 mixed_colour = mix(tinted_terrain_colour, province_colour.rgb, float(province_colour.a != 0.0)); + + float highlight_mix_val = 0.4 * (float(province_index == hover_index) + float(province_index == selected_index)) * float(province_index != 0u); return mix(mixed_colour, highlight_colour, highlight_mix_val); } // Rescale UV coordinates to remove squashing caused by normalisation -vec2 denormalise(vec2 uv, vec2 dims) { +vec2 denormalise(const vec2 uv, const vec2 dims) { return vec2(uv.x * dims.x / dims.y, uv.y); } -vec3 mix_terrain_colour(vec2 uv) { +// Calculate position-specific values and then calculate and blend map colours at the four corners +// of a pixel centred at uv in order to have smooth transitions between terrain types. +vec3 get_map_colour(vec2 uv) { + args_t args; + args.uv = uv; + vec2 map_size = vec2(textureSize(province_shape_tex, 0).xy) * province_shape_subdivisions; vec2 uv_centred = fma(uv, map_size, vec2(0.5)); vec2 pixel_offset = fract(uv_centred); - vec2 half_pixel_size = 0.49 / map_size; + args.half_pixel_size = 0.49 / map_size; // UV coords adjusted to remove squashing caused by normalisation relative to map dimensions vec2 unscaled_uv = denormalise(uv, map_size); vec2 stripe_uv = unscaled_uv * stripe_tile_factor; // Stripe mask value - between 0 (base) and 1 (stripe) - float stripe_mask = texture(stripe_tex, stripe_uv).b; + args.stripe_mask = texture(stripe_tex, stripe_uv).b; - vec2 terrain_uv = unscaled_uv * terrain_tile_factor; + args.terrain_uv = 0.5 - unscaled_uv * terrain_tile_factor; vec2 colormap_uv = vec2(uv.x, 1.0 - uv.y); - vec3 colormap_land_colour = texture(colormap_land_tex, colormap_uv).rgb; - vec3 colormap_water_colour = texture(colormap_water_tex, colormap_uv).rgb; + args.land_tint_colour = texture(colormap_land_tex, colormap_uv).rgb; + args.water_tint_colour = texture(colormap_water_tex, colormap_uv).rgb; return mix( - mix( - get_terrain_colour( - uv, vec2(-1, -1), half_pixel_size, stripe_mask, - terrain_uv, colormap_land_colour, colormap_water_colour - ), - get_terrain_colour( - uv, vec2(+1, -1), half_pixel_size, stripe_mask, - terrain_uv, colormap_land_colour, colormap_water_colour - ), pixel_offset.x - ), - mix( - get_terrain_colour( - uv, vec2(-1, +1), half_pixel_size, stripe_mask, - terrain_uv, colormap_land_colour, colormap_water_colour - ), - get_terrain_colour( - uv, vec2(+1, +1), half_pixel_size, stripe_mask, - terrain_uv, colormap_land_colour, colormap_water_colour - ), pixel_offset.x - ), + mix(get_terrain_colour(args, vec2(-1, -1)), get_terrain_colour(args, vec2(+1, -1)), pixel_offset.x), + mix(get_terrain_colour(args, vec2(-1, +1)), get_terrain_colour(args, vec2(+1, +1)), pixel_offset.x), pixel_offset.y ); } +// Convert from standard RGB to linear colour space (Godot requires the output to be linear) +vec3 srgb_to_linear(vec3 srgb) { + return mix( + srgb * (1.0 / 12.92), + pow((srgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), + greaterThan(srgb, vec3(0.04045)) + ); +} + void fragment() { - vec3 terrain_colour = mix_terrain_colour(UV); - ALBEDO = terrain_colour; + ALBEDO = srgb_to_linear(get_map_colour(UV)); } -- cgit v1.2.3-56-ga3b1