Skip to content
Photogrammetry & 3D Heritage

When a 3D model misbehaves on the web, the cause is nearly always one of four things: a missing texture, an oversized file, a scale or origin error, or a server header problem. Open the browser developer console first — most failures announce themselves there as a 404 for a texture, a CORS block, or a wrong MIME type. Fix the root cause rather than re-exporting blindly, and the model holds up across browsers and devices.

Why is my published model black or untextured?

This is the most common complaint, and it is a referencing problem, not a corruption problem. OBJ stores geometry in the .obj, material definitions in a sidecar .mtl, and the actual images as separate files. Upload the .obj alone and the viewer has geometry but no skin. Two reliable fixes:

  • Export to glb instead. The binary glTF container embeds geometry, materials, and textures in one self-contained file, so nothing can be separated.
  • If you must use OBJ, upload the .obj, .mtl, and every referenced image map together, and confirm the paths in the .mtl are relative, not absolute Windows paths like C:\Users\....

How do I fix a model that loads too slowly?

Heritage models straight out of photogrammetry are wildly over-detailed for the web — millions of triangles and 8K textures. Reduce both before publishing:

bash
# Convert glTF/OBJ to compressed glb with gltf-transform
npm install -g @gltf-transform/cli

gltf-transform optimize input.glb output.glb \
  --compress draco \
  --texture-compress ktx2 \
  --texture-size 2048

Target roughly 100k–300k triangles and 2K–4K textures for a detailed heritage object. Draco compresses geometry and KTX2 compresses textures on the GPU; together they routinely turn a 120 MB model into 6–10 MB with no visible quality loss at normal viewing distances.

Why does my model appear the wrong size or off-centre?

Three settings cause almost every framing problem:

  1. Units. Author and export in metres. A model built in millimetres will appear a thousand times too large.
  2. Origin. Centre the geometry on the world origin before export, or the viewer's pivot will sit off in space and orbiting will feel broken.
  3. Auto-frame. Most viewers auto-fit on load; if yours does not, set an explicit camera distance.

In model-viewer you can lean on built-in framing:

html
<model-viewer src="corbel.glb" camera-controls auto-rotate
  shadow-intensity="1" exposure="1.0"
  alt="Carved stone corbel, c.1480"></model-viewer>

Self-host or use a platform?

OptionBest forCostControl
SketchfabOne-off models, annotations, quick shareFree tier; paid for privacyLow
model-viewer (self-host)Collections you own, embedding in CMSHosting onlyHigh
Three.js customBespoke interaction, measurement toolsDev timeTotal

For a single annotated object, Sketchfab wins on speed. For an institutional collection you want to control and preserve, self-hosting glb with model-viewer keeps the files and the presentation in your hands.

Why won't my self-hosted glb load from another page?

If the model works on its own URL but fails when embedded elsewhere, suspect the server, not the file. The two classic culprits:

  • CORS: the server must return Access-Control-Allow-Origin: * (or your specific origin) for the .glb.
  • MIME type: serve .glb as model/gltf-binary and .gltf as model/gltf+json; a wrong type makes some browsers refuse to parse it.

Read the exact error in the browser console Network tab — it will name a blocked request or a 404. Guessing wastes time; the console tells you.

How do I keep textures looking sharp online?

Blurry or washed-out textures usually come from either too-low resolution or repeated JPEG re-compression. Export base-colour maps at 2K or 4K in sRGB, never re-save an already-compressed JPEG, and prefer KTX2/Basis compression, which holds detail at a fraction of the size. If colours look flat, check the viewer exposure and tone-mapping settings before blaming the texture.

Key Takeaways

  • Open the browser console first — texture 404s, CORS blocks, and MIME errors are visible there.
  • Black/untextured models are a referencing problem; publish self-contained glb to avoid it.
  • Decimate to 100k–300k triangles and compress with Draco + KTX2 to fix slow loading.
  • Wrong size or position is a units, origin, or auto-frame issue — author and export in metres.
  • Self-host glb with model-viewer for collections; use Sketchfab for quick one-offs.
  • Cross-site load failures are server-side: set CORS headers and the correct MIME type.

Frequently Asked Questions

Why is my 3D model black or untextured when published on the web?

Almost always a missing or unreferenced texture. When you export OBJ, the .mtl and image maps must travel with it; for the web, prefer glb, which embeds textures so nothing can be left behind.

My model loads far too slowly on the web — how do I fix it?

Decimate the mesh and resize textures before upload. Heritage delivery models rarely need more than 100k to 300k triangles and 2K to 4K textures; convert to glb with Draco mesh compression and KTX2 texture compression to cut file size dramatically.

Why does my model appear tiny, huge, or off-centre in the viewer?

Scale and origin problems. Export in metres, centre the geometry on its origin before export, and check the viewer's auto-frame setting. A model authored in millimetres will look enormous in a metre-based viewer.

Should I self-host with model-viewer or use Sketchfab?

Sketchfab is fastest for a one-off with annotations and zero hosting. For a collection you control, self-host glb files with Google's model-viewer web component, which is free, open, and embeds in any page.

Why won't my self-hosted glb load from another website?

Usually a CORS or MIME-type error. The server must send the file with an Access-Control-Allow-Origin header and serve .glb as model/gltf-binary; check the browser console network tab for the exact failure.

How do I stop the texture looking blurry or washed out online?

Export textures at adequate resolution (2K or 4K), keep them in sRGB colour space for base colour, and avoid double JPEG compression. KTX2/Basis compression preserves quality at small sizes far better than re-saving JPEGs.