summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 21:35:44 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 21:35:44 +0100
commitc35fc21f345ac9ecf9017115c4481a1147d05206 (patch)
tree6907a027a097aec5af994b5bdcb20dddce6e984a
parentChams: make Fullbright a white-hot additive glow (diff)
downloadhl2h-c35fc21f345ac9ecf9017115c4481a1147d05206.tar.gz
hl2h-c35fc21f345ac9ecf9017115c4481a1147d05206.zip
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.
-rw-r--r--CONTEXT.md41
-rw-r--r--src/studio.cpp46
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.
294 alpha only. The through-wall pass uses `MATERIAL_VAR_IGNOREZ` (1 << 15) 294 alpha only. The through-wall pass uses `MATERIAL_VAR_IGNOREZ` (1 << 15)
295 through `IMaterial::SetMaterialVarFlag` (slot 29); colours use 295 through `IMaterial::SetMaterialVarFlag` (slot 29); colours use
296 `$color`/`$color2` (IMaterialVar slot 11, SetVecValue 3-float slot 11) and 296 `$color`/`$color2` (IMaterialVar slot 11, SetVecValue 3-float slot 11) and
297 `ColorModulate` (slot 28). Alpha uses `$alpha` (IMaterialVar slot 3, 297 `ColorModulate` (slot 28). `$alpha` is set through IMaterialVar slot 3
298 SetFloatValue) and `MATERIAL_VAR_TRANSLUCENT` (1 << 21) when alpha < 255. 298 (SetFloatValue). Shared materials are restored after each draw.
299 Shared materials are restored after each draw.
300- The GUI colour picker (`add_color_alpha`) has an alpha bar, checkerboard 299- The GUI colour picker (`add_color_alpha`) has an alpha bar, checkerboard
301 preview and `#RRGGBBAA` readout; plain `add_color` keeps the old opaque 300 preview and `#RRGGBBAA` readout; plain `add_color` keeps the old opaque
302 three-component behaviour. Chams colours are four ints (r, g, b, a). 301 three-component behaviour. Chams colours are four ints (r, g, b, a).
303- Styles are built from `debug/debugtranslucentsinglecolor` (UnlitGeneric, 302- **Alpha is stored but has no visual effect yet.** The usable model
304 `$color`) and material flags: 303 materials are opaque: `debug/debugtranslucentsinglecolor` has no
305 0 Flat (solid + FLAT), 1 Wireframe (`debug/debugwireframe`), 2 Additive 304 `$translucent`, and toggling `MATERIAL_VAR_TRANSLUCENT` (1 << 21) or
306 (+ ADDITIVE), 3 Solid, 4 White (colour forced white), 5 Fullbright 305 rebuilding the state snapshot does not change the static shader combo.
307 (ADDITIVE|SELFILLUM, colour forced white so it reads as a white-hot glow 306 `debug/debugtranslucentvertexcolor`/`debug/debughitbox` are translucent but
308 instead of matching Solid), 6 Glow outline 307 need vertex colours and render nothing on skinned models. A custom VMT with
309 (solid plus a `debug/debugwireframe` contour pass drawn with ZNEARER 308 `$model 1` + `$translucent 1` (or a white basetexture) is needed to make the
310 (1 << 10) and a brightened colour so the base cannot bury it). 309 alpha control real.
310- Styles: 0 Flat (`debug/debugtranslucentsinglecolor` + FLAT), 1 Wireframe
311 (`debug/debugwireframe`), 2 Additive
312 (`debug/debugtranslucentmodelhulls`, whose VMT has `$additive 1` + `$color`;
313 the ADDITIVE flag on the solid material did nothing), 3 Solid, 4 White
314 (solid, colour forced white), 5 Fullbright (the additive material, colour
315 forced white, giving a white-hot glow), 6 Glow outline (solid plus a
316 `debug/debugwireframe` contour pass drawn with ZNEARER (1 << 10) and a
317 brightened colour so the base cannot bury it).
311 `vgui/white` is vertex-colour based and has no `$model`/`$basetexture`, so it 318 `vgui/white` is vertex-colour based and has no `$model`/`$basetexture`, so it
312 is never used for chams; `models/debug/debugwhite` is EP2-only and the old 319 is never used for chams; `models/debug/debugwhite` is EP2-only and the old
313 VGUI additive material was invisible on NPCs. 320 VGUI additive material was invisible on NPCs.
314- Style materials are preloaded from `outer_draw` (throttled) and cached; the 321- Style materials are preloaded from `outer_draw` (throttled) and cached; the
315 render hook never calls FindMaterial itself. A missing style uses solid. 322 render hook never calls FindMaterial itself. A missing style uses solid.
316- Viewmodels are collated by the engine as `RENDER_GROUP_VIEW_MODEL_OPAQUE`, 323 `FindMaterial` can return an unreferenced material, so each cached material
317 so an additive forced material can wash out against a bright sky or wall 324 is addref'd (`IncrementReferenceCount`, IMaterial slot 12); without this the
318 (and the engine may not treat the draw as translucent). Additive styles on 325 engine warns "bad reference count 0 when being bound" and the material's
319 the viewmodel therefore draw a faint translucent base (alpha/8) first, then 326 `$color`/`ColorModulate` changes are ignored.
320 the additive pass, so the model keeps a silhouette on bright backgrounds 327- Additive styles still wash out against a bright sky or wall (inherent to
321 without becoming an opaque blob. 328 additive blending).
322- Feature is off by default. Config sections: `[chams]` (active/bind), 329- Feature is off by default. Config sections: `[chams]` (active/bind),
323 `[chams_hostile|neutral|friendly]` (`vis`, `vis_mat`, `vis_r/g/b/a`, `inv`, 330 `[chams_hostile|neutral|friendly]` (`vis`, `vis_mat`, `vis_r/g/b/a`, `inv`,
324 `inv_mat`, `inv_r/g/b/a`) and `[chams_vm]` (`on`, `mat`, `r/g/b/a`). The GUI 331 `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;
199ForceOverrideFn g_force_override = nullptr; 199ForceOverrideFn g_force_override = nullptr;
200void *g_style_mats[7] = {}; 200void *g_style_mats[7] = {};
201 201
202void *viewmodel_material();
203
204const char *chams_material_name(int idx) { 202const char *chams_material_name(int idx) {
205 switch (idx) { 203 switch (idx) {
206 case 1: return "debug/debugwireframe"; 204 case 1: return "debug/debugwireframe";
207 case 2: 205 case 2:
206 case 5: return "debug/debugtranslucentmodelhulls";
208 case 3: 207 case 3:
209 case 4: 208 case 4:
210 case 5:
211 case 6: 209 case 6:
212 default: return "debug/debugtranslucentsinglecolor"; 210 default: return "debug/debugtranslucentsinglecolor";
213 } 211 }
214} 212}
215 213
214// Never call FindMaterial here: this runs inside DrawModelExecute, where
215// loading a material can crash the material system. chams_preload fills the
216// cache from outer_draw instead.
216void *style_material(int idx) { 217void *style_material(int idx) {
217 if (idx < 0) idx = 0; 218 if (idx < 0) idx = 0;
218 if (idx > 6) idx = 6; 219 if (idx > 6) idx = 6;
@@ -220,17 +221,7 @@ void *style_material(int idx) {
220 return g_style_mats[idx]; 221 return g_style_mats[idx];
221 if (g_style_mats[3]) 222 if (g_style_mats[3])
222 return g_style_mats[3]; 223 return g_style_mats[3];
223 return viewmodel_material(); 224 return nullptr;
224}
225
226// Viewmodels need a model shader; vgui materials do not draw skinned models.
227void *viewmodel_material() {
228 if (g_style_mats[3])
229 return g_style_mats[3];
230 static void *mat = nullptr;
231 if (!mat)
232 mat = world_find_material("debug/debugtranslucentsinglecolor");
233 return mat;
234} 225}
235 226
236void chams_color(void *mat, float r, float g, float b) { 227void chams_color(void *mat, float r, float g, float b) {
@@ -260,6 +251,7 @@ void chams_alpha(void *mat, float a) {
260void chams_apply(void *mat, bool ignorez, int style, bool force_white, 251void chams_apply(void *mat, bool ignorez, int style, bool force_white,
261 const int *rgb, bool znearer) { 252 const int *rgb, bool znearer) {
262 using FlagFn = void (*)(void *, int, bool); 253 using FlagFn = void (*)(void *, int, bool);
254 bool additive = style == 2 || style == 5;
263 float r = force_white ? 1.0f : (float)rgb[0] / 255.0f; 255 float r = force_white ? 1.0f : (float)rgb[0] / 255.0f;
264 float g = force_white ? 1.0f : (float)rgb[1] / 255.0f; 256 float g = force_white ? 1.0f : (float)rgb[1] / 255.0f;
265 float b = force_white ? 1.0f : (float)rgb[2] / 255.0f; 257 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,
268 // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright 260 // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright
269 // (additive white glow), 6 Glow outline (solid base; the wireframe overlay 261 // (additive white glow), 6 Glow outline (solid base; the wireframe overlay
270 // is a second draw with ZNEARER so its contour is not hidden by the base). 262 // is a second draw with ZNEARER so its contour is not hidden by the base).
263 // Styles 2/5 use debug/debugtranslucentmodelhulls, whose VMT already has
264 // $additive; ADDITIVE on debugtranslucentsinglecolor did nothing.
271 vfunc<FlagFn>(mat, 29)(mat, 1 << 15 /* IGNOREZ */, ignorez); 265 vfunc<FlagFn>(mat, 29)(mat, 1 << 15 /* IGNOREZ */, ignorez);
272 vfunc<FlagFn>(mat, 29)(mat, 1 << 21 /* TRANSLUCENT */, rgb[3] < 255);
273 vfunc<FlagFn>(mat, 29)(mat, 1 << 10 /* ZNEARER */, znearer); 266 vfunc<FlagFn>(mat, 29)(mat, 1 << 10 /* ZNEARER */, znearer);
274 vfunc<FlagFn>(mat, 29)(mat, 1 << 28 /* WIREFRAME */, style == 1); 267 vfunc<FlagFn>(mat, 29)(mat, 1 << 28 /* WIREFRAME */, style == 1);
275 vfunc<FlagFn>(mat, 29)(mat, 1 << 7 /* ADDITIVE */, 268 vfunc<FlagFn>(mat, 29)(mat, 1 << 7 /* ADDITIVE */, additive);
276 style == 2 || style == 5);
277 vfunc<FlagFn>(mat, 29)(mat, 1 << 12 /* FLAT */, style == 0); 269 vfunc<FlagFn>(mat, 29)(mat, 1 << 12 /* FLAT */, style == 0);
278 vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); 270 vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5);
279} 271}
@@ -281,7 +273,6 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white,
281void chams_reset(void *mat) { 273void chams_reset(void *mat) {
282 using FlagFn = void (*)(void *, int, bool); 274 using FlagFn = void (*)(void *, int, bool);
283 vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false); 275 vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false);
284 vfunc<FlagFn>(mat, 29)(mat, 1 << 21, false);
285 vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false); 276 vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false);
286 vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false); 277 vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false);
287 vfunc<FlagFn>(mat, 29)(mat, 1 << 7, false); 278 vfunc<FlagFn>(mat, 29)(mat, 1 << 7, false);
@@ -317,21 +308,6 @@ void hkdModelExecute(void *self, const void *state, const void *info,
317 return false; 308 return false;
318 bool white = style == 4 || style == 5; 309 bool white = style == 4 || style == 5;
319 const int *use_rgb = rgb; 310 const int *use_rgb = rgb;
320 // Additive blending vanishes against bright backgrounds (sky,
321 // white walls, strong fog), which made the viewmodel disappear in
322 // some places. Lay down a faint translucent base so the first-person
323 // model keeps a silhouette on bright backgrounds without turning
324 // into an opaque blob; the additive pass still provides the glow.
325 if (is_vm && (style == 2 || style == 5) && rgb[3] > 0) {
326 int ba = rgb[3] / 8;
327 if (ba < 16)
328 ba = 16;
329 int drgb[4] = {rgb[0], rgb[1], rgb[2], ba};
330 chams_apply(mat, ignorez, 3, false, drgb, false);
331 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */);
332 g_orig_model_exec(self, state, info, bone);
333 chams_reset(mat);
334 }
335 chams_apply(mat, ignorez, style, white, use_rgb, false); 311 chams_apply(mat, ignorez, style, white, use_rgb, false);
336 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); 312 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */);
337 g_orig_model_exec(self, state, info, bone); 313 g_orig_model_exec(self, state, info, bone);
@@ -391,6 +367,10 @@ void chams_preload() {
391 continue; 367 continue;
392 void *m = world_find_material(chams_material_name(i)); 368 void *m = world_find_material(chams_material_name(i));
393 if (m) { 369 if (m) {
370 // FindMaterial can hand back an unreferenced material; the engine warns
371 // ("bad reference count 0") and may reset its vars when it is bound.
372 using RefFn = void (*)(void *);
373 vfunc<RefFn>(m, 12)(m); // IMaterial::IncrementReferenceCount
394 g_style_mats[i] = m; 374 g_style_mats[i] = m;
395 logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m); 375 logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m);
396 } 376 }