aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author hop311 <hop3114@gmail.com>2024-01-15 01:30:02 +0100
committer hop311 <hop3114@gmail.com>2024-01-15 01:30:02 +0100
commitfe1dd80ca6eb6d024e046f1ab804e5be707e483e (patch)
tree742bcbbeff8169f45a97b35de45aa06b6458d8b9
parent92267a046506077418823a16ac8748579cf7905c (diff)
Improved province colouring and terrain texture tiling
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.cpp7
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.hpp2
-rw-r--r--game/src/Game/GameSession/MapView.gd14
-rw-r--r--game/src/Game/GameSession/TerrainMap.gdshader116
4 files changed, 79 insertions, 60 deletions
diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp
index 8564d54..2730352 100644
--- a/extension/src/openvic-extension/singletons/GameSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp
@@ -472,7 +472,7 @@ void GameSingleton::try_tick() {
game_manager.get_simulation_clock().conditionally_advance_game();
}
-Error GameSingleton::_load_map_images(bool flip_vertical) {
+Error GameSingleton::_load_map_images() {
ERR_FAIL_COND_V_MSG(province_shape_texture.is_valid(), FAILED, "Map images have already been loaded!");
Error err = OK;
@@ -544,7 +544,6 @@ Error GameSingleton::_load_terrain_variants() {
static constexpr int32_t SHEET_DIMS = 8, SHEET_SIZE = SHEET_DIMS * SHEET_DIMS;
- terrain_sheet->flip_y();
const int32_t sheet_width = terrain_sheet->get_width(), sheet_height = terrain_sheet->get_height();
ERR_FAIL_COND_V_MSG(
sheet_width < 1 || sheet_width % SHEET_DIMS != 0 || sheet_width != sheet_height, FAILED, vformat(
@@ -566,7 +565,7 @@ Error GameSingleton::_load_terrain_variants() {
}
Error err = OK;
for (int32_t idx = 0; idx < SHEET_SIZE; ++idx) {
- const Rect2i slice { (idx % SHEET_DIMS) * slice_size, (7 - (idx / SHEET_DIMS)) * slice_size, slice_size, slice_size };
+ const Rect2i slice { idx % SHEET_DIMS * slice_size, idx / SHEET_DIMS * slice_size, slice_size, slice_size };
const Ref<Image> terrain_image = terrain_sheet->get_region(slice);
if (terrain_image.is_null() || terrain_image->is_empty()) {
UtilityFunctions::push_error(
@@ -655,7 +654,7 @@ Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& fi
UtilityFunctions::push_error("Failed to load flag textures!");
err = FAILED;
}
- if (_load_map_images(true) != OK) {
+ if (_load_map_images() != OK) {
UtilityFunctions::push_error("Failed to load map images!");
err = FAILED;
}
diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp
index 047d14c..913bdd4 100644
--- a/extension/src/openvic-extension/singletons/GameSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp
@@ -30,7 +30,7 @@ namespace OpenVic {
static godot::StringName const& _signal_clock_state_changed();
godot::Error _generate_terrain_texture_array();
- godot::Error _load_map_images(bool flip_vertical);
+ godot::Error _load_map_images();
godot::Error _load_terrain_variants();
godot::Error _load_flag_images();
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));
}