When a 3D scene is built around a fixed set of bundled assets, many rendering parameters naturally become tuned to those assets. As long as the content does not change, those values start to look like general system defaults.
That changes once users can upload their own models. GLB files from different sources can use different normal directions, material setups, and texture workflows. A rendering rule that works perfectly for one controlled asset set may fail as soon as the input changes.
I ran into this after adding custom GLB upload to a 3D prototype. The scene already had double-sided rendering and lighting values tuned for its built-in objects. Testing with a real room scan exposed two issues in sequence: from a top view, the interior was covered by a large white surface; after fixing that, the textures still looked heavily overexposed.
Both problems came from the same underlying pattern: assumptions that were valid for fixed internal assets had been treated as if they were valid for every uploaded model.
The root-cause analysis and implementation fixes mainly came from Claude Code. My role was to validate them with real scan data and identify the limits of the resulting behavior.
The first version enabled double-sided rendering for uploaded models as a compatibility measure. The logic seemed reasonable: if a model had unexpected normal directions, rendering both sides would reduce the chance of surfaces disappearing.
The room scan showed why that assumption was too broad.
This type of model behaves more like an interior shell. Its normals facing inward are not necessarily incorrect; they match the fact that the scanned space is meant to be viewed from inside. Once both sides were rendered, surfaces that should have remained invisible from the outside were drawn as well. From above, the back side of the ceiling covered the interior and appeared as a large white layer.
The fix was to render only front-facing surfaces and let back-face culling work again. From the top view, the ceiling's back side no longer blocked the room interior.
The important change was not correcting a single normal. It was correcting the compatibility rule itself: rendering more geometry is not always safer, because it can remove spatial visibility rules that the asset relies on.
After the face-rendering issue was fixed, the scan still looked washed out. Texture detail and contrast were noticeably reduced.
The existing scene lighting had been tuned for simple solid-colored 3D objects. Those objects rely on scene lights to create form, so the lighting setup made sense for them.
The uploaded scan used a different visual pipeline. Its texture already contained much of the lighting information captured during scanning. Applying the scene lighting again effectively added another layer of illumination on top of an already lit texture, producing the overexposed result.
The current solution renders uploaded scan materials without additional scene-light calculations while preserving their original textures, vertex colors, transparency, and related information. This allows the lighting already present in the texture to remain intact.
Again, the useful lesson is broader than the specific material setting: scene lighting is not independent of the asset. It depends on how that asset already represents light and shading.
This fix does not mean that arbitrary GLB files will now render correctly.
A conventional 3D model may not have lighting baked into its textures at all. It may depend on real-time scene lighting to show volume and surface form. If that model is forced into the same unlit treatment as the scan, it can appear flat.
So the current approach is better suited to the scan assets used in this test. It is still a trade-off between different asset types rather than a universal rendering rule.
If the product eventually needs to support both workflows, a better direction would be to let different assets use different rendering strategies — for example, allowing a choice between preserving the model's original lighting behavior and ignoring additional scene lighting.
That has not been implemented yet, so I prefer to document it as a boundary of the current solution rather than present the upload pipeline as fully general.
Neither issue appeared while the scene only used its original built-in models, because the rendering setup had been tuned around those assets from the beginning.
User upload changed the nature of the system. Once the source of the content was no longer controlled, previously invisible assumptions became part of the product surface:
Supporting uploads therefore involves more than adding a file picker. It also means asking: which of the current defaults are genuinely general, and which are only properties of the original asset set?
The specific technical diagnosis and fixes were mainly developed with Claude Code. My responsibility was to validate them against real uploaded models and to identify what new trade-offs each fix introduced.
The reusable lesson is not whether double-sided rendering should be enabled, or whether uploaded models should ignore lighting. Those answers depend on the asset.
The more durable question is: once a system accepts input that it no longer controls, which of its existing defaults are actually hidden assumptions about the old content?