render: M2 Mod and Mod2x batches draw the bare texel, as the reference does

For blend modes 5 and 6 the reference zeroes the tint·M2Color term and
forces the primary colour to the blend identity (0x70c507/0x70c5b8),
keeping only the alpha. benilla multiplied the batch's M2Color (baked
into its vertex colours, or animated through the material tint) and the
body tint into the texel. Mod and Mod2x batches now bake no M2Color, and
the shader draws them from the bare texel; the alpha still reaches them
through the identity lerp.

Of the install's 615 Mod and Mod2x batches, four carry a non-white
M2Color and none an animated one. The Scholomance testing jars (empty,
ghoul and skeleton) lose the (0.93, 1.00, 0.54) cast on their Mod layer,
and Offhand_Naxxramas_D_01's Mod2x sheen loses its (1.00, 0.95, 0.85)
warmth.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-23 21:36:29 +07:00
co-authored by Claude Opus 5.5
parent cbc5a37da0
commit 87d588cf68
2 changed files with 11 additions and 2 deletions
@@ -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<f32>(1.0), vec3<f32>(0.5), is_mod2x);
out_rgb = mix(identity, out_rgb, obj_fade);
@@ -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)