VIS Optimization with hint and skip Brushes

May 21, 2025 Daemon666 8 min read 6 vistas

If your custom map runs fine in a corridor and chugs the moment two open areas can see each other, the problem is almost never your models — it is visibility. GoldSrc decides what to render using the PVS (potentially visible set) that hlvis computes at compile time, and a badly leafed map tells the engine that half the level is visible from the other half. Hint and skip brushes are the manual tool for fixing that. This is what they are and how to place them without making things worse.

1. What hlvis actually computes

The compile toolchain runs in order: hlcsghlbsphlvishlrad. hlbsp chops the world into convex leafs; hlvis then works out, for every leaf, which other leafs are potentially visible from it — the PVS. At runtime the engine only draws faces in the PVS of the leaf your eye is in. When you open the console with developer 1 and type r_speeds 1, the wpoly counter is the number of world polygons being drawn this frame. Keep it under roughly 800–1000 wpolys on a busy scene; spikes into the thousands are what cause the stutter.

2. Why leafs leak visibility

A single large open room becomes a small number of big leafs, and every big leaf sees a lot. Two rooms joined by a doorway will often share one leaf that straddles the opening, so standing in room A the engine thinks all of room B is visible and draws it. That is the exact situation a hint brush fixes: it forces hlbsp to cut a new leaf boundary exactly where you want the visibility to stop.

3. HINT and SKIP: which face does what

A hint brush is a normal brush textured with two special tool textures from zhlt.wad:

  • HINT — the one cutting face. It creates a leaf split along that plane but is not drawn in-game.
  • SKIP — every other face of the brush. SKIP faces are removed entirely by the compiler; they generate no split and no geometry.

So a hint brush is a thin box where only the single face you want to cut along wears the HINT texture and all five other faces wear SKIP. Get this backwards — SKIP on the cutting face, HINT everywhere — and you generate five useless splits and miss the one you wanted.

// A hint brush spanning a doorway:
//   the vertical face across the opening = HINT
//   the other 5 faces                    = SKIP

4. Where to place them

Place hints along natural sight blockers — doorways, the top of a wall, the mouth of a tunnel, the lip of a ledge. The classic placement is a thin hint brush filling a doorway from floor to ceiling: now a player in room A is in a leaf that cannot see past the door plane, and room B is culled until they actually step through. A horizontal hint at the height of a wall stops the engine drawing a whole skybox-facing area you can never see from ground level.

5. Do not carpet the map with hints

Every hint you add is more work for hlvis and more leafs to track at runtime; overdoing it slows compiles dramatically and can make r_speeds worse by fragmenting leafs into thousands of tiny pieces. Add hints surgically: find the spot where r_speeds spikes, look at what is being drawn that you should not be able to see, and place one hint on the plane that should block it. Recompile with VIS on full and re-measure. A dozen well-placed hints beat two hundred sprinkled hopefully.

Common misconceptions

  • "Hint brushes are solid walls." No — they are non-solid, invisible, and do not clip the player. They only influence leaf cutting.
  • "More hints = faster." The opposite past a point. Each hint adds leafs and compile time; place them only where a measured spike justifies one.
  • "I compiled and nothing changed." You almost certainly ran hlvis in fast mode or skipped it. Fast VIS produces a loose PVS; you must run full VIS for hints to pay off.
  • "func_detail needs VIS too." Detail brushes are excluded from the VIS/BSP split on purpose — use them for railings and clutter so those do not chop leafs, and reserve hints for real sight blockers.
  • "SKIP and NULL are the same." They are not: NULL removes a face to save wpolys on normal brushes; SKIP is specifically the filler texture for the non-cutting faces of a hint brush.

How to check on your map

Load the map on a listen server, open the console and run:

developer 1
r_speeds 1
gl_wireframe 2

Walk to the spot that stutters and read the wpolys number; gl_wireframe 2 draws only what is actually being rendered, so you can literally see the far room being drawn through a wall before you add a hint. Note the worst-case number, add one hint on the blocking plane, recompile with full VIS, and compare the same spot. If wpolys dropped and the far geometry vanished from the wireframe, the hint worked. Pair this with keeping standard textures external as covered in embedding WADs vs -wadinclude so the BSP stays lean while you iterate on VIS.

Colaboradores: Daemon666 ✦
Compartir: