diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 21:35:44 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 21:35:44 +0100 |
| commit | c35fc21f345ac9ecf9017115c4481a1147d05206 (patch) | |
| tree | 6907a027a097aec5af994b5bdcb20dddce6e984a /src | |
| parent | Chams: make Fullbright a white-hot additive glow (diff) | |
| download | hl2h-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.
Diffstat (limited to '')
| -rw-r--r-- | src/studio.cpp | 46 |
1 files changed, 13 insertions, 33 deletions
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; | |||
| 199 | ForceOverrideFn g_force_override = nullptr; | 199 | ForceOverrideFn g_force_override = nullptr; |
| 200 | void *g_style_mats[7] = {}; | 200 | void *g_style_mats[7] = {}; |
| 201 | 201 | ||
| 202 | void *viewmodel_material(); | ||
| 203 | |||
| 204 | const char *chams_material_name(int idx) { | 202 | const 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. | ||
| 216 | void *style_material(int idx) { | 217 | void *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. | ||
| 227 | void *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 | ||
| 236 | void chams_color(void *mat, float r, float g, float b) { | 227 | void chams_color(void *mat, float r, float g, float b) { |
| @@ -260,6 +251,7 @@ void chams_alpha(void *mat, float a) { | |||
| 260 | void chams_apply(void *mat, bool ignorez, int style, bool force_white, | 251 | void 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, | |||
| 281 | void chams_reset(void *mat) { | 273 | void 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 | } |
