diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 56a9a3111..ee7ba72d2 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -381,15 +381,15 @@ enable_3d_clouds (3D clouds) bool true [**Filtering and Antialiasing] -# Use mipmaps when scaling textures down. May slightly increase performance, +# Use mipmaps when scaling textures. May slightly increase performance, # especially when using a high-resolution texture pack. # Gamma-correct downscaling is not supported. mip_map (Mipmapping) bool false -# Use bilinear filtering when scaling textures down. +# Use bilinear filtering when scaling textures. bilinear_filter (Bilinear filtering) bool false -# Use trilinear filtering when scaling textures down. +# Use trilinear filtering when scaling textures. # If both bilinear and trilinear filtering are enabled, trilinear filtering # is applied. trilinear_filter (Trilinear filtering) bool false @@ -1827,7 +1827,14 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc # Warning: This option is EXPERIMENTAL! autoscale_mode (Autoscaling mode) enum disable disable,enable,force -# The base node texture size used for world-aligned texture autoscaling. +# When using bilinear/trilinear/anisotropic filters, low-resolution textures +# can be blurred, so automatically upscale them with nearest-neighbor +# interpolation to preserve crisp pixels. This sets the minimum texture size +# for the upscaled textures; higher values look sharper, but require more +# memory. Powers of 2 are recommended. This setting is ONLY applied if +# bilinear/trilinear/anisotropic filtering is enabled. +# This is also used as the base node texture size for world-aligned +# texture autoscaling. texture_min_size (Base texture size) int 64 1 32768 # Side length of a cube of map blocks that the client will consider together diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index a22e68f6e..1c66062e0 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1381,6 +1381,13 @@ void GenericCAO::updateTextures(std::string mod) material.Lighting = true; material.BackfaceCulling = m_prop.backface_culling; + // don't filter low-res textures, makes them look blurry + // player models have a res of 64 + const core::dimension2d &size = texture->getOriginalSize(); + const u32 res = std::min(size.Height, size.Width); + use_trilinear_filter &= res > 64; + use_bilinear_filter &= res > 64; + material.forEachTexture([=] (auto &tex) { setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, use_anisotropic_filter); diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 7fff2b314..711f7e1c6 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -506,7 +506,8 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, return dst_mesh; } -void setMaterialFilters(video::SMaterialLayer &tex, bool bilinear, bool trilinear, bool anisotropic) { +void setMaterialFilters(video::SMaterialLayer &tex, bool bilinear, bool trilinear, bool anisotropic) +{ if (trilinear) tex.MinFilter = video::ETMINF_LINEAR_MIPMAP_LINEAR; else if (bilinear) @@ -514,9 +515,7 @@ void setMaterialFilters(video::SMaterialLayer &tex, bool bilinear, bool trilinea else tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; - // "We don't want blurriness after all." ~ Desour, #13108 - // (because of pixel art) - tex.MagFilter = video::ETMAGF_NEAREST; + tex.MagFilter = (trilinear || bilinear) ? video::ETMAGF_LINEAR : video::ETMAGF_NEAREST; tex.AnisotropicFilter = anisotropic ? 0xFF : 0; } diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 867c28dd3..e04b11621 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -433,8 +433,10 @@ private: // Maps image file names to loaded palettes. std::unordered_map m_palettes; - // Cached settings needed for making textures for meshes - bool m_mesh_texture_prefilter; + // Cached settings needed for making textures from meshes + bool m_setting_mipmap; + bool m_setting_trilinear_filter; + bool m_setting_bilinear_filter; }; IWritableTextureSource *createTextureSource() @@ -453,9 +455,9 @@ TextureSource::TextureSource() // Cache some settings // Note: Since this is only done once, the game must be restarted // for these settings to take effect - m_mesh_texture_prefilter = - g_settings->getBool("mip_map") || g_settings->getBool("bilinear_filter") || - g_settings->getBool("trilinear_filter") || g_settings->getBool("anisotropic_filter"); + m_setting_mipmap = g_settings->getBool("mip_map"); + m_setting_trilinear_filter = g_settings->getBool("trilinear_filter"); + m_setting_bilinear_filter = g_settings->getBool("bilinear_filter"); } TextureSource::~TextureSource() @@ -700,7 +702,11 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) { // Avoid duplicating texture if it won't actually change - if (m_mesh_texture_prefilter) + static thread_local bool filter_needed = + m_setting_mipmap || + ((m_setting_trilinear_filter || m_setting_bilinear_filter) && + g_settings->getS32("texture_min_size") > 1); + if (filter_needed) return getTexture(name + "^[applyfiltersformesh", id); return getTexture(name, id); } @@ -1735,8 +1741,46 @@ bool TextureSource::generateImagePart(std::string part_of_name, } // Apply the "clean transparent" filter, if needed - if (m_mesh_texture_prefilter) + if (m_setting_mipmap) imageCleanTransparent(baseimg, 127); + + /* Upscale textures to user's requested minimum size. This is a trick to make + * filters look as good on low-res textures as on high-res ones, by making + * low-res textures BECOME high-res ones. This is helpful for worlds that + * mix high- and low-res textures, or for mods with least-common-denominator + * textures that don't have the resources to offer high-res alternatives. + */ + const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter; + const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1; + if (scaleto > 1) { + const core::dimension2d dim = baseimg->getDimension(); + + /* Calculate scaling needed to make the shortest texture dimension + * equal to the target minimum. If e.g. this is a vertical frames + * animation, the short dimension will be the real size. + */ + if (dim.Width == 0 || dim.Height == 0) { + errorstream << "generateImagePart(): Illegal 0 dimension " + << "for part_of_name=\""<< part_of_name + << "\", cancelling." << std::endl; + return false; + } + u32 xscale = scaleto / dim.Width; + u32 yscale = scaleto / dim.Height; + const s32 scale = std::max(xscale, yscale); + + // Never downscale; only scale up by 2x or more. + if (scale > 1) { + u32 w = scale * dim.Width; + u32 h = scale * dim.Height; + const core::dimension2d newdim(w, h); + video::IImage *newimg = driver->createImage( + baseimg->getColorFormat(), newdim); + baseimg->copyToScaling(newimg); + baseimg->drop(); + baseimg = newimg; + } + } } /* [resize:WxH diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index d6d2468f5..e83a79f92 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -297,8 +297,11 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.BackfaceCulling = true; + // Enable bi/trilinear filtering only for high resolution textures + bool bilinear_filter = dim.Width > 32 && m_bilinear_filter; + bool trilinear_filter = dim.Width > 32 && m_trilinear_filter; material.forEachTexture([=] (auto &tex) { - setMaterialFilters(tex, m_bilinear_filter, m_trilinear_filter, + setMaterialFilters(tex, bilinear_filter, trilinear_filter, m_anisotropic_filter); }); // mipmaps cause "thin black line" artifacts