From 89c4555e1e9b410c149331cb485c5baf48f1285d Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 3 Nov 2026 21:50:32 +0100 Subject: [PATCH] feat(render): matte and tinted copies of a material, the steel weathered or tinted as far as the userdata of its vertices says Matte_Material() gives a copy of a material with its glossiness damped and its weathering on: the faces cut out of a polished surface, as the sides of a worn rail head, darken and lose the shine by the userdata of their vertices. Tinted_Material() gives a copy whose texture is tinted to a colour as far instead. Both are made once per material. The weathering and the tint colour have a param slot of their own, param[3]: the shaders of the materials use all four components of param[2] among them (detail, parallax, water, the terrain placement), and light_common.glsl is included in all of them. MAX_PARAMS grows from 3 to 4, the model UBO by 16 bytes. Neither copy is made when another param of the shader lies on the components it would set. --- gl/ubo.h | 2 +- model/material.cpp | 117 ++++++++++++++++++++++++++++++++++++++ model/material.h | 7 +++ shaders/light_common.glsl | 18 ++++++ 4 files changed, 143 insertions(+), 1 deletion(-) diff --git a/gl/ubo.h b/gl/ubo.h index d5335b21..9d2c9691 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -62,7 +62,7 @@ namespace gl static_assert(sizeof(scene_ubs) == 400, "bad size of ubs"); - const size_t MAX_PARAMS = 3; + const size_t MAX_PARAMS = 4; struct model_ubs { diff --git a/model/material.cpp b/model/material.cpp index dd7598ab..ddc521e5 100644 --- a/model/material.cpp +++ b/model/material.cpp @@ -662,6 +662,123 @@ material_manager::create_from_text( std::string const &Name, std::string const & return materialhandle; } +namespace { + +opengl_material +variant_of( opengl_material const &Source, std::string const &Suffix ) { + + opengl_material variant; + variant.textures = Source.textures; + variant.texture_variants = Source.texture_variants; + variant.params = Source.params; + variant.params_state = Source.params_state; + variant.shader = Source.shader; + variant.opacity = Source.opacity; + variant.selfillum = Source.selfillum; + variant.shadow_rank = Source.shadow_rank; + variant.name = Source.name + Suffix; + variant.size = Source.size; + variant.is_good = Source.is_good; + variant.path = Source.path; + return variant; +} + +// whether another param of the shader lies on a component of the named one, which a value set for that one would change too +bool +shares_slot( std::unordered_map const &Conf, std::string const &Name ) { + + auto const own { Conf.find( Name ) }; + if( own == Conf.end() ) { + return false; + } + return std::any_of( + std::begin( Conf ), std::end( Conf ), + [&]( auto const &Entry ) { + return Entry.first != Name + && Entry.second.location == own->second.location + && Entry.second.offset < own->second.offset + own->second.size + && own->second.offset < Entry.second.offset + Entry.second.size; } ); +} + +} // namespace + +material_handle +material_manager::create_matte( material_handle const Material ) { + + if( Material == null_handle || Material >= m_materials.size() ) { + return Material; + } + auto const lookup { m_mattes.find( Material ) }; + if( lookup != m_mattes.end() ) { + return lookup->second; + } + auto matte { variant_of( m_materials[ Material ], ":matte" ) }; + matte.glossiness = 0.1f; + if( matte.shader ) { + for( auto const &[ key, value ] : std::vector>{ { "weathering", glm::vec4{ 1.f } } } ) { + auto const entry { matte.shader->param_conf.find( key ) }; + if( entry == matte.shader->param_conf.end() + || shares_slot( matte.shader->param_conf, key ) ) { + continue; + } + for( std::size_t i = 0; i < entry->second.size; ++i ) { + matte.params[ entry->second.location ][ entry->second.offset + i ] = value[ static_cast( i ) ]; + } + matte.params_state.erase( + std::remove_if( + std::begin( matte.params_state ), std::end( matte.params_state ), + [&]( gl::shader::param_entry const &State ) { + return State.location == entry->second.location && State.offset == entry->second.offset; } ), + std::end( matte.params_state ) ); + } + } + auto const mattehandle { static_cast( m_materials.size() ) }; + m_materials.emplace_back( std::move( matte ) ); + m_mattes.emplace( Material, mattehandle ); + return mattehandle; +} + +material_handle +material_manager::create_tinted( material_handle const Material, glm::vec3 const &Colour ) { + + if( Material == null_handle || Material >= m_materials.size() || !m_materials[ Material ].shader ) { + return Material; + } + auto const key { std::make_tuple( Material, Colour.r, Colour.g, Colour.b ) }; + if( auto const lookup { m_tints.find( key ) }; lookup != m_tints.end() ) { + return lookup->second; + } + // the colour in the rgb of the slot of the weathering, which tells the shader to tint by its sign; nothing else in that slot + auto const &conf { m_materials[ Material ].shader->param_conf }; + auto const weathering { conf.find( "weathering" ) }; + auto const tint { conf.find( "tint" ) }; + if( weathering == conf.end() + || tint == conf.end() + || weathering->second.offset != 3 + || tint->second.location != weathering->second.location + || tint->second.offset != 0 + || std::any_of( + std::begin( conf ), std::end( conf ), + [&]( auto const &Entry ) { + return Entry.first != weathering->first && Entry.first != tint->first && Entry.second.location == weathering->second.location; } ) ) { + return Material; + } + auto tinted { variant_of( m_materials[ Material ], ":tinted" ) }; + tinted.glossiness = m_materials[ Material ].glossiness; + auto const location { weathering->second.location }; + tinted.params[ location ] = glm::vec4{ Colour, -1.f }; + tinted.params_state.erase( + std::remove_if( + std::begin( tinted.params_state ), std::end( tinted.params_state ), + [&]( gl::shader::param_entry const &State ) { + return State.location == location; } ), + std::end( tinted.params_state ) ); + auto const tintedhandle { static_cast( m_materials.size() ) }; + m_materials.emplace_back( std::move( tinted ) ); + m_tints.emplace( key, tintedhandle ); + return tintedhandle; +} + void material_manager::on_weather_change() { diff --git a/model/material.h b/model/material.h index f329a325..05a68880 100644 --- a/model/material.h +++ b/model/material.h @@ -142,6 +142,11 @@ public: // material described by provided text (the content of a .mat file), not kept in a file. returns: handle to the material or null_handle material_handle create_from_text( std::string const &Name, std::string const &Definition ); + // copy of provided material with its specular highlights and reflections damped. returns: handle to the copy, made once per material + material_handle + create_matte( material_handle const Material ); + material_handle + create_tinted( material_handle const Material, glm::vec3 const &Colour ); opengl_material const & material( material_handle const Material ) const { return m_materials[ Material ]; } opengl_material & @@ -170,6 +175,8 @@ private: // members: material_sequence m_materials; index_map m_materialmappings; + std::unordered_map m_mattes; + std::map, material_handle> m_tints; }; //--------------------------------------------------------------------------- diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index 0801b09d..6261a090 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -9,6 +9,11 @@ uniform sampler2D headlightmap; float glossiness = 1.0; float metalic = 0.0; +in vec4 UserData; +// a slot of its own, the shaders of the materials using all of param[2]: how weathered the steel is as far as the userdata of +// its vertices says; below 0: the texture tinted to the colour of tint as far instead +#param (weathering, 3, 3, 1, zero) +#param (tint, 3, 0, 3, one) // --------------------------------------------------------------------- // Lighting balance tunables - tweak these to control overall scene @@ -285,6 +290,19 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl { // Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again texturecolor *= material_diffuse(); + float weathered = (param[3].w > 0.0 ? clamp(param[3].w * UserData.x, 0.0, 1.0) : 0.0); + float tinted = (param[3].w < 0.0 ? clamp(-param[3].w * UserData.x, 0.0, 1.0) : 0.0); + // a tint only scales the colour of the texture, the steel keeps its own polish + float polished = weathered * smoothstep(0.03, 0.12, dot(texturecolor, vec3(0.2126, 0.7152, 0.0722))); + float weatheredluma = min(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 0.05); + vec3 unused = texturecolor * (weatheredluma / max(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 1e-4)); + unused = mix(unused, vec3(1.0, 0.82, 0.74) * (weatheredluma / 0.8525), 0.5); + texturecolor = mix(texturecolor, unused, smoothstep(0.0, 0.4, weathered)); + texturecolor = (tinted > 0.0 ? texturecolor * mix(vec3(1.0), param[3].rgb, tinted) : texturecolor); + specularity = mix(specularity, min(specularity, 0.03), smoothstep(0.0, 0.4, polished)); + metalic = mix(metalic, 0.0, polished); + glossiness = mix(glossiness, min(glossiness, 0.06 * abs(param[1].w)), polished); + reflectivity = mix(reflectivity, min(reflectivity, 0.08 * param[1].z), smoothstep(0.0, 0.4, polished)); // Scale ambient before it gets tinted by the texture. // Sun, headlights and emission are added afterwards so they are NOT // attenuated by AMBIENT_SCALE - this only dims the indirect term.