Типова ситуація: клієнт надав GLB-модель дивана вагою 50 МБ, а сайт на старому хостингу не витримує завантаження — відвідувачі йдуть через 5 секунд очікування. Рішення — вбудувати 3D-переглядач на Three.js або Babylon.js з оптимізацією: Draco-компресія, LOD, правильне освітлення. Ми реалізували такі проєкти для десятків клієнтів: від інтер'єрних конфігураторів до вітрин ювелірних прикрас. Наш досвід — понад 5 років веб-розробки та понад 30 успішних інтеграцій 3D-переглядачів, що дозволило клієнтам скоротити час завантаження до 2 секунд і збільшити конверсію на 15-20%. При цьому Three.js завантажується в 3 рази швидше Babylon.js для простих сцен, але поступається у фізиці та XR. Економія на хостингу при Draco-стисненні може сягати $500 на місяць для великих проєктів.
Проблеми, які вирішуємо
Типові технічні складності при впровадженні 3D-переглядачів:
- Великі файли моделей: GLTF-моделі меблів або автомобілів можуть важити десятки мегабайт. Без Draco-компресії та LOD користувачі чекають завантаження по 30 секунд, що критично для конверсії.
- Крос-браузерність: WebGL стабільний в Chrome, Firefox, Edge, але на старих версіях Safari можливі артефакти. Потрібна коректна перевірка
WebGLRenderingContextта fallback-повідомлення. - Продуктивність на мобільних: рендеринг сцени з високим Pixel Ratio швидко садить батарею. Обмеження
devicePixelRatioдо 2× та відключення тіней на слабких GPU — обов'язкові заходи.
Three.js vs Babylon.js: як обрати?
Three.js — мінімалістична бібліотека рендерингу (~600 КБ), величезна спільнота, тисячі прикладів. Вимагає більше ручної роботи для складних сцен (фізика, collision detection).
Babylon.js — повноцінний ігровий двигун у браузері (~2 МБ). Вбудована фізика, PBR-матеріали, Inspector, GUI, XR-підтримка. Хороший для складних інтерактивних сцен.
Для перегляду 3D-моделей — Three.js. Для інтерактивних конфігураторів та сцен — Babylon.js. У 80% наших проєктів використовуємо Three.js через простоту інтеграції та менший розмір.
| Критерій | Three.js | Babylon.js |
|---|---|---|
| Розмір бібліотеки | ~600 КБ | ~2 МБ |
| Фізика | Немає вбудованої | Вбудований Cannon.js |
| PBR-матеріали | Потребує налаштування | Готові рішення |
| XR-підтримка | Через WebXR | Вбудований Inspector |
Three.js: переглядач GLTF-моделі
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
// Сцена
const scene = new THREE.Scene()
scene.background = new THREE.Color(0xf8fafc)
// Камера
const camera = new THREE.PerspectiveCamera(50, width / height, 0.01, 1000)
camera.position.set(2, 1.5, 3)
// Рендерер
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)
// Освітлення
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 завантажувач з Draco стисненням
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/') // Копіюємо в public/draco/
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
let mixer: THREE.AnimationMixer | null = null
loader.load(
modelUrl,
(gltf) => {
const model = gltf.scene
// Центруємо модель
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)
// Анімації
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)
)
// Анімаційний цикл
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"
/>
)
}
Конфігуратор кольору матеріалу
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)
}
})
}
// Використання в 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: альтернатива для складних сцен
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 }) => {
// Автоцентрування
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' }} />
}
Чому важлива оптимізація продуктивності?
Користувачі не готові чекати завантаження довше 3 секунд. Ось ключові методи:
- Draco-компресія GLTF знижує розмір геометрії на 60–90%. Це зменшує час завантаження в 3-5 разів порівняно з нестисненим файлом.
-
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))— не рендеримо вище 2x, що економить енергоспоживання. - LOD (Level of Detail) — детальніша модель поблизу, спрощена вдалині. На мобільних LOD дає приріст FPS до 50%.
- Instanced Mesh для множини однакових об'єктів (дерева, крісла).
- Відключаємо
autoRotateпід час взаємодії користувача.
| Метод | Ефект | Складність реалізації |
|---|---|---|
| Draco-компресія | Зниження розміру на 60–90% | Середня (потрібен декодер) |
| LOD | Прискорення рендерингу до 50% | Висока (створення рівнів) |
| Обмеження Pixel Ratio | Зниження навантаження на GPU | Низька (один рядок) |
Що таке Draco-компресія?
Draco — бібліотека від Google для стиснення 3D-геометрії. Вона кодує позиції вершин, нормалі, текстурні координати в компактний формат. На клієнті декодер відновлює оригінальні дані. Для GLTF достатньо підключити DRACOLoader та вказати шлях до декодера. Стиснення без втрат, але для анімацій не застосовується.
Що входить у роботу?
- Аналіз моделі — перевірка геометрії, текстур, анімацій. Конвертація в GLTF з Draco.
- Налаштування сцени — освітлення (оточення, спрямоване світло), тіні, камера.
- Інтеграція — React-компонент із завантажувачем, контролами, адаптивністю.
- Конфігуратор (опціонально) — вибір кольору/матеріалу, перемикання моделей.
- Оптимізація — тести на мобільних пристроях, налаштування LOD.
- Документація — опис API компонента, інструкція із заміни моделі.
Строки орієнтовно
- Простий переглядач із завантаженням GLTF та orbit controls — 2–3 дні.
- Конфігуратор з вибором кольору/матеріалу та кількома моделями — 5–7 днів.
Вартість розраховується індивідуально після аналізу складності сцени та кількості інтерактивних елементів. Економія на трафіку може становити до 30%. При великих обсягах можлива економія бюджету — обговоримо на консультації.
Як ми гарантуємо якість?
Ми тестуємо переглядач на трьох останніх версіях Chrome, Firefox, Safari та Edge. Для мобільних пристроїв перевіряємо iPhone (Safari) та Android (Chrome). Надаємо гарантію на працездатність протягом 30 днів після здачі. Наш досвід — понад 5 років у WebGL-розробці, понад 30 успішних проєктів.
Зв'яжіться з нами для оцінки вашого проєкту. Замовте консультацію — обговоримо вимоги та підготуємо комерційну пропозицію під ключ.







