Reduce weathering decal strength and mip-filter clamped decals

This commit is contained in:
chazmm committed 2026-09-08 19:27:12 +01:00
1 parent 151a69d619
commit 79ea829ca9
5 files changed
+53 -7

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@
#ifdef MOTION_VECTOR_PREPASS
#import bevy_pbr::pbr_prepass_functions::calculate_motion_vector
#endif
struct WorldParams { mode: vec4<f32>, foliage_debug: vec4<f32>, surface: vec4<f32>, family: vec4<f32>, fog_ramp: vec4<f32>, fog_color: vec4<f32>, shadow_color: vec4<f32>, sun_direction: vec4<f32>, water: array<vec4<f32>, 4> }
struct WorldParams { mode: vec4<f32>, foliage_debug: vec4<f32>, surface: vec4<f32>, family: vec4<f32>, fog_ramp: vec4<f32>, fog_color: vec4<f32>, shadow_color: vec4<f32>, sun_direction: vec4<f32>, decal: vec4<f32>, water: array<vec4<f32>, 4> }
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> p: WorldParams;
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(2) var diffuse_sampler: sampler;
+28 -2
View File
@@ -47,7 +47,7 @@ fn compare_weathering(
1 => "Repeating grime OFF; decals ON",
2 => "Repeating grime ON; decals OFF",
3 => "Repeating grime OFF; decals OFF",
_ => "Authored grime and decals ON",
_ => "Normal grime and decals ON",
};
for (_, material) in materials.iter_mut() {
let family = material.params.mode.x as u32;
@@ -98,6 +98,8 @@ pub(crate) struct WorldParams {
pub fog_color: Vec4,
pub shadow_color: Vec4,
pub sun_direction: Vec4,
// Wear-only visual tuning; artwork retains unit opacity.
pub decal: Vec4,
pub water: [Vec4; 4],
}
@@ -270,7 +272,7 @@ impl Definition {
texture(
b.map_or(fallback, |b| b.texture),
if clamp || b.is_some_and(|b| b.u == 1 && b.v == 1) {
4
if role == "decal" { 6 } else { 4 }
} else {
3
},
@@ -339,6 +341,7 @@ impl Definition {
fog_color: Vec4::ZERO,
shadow_color: Vec4::ZERO,
sun_direction: Vec3::new(4., 7., 4.).normalize().extend(0.),
decal: Vec4::new(stain_opacity(self.parameters.get("decal").and_then(|v| v.first()).map(String::as_str).unwrap_or("")), 0., 0., 0.),
water,
},
diffuse,
@@ -356,6 +359,29 @@ impl Definition {
}
}
// The source labels distinguish weathering from graphics. This is an explicit
// visual tuning choice, not a recovered native material constant. Keep arrows,
// logos, paint, scratches and edge wear at their authored alpha.
fn stain_opacity(texture_label: &str) -> f32 {
let label = texture_label.to_ascii_lowercase();
if ["grime", "grunge", "stain", "oildirt", "drainage", "ground_decals"]
.iter().any(|word| label.contains(word)) { 0.35 } else { 1. }
}
#[cfg(test)]
mod stain_tests {
use super::stain_opacity;
#[test]
fn weathering_tuning_preserves_artwork() {
for name in ["decal_WEAR_WaterStain_01", "subway_grunge03", "decal_GrimePuddle", "OT_Ground_decals"] {
assert_eq!(stain_opacity(name), 0.35);
}
for name in ["decal_Graphic_SP_UN_Shark_01", "decal_other_sp_arrowramps_01", "decal_Graphic_SP_UN_MegaRmp_01", "decal_Wear_GL_UN_MPedge_01", "decal_skateboardscratcheswood01", ""] {
assert_eq!(stain_opacity(name), 1.);
}
}
}
/// Native renderer box-filters decoded UNORM cube faces down to 1x1.
/// The same chain prevents aliasing of repeating world maps at distance.
pub(crate) fn mip_chain(rgba: &[u8], width: u32, height: u32, layers: u32) -> (Vec<u8>, u32) {
+2 -2
View File
@@ -3,7 +3,7 @@
struct WorldParams {
mode: vec4<f32>, foliage_debug: vec4<f32>, surface: vec4<f32>, family: vec4<f32>,
fog_ramp: vec4<f32>, fog_color: vec4<f32>, shadow_color: vec4<f32>, sun_direction: vec4<f32>, water: array<vec4<f32>, 4>,
fog_ramp: vec4<f32>, fog_color: vec4<f32>, shadow_color: vec4<f32>, sun_direction: vec4<f32>, decal: vec4<f32>, water: array<vec4<f32>, 4>,
}
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> p: WorldParams;
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d<f32>;
@@ -174,7 +174,7 @@ fn fragment(i: VertexOutput) -> @location(0) vec4<f32> {
lin = d;
if fam == 11u { lin *= p.family.w; }
} else {
if (fam == 3u || fam == 4u) && (flags & 8u) != 0u && (flags & 512u) == 0u { d = mix(d,art.rgb*art.rgb,art.a); }
if (fam == 3u || fam == 4u) && (flags & 8u) != 0u && (flags & 512u) == 0u { d = mix(d,art.rgb*art.rgb,art.a*p.decal.x); }
if (flags & 4u) != 0u && fam < 13u && (flags & 256u) == 0u { d *= saturate((overlay_sample-0.5)*p.surface.y+0.5); }
var kd = 0.93429;
if (fam <= 6u || fam == 13u) && (flags & 1u) != 0u {
+2 -2
View File
@@ -481,7 +481,7 @@ pub(crate) fn spawn(
format,
RenderAssetUsages::RENDER_WORLD,
);
if role == 3 || cube {
if role == 3 || role == 6 || cube {
let (bytes, levels) = crate::retail_render::mip_chain(&source.rgba, source.width, height, layers);
image.data = Some(bytes);
image.texture_descriptor.mip_level_count = levels;
@@ -492,7 +492,7 @@ pub(crate) fn spawn(
});
}
let mut sampler = bevy::image::ImageSamplerDescriptor::linear();
if role != 1 && role != 4 && !cube {
if role != 1 && role != 4 && role != 6 && !cube {
sampler.address_mode_u = bevy::image::ImageAddressMode::Repeat;
sampler.address_mode_v = bevy::image::ImageAddressMode::Repeat;
}
+20
View File
@@ -1,5 +1,25 @@
# Authored props and grime follow-up
## Targeted decal correction after layer test
The user rechecked the comparison and identified decals as the unwanted layer.
Repeating grime is unchanged. Wear/stain decals now use 35% of their texture's
alpha; source UVs, dimensions, texture RGB and placement are retained. This is
explicit visual tuning based on the user's feedback, not proof of the original
game's intended opacity and not a recovered native shader constant.
Classification uses the material's authored `decal` texture label for grime,
grunge, stain, oil-dirt, drainage and ground-decal entries. Arrows, logos,
scratches and ramp edge paint retain full authored opacity. A regression test
covers this separation. This is not a blanket fade on every decal material.
Clamped decals also now receive a mip chain, independently of lightmaps, which
remain bilinear mip zero. Previously the shared clamp texture role omitted mips
for both, leaving fine decal alpha detail unfiltered at distance. The new decal
role retains clamp addressing while permitting mip filtering. No map re-export
or owned texture modification is needed. GPU visual verification remains for
the user; source and shader checks cannot confirm the preferred visual strength.
## Grime investigation after user retest
The UV-scale correction did not resolve the user's reported visual mismatch.