Assets for the room¶
What to fetch, from where, at what cost, and what not to bother with. Companion
to art-direction.md, which says what the room is; this says what it is made of.
Status: researched. The shelf's three surfaces are fetched, converted and shipped; the rest of the room is still a shopping list.
1. The pipeline already exists¶
This is the most useful thing on the page. /fun already loads CC0 PBR surfaces
and glTF models through a working helper, so none of this needs building:
portfolio/src/components/materials/surface.ts holds useSurface(slug, repeat), which loads a
three-map set and returns cloned, tiled textures. portfolio/src/components/fun/props.tsx
holds useGLTF-based prop loading with per-instance cloning. Assets live at
portfolio/public/textures/{shelf,fun}/<name>_{diff,nor,arm}.webp and
portfolio/public/models/fun/<name>/<name>.gltf.
The ARM convention is already handled: ambient occlusion, roughness and metalness
packed into R/G/B of one file, with three.js reading roughness from green and
metalness from blue, so a single file serves both slots. AO is deliberately
skipped because three.js samples aoMap from a second UV set that plane geometry
does not carry.
Adding a surface is: fetch, convert, drop in the folder, add the name to the
SurfaceSlug union and its folder to SOURCES. That is the whole job.
2. Licence¶
Everything below is from Poly Haven and is CC0, verified at polyhaven.com/license
rather than assumed. Public domain in effect: commercial use permitted,
redistribution permitted, attribution not required though appreciated. Nothing
here needs a licence row in brand.ts, unlike the fonts.
3. The diagnosis, before the shopping list¶
/fun already uses this pipeline and already looks, in your words, lackluster
and too modern. That is not because assets do not help. It is because the
specific assets currently loaded describe a different room:
| Currently loaded | What it says |
|---|---|
laminate_floor_02 |
rented flat, 2010s |
plastered_wall_04 |
painted plasterboard |
dirty_carpet |
office |
env_studio_1k.hdr |
photography studio |
modern_ceiling_lamp_01 |
modern |
Four of the five say "modern interior" on their own, and together they say it loudly. The room is doing exactly what it was told to do. Swapping the names below changes the register without touching a line of loading code, which makes this the cheapest large improvement available anywhere in the project.
3b. Shipped: the portfolio shelf¶
Live under portfolio/public/textures/shelf/, 456 KB for the whole object.
| Slug | Role | Files | Size |
|---|---|---|---|
book_pattern |
Linen bookcloth, every volume | diff, nor | 97 KB |
black_oak_veneer |
Shelf boards and uprights | diff, nor, arm | 111 KB |
wooden_panels |
The back panel behind the books | diff, nor, arm | 180 KB |
Two decisions that made the difference to the byte count:
The cloth ARM map was dropped entirely. It was 499 KB on its own, the single
largest file in the set, and bookcloth has no metalness and near-uniform
roughness, so a scalar roughness: 0.7 does the same job for nothing. Reach for
the ARM map when a surface actually varies; a woven cloth does not.
The cloth diffuse is pre-baked to a tintable luminance map at build time rather than desaturated per-pixel in JavaScript at load. The prototype ran that remap over a million pixels on the main thread; baking it moved the cost to zero and made the file compress as greyscale. 512px is enough: the weave is below the resolution the frame can resolve anyway and mips away.
No HDRI ships. Brass still needs something to reflect, so the scene builds a
tiny environment from two drei <Lightformer>s at resolution={128} and
frames={1}. Keep them dim — they exist for the reflection, not to light the
shelf, and turned up they wash every cloth tint out to a uniform amber.
4. Surfaces to fetch¶
Slugs are real and verified against the Poly Haven API.
| Role | Slug | Why this one |
|---|---|---|
| Floor | wooden_floor_02 |
Dark, planed, wide board. The English-discipline half. |
| Floor, alternative | herringbone_parquet |
Parquet is the strongest single signal of a good house. Costs more tiling care. |
| Desk and shelving | black_oak_veneer |
Dark oak, finished, cabinetmaker rather than carpenter. |
| Desk, warmer option | mocha_oak_veneer |
If black oak reads too cold beside the lamp. |
| Wall panelling | wooden_panels |
The wainscot. This is the asset that kills the painted-box look. |
| Wall above the rail | plastered_wall_04 |
Keep. It is correct above a panelled dado and it is tiny. |
| Rug | replace dirty_carpet |
It is 932 KB and it says office. A plain wool would be a fraction of that. |
Avoid from the same category: anything named weathered, rough, raw,
old_planks, distressed. Those are the cabin, and the direction bans it.
5. Light¶
| Role | Slug | Note |
|---|---|---|
| Interior IBL | reading_room |
On the nose. Replaces env_studio_1k. |
| Interior, alternates | lythwood_room, old_room, solitude_interior |
If reading_room reads too warm. |
| The view out of the window | misty_pines |
Norwegian without being a postcard. |
| Window alternates | snowy_forest_path_01, niederwihl_forest, birchwood |
Colder, or more birch. |
The window wants a backplate, not an environment map. A flat equirectangular image behind the glass is cheaper and looks better at a fixed camera angle than lighting the room from a forest.
6. Models¶
| Slug | Use |
|---|---|
book_encyclopedia_set_01 |
The shelf. Real books instead of coloured boxes. |
decorative_book_set_01 |
Second run, so the shelf is not one repeated set. |
desk_lamp_arm_01 |
The lamp, if the key stays on the desk rather than a standard. |
mantel_clock_01 |
One good object on the shelf. Reads as inherited. |
brass_vase_01 … _04 |
Brass is a house material. Cheap way to repeat it. |
vintage_wooden_drawer_01 |
The sideboard. |
potted_plant_01 |
The forest, brought one step inside. |
7. The gap you should know about¶
There is no writing desk in the catalogue. metal_office_desk is the wrong
register entirely, and the tables are dining tables. The desk is the most
important object in the room and it will have to be built from primitives.
That is fine, and arguably better. At the camera distances the route uses, the
tell is never the silhouette, it is the material and the light. A box desk
carrying black_oak_veneer with a correct normal map and a real contact shadow
will out-perform a downloaded model with flat lighting every time.
The globe has the same answer, and already does: the hero globe is built from a sphere, a torus and two cylinders.
8. What this actually costs¶
Measured from the files already in the repository, which is the only honest source. Raw 1K PNG from Poly Haven runs 30 to 36 MB per surface across diffuse, normal and ARM. Converted to 1K WebP, the same three maps are:
| Existing file | Size |
|---|---|
oak_veneer_01 (3 maps) |
322 KB |
laminate_floor_02 (3 maps) |
255 KB |
plastered_wall_04 (3 maps) |
44 KB |
dirty_carpet (3 maps) |
932 KB |
So roughly 250 to 350 KB per surface, and a 30 to 40 times reduction. High frequency detail costs more, which is why the carpet is an outlier and the plaster is almost free.
A full re-skin at those rates:
| Item | Estimate |
|---|---|
| 5 surfaces | 1.5 MB |
| Interior HDRI, downsampled | 250 to 400 KB |
| Window backplate, WebP | 200 to 300 KB |
| 3 to 4 models with textures | 1.2 to 2.4 MB |
| Total | 3.2 to 4.6 MB |
/fun currently ships 3.3 MB of models and 3.2 MB of textures, about 6.5 MB. So
a well chosen re-skin is a swap that comes out smaller than what is already
there, not an addition. That is the number to hold on to.
9. Two existing wins worth taking regardless¶
env_studio_1k.hdr is 1.65 MB and is shipped as a raw .hdr, which is half of
the fun room's entire texture budget. Image based lighting for a soft interior
does not need 1K; 256 to 512 pixels is enough for the diffuse and specular
convolution, and nobody has ever seen the difference in a lit room. Downsampling
it is the single largest byte saving available in the project right now.
dirty_carpet at 932 KB is the second largest and is also the asset least suited
to the theme.
10. Rules for anything fetched¶
Convert to WebP at 1K before it enters portfolio/public/. The raw PNG is never committed.
Take diffuse, normal (GL, not DX, since three.js expects OpenGL convention) and ARM. Skip AO as a separate file; it is in the ARM red channel and unusable on plane geometry anyway.
Nothing loads before interaction. The room is already gated behind a real user gesture and every asset here inherits that gate.
Any asset that makes the room read as rustic goes back, however good it looks on
its own. The failure mode named in art-direction.md is drift toward the cabin,
and it will arrive one defensible texture at a time.
11. The bedroom easter egg¶
The two files under portfolio/public/fun/ are not Poly Haven.
goggins.webp is a US Navy photo, US Navy 070723-N-6138R-001 on Wikimedia
Commons (Badwater Ultra Marathon, 2007, Mass Communication Specialist Seaman
Brandon Rogers). A work of the US Navy made on official duty, so public domain
under PD-USGov-Military, checked on the Commons file page rather than assumed.
It is cropped with a hand-drawn polygon on purpose.
order.mp3 is five lines written for the joke and generated with Voiceley using
one of its stock voices, joined in ffmpeg. It is not a clone of his voice and
must not be replaced with one. Check the Voiceley plan terms cover use on a
public site before it ships. The caption timings in Sleep.tsx are read off
this file, so a re-cut has to re-time them.
Public domain covers copyright only. A real person's likeness is a separate
right, and this stays a joke inside /fun: it must never be used to suggest he
endorses the site or anything on it.