From c35fc21f345ac9ecf9017115c4481a1147d05206 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 21:35:44 +0100 Subject: Chams: real additive material, addref cached materials Testing on :99 showed ADDITIVE on debugtranslucentsinglecolor did nothing and alpha lost the colour, because that VMT has no $translucent and the engine bound an unreferenced material. Use debug/debugtranslucentmodelhulls (VMT has $additive 1 + $color) for Additive/Fullbright, and addref every preloaded material so the engine stops warning "bad reference count 0" and applies $color/ColorModulate. Verified: Solid is flat blue, Additive is a translucent coloured glow, Fullbright is a white additive glow, White is opaque white. Also drop the viewmodel additive base (the solid material is opaque, so it made an opaque blob) and never call FindMaterial inside DrawModelExecute. Document that the alpha control is stored but has no effect until a custom $translucent model VMT is added. --- CONTEXT.md | 41 ++++++++++++++++++++++++----------------- src/studio.cpp | 46 +++++++++++++--------------------------------- 2 files changed, 37 insertions(+), 50 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index bfb0d8e..5b96c33 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -294,31 +294,38 @@ vocabulary / decisions. alpha only. The through-wall pass uses `MATERIAL_VAR_IGNOREZ` (1 << 15) through `IMaterial::SetMaterialVarFlag` (slot 29); colours use `$color`/`$color2` (IMaterialVar slot 11, SetVecValue 3-float slot 11) and - `ColorModulate` (slot 28). Alpha uses `$alpha` (IMaterialVar slot 3, - SetFloatValue) and `MATERIAL_VAR_TRANSLUCENT` (1 << 21) when alpha < 255. - Shared materials are restored after each draw. + `ColorModulate` (slot 28). `$alpha` is set through IMaterialVar slot 3 + (SetFloatValue). Shared materials are restored after each draw. - The GUI colour picker (`add_color_alpha`) has an alpha bar, checkerboard preview and `#RRGGBBAA` readout; plain `add_color` keeps the old opaque three-component behaviour. Chams colours are four ints (r, g, b, a). -- Styles are built from `debug/debugtranslucentsinglecolor` (UnlitGeneric, - `$color`) and material flags: - 0 Flat (solid + FLAT), 1 Wireframe (`debug/debugwireframe`), 2 Additive - (+ ADDITIVE), 3 Solid, 4 White (colour forced white), 5 Fullbright - (ADDITIVE|SELFILLUM, colour forced white so it reads as a white-hot glow - instead of matching Solid), 6 Glow outline - (solid plus a `debug/debugwireframe` contour pass drawn with ZNEARER - (1 << 10) and a brightened colour so the base cannot bury it). +- **Alpha is stored but has no visual effect yet.** The usable model + materials are opaque: `debug/debugtranslucentsinglecolor` has no + `$translucent`, and toggling `MATERIAL_VAR_TRANSLUCENT` (1 << 21) or + rebuilding the state snapshot does not change the static shader combo. + `debug/debugtranslucentvertexcolor`/`debug/debughitbox` are translucent but + need vertex colours and render nothing on skinned models. A custom VMT with + `$model 1` + `$translucent 1` (or a white basetexture) is needed to make the + alpha control real. +- Styles: 0 Flat (`debug/debugtranslucentsinglecolor` + FLAT), 1 Wireframe + (`debug/debugwireframe`), 2 Additive + (`debug/debugtranslucentmodelhulls`, whose VMT has `$additive 1` + `$color`; + the ADDITIVE flag on the solid material did nothing), 3 Solid, 4 White + (solid, colour forced white), 5 Fullbright (the additive material, colour + forced white, giving a white-hot glow), 6 Glow outline (solid plus a + `debug/debugwireframe` contour pass drawn with ZNEARER (1 << 10) and a + brightened colour so the base cannot bury it). `vgui/white` is vertex-colour based and has no `$model`/`$basetexture`, so it is never used for chams; `models/debug/debugwhite` is EP2-only and the old VGUI additive material was invisible on NPCs. - Style materials are preloaded from `outer_draw` (throttled) and cached; the render hook never calls FindMaterial itself. A missing style uses solid. -- Viewmodels are collated by the engine as `RENDER_GROUP_VIEW_MODEL_OPAQUE`, - so an additive forced material can wash out against a bright sky or wall - (and the engine may not treat the draw as translucent). Additive styles on - the viewmodel therefore draw a faint translucent base (alpha/8) first, then - the additive pass, so the model keeps a silhouette on bright backgrounds - without becoming an opaque blob. + `FindMaterial` can return an unreferenced material, so each cached material + is addref'd (`IncrementReferenceCount`, IMaterial slot 12); without this the + engine warns "bad reference count 0 when being bound" and the material's + `$color`/`ColorModulate` changes are ignored. +- Additive styles still wash out against a bright sky or wall (inherent to + additive blending). - Feature is off by default. Config sections: `[chams]` (active/bind), `[chams_hostile|neutral|friendly]` (`vis`, `vis_mat`, `vis_r/g/b/a`, `inv`, `inv_mat`, `inv_r/g/b/a`) and `[chams_vm]` (`on`, `mat`, `r/g/b/a`). The GUI diff --git a/src/studio.cpp b/src/studio.cpp index 086d213..cf145d1 100644 --- a/src/studio.cpp +++ b/src/studio.cpp @@ -199,20 +199,21 @@ ModelExecFn g_orig_model_exec = nullptr; ForceOverrideFn g_force_override = nullptr; void *g_style_mats[7] = {}; -void *viewmodel_material(); - const char *chams_material_name(int idx) { switch (idx) { case 1: return "debug/debugwireframe"; case 2: + case 5: return "debug/debugtranslucentmodelhulls"; case 3: case 4: - case 5: case 6: default: return "debug/debugtranslucentsinglecolor"; } } +// Never call FindMaterial here: this runs inside DrawModelExecute, where +// loading a material can crash the material system. chams_preload fills the +// cache from outer_draw instead. void *style_material(int idx) { if (idx < 0) idx = 0; if (idx > 6) idx = 6; @@ -220,17 +221,7 @@ void *style_material(int idx) { return g_style_mats[idx]; if (g_style_mats[3]) return g_style_mats[3]; - return viewmodel_material(); -} - -// Viewmodels need a model shader; vgui materials do not draw skinned models. -void *viewmodel_material() { - if (g_style_mats[3]) - return g_style_mats[3]; - static void *mat = nullptr; - if (!mat) - mat = world_find_material("debug/debugtranslucentsinglecolor"); - return mat; + return nullptr; } void chams_color(void *mat, float r, float g, float b) { @@ -260,6 +251,7 @@ void chams_alpha(void *mat, float a) { void chams_apply(void *mat, bool ignorez, int style, bool force_white, const int *rgb, bool znearer) { using FlagFn = void (*)(void *, int, bool); + bool additive = style == 2 || style == 5; float r = force_white ? 1.0f : (float)rgb[0] / 255.0f; float g = force_white ? 1.0f : (float)rgb[1] / 255.0f; float b = force_white ? 1.0f : (float)rgb[2] / 255.0f; @@ -268,12 +260,12 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white, // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright // (additive white glow), 6 Glow outline (solid base; the wireframe overlay // is a second draw with ZNEARER so its contour is not hidden by the base). + // Styles 2/5 use debug/debugtranslucentmodelhulls, whose VMT already has + // $additive; ADDITIVE on debugtranslucentsinglecolor did nothing. vfunc(mat, 29)(mat, 1 << 15 /* IGNOREZ */, ignorez); - vfunc(mat, 29)(mat, 1 << 21 /* TRANSLUCENT */, rgb[3] < 255); vfunc(mat, 29)(mat, 1 << 10 /* ZNEARER */, znearer); vfunc(mat, 29)(mat, 1 << 28 /* WIREFRAME */, style == 1); - vfunc(mat, 29)(mat, 1 << 7 /* ADDITIVE */, - style == 2 || style == 5); + vfunc(mat, 29)(mat, 1 << 7 /* ADDITIVE */, additive); vfunc(mat, 29)(mat, 1 << 12 /* FLAT */, style == 0); vfunc(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); } @@ -281,7 +273,6 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white, void chams_reset(void *mat) { using FlagFn = void (*)(void *, int, bool); vfunc(mat, 29)(mat, 1 << 15, false); - vfunc(mat, 29)(mat, 1 << 21, false); vfunc(mat, 29)(mat, 1 << 10, false); vfunc(mat, 29)(mat, 1 << 28, false); vfunc(mat, 29)(mat, 1 << 7, false); @@ -317,21 +308,6 @@ void hkdModelExecute(void *self, const void *state, const void *info, return false; bool white = style == 4 || style == 5; const int *use_rgb = rgb; - // Additive blending vanishes against bright backgrounds (sky, - // white walls, strong fog), which made the viewmodel disappear in - // some places. Lay down a faint translucent base so the first-person - // model keeps a silhouette on bright backgrounds without turning - // into an opaque blob; the additive pass still provides the glow. - if (is_vm && (style == 2 || style == 5) && rgb[3] > 0) { - int ba = rgb[3] / 8; - if (ba < 16) - ba = 16; - int drgb[4] = {rgb[0], rgb[1], rgb[2], ba}; - chams_apply(mat, ignorez, 3, false, drgb, false); - g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); - g_orig_model_exec(self, state, info, bone); - chams_reset(mat); - } chams_apply(mat, ignorez, style, white, use_rgb, false); g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); g_orig_model_exec(self, state, info, bone); @@ -391,6 +367,10 @@ void chams_preload() { continue; void *m = world_find_material(chams_material_name(i)); if (m) { + // FindMaterial can hand back an unreferenced material; the engine warns + // ("bad reference count 0") and may reset its vars when it is bound. + using RefFn = void (*)(void *); + vfunc(m, 12)(m); // IMaterial::IncrementReferenceCount g_style_mats[i] = m; logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m); } -- cgit v1.2.3