diff --git a/gl/ubo.h b/gl/ubo.h index d5335b21d..9d2c96912 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 dd7598ab1..4e96ddd8c 100644 --- a/model/material.cpp +++ b/model/material.cpp @@ -662,12 +662,176 @@ 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.glossiness = Source.glossiness; + variant.name = Source.name + Suffix; + variant.size = Source.size; + variant.is_good = Source.is_good; + variant.path = Source.path; + return variant; +} + +// the params of a shader, as the shader keeps them +using param_confs = decltype( gl::shader::param_conf ); + +// 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( param_confs const &Conf, std::string const &Name ) { + + auto const own { Conf.find( Name ) }; + if( own == Conf.end() ) { + return false; + } + return std::ranges::any_of( + Conf, + [ &Name, &own ]( 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; } ); +} + +// the highlights of a copy of a material damped and its weathering on +void +make_matte( opengl_material &Variant ) { + + Variant.glossiness = 0.1f; + if( !Variant.shader ) { + return; + } + for( auto const &[ key, value ] : std::vector>{ { "weathering", glm::vec4{ 1.f } } } ) { + auto const entry { Variant.shader->param_conf.find( key ) }; + if( entry == Variant.shader->param_conf.end() + || shares_slot( Variant.shader->param_conf, key ) ) { + continue; + } + auto &slot { Variant.params[ entry->second.location ] }; + auto const offset { static_cast( entry->second.offset ) }; + auto const size { static_cast( entry->second.size ) }; + for( glm::length_t i = 0; i < size; ++i ) { + slot[ offset + i ] = value[ i ]; + } + std::erase_if( + Variant.params_state, + [ &entry ]( gl::shader::param_entry const &State ) { + return State.location == entry->second.location && State.offset == entry->second.offset; } ); + } +} + +// the colour in the rgb of the slot of the weathering of a copy of a material, which tells the shader to tint by its sign. returns: +// whether the shader can tint it, the weathering and the tint alone in that slot +bool +make_tinted( opengl_material &Variant, glm::vec3 const &Colour ) { + + if( !Variant.shader ) { + return false; + } + auto const &conf { Variant.shader->param_conf }; + auto const weathering { conf.find( "weathering" ) }; + if( auto const tint { conf.find( "tint" ) }; + weathering == conf.end() + || tint == conf.end() + || weathering->second.offset != 3 + || tint->second.location != weathering->second.location + || tint->second.offset != 0 + || std::ranges::any_of( + conf, + [ &weathering, &tint ]( auto const &Entry ) { + return Entry.first != weathering->first && Entry.first != tint->first && Entry.second.location == weathering->second.location; } ) ) { + return false; + } + auto const location { weathering->second.location }; + Variant.params[ location ] = glm::vec4{ Colour, -1.f }; + std::erase_if( + Variant.params_state, + [ location ]( gl::shader::param_entry const &State ) { + return State.location == location; } ); + return true; +} + +} // namespace + +material_handle +material_manager::create_matte( material_handle const Material ) { + + if( Material == null_handle || Material >= m_materials.size() ) { + return Material; + } + if( auto const lookup { m_mattes.find( Material ) }; lookup != m_mattes.end() ) { + return lookup->second; + } + auto matte { variant_of( m_materials[ Material ], ":matte" ) }; + make_matte( matte ); + 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; + } + auto tinted { variant_of( m_materials[ Material ], ":tinted" ) }; + if( false == make_tinted( tinted, Colour ) ) { + return Material; + } + 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::update_variants( bool const Weather ) { + + // the copies don't update themselves, their names aren't files to read again + auto const changed = [&]( material_handle const Source ) { + auto const &source { m_materials[ Source ] }; + return Weather ? source.update_on_weather_change : source.update_on_season_change; }; + for( auto const &[ source, matte ] : m_mattes ) { + if( changed( source ) ) { + auto variant { variant_of( m_materials[ source ], ":matte" ) }; + make_matte( variant ); + m_materials[ matte ] = std::move( variant ); + } + } + for( auto const &[ key, tinted ] : m_tints ) { + auto const source { std::get<0>( key ) }; + if( changed( source ) ) { + auto variant { variant_of( m_materials[ source ], ":tinted" ) }; + make_tinted( variant, glm::vec3 { std::get<1>( key ), std::get<2>( key ), std::get<3>( key ) } ); + m_materials[ tinted ] = std::move( variant ); + } + } +} + void material_manager::on_weather_change() { for( auto &material : m_materials ) { if( material.update_on_weather_change ) { material.update(); } } + update_variants( true ); } void @@ -676,6 +840,7 @@ material_manager::on_season_change() { for( auto &material : m_materials ) { if( material.update_on_season_change ) { material.update(); } } + update_variants( false ); } // checks whether specified material is in the material bank. returns handle to the material, or a null handle diff --git a/model/material.h b/model/material.h index f329a3256..82d14557c 100644 --- a/model/material.h +++ b/model/material.h @@ -142,6 +142,13 @@ 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 ); + // copy of provided material with its texture tinted to the colour as far as the userdata of the vertices says. returns: handle to + // the copy, made once per material and colour; the material itself when its shader can't tint it + 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 & @@ -161,6 +168,9 @@ private: // checks whether specified texture is in the texture bank. returns texture id, or npos. material_handle find_in_databank( std::string const &Materialname ) const; + // the matte and tinted copies of the materials changed with the weather or the season made again from them, under their handles + void + update_variants( bool const Weather ); public: // checks whether specified file exists. returns name of the located file, or empty string. static std::pair @@ -170,6 +180,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/rendering/geometrybank.cpp b/rendering/geometrybank.cpp index f2db8b8bf..aa7c1f8b5 100644 --- a/rendering/geometrybank.cpp +++ b/rendering/geometrybank.cpp @@ -318,6 +318,20 @@ geometry_bank::append( gfx::vertex_array &Vertices, userdata_array &Userdata, gf return replace( Vertices, Userdata, Geometry, gfx::geometry_bank::chunk( Geometry ).vertices.size() ); } +void +geometry_bank::erase( gfx::geometry_handle const &Geometry ) { + + if( ( Geometry.chunk == 0 ) || ( Geometry.chunk > m_chunks.size() ) ) { return; } + + auto &chunk = gfx::geometry_bank::chunk( Geometry ); + if( chunk.vertices.empty() && chunk.indices.empty() ) { return; } + + gfx::vertex_array().swap( chunk.vertices ); + gfx::index_array().swap( chunk.indices ); + gfx::userdata_array().swap( chunk.userdata ); + replace_( Geometry ); +} + // draws geometry stored in specified chunk std::size_t geometry_bank::draw( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) { @@ -360,6 +374,15 @@ geometry_bank::vertices( gfx::geometry_handle const &Geometry ) const { return geometry_bank::chunk( Geometry ).vertices; } +// kind of geometry of specified chunk +unsigned int +geometry_bank::primitive( gfx::geometry_handle const &Geometry ) const { + + if( ( Geometry.chunk == 0 ) || ( Geometry.chunk > m_chunks.size() ) ) { return no_primitive; } + + return geometry_bank::chunk( Geometry ).type; +} + // provides direct access to vertex user data of specfied chunk userdata_array const & geometry_bank::userdata( gfx::geometry_handle const &Geometry ) const { @@ -418,6 +441,14 @@ geometrybank_manager::append( gfx::vertex_array &Vertices, userdata_array &Userd return bank( Geometry ).first->append( Vertices, Userdata, Geometry ); } + +void +geometrybank_manager::erase( gfx::geometry_handle const &Geometry ) { + + if( ( Geometry.bank == 0 ) || ( Geometry.bank > m_geometrybanks.size() ) ) { return; } + + bank( Geometry ).first->erase( Geometry ); +} // draws geometry stored in specified chunk void geometrybank_manager::draw( gfx::geometry_handle const &Geometry, unsigned int const Streams ) { @@ -483,6 +514,15 @@ geometrybank_manager::vertices( gfx::geometry_handle const &Geometry ) const { return bank( Geometry ).first->vertices( Geometry ); } +// kind of geometry of specified chunk +unsigned int +geometrybank_manager::primitive( gfx::geometry_handle const &Geometry ) const { + + if( ( Geometry.bank == 0 ) || ( Geometry.bank > m_geometrybanks.size() ) ) { return geometry_bank::no_primitive; } + + return bank( Geometry ).first->primitive( Geometry ); +} + // provides direct access to vertex user data of specfied chunk gfx::userdata_array const & geometrybank_manager::userdata( gfx::geometry_handle const &Geometry ) const { diff --git a/rendering/geometrybank.h b/rendering/geometrybank.h index 20baf4ed2..c01e46fe3 100644 --- a/rendering/geometrybank.h +++ b/rendering/geometrybank.h @@ -117,6 +117,7 @@ public: auto replace( gfx::vertex_array &Vertices, gfx::userdata_array& Userdata, gfx::geometry_handle const &Geometry, std::size_t const Offset = 0 ) -> bool; // adds supplied vertex data at the end of specified chunk auto append( gfx::vertex_array &Vertices, gfx::userdata_array& Userdata, gfx::geometry_handle const &Geometry ) -> bool; + void erase( gfx::geometry_handle const &Geometry ); // draws geometry stored in specified chunk auto draw( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams = basic_streams ) -> std::size_t; // draws geometry stored in specified chunk N times via glDrawElementsInstanced* @@ -138,6 +139,9 @@ public: auto vertices( gfx::geometry_handle const &Geometry ) const -> gfx::vertex_array const &; // provides direct access to vertex user data of specfied chunk auto userdata( gfx::geometry_handle const &Geometry ) const -> gfx::userdata_array const &; + // kind of geometry of specified chunk, GL_TRIANGLES, GL_TRIANGLE_STRIP...; no_primitive for a handle to no chunk + auto primitive( gfx::geometry_handle const &Geometry ) const -> unsigned int; + static constexpr unsigned int no_primitive { ~0u }; protected: // types: @@ -220,6 +224,7 @@ public: auto replace( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, std::size_t const Offset = 0 ) -> bool; // adds supplied vertex data at the end of specified chunk auto append( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry ) -> bool; + void erase( gfx::geometry_handle const &Geometry ); // draws geometry stored in specified chunk void draw( gfx::geometry_handle const &Geometry, unsigned int const Streams = basic_streams ); // draws geometry stored in specified chunk InstanceCount times via GPU instancing. @@ -238,6 +243,8 @@ public: auto vertices( gfx::geometry_handle const &Geometry ) const -> gfx::vertex_array const &; // provides direct access to vertex data of specfied chunk auto userdata( gfx::geometry_handle const &Geometry ) const -> gfx::userdata_array const &; + // kind of geometry of specified chunk + auto primitive( gfx::geometry_handle const &Geometry ) const -> unsigned int; // sets target texture unit for the texture data stream auto units() -> gfx::stream_units & { return m_units; } // provides access to primitives count diff --git a/rendering/opengl33geometrybank.cpp b/rendering/opengl33geometrybank.cpp index 4713ec69b..978a1b75d 100644 --- a/rendering/opengl33geometrybank.cpp +++ b/rendering/opengl33geometrybank.cpp @@ -33,7 +33,8 @@ opengl33_vaogeometrybank::replace_( gfx::geometry_handle const &Geometry ) { auto &chunkrecord = m_chunkrecords[ Geometry.chunk - 1 ]; chunkrecord.is_good = false; // if the overall length of the chunk didn't change we can get away with reusing the old buffer... - if( geometry_bank::chunk( Geometry ).vertices.size() != chunkrecord.vertex_count ) { + if( ( geometry_bank::chunk( Geometry ).vertices.size() != chunkrecord.vertex_count ) + || ( geometry_bank::chunk( Geometry ).userdata.empty() == chunkrecord.has_userdata ) ) { // ...but otherwise we'll need to allocate a new one // TBD: we could keep and reuse the old buffer also if the new chunk is smaller than the old one, // but it'd require some extra tracking and work to keep all chunks up to date; also wasting vram; may be not worth it? diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index a8a1b2139..76750472d 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -2201,8 +2201,13 @@ bool opengl33_renderer::Append(gfx::vertex_array &Vertices, gfx::userdata_array return m_geometry.append(Vertices, Userdata, Geometry); } +void opengl33_renderer::Erase(gfx::geometry_handle const &Geometry) +{ + m_geometry.erase(Geometry); +} + // provides direct access to index data of specfied chunk -gfx::index_array const & opengl33_renderer::Indices(gfx::geometry_handle const &Geometry) const +gfx::index_array const & opengl33_renderer::Indices(gfx::geometry_handle const &Geometry) const { return m_geometry.indices(Geometry); } @@ -2218,6 +2223,12 @@ gfx::userdata_array const &opengl33_renderer::UserData(const gfx::geometry_handl return m_geometry.userdata(Geometry); } +int opengl33_renderer::Primitive(gfx::geometry_handle const &Geometry) const +{ + auto const primitive{m_geometry.primitive(Geometry)}; + return primitive == gfx::geometry_bank::no_primitive ? -1 : static_cast(primitive); +} + // material methods material_handle opengl33_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow) { @@ -2225,6 +2236,18 @@ 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); +} + +material_handle opengl33_renderer::Tinted_Material(material_handle const Material, glm::vec3 const &Colour) +{ + + return m_materials.create_tinted(Material, Colour); +} + std::shared_ptr opengl33_renderer::Fetch_Shader(const std::string &name) { auto it = m_shaders.find(name); diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 0f17ede7f..d48055f9f 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -66,6 +66,7 @@ class opengl33_renderer : public gfx_renderer { bool Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, const std::size_t Offset = 0) override; // adds supplied vertex data at the end of specified chunk bool Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type) override; + void Erase( gfx::geometry_handle const &Geometry ) override; // provides direct access to index data of specfied chunk gfx::index_array const & Indices( gfx::geometry_handle const &Geometry ) const override; @@ -75,9 +76,15 @@ class opengl33_renderer : public gfx_renderer { // provides direct access to vertex data of specfied chunk gfx::userdata_array const & UserData( gfx::geometry_handle const &Geometry ) const override; + int + Primitive( gfx::geometry_handle const &Geometry ) const override; // material methods material_handle Fetch_Material( std::string const &Filename, bool const Loadnow = true ) override; + material_handle + Matte_Material( material_handle const Material ) override; + material_handle + Tinted_Material( material_handle const Material, glm::vec3 const &Colour ) 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 6313aa55f..7516f5a22 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -1758,6 +1758,11 @@ bool opengl_renderer::Append(gfx::vertex_array &Vertices, gfx::userdata_array &U return m_geometry.append( Vertices, Userdata, Geometry ); } +void opengl_renderer::Erase( gfx::geometry_handle const &Geometry ) +{ + m_geometry.erase( Geometry ); +} + // provides direct access to vertex data of specfied chunk gfx::index_array const & opengl_renderer::Indices( gfx::geometry_handle const &Geometry ) const { diff --git a/rendering/openglrenderer.h b/rendering/openglrenderer.h index ada032808..9779388c7 100644 --- a/rendering/openglrenderer.h +++ b/rendering/openglrenderer.h @@ -65,6 +65,7 @@ public: bool Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, const std::size_t Offset = 0) override; // adds supplied vertex data at the end of specified chunk bool Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type) override; + void Erase( gfx::geometry_handle const &Geometry ) override; // provides direct access to index data of specfied chunk gfx::index_array const & Indices( gfx::geometry_handle const &Geometry ) const override; diff --git a/rendering/renderer.h b/rendering/renderer.h index 5850e1754..1a110e689 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -59,16 +59,26 @@ public: virtual auto Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, const std::size_t Offset = 0) -> bool = 0; // adds supplied vertex data at the end of specified chunk virtual auto Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type) -> bool = 0; + // empties specified chunk, its handle stays valid to be filled again + virtual void Erase( gfx::geometry_handle const & /*Geometry*/ ) { + // a renderer which keeps no chunks of its own has none to empty + } // provides direct access to index data of specfied chunk virtual auto Indices( gfx::geometry_handle const &Geometry ) const->gfx::index_array const & = 0; // provides direct access to vertex data of specfied chunk virtual auto Vertices( gfx::geometry_handle const &Geometry ) const ->gfx::vertex_array const & = 0; // provides direct access to vertex user data of specfied chunk virtual auto UserData( gfx::geometry_handle const &Geometry ) const ->gfx::userdata_array const & = 0; + // kind of geometry of specified chunk, -1 when the renderer doesn't tell + virtual auto Primitive( gfx::geometry_handle const &Geometry ) const -> int { return -1; } // material methods 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; } + // the same material with its texture tinted by the colour, as far as the userdata of the vertices says. backends which can't make one give back the material itself + virtual auto Tinted_Material( material_handle const Material, glm::vec3 const &Colour ) -> 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 0801b09dc..6261a090d 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.