vidar.games

Interior mapping

2026-11-07 ยท Discuss on X

1,490 rooms in this city, and not one of them is geometry. Each window is a flat quad. Per pixel, a ray goes into an imagined box and picks floor, ceiling or wall.

The problem

A tower has hundreds of windows. A flat window texture looks fine head-on, but the room painted on it never shifts as the camera moves, so the whole facade reads as wallpaper. Modelling every room costs geometry, draw calls and memory for spaces nobody walks into.

How it works

The source (a Unity surface shader) does it per pixel, in the facade's tangent space:

  1. Split the facade UV into window cells. Each cell is the front of a box: floor, ceiling, two side walls, a back wall a set depth behind the glass.
  2. Cast the view ray from the glass into that box and intersect all five planes. The nearest hit wins and gives a UV on that surface.
  3. Read that surface from an atlas: one column each for floor, ceiling, side wall and back wall.
  4. Hash the room's cell ID to choose blinds, and the floor number to choose a room style, so neighbours don't look copied.
  5. Light the room: direct sun only where the light ray passes the window mask, an ambient level from the sun angle, a fade with depth, and fresnel on the glass.

Rooms can span several window cells. The shader shifts the side walls left and right by the cell's position in its room, so a 3-window room has one continuous interior.

What I hit

My first render sparkled with noise inside every window. The cause: the per-facade seed and room width were interpolated varyings. The hash amplified tiny float drift into a different room per pixel. Making them flat varyings fixed it.

Atlas lookups wrap at cell edges, which breaks mip selection. Like the source, I sample with gradients taken from the continuous window UV.

My build

three.js (r187), WebGL2, one ShaderMaterial. 5 towers, 15 facades merged into 1 mesh: 1 draw call, 2,726 window cells, 1,490 rooms, no room geometry. Window cell 3 m, room depth 6 m, rooms 1 to 3 windows wide. Room textures are drawn in code; each floor uses one of 3 styles.

The left side of the split runs the same shader with the eye fixed in front of each window, which is what a baked texture shows.

I added a lamp per room, switched on from its cell hash, and a simple sky reflection on the glass.

Trade-offs

When to use it

City skylines, background towers, any facade the player sees but never enters. For rooms the camera can get into, use real geometry.

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

Source and credits

Technique source: Unity-InteriorMapping by Gaxil, https://github.com/Gaxil/Unity-InteriorMapping, MIT license, commit 78194254efe646aee5c9784dc23338a263c14343. Ported to three.js and GLSL from its interior mapping surface shader: the tangent-space ray against the room box (floor, ceiling, side walls with multi-window rooms, back wall), the floor/ceiling/wall/back atlas lookup with gradients from the window UV, random blinds per room and room style per floor from the cell hash, direct sun through the window mask, ambient from the sun angle, depth fade and fresnel on the glass. No files were copied unchanged.

Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, branch dev at commit fa01dd6ad164c48554f1bb9456f6500db052ec4f. Used for rendering and materials. It is not the source of the interior mapping technique.

My own code: the per-room lamps, the sky reflection on the glass, the fixed-eye "flat texture" comparison, the procedural towers, room and window textures, the room box and ray overlay, labels and video. No external models or assets. No IA model.