[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81374":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":14,"stars7d":16,"stars30d":17,"stars90d":15,"forks30d":15,"starsTrendScore":16,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":15,"starSnapshotCount":15,"syncStatus":27,"lastSyncTime":28,"discoverSource":29},81374,"three-fenestra","codedgar\u002Fthree-fenestra","codedgar","Faux window interiors for Three.js | Interior mapping shader with an optional PBR front layer for curtains, blinds, and glass detail.",null,"TypeScript",45,8,38,1,0,3,7,47.06,"MIT License",false,"master",true,[],"2026-06-12 04:01:33","# Three-Fenestra: an interior mapping shader for Three.js\n\n**Status:** v0.2.0, pre-1.0.\n\nA `MeshStandardMaterial` subclass that adds parallax-based fake-3D rooms\ninside flat window planes, plus an optional PBR front layer for curtains,\nblinds, mullions, and glass dirt. Works in vanilla Three.js and React\nThree Fiber.\n\n![Three Fenestra: faux window interiors with interior mapping](assets\u002Fheader-cinematic.webp)\n\n**Live demo:** [three-fenestra.codedgar.com](https:\u002F\u002Fthree-fenestra.codedgar.com)\n\n---\n\n## Why Three-Fenestra\n\nThe interior mapping technique was proposed by Joost van Dongen in 2008\nto fake the look of furnished rooms behind window planes without modelling\nor lighting them. The original is one shader, one texture, no front layer.\n\nModern building renders need more than that: curtains that catch sun, glass\nthat gets dirty, mullions that cast shadow, windows that switch from \"lit\"\nto \"dark\" by time of day. Three-Fenestra keeps the cheap ray-march at the\ncore and stacks the modern bits on top:\n\n- A back atlas of interior rooms (the original technique)\n- A PBR front overlay with optional normal \u002F roughness \u002F metalness atlases\n- A transmission term so curtains can still bleed warm light at night\n- Uniforms wired for a day\u002Fnight controller (you supply the controller)\n\nIt is one material per window mesh. Drop it into any existing scene that\nalready uses Three's standard lighting and it composites correctly.\n\n---\n\n## Install\n\n```bash\nnpm install three-fenestra three\n```\n\n`three >= 0.150` is a peer dependency.\n\n### Starter atlases\n\nThe package ships two ready-to-use 4×4 atlases under `three-fenestra\u002Fstarter\u002F`\nso you don't have to author your own to see something render on day one:\n\n| Path | What it is |\n|---|---|\n| `three-fenestra\u002Fstarter\u002Frooms.webp` | Back atlas — 4×4 grid of interior rooms. |\n| `three-fenestra\u002Fstarter\u002Foverlay.webp` | Front atlas — 4×4 grid of curtain \u002F blind variants with alpha. |\n\nAny modern bundler (Vite, webpack, Parcel, esbuild) imports them as URLs.\nOnce you outgrow the starters, follow [Creating your own atlases](#creating-your-own-atlases).\n\n---\n\n## Quick start (vanilla Three.js)\n\n```ts\nimport * as THREE from 'three';\nimport { InteriorMappingMaterial } from 'three-fenestra';\nimport roomsUrl   from 'three-fenestra\u002Fstarter\u002Frooms.webp';\nimport overlayUrl from 'three-fenestra\u002Fstarter\u002Foverlay.webp';\n\nconst atlas = new THREE.TextureLoader().load(roomsUrl);\natlas.colorSpace = THREE.SRGBColorSpace;\natlas.wrapS = atlas.wrapT = THREE.ClampToEdgeWrapping;\n\nconst material = new InteriorMappingMaterial({\n  backAtlas: atlas,\n  backAtlasCols: 4,\n  backAtlasRows: 4,\n  depth: 1.0,\n  backScale: 0.66,\n  planeSize: new THREE.Vector2(width, height),\n  windowId: new THREE.Vector3(x, y, z), \u002F\u002F per-window seed for cell picking\n  roughness: 0.15,\n  metalness: 0.0,\n});\n\nconst mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), material);\n```\n\nThe interior renders with no front textures. Add the PBR front layer at any\ntime — the starter `overlay.png` is a 4×4 curtain atlas you can drop in:\n\n```ts\nconst overlay = new THREE.TextureLoader().load(overlayUrl);\noverlay.colorSpace = THREE.SRGBColorSpace;\nmaterial.setFrontAtlas(overlay, 4, 4);\n\n\u002F\u002F Optional companion PBR maps if you have your own:\nmaterial.setFrontNormalAtlas(curtainNormal, 1);   \u002F\u002F samples .xy\nmaterial.setFrontRoughnessAtlas(curtainRough);    \u002F\u002F samples .g\nmaterial.setFrontMetalnessAtlas(curtainMetal);    \u002F\u002F samples .b\n```\n\nWhere front alpha is `0`, you see the interior through the \"glass.\" Where\nit is `1`, you see the front layer lit by scene lights via standard PBR,\nfresnel included.\n\n---\n\n## React Three Fiber\n\nR3F passes constructor arguments via `args` as a single-element array\n(the material takes one options object):\n\n```tsx\nimport { extend, type ThreeElement } from '@react-three\u002Ffiber';\nimport { InteriorMappingMaterial } from 'three-fenestra';\n\nextend({ InteriorMappingMaterial });\n\ndeclare module '@react-three\u002Ffiber' {\n  interface ThreeElements {\n    interiorMappingMaterial: ThreeElement\u003Ctypeof InteriorMappingMaterial>;\n  }\n}\n\n\u003Cmesh>\n  \u003CplaneGeometry args={[width, height]} \u002F>\n  \u003CinteriorMappingMaterial\n    args={[{\n      backAtlas: atlas,\n      planeSize: new THREE.Vector2(width, height),\n      windowId: new THREE.Vector3(x, y, z),\n    }]}\n  \u002F>\n\u003C\u002Fmesh>\n```\n\nSetting individual props on `\u003CinteriorMappingMaterial \u002F>` after construction\nworks for the runtime knobs (`depth`, `backScale`, `interiorEmissive`,\n`frontTransmission`, `frontAlphaBoost`) because those are wired to setters.\nTexture swaps should go through `setFrontAtlas(...)` \u002F `setBackAtlas(...)`\nvia a ref.\n\n---\n\n## API\n\n### Constructor parameters\n\nAll `MeshStandardMaterialParameters` are accepted, plus:\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `backAtlas` | `Texture` | **required** | The interior (rooms) atlas. |\n| `backAtlasCols`, `backAtlasRows` | `number` | `4`, `4` | Grid dimensions of the back atlas. |\n| `depth` | `number` | `1.0` | Apparent room depth, in plane-local units. |\n| `backScale` | `number` | `0.66` | Back-wall fill factor (0.05–0.999). |\n| `planeSize` | `Vector2` | **required** | Must match the geometry's width × height. |\n| `windowId` | `Vector3` | **required** | Per-window seed (typically the window center) for atlas cell picking. |\n| `interiorEmissive` | `Color` | `(1, 1, 1)` | Multiplier on the interior contribution before adding to the lit output. Use to tint warm and scale up for \"lights on\" night mode (e.g. `new Color(2, 1.5, 1)`). |\n| `frontAtlas` | `Texture?` | — | Front overlay atlas (RGBA: color + alpha). |\n| `frontAtlasCols`, `frontAtlasRows` | `number` | `1`, `1` | Front atlas grid. |\n| `frontNormalAtlas` | `Texture?` | — | Tangent-space normal map atlas. |\n| `frontNormalScale` | `number` | `1.0` | Multiplier on `.xy` of the normal sample. |\n| `frontRoughnessAtlas` | `Texture?` | — | Roughness atlas (samples `.g`). |\n| `frontMetalnessAtlas` | `Texture?` | — | Metalness atlas (samples `.b`). |\n| `frontTransmission` | `number` | `0.25` | Fraction of interior light that bleeds through the opaque front layer, tinted by the front color. `0` = front fully blocks interior, `1` = no blocking. |\n| `frontAlphaBoost` | `number` | `1.0` | Raises effective opacity of the front layer (`pow(alpha, 1\u002Fboost)`). `> 1` makes semi-transparent pixels read as more opaque without re-authoring the texture. |\n\n#### Glass surface (optional)\n\nThese give the glass area (where the front layer is transparent) the look of\na real pane: dirt, refraction, fresnel sheen. All default to zero \u002F off; turn\non the ones you want.\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `glassThickness` | `number` | `0` | Apparent glass thickness in plane-local units. Parallax-shifts the front-overlay sample so it appears to sit on the *inside* face of the pane rather than glued to the outside surface. `0` disables. |\n| `refractionStrength` | `number` | `0` | Magnitude (in cell-UV units) of the interior ray-march perturbation driven by `glassDirtMap`. Sells the \"looking through real glass\" effect. Keep tiny — typical range `0.003`–`0.015`. `0` disables. |\n| `glassDirtMap` | `Texture?` | — | Grayscale noise texture used as the dirt\u002Fspecular modulator over the glass area, *and* as the source of the refraction perturbation. Centered around `0.5`; values `> 0.5` roughen the glass, `\u003C 0.5` polish it. |\n| `glassDirtStrength` | `number` | `0.35` | How strongly the dirt map modulates roughness on the glass area. |\n| `glassFresnelStrength` | `number` | `0` | Schlick fresnel sheen added to the glass at grazing angles. Primary \"this is a pane of glass\" cue. Demo uses `~0.5`. |\n| `glassFresnelColor` | `Color` | `(0.85, 0.92, 1.0)` | Tint of the fresnel sheen. Cool white reads as sky reflection. |\n| `glassSmudgeStrength` | `number` | `0` | Additive brightness of dirt visible as smudges on the glass surface. Different from `glassDirtStrength` (roughness modulation). |\n\n### Runtime setters\n\n```ts\n\u002F\u002F Core knobs\nmaterial.depth              = 0.8;\nmaterial.backScale          = 0.6;\nmaterial.interiorEmissive   = new THREE.Color(2.0, 1.5, 1.0);  \u002F\u002F copies into uniform\nmaterial.frontTransmission  = 0.10;\nmaterial.frontAlphaBoost    = 1.0;\n\n\u002F\u002F Glass-surface knobs\nmaterial.glassThickness       = 0.04;\nmaterial.refractionStrength   = 0.005;\nmaterial.glassDirtStrength    = 0.35;\nmaterial.glassFresnelStrength = 0.5;\nmaterial.glassFresnelColor    = new THREE.Color(0.85, 0.92, 1.0);  \u002F\u002F copies into uniform\nmaterial.glassSmudgeStrength  = 0.1;\n\n\u002F\u002F Texture swaps (pass null to disable)\nmaterial.setBackAtlas(newAtlas);\nmaterial.setFrontAtlas(tex, cols, rows);\nmaterial.setFrontNormalAtlas(tex, scale);\nmaterial.setFrontRoughnessAtlas(tex);\nmaterial.setFrontMetalnessAtlas(tex);\nmaterial.setGlassDirtMap(tex);\n```\n\n---\n\n## Day \u002F night\n\nThe library does not ship a day\u002Fnight controller. It gives you the\nuniforms to build one. Typical recipe:\n\n```ts\nconst day   = { emissive: new THREE.Color(1, 1, 1),         transmission: 0.15 };\nconst night = { emissive: new THREE.Color(1.7, 1.35, 0.95), transmission: 0.10 };\n\nfunction setMode(p: typeof day) {\n  for (const m of materials) {\n    m.interiorEmissive  = p.emissive;\n    m.frontTransmission = p.transmission;\n  }\n}\n```\n\nFor full \"lights on at night,\" pair this with:\n\n- Reduced scene ambient and sun, **but not to zero**. Building exteriors at\n  night still receive skyglow, streetlights, and reflections. Curtain\n  colours need ambient to read as fabric, not as backlit cutouts.\n- A cool-tinted ambient with a warm interior `emissive` for the classic\n  night-city contrast.\n- `UnrealBloomPass` on the composer (low strength, around `0.4`) to spill\n  the lit-window contribution onto neighbouring pixels.\n\nThe bundled `examples\u002Fasia-building` demo wires all three; check `main.ts`\nfor a working reference.\n\n---\n\n## Creating your own atlases\n\nTwo textures drive the look: the **back atlas** (interior rooms) and the\noptional **front atlas** (curtains, blinds, glass overlays). Both are\ngrids of square cells; the shader picks a cell per window using a\ndeterministic hash of `windowId`, so the same window always gets the\nsame room across re-renders.\n\n### Back atlas (interior rooms)\n\nA grid of square room photos. Each cell is one \"room\" the ray-march will\nland you inside.\n\n| Spec | Recommendation |\n|---|---|\n| Grid | 4×4 (16 variants) is the sweet spot for masking repetition across hundreds of windows. 2×2 is fine for small scenes. |\n| Cell aspect | Square (1:1). The ray-march assumes a unit cube per cell. |\n| Image size | Power of two (`1024×1024`, `2048×2048`). Lets Three generate mipmaps. |\n| Color space | sRGB. Set `texture.colorSpace = SRGBColorSpace` so sampling converts to linear for PBR. |\n| Edge bleed | The shader insets each cell by `0.001` to prevent bleed; keep ~2 px gutter inside each cell as insurance. |\n| Wrap | `ClampToEdgeWrapping` on both axes. |\n| Filter | `LinearMipmapLinearFilter` (min) + `LinearFilter` (mag), `anisotropy: 8+`. |\n| Content | Frame each cell as if looking through a window from outside. Centre the composition; the back wall should fill ~60–70% of the cell (matches default `backScale`). Already-lit photography works best; the shader treats interior pixels as pre-lit. |\n\n### Front atlas (curtains, blinds, overlays)\n\nA grid of window dressings. Each cell sits on top of one window using the\nsame cell-picking logic.\n\n| Spec | Recommendation |\n|---|---|\n| Grid | Match the variety you want. 4×4 = 16 variants. |\n| Cell aspect | Square. Real windows are not square; the shader stretches the cell to the window's actual aspect, so pick curtain compositions that survive a moderate stretch. |\n| Format | PNG with alpha (RGBA). |\n| Color space | sRGB. |\n| Trim to edge | Each curtain should fill its cell edge-to-edge with no transparent gutter. If your source has padding, trim it:\u003Cbr>`magick in.png -alpha set -fuzz 10% -bordercolor none -border 1 -trim +repage -resize 256x256^ -gravity center -extent 256x256 out.png` |\n| Alpha encoding | The single biggest authoring decision. Opaque (alpha = 1) is curtain fabric. Transparent (alpha = 0) is the glass area you want the interior to show through. Anywhere between is \"semi-sheer\" and the shader reads it as fractional transmission. |\n\n#### Sheer and lace curtains\n\nDo not author the fabric itself at low alpha unless you genuinely want light\nto pour through it. Anti-aliased edges are fine; intentional partial\ntransparency on every pixel of the curtain is what causes the \"windows\nevaporate at night\" problem.\n\nIf you already have a texture with semi-transparent fabric and want it to\nbehave more solidly at night, raise `frontAlphaBoost` (try `2.0`–`2.5`). It\nis a render-time knob; no re-export needed.\n\n#### Front PBR maps (optional)\n\nIf you want the curtain fabric to receive proper PBR lighting (fresnel,\nscene light response):\n\n- **Normal atlas:** tangent-space, same grid as the albedo atlas. `RGB`\n  channels = `XYZ`, encoded `[0..1]` mapping to `[-1..1]`.\n- **Roughness atlas:** single-channel; the shader samples `.g`. White =\n  rough, black = mirror.\n- **Metalness atlas:** single-channel; the shader samples `.b`.\n  Curtains and glass are non-metallic, so almost always `0`.\n\nAll three must share the front albedo atlas's grid dimensions.\n\n### Window mesh setup\n\nEach window is a `PlaneGeometry` sized to the real window dimensions.\nThree things every material needs:\n\n1. **`planeSize`**: the geometry's `(width, height)` as a `Vector2`. The\n   shader uses it to normalise object-space `position` into local UV.\n2. **`windowId`**: a `Vector3` unique per window. The window's centre in\n   world space is a natural choice.\n3. **The plane's local +Z** must be the outward-facing normal. If your\n   geometry comes from a model with arbitrary orientation, build a basis\n   from `(right, up, normal)` and apply it via\n   `mesh.quaternion.setFromRotationMatrix(makeBasis(right, up, normal))`.\n\nSee `examples\u002Fasia-building\u002Fmain.ts` for a complete example pulling\nper-window data from a JSON descriptor.\n\n### Helper scripts\n\nThe `examples\u002Fasia-building\u002Ftools\u002F` folder has small Python scripts used\nto build the demo's atlases:\n\n- `detect_windows.py`, `extract_windows.py`: pull window crops from a\n  facade photo\n- `analyze_atlas.py`: sanity-check cell layout and channel content\n- `glass_dirt.svg`: source for the glass-dirt overlay used in the demo\n\nThey are unsupported, not packaged, and exist as references. Adapt or\nignore.\n\n---\n\n## Limitations and roadmap\n\n- **No envmap \u002F cubemap reflections** on the glass area. Would require\n  fresnel-modulated env sampling.\n- **No refraction distortion.** A planned opt-in `refractionStrength`\n  (default `0`) would perturb the ray direction using the front normal map.\n- **One material per window mesh.** Each window carries its own\n  `windowId` \u002F `planeSize` uniforms. For very high window counts, an\n  instanced-attribute variant (single material, per-instance attributes)\n  is on the radar.\n- **Pre-lit interior.** The atlas is treated as already-shaded photography;\n  scene lights do not relight the interior. This is by design; relighting\n  fake rooms would defeat the cost saving the technique exists for.\n\n---\n\n## Development\n\n```bash\nnpm install\nnpm run dev          # serves examples\u002Fasia-building on :5173\nnpm run build        # produces dist\u002F (the publishable package)\nnpm run build:demo   # produces dist-demo\u002F (static export of the demo)\nnpm run typecheck\n```\n\n### Examples\n\n- `examples\u002Fasia-building\u002F` — the full demo: 160 windows on a real building,\n  cinematic camera, day\u002Fnight palette, glass dirt, PBR curtains. What\n  `npm run dev` serves and what powers the [live demo](https:\u002F\u002Fthree-fenestra.codedgar.com).\n- `examples\u002Fminimal\u002F` — single window plane, ~60 lines. The shortest\n  runnable example for understanding the API surface.\n\n---\n\n## Credits\n\n- Joost van Dongen, *Interior Mapping: A new technique for rendering\n  realistic buildings* (2008).\n- The Three.js team for `MeshStandardMaterial` and the onBeforeCompile\n  hook this material extends.\n\n---\n\n## License\n\n[MIT](.\u002FLICENSE).\n","Three-Fenestra 是一个基于 Three.js 的内部映射着色器项目，用于在平面窗户上模拟3D房间效果，并可选配PBR前层以增加窗帘、百叶窗和玻璃细节。其核心功能包括基于视差的假3D房间渲染技术以及支持多种材质属性（如法线、粗糙度和金属度）的PBR前层叠加，同时具备日夜控制器兼容性以便根据时间变化调整窗户状态。适用于需要高质量建筑可视化但又希望保持性能高效的场景，比如虚拟现实环境下的建筑设计展示或游戏开发中的背景构建。",2,"2026-06-11 04:04:48","CREATED_QUERY"]