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:
parent
1c5776d13a
commit
4c4e296274
8 changed files with 70 additions and 65 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue