Developing 3D Product Viewer for E-commerce
3D product viewer — an interactive model that users can rotate, zoom, and examine from any angle directly in the browser. This is a fundamentally different experience compared to a set of photos: the user explores the object independently rather than seeing only what the photographer decided to capture. High-impact categories: furniture, jewelry, shoes, complex technical products, souvenirs.
3D Model Formats
| Format | Extension | Size | Browser Support | Application |
|---|---|---|---|---|
| glTF 2.0 | .gltf + .bin + textures | Depends on complexity | Via Three.js/Babylon | Web standard |
| GLB | .glb | Compact (binary) | Via Three.js/Babylon, <model-viewer> |
Preferred for web |
| USDZ | .usdz | Medium | iOS Safari native | AR Quick Look on iOS |
| USD / USDC | .usdc | Large | Limited | Apple/Pixar native |
| OBJ + MTL | .obj | Large | Via Three.js | Legacy, not recommended |
| FBX | .fbx | Large | Conversion only | From DCC tools |
GLB — de facto standard for 3D on web. Everything is packaged in one file: geometry, materials, textures, animations. Supports PBR materials (metalness/roughness workflow), which gives physically accurate rendering.
Three.js — Base Stack
Minimal 3D widget on Three.js:
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, container.width / container.height, 0.1, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.outputColorSpace = THREE.SRGBColorSpace;
// OrbitControls — rotation, zoom, pan via mouse/touch
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 1;
controls.maxDistance = 10;
// DRACO decompression for compressed models
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.load('/models/product.glb', gltf => {
scene.add(gltf.scene);
fitCameraToObject(camera, controls, gltf.scene);
});
// HDR environment for PBR lighting
new RGBELoader().load('/env/studio.hdr', texture => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
});
Draco Compression
DRACO — 3D geometry compression algorithm from Google. Typical compression: geometry reduced by 5–10x without visible quality loss. A chair model 8MB → 800KB after Draco.
Conversion in pipeline (offline, when admin loads the model):
# gltf-pipeline from Khronos
npx gltf-pipeline -i input.glb -o output.glb --draco.compressionLevel 7
# or via blender CLI
blender --background --python export_with_draco.py
Draco decoder in browser — WASM module (~200KB). Must be hosted on server and path specified in DRACOLoader.setDecoderPath().
<model-viewer> as Ready Component
If deep customization isn't needed, <model-viewer> from Google covers 90% of tasks:
<model-viewer
src="/models/chair.glb"
ios-src="/models/chair.usdz"
alt="Herman Miller Office Chair"
camera-controls
auto-rotate
auto-rotate-delay="3000"
rotation-per-second="30deg"
shadow-intensity="1"
exposure="0.8"
environment-image="/env/neutral.hdr"
ar
ar-modes="webxr scene-viewer quick-look"
loading="lazy"
style="width: 100%; aspect-ratio: 1; background: #f5f5f5;"
>
<div slot="progress-bar">
<div class="progress-bar" style="..."></div>
</div>
<button slot="ar-button" class="ar-button">
View in your space
</button>
</model-viewer>
Built-in: OrbitControls, touch support, AR (WebXR + Quick Look), shadow, lazy loading, progress indication.
Lighting and PBR Materials
Rendering quality in browser is determined by lighting. Without proper environment even a good model looks flat.
HDR environment map — spherical HDR panorama of studio or neutral lighting. Creates correct reflections on metallic and glossy surfaces. Size: 2MB–8MB, loaded once and cached.
For products: neutral studio HDRI (gray background, even light) — neutral.hdr from model-viewer repository. For jewelry — HDRI with pronounced highlights.
Animated materials: switching variant (color, material) — change material.color, material.roughness, material.metalnessMap. Without reloading the model:
const materials = model.querySelector('model-viewer').model.materials;
const chairMaterial = materials.find(m => m.name === 'Fabric');
chairMaterial.pbrMetallicRoughness.setBaseColorFactor([0.8, 0.2, 0.2, 1]); // red
Performance Optimization
Model size: target GLB size for product card — up to 2MB. More — user waits, conversion drops. Optimization methods:
- Draco geometry compression (5–10x)
- KTX2 / Basis Universal texture compression (3–5x, with GPU decoding)
- Model retopology: remove hidden faces, simplify invisible parts
- Bake details from high-poly to normal map on low-poly model
Lazy loading: Three.js/WebGL initializes only when widget enters viewport:
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
initViewer(container); // create Scene, Renderer only now
observer.disconnect();
}
}, { threshold: 0.1 });
observer.observe(container);
Suspended rendering: when widget outside viewport — renderer.setAnimationLoop(null), resume on return.
Pixel ratio: on mobile limit devicePixelRatio to 1.5 max — saves GPU on retina displays.
Annotations and Hotspots
Annotations — points on 3D model with explanations. User hovers / clicks — tooltip appears with detail information.
In <model-viewer> annotations — via slots:
<model-viewer src="headphones.glb" camera-controls>
<button
slot="hotspot-ear-cup"
data-position="-0.3 0.1 0.2"
data-normal="-1 0 0"
data-visibility-attribute="visible"
>
<div class="hotspot-label">
<strong>Ear Cups</strong>
Memory foam with memory effect, 40mm
</div>
</button>
</model-viewer>
data-position coordinates — world coordinates in model's coordinate system. Determined in Blender or via built-in picking in Three.js.
Content Preparation Pipeline
Widget development and model preparation — parallel tasks. Typical pipeline:
- 3D modeling (Blender, Cinema 4D) or photogrammetry (RealityCapture, Meshroom)
- Optimization (Blender: retopology, LOD), Draco compression
- PBR texturing (Substance Painter) + texture conversion to KTX2
- QA in browser: check in model-viewer, mobile test
- USDZ conversion for iOS AR (Reality Converter, online converters)
-
Upload to CDN with correct headers (
Content-Type: model/gltf-binary)
Timeline
-
<model-viewer>integration (ready component, GLB files from client): 3–5 business days - Custom Three.js widget (annotations, material switching, custom lighting): 2–4 weeks
- Full platform (model upload to CMS, optimization pipeline, AR, annotations): 4–7 weeks
- Preparing 3D models (if not provided): separate task — 4 hours to 2 days per item
For large catalogs, photogrammetry (creating 3D from photo series) reduces content production cost 5–10x compared to manual modeling.







