vidar.games

Occlusion wall cutout (dithered)

2026-11-01 · Discuss on X

A wall sits between the camera and the character.
Left: the wall wins and the character disappears.
Right: a camera ray hits that wall, and only that wall cuts a dithered hole around the character.

The problem

Top-down and three-quarter cameras look over walls, so the player character walks behind them all the time. Hiding whole walls loses the room. The left half shows no handling at all.

How it works

Two parts: a script that decides which walls to cut, and a shader that cuts them.

  1. Every frame the script casts a ray from the camera to the character. Every wall the ray hits gets 3 values: the character's screen position, a hole size (0.1) and a falloff (0.05). Both sizes are fractions of the screen width.
  2. In the wall shader, the pixel's screen position and the hole centre both get their y divided by width / height, so the hole stays a circle instead of stretching with the screen.
  3. Distance to the centre goes through smoothstep(size - falloff, size, d). That gives 0 in the middle and 1 outside.
  4. That value is multiplied by 2, then a 4×4 Bayer threshold (1/17 to 16/17) is subtracted. Pixels below 0.5 are discarded.

Step 4 is the soft edge: a dither pattern of kept and dropped pixels, with no transparency or sorting.

What I hit

The script only sets walls the ray hits. It never clears a wall the ray has stopped hitting, so that wall keeps its last hole. I reset every wall that isn't on the ray and let the size ease open and closed at a rate of 7 per second, which is the shrink at the end of the clip.

The script aims at the target's pivot, which on a character usually sits at the feet. I aimed at the chest bone instead, so the hole frames the body.

The script also divides by Screen.width / Screen.height as integers. At 1920×1080 that's 1, so it changes nothing, and the shader does the aspect fix on its own.

My build

three.js 0.170, WebGL. The cutout is a few lines in MeshStandardMaterial via onBeforeCompile, with a discard in the fragment shader. A three.js Raycaster plays the part of the physics raycast. IA walks on a procedural walk cycle driven through the rig, in a brick room with 4 torch lights and a moonlight key, all with soft shadows. Both halves use the same camera and the same crop around the character, rendered twice.

Trade-offs

When to use it

Indoor scenes with an overhead or three-quarter camera: RPGs, tactics games, colony sims. If the camera zooms a lot, scale the size by distance first.

New breakdowns go out on X first: @vidar_0208. If this one helped, you can buy me a coffee.

Source and credits

Repo: github.com/daniel-ilett/shaders-wall-cutout (MIT license)

Code: I ported the cutout from Assets/Shaders/WallCutout.shadergraph (aspect-corrected screen distance, smoothstep falloff, ×2, 4×4 dither, alpha clip 0.5) to GLSL in three.js, and the camera-to-character raycast from Assets/Scripts/CutoutObject.cs, with the repo's values (size 0.1, falloff 0.05). Changes: walls off the ray get reset, the hole eases open and closed, the target is the chest bone instead of the feet. The room, textures, lighting and walk cycle are my own; no assets from the repo.

Character: モデル:葉葉波、キャラクターデザイン:えびごはん (©はっぱろぐ!・えびごはん・EAUploader). Fan-made reproduction, not official. #いあ3D

Topic inspired by @artofsully's occlusion cutout, shared by @VFX_Therapy.