From f067824014538ba0a6389133257d20e427129326 Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 13 Oct 2026 18:30:16 +0200 Subject: [PATCH] feat(renderer): a matte variant of a material for the parts of rails no wheel rolls on: it lights the same maps as weathered steel, with the weathering scaled per vertex by userdata, so a polished running surface fades into unused steel without a bright stripe; the cut faces of rails are drawn in it in an extra pass --- model/material.cpp | 48 ++++++++++++++++++++++++++++++++++ model/material.h | 4 +++ rendering/opengl33renderer.cpp | 39 +++++++++++++++++++++++++++ rendering/opengl33renderer.h | 2 ++ rendering/openglrenderer.cpp | 26 ++++++++++++++++++ rendering/renderer.h | 2 ++ shaders/light_common.glsl | 12 +++++++++ 7 files changed, 133 insertions(+) diff --git a/model/material.cpp b/model/material.cpp index dd7598ab..a375e85e 100644 --- a/model/material.cpp +++ b/model/material.cpp @@ -662,6 +662,54 @@ material_manager::create_from_text( std::string const &Name, std::string const & return materialhandle; } +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 const &source { m_materials[ Material ] }; + opengl_material matte; + matte.textures = source.textures; + matte.texture_variants = source.texture_variants; + matte.params = source.params; + matte.params_state = source.params_state; + matte.shader = source.shader; + matte.opacity = source.opacity; + matte.selfillum = source.selfillum; + matte.glossiness = 0.1f; + matte.shadow_rank = source.shadow_rank; + matte.name = source.name + ":matte"; + matte.size = source.size; + matte.is_good = source.is_good; + matte.path = source.path; + 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() ) { + 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; +} + void material_manager::on_weather_change() { diff --git a/model/material.h b/model/material.h index f329a325..20d2022a 100644 --- a/model/material.h +++ b/model/material.h @@ -142,6 +142,9 @@ 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 ); opengl_material const & material( material_handle const Material ) const { return m_materials[ Material ]; } opengl_material & @@ -170,6 +173,7 @@ private: // members: material_sequence m_materials; index_map m_materialmappings; + std::unordered_map m_mattes; }; //--------------------------------------------------------------------------- diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index d20454c3..3c5d93e1 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -2225,6 +2225,12 @@ material_handle opengl33_renderer::Fetch_Material(std::string const &Filename, b return m_materials.create(Filename, Loadnow); } +material_handle opengl33_renderer::Matte_Material(material_handle const Material) +{ + + return m_materials.create_matte(Material); +} + std::shared_ptr opengl33_renderer::Fetch_Shader(const std::string &name) { auto it = m_shaders.find(name); @@ -4476,6 +4482,39 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator } flush_batch(); + // cut faces of rails, in matte variants of the rail materials + if ((m_renderpass.draw_mode == rendermode::color) || (m_renderpass.draw_mode == rendermode::reflections)) + { + for (auto first{First}; first != Last; ++first) + { + auto const track{*first}; + + if (false == track->m_visible) + { + continue; + } + for (auto const &cuts : {std::make_pair(track->m_materialcut1, &track->GeometryCut1), std::make_pair(track->m_materialcut2, &track->GeometryCut2)}) + { + if ((cuts.first == null_handle) || cuts.second->empty()) + { + continue; + } + if (track->eEnvironment != e_flat) + { + flush_batch(); + setup_environment_light(track->eEnvironment); + Bind_Material(cuts.first); + draw(std::begin(*cuts.second), std::end(*cuts.second)); + setup_environment_light(); + continue; + } + begin_batch(cuts.first); + m_pathbatch.insert(std::end(m_pathbatch), std::begin(*cuts.second), std::end(*cuts.second)); + } + } + flush_batch(); + } + // fourth pass: per-track sleeper models (sleepermodel optional directive). // drawn after rails/trackbeds so depth pre-pass culling is favourable, and only in passes // where Render_Sleepers actually does work (it gates itself on draw mode / distance). diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 64574420..b2359c55 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -78,6 +78,8 @@ class opengl33_renderer : public gfx_renderer { // material methods material_handle Fetch_Material( std::string const &Filename, bool const Loadnow = true ) override; + material_handle + Matte_Material( material_handle const Material ) override; void Bind_Material( material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr ) override; IMaterial const * diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index 59152c64..9c7821ee 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -3327,6 +3327,32 @@ opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, } } } + // cut faces of rails + if( ( m_renderpass.draw_mode == rendermode::color ) + || ( m_renderpass.draw_mode == rendermode::reflections ) ) { + for( auto first { First }; first != Last; ++first ) { + + auto const track { *first }; + + if( false == track->m_visible ) { + continue; + } + for( auto const &cuts : { std::make_pair( track->m_materialcut1, &track->GeometryCut1 ), std::make_pair( track->m_materialcut2, &track->GeometryCut2 ) } ) { + if( ( cuts.first == null_handle ) + || ( cuts.second->empty() ) ) { + continue; + } + if( track->eEnvironment != e_flat ) { + setup_environment_light( track->eEnvironment ); + } + Bind_Material( cuts.first ); + m_geometry.draw( std::begin( *cuts.second ), std::end( *cuts.second ) ); + if( track->eEnvironment != e_flat ) { + setup_environment_light(); + } + } + } + } // post-render reset switch( m_renderpass.draw_mode ) { case rendermode::shadows: diff --git a/rendering/renderer.h b/rendering/renderer.h index 0b9eb60f..12ca8540 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -69,6 +69,8 @@ public: virtual auto Fetch_Material( std::string const &Filename, bool const Loadnow = true ) -> material_handle = 0; virtual void Bind_Material( material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr ) = 0; virtual auto Material( material_handle const Material ) const -> IMaterial const * = 0; + // the same material with its highlights damped, for faces which aren't polished like the surfaces they are cut from. backends which can't make one give back the material itself + virtual auto Matte_Material( material_handle const Material ) -> material_handle { return Material; } // heightmap terrain: material of a chunk blending the materials of up to 8 layers by their weights. // Weights: (Samples + 1)^2 values per layer, planar, rows of growing z, the weights of a sample adding up to 255; null with a single layer. // Placement: corner of the chunk (x, z) in the texture coordinates of its mesh, and the size of the chunk. diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index 0801b09d..bc25b246 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -9,6 +9,8 @@ uniform sampler2D headlightmap; float glossiness = 1.0; float metalic = 0.0; +in vec4 UserData; +#param (weathering, 2, 3, 1, zero) // --------------------------------------------------------------------- // Lighting balance tunables - tweak these to control overall scene @@ -285,6 +287,16 @@ 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[2].w > 0.0 ? clamp(param[2].w * UserData.x, 0.0, 1.0) : 0.0); + 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)); + 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.