1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +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

@ -2106,7 +2106,7 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc
# World-aligned textures may be scaled to span several nodes. However,
# the server may not send the scale you want, especially if you use
# a specially-designed texture pack; with this option, the client tries
# to determine the scale automatically basing on the texture size.
# to determine the scale automatically based on the texture size.
# See also texture_min_size.
# Warning: This option is EXPERIMENTAL!
autoscale_mode (Autoscaling mode) enum disable disable,enable,force
@ -2118,7 +2118,7 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# This setting is ONLY applied if any of the mentioned filters are 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
texture_min_size (Base texture size) int 192 192 16384
# Side length of a cube of map blocks that the client will consider together
# when generating meshes.

View file

@ -518,6 +518,15 @@ stripping out the file extension:
Supported texture formats are PNG (`.png`), JPEG (`.jpg`) and Targa (`.tga`).
Luanti generally uses nearest-neighbor upscaling for textures to preserve the crisp
look of pixel art (low-res textures).
Users can optionally enable bilinear and/or trilinear filtering. However, to avoid
everything becoming blurry, textures smaller than 192px will either not be filtered,
or will be upscaled to that minimum resolution first without filtering.
This is subject to change to move more control to the Lua API, but you can rely on
low-res textures not suddenly becoming filtered.
Texture modifiers
-----------------

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");