diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index ed0404cea..09811bfe8 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -1,4 +1,6 @@ uniform sampler2D baseTexture; +#define crackTexture texture1 +uniform sampler2D crackTexture; uniform vec3 dayLight; uniform lowp vec4 fogColor; @@ -9,6 +11,10 @@ uniform float fogShadingParameter; uniform highp vec3 cameraOffset; uniform vec3 cameraPosition; uniform float animationTimer; +uniform float crackAnimationLength; +uniform float crackLevel; +uniform float crackTextureScale; + #ifdef ENABLE_DYNAMIC_SHADOWS // shadow texture uniform sampler2D ShadowMapSampler; @@ -411,9 +417,18 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) #endif #endif +// maps [0, N] to [0, 1] like GL_REPEAT would +vec2 uv_repeat(vec2 v) +{ + if (v.x > 1.0) + v.x = fract(v.x); + if (v.y > 1.0) + v.y = fract(v.y); + return v; +} + void main(void) { - vec3 color; vec2 uv = varTexCoord.st; vec4 base = texture2D(baseTexture, uv).rgba; @@ -429,8 +444,19 @@ void main(void) discard; #endif - color = base.rgb; - vec4 col = vec4(color.rgb * varColor.rgb, 1.0); + // Apply crack overlay + float crack_progress = min(crackLevel, crackAnimationLength - 1.0); + if (crack_progress >= 0.0) { + // undo scaling of e.g. world-aligned nodes + vec2 orig_uv = uv_repeat(uv * vec2(crackTextureScale)); + + vec2 cuv_offset = vec2(0.0, crack_progress / crackAnimationLength); + vec2 cuv_factor = vec2(1.0, 1.0 / crackAnimationLength); + vec4 crack = texture2D(crackTexture, cuv_offset + orig_uv * cuv_factor); + base = mix(base, crack, crack.a); + } + + vec4 col = vec4(base.rgb * varColor.rgb, 1.0); #ifdef ENABLE_DYNAMIC_SHADOWS // Fragment normal, can differ from vNormal which is derived from vertex normals. @@ -570,5 +596,5 @@ void main(void) col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity); col = vec4(col.rgb, base.a); - gl_FragData[0] = col; + gl_FragColor = col; } diff --git a/src/client/client.h b/src/client/client.h index d21618e7a..2d1a47bca 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -285,6 +285,7 @@ public: return m_animation_time; } + /// @return integer ∊ [0, crack_animation_length] or -1 for invalid int getCrackLevel(); v3s16 getCrackPos(); void setCrack(int level, v3s16 pos); diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 35da8b51a..9f402a007 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -1054,7 +1054,7 @@ void MapblockMeshGenerator::drawTorchlikeNode() default: tileindex = 2; // side (or invalid, shouldn't happen) } TileSpec tile; - useTile(&tile, tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); + useTile(&tile, tileindex, 0, MATERIAL_FLAG_BACKFACE_CULLING); float size = BS / 2 * cur_node.f->visual_scale; v3f vertices[4] = { @@ -1108,7 +1108,7 @@ void MapblockMeshGenerator::drawSignlikeNode() { u8 wall = cur_node.n.getWallMounted(nodedef); TileSpec tile; - useTile(&tile, 0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); + useTile(&tile, 0, 0, MATERIAL_FLAG_BACKFACE_CULLING); static const float offset = BS / 16; float size = BS / 2 * cur_node.f->visual_scale; // Wall at X+ of node @@ -1294,9 +1294,10 @@ void MapblockMeshGenerator::drawPlantlikeNode() void MapblockMeshGenerator::drawPlantlikeRootedNode() { drawSolidNode(); + TileSpec tile; - useTile(&tile, 0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true); - cur_node.origin += v3f(0.0, BS, 0.0); + useTile(&tile, 0, 0, 0, true); + cur_node.origin += v3f(0, BS, 0); cur_node.p.Y++; if (data->m_smooth_lighting) { getSmoothLightFrame(); @@ -1379,10 +1380,7 @@ void MapblockMeshGenerator::drawFirelikeNode() void MapblockMeshGenerator::drawFencelikeNode() { TileSpec tile_nocrack; - useTile(&tile_nocrack, 0, 0, 0); - - for (auto &layer : tile_nocrack.layers) - layer.material_flags &= ~MATERIAL_FLAG_CRACK; + useTile(&tile_nocrack, 0, 0, MATERIAL_FLAG_CRACK); // Put wood the right way around in the posts TileSpec tile_rot = tile_nocrack; @@ -1529,7 +1527,7 @@ void MapblockMeshGenerator::drawRaillikeNode() } TileSpec tile; - useTile(&tile, tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); + useTile(&tile, tile_index, 0, MATERIAL_FLAG_BACKFACE_CULLING); static const float offset = BS / 64; static const float size = BS / 2; diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 8f938eab3..d1dd6f20b 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -72,7 +72,7 @@ private: video::SColor blendLightColor(const v3f &vertex_pos); video::SColor blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal); - void useTile(TileSpec *tile_ret, int index = 0, u8 set_flags = MATERIAL_FLAG_CRACK_OVERLAY, + void useTile(TileSpec *tile_ret, int index = 0, u8 set_flags = 0, u8 reset_flags = 0, bool special = false); void getTile(int index, TileSpec *tile_ret); void getTile(v3s16 direction, TileSpec *tile_ret); diff --git a/src/client/game.cpp b/src/client/game.cpp index 3650eebae..d88385f6b 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -179,21 +179,29 @@ class GameGlobalShaderUniformSetter : public IShaderUniformSetter { Sky *m_sky; Client *m_client; + CachedVertexShaderSetting m_animation_timer_vertex{"animationTimer"}; CachedPixelShaderSetting m_animation_timer_pixel{"animationTimer"}; CachedVertexShaderSetting m_animation_timer_delta_vertex{"animationTimerDelta"}; CachedPixelShaderSetting m_animation_timer_delta_pixel{"animationTimerDelta"}; + int m_crack_animation_length_i; + CachedPixelShaderSetting m_crack_animation_length{"crackAnimationLength"}; + int m_crack_level_i = -1; + CachedPixelShaderSetting m_crack_level{"crackLevel"}; + int m_crack_texture_scale_i = 0; + CachedPixelShaderSetting m_crack_texture_scale{"crackTextureScale"}; CachedPixelShaderSetting m_day_light{"dayLight"}; CachedPixelShaderSetting m_minimap_yaw{"yawVec"}; CachedPixelShaderSetting m_camera_offset_pixel{"cameraOffset"}; CachedVertexShaderSetting m_camera_offset_vertex{"cameraOffset"}; - CachedPixelShaderSetting m_camera_position_pixel{ "cameraPosition" }; - CachedVertexShaderSetting m_camera_position_vertex{ "cameraPosition" }; + CachedPixelShaderSetting m_camera_position_pixel{"cameraPosition"}; + CachedVertexShaderSetting m_camera_position_vertex{"cameraPosition"}; CachedVertexShaderSetting m_texel_size0_vertex{"texelSize0"}; CachedPixelShaderSetting m_texel_size0_pixel{"texelSize0"}; v2f m_texel_size0; + CachedStructPixelShaderSetting m_exposure_params_pixel{ "exposureParams", std::array { @@ -235,9 +243,9 @@ public: void setSky(Sky *sky) { m_sky = sky; } - GameGlobalShaderUniformSetter(Sky *sky, Client *client) : + GameGlobalShaderUniformSetter(Sky *sky, Game *game) : m_sky(sky), - m_client(client) + m_client(game->getClient()) { for (auto &name : SETTING_CALLBACKS) g_settings->registerChangedCallback(name, settingsCallback, this); @@ -245,6 +253,7 @@ public: m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f); m_bloom_enabled = g_settings->getBool("enable_bloom"); m_volumetric_light_enabled = g_settings->getBool("enable_volumetric_lighting") && m_bloom_enabled; + m_crack_animation_length_i = game->crack_animation_length; } ~GameGlobalShaderUniformSetter() @@ -284,6 +293,15 @@ public: m_texel_size0_vertex.set(m_texel_size0, services); m_texel_size0_pixel.set(m_texel_size0, services); + { + float tmp = m_crack_animation_length_i; + m_crack_animation_length.set(&tmp, services); + tmp = m_crack_level_i; + m_crack_level.set(&tmp, services); + tmp = m_crack_texture_scale_i; + m_crack_texture_scale.set(&tmp, services); + } + const auto &lighting = m_client->getEnv().getLocalPlayer()->getLighting(); const AutoExposure &exposure_params = lighting.exposure; @@ -357,6 +375,11 @@ public: void onSetMaterial(const video::SMaterial &material) override { + // This is set only for node materials which have a crack, see mapblock_mesh.cpp. + auto pair = MapBlockMesh::unpackCrackMaterialParam(material.MaterialTypeParam); + m_crack_level_i = pair.first; + m_crack_texture_scale_i = pair.second; + video::ITexture *texture = material.getTexture(0); if (texture) { core::dimension2du size = texture->getSize(); @@ -371,11 +394,11 @@ public: class GameGlobalShaderUniformSetterFactory : public IShaderUniformSetterFactory { Sky *m_sky = nullptr; - Client *m_client; - std::vector created_nosky; + Game *m_game; + std::vector created_nosky; public: - GameGlobalShaderUniformSetterFactory(Client *client) : - m_client(client) + GameGlobalShaderUniformSetterFactory(Game *game) : + m_game(game) {} void setSky(Sky *sky) @@ -391,7 +414,7 @@ public: { if (str_starts_with(name, "shadow/")) return nullptr; - auto *scs = new GameGlobalShaderUniformSetter(m_sky, m_client); + auto *scs = new GameGlobalShaderUniformSetter(m_sky, m_game); if (!m_sky) created_nosky.push_back(scs); return scs; @@ -957,10 +980,19 @@ bool Game::createClient(const GameStartData &start_data) return false; } + // Pre-calculate crack length + video::ITexture *t = texture_src->getTexture("crack_anylength.png"); + if (t) { + v2u32 size = t->getOriginalSize(); + crack_animation_length = size.Y / size.X; + } else { + crack_animation_length = 5; + } + shader_src->addShaderConstantSetter( std::make_unique()); - auto scsf_up = std::make_unique(client); + auto scsf_up = std::make_unique(this); auto* scsf = scsf_up.get(); shader_src->addShaderUniformSetterFactory(std::move(scsf_up)); @@ -988,16 +1020,6 @@ bool Game::createClient(const GameStartData &start_data) sky = make_irr(-1, m_rendering_engine, texture_src, shader_src); scsf->setSky(sky.get()); - /* Pre-calculated values - */ - video::ITexture *t = texture_src->getTexture("crack_anylength.png"); - if (t) { - v2u32 size = t->getOriginalSize(); - crack_animation_length = size.Y / size.X; - } else { - crack_animation_length = 5; - } - if (!initGui()) return false; diff --git a/src/client/imagesource.cpp b/src/client/imagesource.cpp index e2a2a1554..6403017e5 100644 --- a/src/client/imagesource.cpp +++ b/src/client/imagesource.cpp @@ -1039,17 +1039,14 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, // A special texture modification /* - [crack:N:P - [cracko:N:P + [crack[o][:]:: Adds a cracking texture - N = animation frame count, P = crack progression + NOTE: Crack rendering does not use this, it's for mods only. */ if (str_starts_with(part_of_name, "[crack")) { CHECK_BASEIMG(); - // Crack image number and overlay option - // Format: crack[o][:]:: bool use_overlay = (part_of_name[6] == 'o'); Strfnd sf(part_of_name); sf.next(":"); @@ -1276,9 +1273,7 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, [inventorycube{topimage{leftimage{rightimage In every subimage, replace ^ with &. Create an "inventory cube". - NOTE: This should be used only on its own. - Example (a grass block (not actually used in game): - "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png" + NOTE: Inventory rendering does not use this, it's for mods only. */ else if (str_starts_with(part_of_name, "[inventorycube")) { diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index e8835d31e..ce939980a 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -648,25 +648,6 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data): p.applyTileColor(); // Generate animation data - // - Cracks - if (p.layer.material_flags & MATERIAL_FLAG_CRACK) { - // Find the texture name plus ^[crack:N: - std::ostringstream os(std::ios::binary); - os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack"; - if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) - os << "o"; // use ^[cracko - u8 tiles = p.layer.scale; - if (tiles > 1) - os << ":" << (u32)tiles; - os << ":" << (u32)p.layer.animation_frame_count << ":"; - m_crack_materials.insert(std::make_pair( - std::pair(layer, i), os.str())); - // Replace tile texture with the cracked one - p.layer.texture = m_tsrc->getTextureForMesh( - os.str() + "0", - &p.layer.texture_id); - } - // - Texture animation if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { // Add to MapBlockMesh in order to animate these tiles m_animation_info.emplace(std::make_pair(layer, i), AnimationInfo(p.layer)); @@ -689,6 +670,17 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data): p.layer.applyMaterialOptions(material, layer); } + // Handle crack + if (p.layer.material_flags & MATERIAL_FLAG_CRACK) { + auto *t = m_tsrc->getTextureForMesh("crack_anylength.png"); + material.setTexture(TEXTURE_LAYER_CRACK, t); + material.MaterialTypeParam = + packCrackMaterialParam(-1, MYMAX(1, p.layer.scale)); + + m_crack_materials.emplace_back(layer, i); + } + + // Add to buffer scene::SMeshBuffer *buf = new scene::SMeshBuffer(); buf->Material = material; if (p.layer.isTransparent()) { @@ -752,22 +744,14 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, // Cracks if (crack != m_last_crack) { - for (auto &crack_material : m_crack_materials) { + for (auto &it : m_crack_materials) { + scene::IMeshBuffer *buf = m_mesh[it.first]->getMeshBuffer(it.second); + assert(buf); + video::SMaterial &mat = buf->getMaterial(); - // TODO crack on animated tiles does not work - auto anim_it = m_animation_info.find(crack_material.first); - if (anim_it != m_animation_info.end()) - continue; - - scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]-> - getMeshBuffer(crack_material.first.second); - - // Create new texture name from original - std::string s = crack_material.second + itos(crack); - u32 new_texture_id = 0; - video::ITexture *new_texture = - m_tsrc->getTextureForMesh(s, &new_texture_id); - buf->getMaterial().setTexture(0, new_texture); + auto pair = unpackCrackMaterialParam(mat.MaterialTypeParam); + pair.first = crack; + mat.MaterialTypeParam = packCrackMaterialParam(pair.first, pair.second); } m_last_crack = crack; @@ -776,6 +760,7 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, // Texture animation for (auto &it : m_animation_info) { scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second); + assert(buf); video::SMaterial &material = buf->getMaterial(); it.second.updateTexture(material, time); } diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index d0fce2140..a4c7a4a79 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -185,7 +185,7 @@ public: // faraway: whether the block is far away from the camera (~50 nodes) // time: the global animation time, 0 .. 60 (repeats every minute) // daynight_ratio: 0 .. 1000 - // crack: -1 .. CRACK_ANIMATION_LENGTH-1 (-1 for off) + // crack: -1 .. CRACK_ANIMATION_LENGTH (-1 for off) // Returns true if anything has been changed. bool animate(bool faraway, float time, int crack, u32 daynight_ratio); @@ -257,8 +257,28 @@ public: return m_transparent_buffers; } + /** + * Texture layer in SMaterial where the crack texture is put + */ + static const int TEXTURE_LAYER_CRACK = 1; + + static float packCrackMaterialParam(int crack, u8 layer_scale) + { + // +1 so that the default MaterialTypeParam = 0 is a no-op, + // since the shader needs to know when to actually apply the crack. + u32 n = (layer_scale << 16) | (u16) (crack + 1); + return n; + } + static std::pair unpackCrackMaterialParam(float param) + { + u32 n = param; + return std::make_pair((n & 0xffff) - 1, (n >> 16) & 0xff); + } + private: + typedef std::pair MeshIndex; + irr_ptr m_mesh[MAX_TILE_LAYERS]; std::vector m_minimap_mapblocks; ITextureSource *m_tsrc; @@ -274,13 +294,12 @@ private: // Animation info: cracks // Last crack value passed to animate() int m_last_crack; - // Maps mesh and mesh buffer (i.e. material) indices to base texture names - std::map, std::string> m_crack_materials; + // Indicates which materials to apply the crack to + std::vector m_crack_materials; // Animation info: texture animation // Maps mesh and mesh buffer indices to TileSpecs - // Keys are pairs of (mesh index, buffer index in the mesh) - std::map, AnimationInfo> m_animation_info; + std::map m_animation_info; // list of all semitransparent triangles in the mapblock std::vector m_transparent_triangles; diff --git a/src/client/shader.cpp b/src/client/shader.cpp index f79f1ae48..c54f1e268 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -288,7 +288,7 @@ class MainShaderUniformSetter : public IShaderUniformSetter CachedPixelShaderSetting m_texture2{"texture2"}; CachedPixelShaderSetting m_texture3{"texture3"}; - // commonly used way to pass material color to shader + // common material variables passed to shader video::SColor m_material_color; CachedPixelShaderSetting m_material_color_setting{"materialColor"}; @@ -700,12 +700,8 @@ void ShaderSource::generateShader(ShaderInfo &shaderinfo) )"; } - // map legacy semantic texture names to texture identifiers - fragment_header += R"( - #define baseTexture texture0 - #define normalTexture texture1 - #define textureFlags texture2 - )"; + // legacy semantic texture name + fragment_header += "#define baseTexture texture0\n"; /// Unique name of this shader, for debug/logging std::string log_name = name; diff --git a/src/client/tile.h b/src/client/tile.h index 3293b4dd1..c14ccb9b1 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -30,11 +30,8 @@ enum MaterialType : u8 { #define MATERIAL_FLAG_BACKFACE_CULLING 0x01 // Should a crack be drawn? #define MATERIAL_FLAG_CRACK 0x02 -// Should the crack be drawn on transparent pixels (unset) or not (set)? -// Ignored if MATERIAL_FLAG_CRACK is not set. -#define MATERIAL_FLAG_CRACK_OVERLAY 0x04 +// Does this layer have texture animation? #define MATERIAL_FLAG_ANIMATION 0x08 -//#define MATERIAL_FLAG_HIGHLIGHTED 0x10 #define MATERIAL_FLAG_TILEABLE_HORIZONTAL 0x20 #define MATERIAL_FLAG_TILEABLE_VERTICAL 0x40 @@ -78,7 +75,7 @@ struct TileLayer } /*! - * Two tiles are not equal if they must have different vertices. + * Two layers are not equal if they must have different vertices. */ bool operator!=(const TileLayer &other) const { @@ -127,7 +124,6 @@ struct TileLayer MaterialType material_type = TILE_MATERIAL_BASIC; u8 material_flags = - //0 // <- DEBUG, Use the one below MATERIAL_FLAG_BACKFACE_CULLING | MATERIAL_FLAG_TILEABLE_HORIZONTAL| MATERIAL_FLAG_TILEABLE_VERTICAL;