1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-09-15 18:57:08 +00:00

Handle texture filtering sanely to avoid blurriness (#16034)

This commit is contained in:
sfan5 2025-04-21 12:31:44 +02:00 committed by GitHub
parent 1c5776d13a
commit 4c4e296274
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 70 additions and 65 deletions

View file

@ -144,6 +144,31 @@ void SmoothTranslatorWrappedv3f::translate(f32 dtime)
Other stuff
*/
static bool setMaterialTextureAndFilters(video::SMaterial &material,
const std::string &texturestring, ITextureSource *tsrc)
{
bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");
video::ITexture *texture = tsrc->getTextureForMesh(texturestring);
if (!texture)
return false;
material.setTexture(0, texture);
// don't filter low-res textures, makes them look blurry
const core::dimension2d<u32> &size = texture->getOriginalSize();
if (std::min(size.Width, size.Height) < TEXTURE_FILTER_MIN_SIZE)
use_trilinear_filter = use_bilinear_filter = false;
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
return true;
}
static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
float txs, float tys, int col, int row)
{
@ -1238,10 +1263,6 @@ void GenericCAO::updateTextures(std::string mod)
{
ITextureSource *tsrc = m_client->tsrc();
bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");
m_previous_texture_modifier = m_current_texture_modifier;
m_current_texture_modifier = mod;
@ -1254,12 +1275,7 @@ void GenericCAO::updateTextures(std::string mod)
video::SMaterial &material = m_spritenode->getMaterial(0);
material.MaterialType = m_material_type;
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, texturestring, tsrc);
}
}
@ -1273,29 +1289,12 @@ void GenericCAO::updateTextures(std::string mod)
if (texturestring.empty())
continue; // Empty texture string means don't modify that material
texturestring += mod;
video::ITexture *texture = tsrc->getTextureForMesh(texturestring);
if (!texture) {
errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl;
continue;
}
// Set material flags and texture
video::SMaterial &material = m_animated_meshnode->getMaterial(i);
material.MaterialType = m_material_type;
material.TextureLayers[0].Texture = texture;
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<u32> &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);
});
setMaterialTextureAndFilters(material, texturestring, tsrc);
}
}
}
@ -1313,13 +1312,7 @@ void GenericCAO::updateTextures(std::string mod)
// Set material flags and texture
video::SMaterial &material = m_meshnode->getMaterial(i);
material.MaterialType = m_material_type;
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
material.getTextureMatrix(0).makeIdentity();
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, texturestring, tsrc);
}
} else if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) {
scene::IMesh *mesh = m_meshnode->getMesh();
@ -1330,12 +1323,7 @@ void GenericCAO::updateTextures(std::string mod)
tname += mod;
auto &material = m_meshnode->getMaterial(0);
material.setTexture(0, tsrc->getTextureForMesh(tname));
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, tname, tsrc);
}
{
std::string tname = "no_texture.png";
@ -1346,12 +1334,7 @@ void GenericCAO::updateTextures(std::string mod)
tname += mod;
auto &material = m_meshnode->getMaterial(1);
material.setTexture(0, tsrc->getTextureForMesh(tname));
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, tname, tsrc);
}
// Set mesh color (only if lighting is disabled)
if (m_prop.glow < 0)

View file

@ -1479,21 +1479,28 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
/* 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
* 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.
*
* Q: why not just enable/disable filtering depending on texture size?
* A: large texture resolutions apparently allow getting rid of the Moire
* effect way better than anisotropic filtering alone could.
* see <https://github.com/luanti-org/luanti/issues/15604> and related
* linked discussions.
*/
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<u32> dim = baseimg->getDimension();
if (filter) {
const f32 scaleto = rangelim(g_settings->getU16("texture_min_size"),
TEXTURE_FILTER_MIN_SIZE, 16384);
/* 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.
/* Calculate integer-scaling needed to make the shortest texture
* dimension equal to the target minimum. If this is e.g. a
* vertical frames animation, the short dimension will be the real size.
*/
u32 xscale = scaleto / dim.Width;
u32 yscale = scaleto / dim.Height;
const auto &dim = baseimg->getDimension();
u32 xscale = std::ceil(scaleto / dim.Width);
u32 yscale = std::ceil(scaleto / dim.Height);
const s32 scale = std::max(xscale, yscale);
// Never downscale; only scale up by 2x or more.

View file

@ -281,12 +281,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;
// don't filter low-res textures, makes them look blurry
bool f_ok = std::min(dim.Width, dim.Height) >= TEXTURE_FILTER_MIN_SIZE;
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, bilinear_filter, trilinear_filter,
m_anisotropic_filter);
setMaterialFilters(tex, m_bilinear_filter && f_ok,
m_trilinear_filter && f_ok, m_anisotropic_filter);
});
// mipmaps cause "thin black line" artifacts
material.UseMipMaps = false;

View file

@ -99,3 +99,9 @@
#define SCREENSHOT_MAX_SERIAL_TRIES 1000
#define TTF_DEFAULT_FONT_SIZE (16)
// Minimum texture size enforced/checked for enabling linear filtering
// This serves as the minimum for `texture_min_size`.
// The intent is to ensure that the rendering doesn't turn terribly blurry
// when filtering is enabled.
#define TEXTURE_FILTER_MIN_SIZE 192U

View file

@ -246,7 +246,7 @@ void set_default_settings()
settings->setDefault("undersampling", "1");
settings->setDefault("world_aligned_mode", "enable");
settings->setDefault("autoscale_mode", "disable");
settings->setDefault("texture_min_size", "64");
settings->setDefault("texture_min_size", std::to_string(TEXTURE_FILTER_MIN_SIZE));
settings->setDefault("enable_fog", "true");
settings->setDefault("fog_start", "0.4");
settings->setDefault("3d_mode", "none");

View file

@ -272,7 +272,8 @@ void TextureSettings::readSettings()
connected_glass = g_settings->getBool("connected_glass");
translucent_liquids = g_settings->getBool("translucent_liquids");
enable_minimap = g_settings->getBool("enable_minimap");
node_texture_size = std::max<u16>(g_settings->getU16("texture_min_size"), 1);
node_texture_size = rangelim(g_settings->getU16("texture_min_size"),
TEXTURE_FILTER_MIN_SIZE, 16384);
std::string leaves_style_str = g_settings->get("leaves_style");
std::string world_aligned_mode_str = g_settings->get("world_aligned_mode");
std::string autoscale_mode_str = g_settings->get("autoscale_mode");