Typical Scenario and Problems Solved
A client provides a GLB model of a sofa weighing 50 MB, and the website on an old hosting struggles to load it — visitors leave after 5 seconds. The solution: embed a 3D viewer using Three.js or Babylon.js with optimization: Draco compression, LOD, proper lighting. This is a complete 3D viewer website solution. We’ve implemented such projects for dozens of clients: from interior configurators to jewelry display cases. With 5+ years in WebGL development and 30+ successful projects, we deliver reliable 3D viewers. Our experience helped clients reduce loading times to 2 seconds and increase conversion by 15–20%. For example, one client saved $1,200 annually by reducing bandwidth with Draco compression. Three.js loads 3 times faster than Babylon.js for simple scenes but lacks advanced physics and XR. Hosting cost savings from Draco compression can be substantial for large projects. Implementation cost: from $1,500 for basic viewer; average project cost is $2,500.
Common technical challenges include:
- Large model files: GLTF models of furniture or cars can weigh tens of megabytes. Without Draco compression and LOD, users wait up to 30 seconds for loading, hurting conversion. In one project, LOD increased FPS from 20 to 55 on mobile.
- Cross-browser compatibility: WebGL is stable in Chrome, Firefox, Edge, but older versions of Safari may show artifacts. Proper detection of
WebGLRenderingContextand fallback messages are necessary. - Mobile performance: Rendering scenes with high Pixel Ratio drains battery quickly. Limiting
devicePixelRatioto 2× and disabling shadows on weak GPUs are essential measures.
Three.js or Babylon.js: Which One Should I Choose?
Three.js is a minimalistic rendering library (~600 KB), huge community, thousands of examples. Requires more manual work for complex scenes (physics, collision detection).
Babylon.js is a full-fledged game engine in the browser (~2 MB). Built-in physics, PBR materials, Inspector, GUI, XR support. Great for complex interactive scenes.
For viewing 3D models, use Three.js. For interactive configurators and scenes, Babylon.js. In 80% of our projects, we use Three.js due to ease of integration and smaller size. Both are powerful browser 3D engines for web applications.
| Criteria | Three.js | Babylon.js |
|---|---|---|
| Library size | ~600 KB | ~2 MB |
| Physics | None built-in | Built-in Cannon.js |
| PBR materials | Requires setup | Ready-made solutions |
| XR support | Via WebXR | Built-in Inspector |
Three.js: GLTF Model Viewer with Draco
npm install three @types/three
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
import { useEffect, useRef } from 'react'
interface ModelViewerProps {
modelUrl: string
envMapUrl?: string
}
export function ModelViewer({ modelUrl, envMapUrl }: ModelViewerProps) {
const mountRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = mountRef.current!
const width = container.clientWidth
const height = container.clientHeight
// Scene
const scene = new THREE.Scene()
scene.background = new THREE.Color(0xf8fafc)
// Camera
const camera = new THREE.PerspectiveCamera(50, width / height, 0.01, 1000)
camera.position.set(2, 1.5, 3)
// Renderer
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1.2
renderer.outputColorSpace = THREE.SRGBColorSpace
container.appendChild(renderer.domElement)
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
const dirLight = new THREE.DirectionalLight(0xffffff, 2)
dirLight.position.set(5, 10, 5)
dirLight.castShadow = true
dirLight.shadow.mapSize.set(2048, 2048)
scene.add(dirLight)
const fillLight = new THREE.DirectionalLight(0x8bb8e8, 0.5)
fillLight.position.set(-5, 2, -5)
scene.add(fillLight)
// Orbit Controls
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.08
controls.minDistance = 0.5
controls.maxDistance = 20
controls.autoRotate = true
controls.autoRotateSpeed = 1.5
// GLTF loader with Draco compression
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/') // Copy into public/draco/
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
let mixer: THREE.AnimationMixer | null = null
loader.load(
modelUrl,
(gltf) => {
const model = gltf.scene
// Center model
const box = new THREE.Box3().setFromObject(model)
const center = box.getCenter(new THREE.Vector3())
const size = box.getSize(new THREE.Vector3())
const maxDim = Math.max(size.x, size.y, size.z)
model.position.sub(center)
camera.position.multiplyScalar(maxDim * 0.8)
controls.update()
scene.add(model)
// Animations
if (gltf.animations.length > 0) {
mixer = new THREE.AnimationMixer(model)
gltf.animations.forEach((clip) => {
mixer!.clipAction(clip).play()
})
}
},
(xhr) => {
const progress = Math.round((xhr.loaded / xhr.total) * 100)
console.log(`Loading: ${progress}%`)
},
(error) => console.error('Error loading model:', error)
)
// Animation loop
const clock = new THREE.Clock()
let animFrameId: number
function animate() {
animFrameId = requestAnimationFrame(animate)
const delta = clock.getDelta()
mixer?.update(delta)
controls.update()
renderer.render(scene, camera)
}
animate()
// Resize
function handleResize() {
const w = container.clientWidth
const h = container.clientHeight
camera.aspect = w / h
camera.updateProjectionMatrix()
renderer.setSize(w, h)
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
cancelAnimationFrame(animFrameId)
controls.dispose()
renderer.dispose()
container.removeChild(renderer.domElement)
}
}, [modelUrl])
return (
<div
ref={mountRef}
style={{ width: '100%', height: '500px' }}
className="rounded-xl overflow-hidden cursor-grab active:cursor-grabbing"
/>
)
}
Material Color Configurator
function changeModelColor(scene: THREE.Scene, meshName: string, color: string) {
scene.traverse((object) => {
if (object instanceof THREE.Mesh && object.name === meshName) {
const material = object.material as THREE.MeshStandardMaterial
material.color.set(color)
}
})
}
// Usage in UI
<div className="flex gap-2">
{['#ef4444', '#3b82f6', '#22c55e', '#f59e0b', '#8b5cf6'].map((color) => (
<button
key={color}
onClick={() => changeModelColor(sceneRef.current!, 'Body', color)}
style={{ background: color }}
className="w-8 h-8 rounded-full border-2 border-white shadow"
/>
))}
</div>
Babylon.js: Alternative for Complex Scenes
npm install @babylonjs/core @babylonjs/loaders
import { Engine, Scene, ArcRotateCamera, HemisphericLight, Vector3 } from '@babylonjs/core'
import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader'
import '@babylonjs/loaders/glTF'
function BabylonViewer({ modelUrl }: { modelUrl: string }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const engine = new Engine(canvasRef.current!, true, {
preserveDrawingBuffer: true,
stencil: true,
})
const scene = new Scene(engine)
const camera = new ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 4, 5, Vector3.Zero(), scene)
camera.attachControl(canvasRef.current!, true)
camera.lowerRadiusLimit = 1
camera.upperRadiusLimit = 20
new HemisphericLight('light', new Vector3(0, 1, 0), scene)
SceneLoader.ImportMeshAsync('', '', modelUrl, scene).then(({ meshes }) => {
// Auto-centering
camera.setTarget(meshes[0])
})
engine.runRenderLoop(() => scene.render())
const handleResize = () => engine.resize()
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
engine.dispose()
}
}, [modelUrl])
return <canvas ref={canvasRef} style={{ width: '100%', height: '500px' }} />
}
How to Optimize 3D Model Loading with Compression and LOD?
Users won’t wait longer than 3 seconds for loading. Key methods:
- Draco compression reduces geometry size by 60–90%. This cuts loading time by 3–5 times vs. uncompressed files. For example, a 50MB model can become 15MB, cutting load time from 20s to 4s.
-
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))— never render above 2x, saving battery. - LOD (Level of Detail) — detailed model close up, simplified far away. On mobile, LOD boosts FPS by up to 50%.
- Instanced Mesh for many identical objects (trees, chairs).
- Disable
autoRotateduring user interaction.
| Method | Effect | Implementation Complexity |
|---|---|---|
| Draco compression | Size reduction 60–90% | Medium (decoder needed) |
| LOD | Rendering speedup up to 50% | High (creating levels) |
| Pixel Ratio limit | Reduced GPU load | Low (one line) |
Draco is a Google library for compressing 3D geometry. It encodes vertex positions, normals, texture coordinates into a compact format. On the client, the decoder restores the original data. For GLTF, simply connect DRACOLoader and specify the decoder path. Lossless compression; not used for animations.
What’s Included in the Work (Deliverables)
- Model analysis — check geometry, textures, animations. Convert to GLTF with Draco.
- Scene setup — lighting (environment, directional light), shadows, camera.
- Integration — React component with loader, controls, responsiveness.
- Configurator (optional) — color/material selection, model switching.
- Optimization — mobile device testing, LOD configuration.
- Documentation — component API description, model replacement instructions.
- Training — one session for your team on how to use and maintain the viewer.
- Source code access — full repository with comments and build instructions.
- Support — 30-day warranty and optional maintenance contract.
Estimated Timelines and Cost
- Simple viewer with GLTF loading and orbit controls: 2–3 days, cost from $1,500.
- Configurator with color/material selection and multiple models: 5–7 days, cost from $4,000.
- Cost determined after analyzing specific requirements; typical project ranges $1,500–$5,000. For example, a basic viewer costs $1,500 and can be delivered in 2 days.
Quality Assurance
We test the viewer on the latest three versions of Chrome, Firefox, Safari, and Edge. For mobile, we verify on iPhone (Safari) and Android (Chrome). We provide a 30-day warranty post-delivery. Our experience: 5+ years in WebGL development, 30+ successful projects involving over 50 different 3D models. Our approach to 3D model embedding ensures fast loading and smooth interaction.
Contact us to evaluate your project. Schedule a consultation — we’ll discuss requirements and prepare a tailored commercial proposal.







