diff --git a/crates/benilla-assets/src/shaders/wow_model.wgsl b/crates/benilla-assets/src/shaders/wow_model.wgsl index 8c538d13..0b61fce9 100644 --- a/crates/benilla-assets/src/shaders/wow_model.wgsl +++ b/crates/benilla-assets/src/shaders/wow_model.wgsl @@ -742,6 +742,14 @@ fn fragment(in: WowVsOut, @builtin(front_facing) is_front: bool) -> WowFragOut { // The unlit path takes the body tint: with GL_LIGHTING off, gx SetState(1) is a plain // `glColor` modulate. var rgb = select(lit_rgb, albedo * inst_tint, is_emissive); + // An M2 Mod or Mod2x batch draws the bare texel: the reference zeroes its tint·M2Color term and + // forces the primary colour to the blend identity (`0x70c507`/`0x70c5b8`), so neither the + // animated M2Color nor the body tint reaches it. Its alpha still does, through the lerp below. + let is_mod = (u32(m.clutter_fade.z) & 128u) != 0u; + let is_mod2x = (u32(m.clutter_fade.z) & 256u) != 0u; + if ((is_mod || is_mod2x) && !is_wmo) { + rgb = base.rgb; + } // Linear fog by planar eye depth, as in terrain.wgsl. Per-batch colour policy (`clutter_fade.z` // bits 4-6, the M2 state setter `0x70baf0`): 0 scene, 1 black (additive), 2 white (Mod), @@ -783,8 +791,6 @@ fn fragment(in: WowVsOut, @builtin(front_facing) is_front: bool) -> WowFragOut { // reference: texenv preset 5, `mix(prev.rgb, tex.rgb, prev.a)`, with the primary colour forced // to the blend identity (white, or 0.5 grey for Mod2x) and prev.a the instance alpha. The fog // above commutes with this because its white and grey are that identity. - let is_mod = (u32(m.clutter_fade.z) & 128u) != 0u; - let is_mod2x = (u32(m.clutter_fade.z) & 256u) != 0u; if (is_mod || is_mod2x) { let identity = select(vec3(1.0), vec3(0.5), is_mod2x); out_rgb = mix(identity, out_rgb, obj_fade); diff --git a/crates/benilla-formats/src/models/m2_batches.rs b/crates/benilla-formats/src/models/m2_batches.rs index a9c1d111..ad47bbf6 100644 --- a/crates/benilla-formats/src/models/m2_batches.rs +++ b/crates/benilla-formats/src/models/m2_batches.rs @@ -656,6 +656,9 @@ pub fn parse_m2_render_submeshes( // fade, so folding the (possibly animated) M2Color alpha in here too would double-count. let color_tint: Option<[f32; 4]> = match &rgb_anim { Some(_) => None, + // Mod and Mod2x discard the M2Color RGB (`0x70c507`/`0x70c5b8` zero the tint·M2Color + // term), so their vertex colours stay untinted. + None if matches!(blend, ModelBlend::Mod | ModelBlend::Mod2x) => None, None => model .color_rgb_tracks .get(batch.color_index as usize)