17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-24 09:16:25 +02:00

feat(track): normal mapped track materials blended at the joints too

This commit is contained in:
2026-10-21 23:04:53 +02:00
parent 6b87fe43b3
commit 283b8523e6
5 changed files with 148 additions and 36 deletions

View File

@@ -756,90 +756,129 @@ material_manager::create_tinted( material_handle const Material, glm::vec3 const
return tintedhandle;
}
bool
material_manager::blendable( material_handle const Material ) {
int
material_manager::blend_lighting( material_handle const Material ) {
if( Material == null_handle || Material >= m_materials.size() ) {
return false;
return -1;
}
if( auto const lookup { m_blendables.find( Material ) }; lookup != m_blendables.end() ) {
return lookup->second;
}
auto result { false };
auto result { -1 };
auto const &material { m_materials[ Material ] };
auto const &conf { material.shader ? material.shader->texture_conf : decltype( material.shader->texture_conf ) {} };
auto const has = [&]( char const *Name ) {
auto const found { conf.find( Name ) };
return found != conf.end() && material.textures[ found->second.id ] != null_handle; };
if( material.is_good
&& material.shader
&& material.texture_variants.empty()
&& false == material.update_on_weather_change
&& false == material.update_on_season_change
&& material.shader->texture_conf.size() == 1
&& material.shader->texture_conf.count( "diffuse" ) == 1
&& material.textures[ material.shader->texture_conf.at( "diffuse" ).id ] != null_handle ) {
// a tint takes the parameters the blending shader keeps its lighting in
&& false == ( material.params[ 2 ].w < 0.f )
&& has( "diffuse" ) ) {
try {
// the parameters of the material go to the blending one as they are, so they have to be in the same places in both
auto const blend { GfxRenderer->Fetch_Shader( "trackblend" ) };
result = std::all_of(
std::begin( material.shader->param_conf ), std::end( material.shader->param_conf ),
[&]( auto const &Entry ) {
auto const found { blend->param_conf.find( Entry.first ) };
return found != blend->param_conf.end()
&& found->second.location == Entry.second.location
&& found->second.offset == Entry.second.offset
&& found->second.size == Entry.second.size; } );
auto lighting { -1 };
if( conf.size() == 1 ) {
lighting = 0;
}
else if( conf.size() == 2 && has( "normalmap" ) ) {
// the shaders lit as the blending one can be, by the names they're fetched by
char const *const shaders[] = { "normalmap", "sunlessnormalmap", "shadowlessnormalmap" };
for( int index = 0; index < 3; ++index ) {
if( material.shader == GfxRenderer->Fetch_Shader( shaders[ index ] ) ) {
lighting = index + 1;
}
}
}
if( lighting >= 0 ) {
// the parameters of the material go to the blending one as they are, so they have to be in the same places in both
auto const blend { GfxRenderer->Fetch_Shader( lighting > 0 ? "trackblend_normalmap" : "trackblend" ) };
auto const fits { std::all_of(
std::begin( material.shader->param_conf ), std::end( material.shader->param_conf ),
[&]( auto const &Entry ) {
auto const found { blend->param_conf.find( Entry.first ) };
return found != blend->param_conf.end()
&& found->second.location == Entry.second.location
&& found->second.offset == Entry.second.offset
&& found->second.size == Entry.second.size; } ) };
result = ( fits ? lighting : -1 );
}
}
catch( gl::shader_exception const &e ) {
ErrorLog( "invalid shader: " + std::string( e.what() ) );
}
}
if( false == result ) {
if( result < 0 ) {
// once for each material a track would blend, which of the conditions it fails
std::string textures;
if( material.shader ) {
for( auto const &entry : material.shader->texture_conf ) {
textures += ( textures.empty() ? "" : ", " ) + entry.first;
}
for( auto const &entry : conf ) {
textures += ( textures.empty() ? "" : ", " ) + entry.first;
}
WriteLog(
"track blend: material \"" + material.name + "\" isn't blended:"
+ ( material.is_good ? "" : " not good;" )
+ ( material.shader ? " a shader with the textures " + textures + ";" : " no shader;" )
+ ( material.texture_variants.empty() ? "" : " texture variants;" )
+ ( material.update_on_weather_change || material.update_on_season_change ? " changes with the weather or seasons;" : "" ) );
+ ( material.update_on_weather_change || material.update_on_season_change ? " changes with the weather or seasons;" : "" )
+ ( material.params[ 2 ].w < 0.f ? " tinted;" : "" ) );
}
m_blendables.emplace( Material, result );
return result;
}
bool
material_manager::blendable( material_handle const Material ) {
return blend_lighting( Material ) >= 0;
}
material_handle
material_manager::create_blend( material_handle const Material, std::array<material_handle, 3> const &Others, bool const Grain ) {
if( false == blendable( Material ) ) {
auto const lighting { blend_lighting( Material ) };
if( lighting < 0 ) {
return Material;
}
auto const key { std::make_tuple( Material, Others[ 0 ], Others[ 1 ], Others[ 2 ], Grain ) };
if( auto const lookup { m_blends.find( key ) }; lookup != m_blends.end() ) {
return lookup->second;
}
auto const diffuse_of = [&]( material_handle const Source ) -> texture_handle {
auto const texture_of = [&]( material_handle const Source, char const *Name ) -> texture_handle {
if( false == blendable( Source ) ) {
return null_handle;
}
auto const &source { m_materials[ Source ] };
return source.textures[ source.shader->texture_conf.at( "diffuse" ).id ];
auto const found { source.shader->texture_conf.find( Name ) };
return ( found != source.shader->texture_conf.end() ? source.textures[ found->second.id ] : null_handle );
};
auto const normals { lighting > 0 };
auto blend { variant_of( m_materials[ Material ], Grain ? ":blendgrain" : ":blend" ) };
auto result { Material };
try {
blend.shader = GfxRenderer->Fetch_Shader( Grain ? "trackblend_grain" : "trackblend" );
blend.shader = GfxRenderer->Fetch_Shader( std::string { "trackblend" } + ( normals ? "_normalmap" : "" ) + ( Grain ? "_grain" : "" ) );
blend.texture_variants.clear();
blend.textures.fill( null_handle );
auto const own { diffuse_of( Material ) };
blend.textures[ 0 ] = own;
for( std::size_t other = 0; other < Others.size(); ++other ) {
// the slots of the ends nothing is blended at take the own texture, the shader samples them only with a weight
auto const texture { diffuse_of( Others[ other ] ) };
blend.textures[ 1 + other ] = ( texture != null_handle ? texture : own );
auto const owndiffuse { texture_of( Material, "diffuse" ) };
auto const ownnormal { texture_of( Material, "normalmap" ) };
blend.textures[ 0 ] = owndiffuse;
if( normals ) {
blend.textures[ 4 ] = ownnormal;
}
for( std::size_t other = 0; other < Others.size(); ++other ) {
// the slots of the ends nothing is blended at take the own textures, the shader samples them only with a weight; a
// material without a normal map blended into one with it takes its normal map
auto const diffuse { texture_of( Others[ other ], "diffuse" ) };
blend.textures[ 1 + other ] = ( diffuse != null_handle ? diffuse : owndiffuse );
if( normals ) {
auto const normal { texture_of( Others[ other ], "normalmap" ) };
blend.textures[ 5 + other ] = ( normal != null_handle ? normal : ownnormal );
}
}
// lit as the shader of the material: normalmap, sunlessnormalmap or shadowlessnormalmap
blend.params[ 2 ].x = static_cast<float>( lighting );
// the parameters taken from the bindings again, by the shader
blend.params_state.clear();
blend.finalize( true );

View File

@@ -152,7 +152,8 @@ public:
// itself where it can't be blended
material_handle
create_blend( material_handle const Material, std::array<material_handle, 3> const &Others, bool const Grain );
// whether the material can be blended so: one diffuse texture, lit as mat_default, not changing with the weather and seasons
// whether the material can be blended so: a diffuse texture lit as mat_default, or with a normal map lit as normalmap,
// sunlessnormalmap or shadowlessnormalmap; not changing with the weather and seasons, not tinted
bool
blendable( material_handle const Material );
opengl_material const &
@@ -174,6 +175,10 @@ 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;
// how the material is lit, of the materials a track blends: 0 as mat_default, 1 normalmap, 2 sunlessnormalmap,
// 3 shadowlessnormalmap; -1 where it can't be blended
int
blend_lighting( material_handle const Material );
public:
// checks whether specified file exists. returns name of the located file, or empty string.
static std::pair<std::string, std::string>
@@ -186,7 +191,7 @@ private:
std::unordered_map<material_handle, material_handle> m_mattes;
std::map<std::tuple<material_handle, float, float, float>, material_handle> m_tints;
std::map<std::tuple<material_handle, material_handle, material_handle, material_handle, bool>, material_handle> m_blends;
std::unordered_map<material_handle, bool> m_blendables;
std::unordered_map<material_handle, int> m_blendables;
};
//---------------------------------------------------------------------------

View File

@@ -0,0 +1,11 @@
// the rails of a track of a normal mapped material faded into those of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_NORMALMAP 1
#texture (normalmap, 4, RGBA)
uniform sampler2D normalmap;
#texture (normalmap1, 5, RGBA)
uniform sampler2D normalmap1;
#texture (normalmap2, 6, RGBA)
uniform sampler2D normalmap2;
#texture (normalmap3, 7, RGBA)
uniform sampler2D normalmap3;
#include <trackblend.glsl>

View File

@@ -0,0 +1,12 @@
// the ballast of a track of a normal mapped material mixed in grains into that of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_GRAIN 1
#define TRACKBLEND_NORMALMAP 1
#texture (normalmap, 4, RGBA)
uniform sampler2D normalmap;
#texture (normalmap1, 5, RGBA)
uniform sampler2D normalmap1;
#texture (normalmap2, 6, RGBA)
uniform sampler2D normalmap2;
#texture (normalmap3, 7, RGBA)
uniform sampler2D normalmap3;
#include <trackblend.glsl>

View File

@@ -1,9 +1,14 @@
// a track drawn with the textures of the tracks joined at its ends blended in near them (world/TrackBlend.h): the weights of the
// tracks joined at the ends p1, p2 and p4 of a switch in UserData.yzw, their textures in diffuse1..3, lit as mat_default.
// TRACKBLEND_GRAIN: the others come in as grains, as the stones of two ballasts would mix, rather than faded in
// TRACKBLEND_GRAIN: the others come in as grains, as the stones of two ballasts would mix, rather than faded in.
// TRACKBLEND_NORMALMAP: with the normal maps of the four in normalmap..normalmap3 (declared by the including shader), lit as
// the shader of the material of the track: param[2].x 1 normalmap, 2 sunlessnormalmap, 3 shadowlessnormalmap
in vec3 f_normal;
in vec2 f_coord;
in vec4 f_pos;
#if TRACKBLEND_NORMALMAP
in mat3 f_tbn;
#endif
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
@@ -15,6 +20,7 @@ in vec4 f_clip_future_pos;
#param (specular, 1, 1, 1, specular)
#param (reflection, 1, 2, 1, zero)
#param (glossiness, 1, 3, 1, glossiness)
#param (blendlighting, 2, 0, 1, zero)
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
@@ -96,6 +102,9 @@ void main()
float grained = 1.0 - smoothstep(0.35, 0.9, footprint);
#endif
vec4 tex_color = textureGrad(diffuse, f_coord, dx, dy);
#if TRACKBLEND_NORMALMAP
vec4 tex_normal = textureGrad(normalmap, f_coord, dx, dy);
#endif
if (total > 0.0)
{
#if TRACKBLEND_GRAIN
@@ -108,13 +117,34 @@ void main()
total = weights.x + weights.y + weights.z;
#endif
vec4 blended = tex_color * (1.0 - total);
#if TRACKBLEND_NORMALMAP
vec4 blendednormal = tex_normal * (1.0 - total);
#endif
if (weights.x > 0.0)
{
blended += textureGrad(diffuse1, f_coord, dx, dy) * weights.x;
#if TRACKBLEND_NORMALMAP
blendednormal += textureGrad(normalmap1, f_coord, dx, dy) * weights.x;
#endif
}
if (weights.y > 0.0)
{
blended += textureGrad(diffuse2, f_coord, dx, dy) * weights.y;
#if TRACKBLEND_NORMALMAP
blendednormal += textureGrad(normalmap2, f_coord, dx, dy) * weights.y;
#endif
}
if (weights.z > 0.0)
{
blended += textureGrad(diffuse3, f_coord, dx, dy) * weights.z;
#if TRACKBLEND_NORMALMAP
blendednormal += textureGrad(normalmap3, f_coord, dx, dy) * weights.z;
#endif
}
tex_color = blended;
#if TRACKBLEND_NORMALMAP
tex_normal = blendednormal;
#endif
}
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
@@ -122,10 +152,25 @@ void main()
discard;
vec3 fragcolor = ambient;
#if TRACKBLEND_NORMALMAP
vec3 normal;
normal.xy = (tex_normal.rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * tex_normal.a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
int lighting = int(param[2].x + 0.5);
if (lighting == 2)
fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
else
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, (lighting == 3 ? 1.0 : shadow_tone));
#else
vec3 fragnormal = normalize(f_normal);
matte = true;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, 0.0, 0.0, shadow_tone);
#endif
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
#if POSTFX_ENABLED
out_color = color;