mirror of
https://github.com/luanti-org/luanti.git
synced 2025-07-22 17:18:39 +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,
|
# World-aligned textures may be scaled to span several nodes. However,
|
||||||
# the server may not send the scale you want, especially if you use
|
# the server may not send the scale you want, especially if you use
|
||||||
# a specially-designed texture pack; with this option, the client tries
|
# 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.
|
# See also texture_min_size.
|
||||||
# Warning: This option is EXPERIMENTAL!
|
# Warning: This option is EXPERIMENTAL!
|
||||||
autoscale_mode (Autoscaling mode) enum disable disable,enable,force
|
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 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
|
# This is also used as the base node texture size for world-aligned
|
||||||
# texture autoscaling.
|
# 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
|
# Side length of a cube of map blocks that the client will consider together
|
||||||
# when generating meshes.
|
# when generating meshes.
|
||||||
|
|
|
@ -518,6 +518,15 @@ stripping out the file extension:
|
||||||
|
|
||||||
Supported texture formats are PNG (`.png`), JPEG (`.jpg`) and Targa (`.tga`).
|
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
|
Texture modifiers
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,31 @@ void SmoothTranslatorWrappedv3f::translate(f32 dtime)
|
||||||
Other stuff
|
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,
|
static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
|
||||||
float txs, float tys, int col, int row)
|
float txs, float tys, int col, int row)
|
||||||
{
|
{
|
||||||
|
@ -1238,10 +1263,6 @@ void GenericCAO::updateTextures(std::string mod)
|
||||||
{
|
{
|
||||||
ITextureSource *tsrc = m_client->tsrc();
|
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_previous_texture_modifier = m_current_texture_modifier;
|
||||||
m_current_texture_modifier = mod;
|
m_current_texture_modifier = mod;
|
||||||
|
|
||||||
|
@ -1254,12 +1275,7 @@ void GenericCAO::updateTextures(std::string mod)
|
||||||
|
|
||||||
video::SMaterial &material = m_spritenode->getMaterial(0);
|
video::SMaterial &material = m_spritenode->getMaterial(0);
|
||||||
material.MaterialType = m_material_type;
|
material.MaterialType = m_material_type;
|
||||||
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
|
setMaterialTextureAndFilters(material, texturestring, tsrc);
|
||||||
|
|
||||||
material.forEachTexture([=] (auto &tex) {
|
|
||||||
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
|
|
||||||
use_anisotropic_filter);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1273,29 +1289,12 @@ void GenericCAO::updateTextures(std::string mod)
|
||||||
if (texturestring.empty())
|
if (texturestring.empty())
|
||||||
continue; // Empty texture string means don't modify that material
|
continue; // Empty texture string means don't modify that material
|
||||||
texturestring += mod;
|
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
|
// Set material flags and texture
|
||||||
video::SMaterial &material = m_animated_meshnode->getMaterial(i);
|
video::SMaterial &material = m_animated_meshnode->getMaterial(i);
|
||||||
material.MaterialType = m_material_type;
|
material.MaterialType = m_material_type;
|
||||||
material.TextureLayers[0].Texture = texture;
|
|
||||||
material.BackfaceCulling = m_prop.backface_culling;
|
material.BackfaceCulling = m_prop.backface_culling;
|
||||||
|
setMaterialTextureAndFilters(material, texturestring, tsrc);
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1313,13 +1312,7 @@ void GenericCAO::updateTextures(std::string mod)
|
||||||
// Set material flags and texture
|
// Set material flags and texture
|
||||||
video::SMaterial &material = m_meshnode->getMaterial(i);
|
video::SMaterial &material = m_meshnode->getMaterial(i);
|
||||||
material.MaterialType = m_material_type;
|
material.MaterialType = m_material_type;
|
||||||
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
|
setMaterialTextureAndFilters(material, texturestring, tsrc);
|
||||||
material.getTextureMatrix(0).makeIdentity();
|
|
||||||
|
|
||||||
material.forEachTexture([=] (auto &tex) {
|
|
||||||
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
|
|
||||||
use_anisotropic_filter);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) {
|
} else if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) {
|
||||||
scene::IMesh *mesh = m_meshnode->getMesh();
|
scene::IMesh *mesh = m_meshnode->getMesh();
|
||||||
|
@ -1330,12 +1323,7 @@ void GenericCAO::updateTextures(std::string mod)
|
||||||
tname += mod;
|
tname += mod;
|
||||||
|
|
||||||
auto &material = m_meshnode->getMaterial(0);
|
auto &material = m_meshnode->getMaterial(0);
|
||||||
material.setTexture(0, tsrc->getTextureForMesh(tname));
|
setMaterialTextureAndFilters(material, tname, tsrc);
|
||||||
|
|
||||||
material.forEachTexture([=] (auto &tex) {
|
|
||||||
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
|
|
||||||
use_anisotropic_filter);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::string tname = "no_texture.png";
|
std::string tname = "no_texture.png";
|
||||||
|
@ -1346,12 +1334,7 @@ void GenericCAO::updateTextures(std::string mod)
|
||||||
tname += mod;
|
tname += mod;
|
||||||
|
|
||||||
auto &material = m_meshnode->getMaterial(1);
|
auto &material = m_meshnode->getMaterial(1);
|
||||||
material.setTexture(0, tsrc->getTextureForMesh(tname));
|
setMaterialTextureAndFilters(material, tname, tsrc);
|
||||||
|
|
||||||
material.forEachTexture([=] (auto &tex) {
|
|
||||||
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
|
|
||||||
use_anisotropic_filter);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// Set mesh color (only if lighting is disabled)
|
// Set mesh color (only if lighting is disabled)
|
||||||
if (m_prop.glow < 0)
|
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
|
/* 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
|
* 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
|
* 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.
|
* 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 bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
|
||||||
const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1;
|
if (filter) {
|
||||||
if (scaleto > 1) {
|
const f32 scaleto = rangelim(g_settings->getU16("texture_min_size"),
|
||||||
const core::dimension2d<u32> dim = baseimg->getDimension();
|
TEXTURE_FILTER_MIN_SIZE, 16384);
|
||||||
|
|
||||||
/* Calculate scaling needed to make the shortest texture dimension
|
/* Calculate integer-scaling needed to make the shortest texture
|
||||||
* equal to the target minimum. If e.g. this is a vertical frames
|
* dimension equal to the target minimum. If this is e.g. a
|
||||||
* animation, the short dimension will be the real size.
|
* vertical frames animation, the short dimension will be the real size.
|
||||||
*/
|
*/
|
||||||
u32 xscale = scaleto / dim.Width;
|
const auto &dim = baseimg->getDimension();
|
||||||
u32 yscale = scaleto / dim.Height;
|
u32 xscale = std::ceil(scaleto / dim.Width);
|
||||||
|
u32 yscale = std::ceil(scaleto / dim.Height);
|
||||||
const s32 scale = std::max(xscale, yscale);
|
const s32 scale = std::max(xscale, yscale);
|
||||||
|
|
||||||
// Never downscale; only scale up by 2x or more.
|
// 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.MaterialType = m_material_type;
|
||||||
material.MaterialTypeParam = 0.5f;
|
material.MaterialTypeParam = 0.5f;
|
||||||
material.BackfaceCulling = true;
|
material.BackfaceCulling = true;
|
||||||
// Enable bi/trilinear filtering only for high resolution textures
|
// don't filter low-res textures, makes them look blurry
|
||||||
bool bilinear_filter = dim.Width > 32 && m_bilinear_filter;
|
bool f_ok = std::min(dim.Width, dim.Height) >= TEXTURE_FILTER_MIN_SIZE;
|
||||||
bool trilinear_filter = dim.Width > 32 && m_trilinear_filter;
|
|
||||||
material.forEachTexture([=] (auto &tex) {
|
material.forEachTexture([=] (auto &tex) {
|
||||||
setMaterialFilters(tex, bilinear_filter, trilinear_filter,
|
setMaterialFilters(tex, m_bilinear_filter && f_ok,
|
||||||
m_anisotropic_filter);
|
m_trilinear_filter && f_ok, m_anisotropic_filter);
|
||||||
});
|
});
|
||||||
// mipmaps cause "thin black line" artifacts
|
// mipmaps cause "thin black line" artifacts
|
||||||
material.UseMipMaps = false;
|
material.UseMipMaps = false;
|
||||||
|
|
|
@ -99,3 +99,9 @@
|
||||||
#define SCREENSHOT_MAX_SERIAL_TRIES 1000
|
#define SCREENSHOT_MAX_SERIAL_TRIES 1000
|
||||||
|
|
||||||
#define TTF_DEFAULT_FONT_SIZE (16)
|
#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("undersampling", "1");
|
||||||
settings->setDefault("world_aligned_mode", "enable");
|
settings->setDefault("world_aligned_mode", "enable");
|
||||||
settings->setDefault("autoscale_mode", "disable");
|
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("enable_fog", "true");
|
||||||
settings->setDefault("fog_start", "0.4");
|
settings->setDefault("fog_start", "0.4");
|
||||||
settings->setDefault("3d_mode", "none");
|
settings->setDefault("3d_mode", "none");
|
||||||
|
|
|
@ -272,7 +272,8 @@ void TextureSettings::readSettings()
|
||||||
connected_glass = g_settings->getBool("connected_glass");
|
connected_glass = g_settings->getBool("connected_glass");
|
||||||
translucent_liquids = g_settings->getBool("translucent_liquids");
|
translucent_liquids = g_settings->getBool("translucent_liquids");
|
||||||
enable_minimap = g_settings->getBool("enable_minimap");
|
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 leaves_style_str = g_settings->get("leaves_style");
|
||||||
std::string world_aligned_mode_str = g_settings->get("world_aligned_mode");
|
std::string world_aligned_mode_str = g_settings->get("world_aligned_mode");
|
||||||
std::string autoscale_mode_str = g_settings->get("autoscale_mode");
|
std::string autoscale_mode_str = g_settings->get("autoscale_mode");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue