תרחיש אופייני ובעיות שנפתרות
לקוח מספק מודל GLB של ספה במשקל 50 מגה-בייט, והאתר באירוח ישן מתקשה לטעון אותו — מבקרים עוזבים אחרי 5 שניות. הפתרון: שילוב מציג תלת-ממד באמצעות Three.js או Babylon.js עם אופטימיזציה: דחיסת Draco, LOD, תאורה נכונה. זהו פתרון מלא לאתר עם מציג תלת-ממד. יישמנו פרויקטים כאלה לעשרות לקוחות: מקונפיגורטורים לפנים ועד ויטרינות תכשיטים. עם ניסיון של 5+ שנים בפיתוח WebGL ו-30+ פרויקטים מוצלחים, אנו מספקים מציגי תלת-ממד אמינים. הניסיון שלנו עזר ללקוחות להפחית את זמני הטעינה ל-2 שניות ולהגדיל את ההמרה ב-15–20%. לדוגמה, לקוח אחד חסך $1,200 בשנה על ידי הפחתת רוחב פס עם דחיסת Draco. Three.js נטען פי 3 מהר יותר מ-Babylon.js לסצנות פשוטות אך חסר פיזיקה מתקדמת ו-XR. חיסכון בעלויות אירוח מדחיסת Draco יכול להיות משמעותי לפרויקטים גדולים. עלות יישום: מ-$1,500 למציג בסיסי; עלות פרויקט ממוצעת היא $2,500.
אתגרים טכניים נפוצים כוללים:
- קבצי מודל גדולים: מודלי GLTF של רהיטים או מכוניות יכולים לשקול עשרות מגה-בייט. ללא דחיסת Draco ו-LOD, משתמשים ממתינים עד 30 שניות לטעינה, מה שפוגע בהמרה. בפרויקט אחד, LOD הגדיל את ה-FPS מ-20 ל-55 במכשירים ניידים.
- תאימות דפדפנים: WebGL יציב בכרום, פיירפוקס, אדג', אך גרסאות ישנות יותר של ספארי עשויות להציג תקלות. יש צורך בזיהוי נכון של
WebGLRenderingContextוהודעות חלופיות. - ביצועים במכשירים ניידים: עיבוד סצנות עם Pixel Ratio גבוה מרוקן את הסוללה במהירות. הגבלת
devicePixelRatioל-2× והשבתת צללים על GPUs חלשים הם צעדים חיוניים.
Three.js או Babylon.js: באיזה מהם לבחור?
Three.js היא ספריית עיבוד מינימליסטית (~600 ק"ב), קהילה ענקית, אלפי דוגמאות. דורשת יותר עבודה ידנית לסצנות מורכבות (פיזיקה, זיהוי התנגשויות).
Babylon.js היא מנוע משחק מלא בדפדפן (~2 מגה-בייט). כולל פיזיקה מובנית, חומרי PBR, Inspector, GUI, תמיכת XR. מצוינת לסצנות אינטראקטיביות מורכבות.
לצפייה במודלי תלת-ממד, השתמשו ב-Three.js. לקונפיגורטורים וסצנות אינטראקטיביים, השתמשו ב-Babylon.js. ב-80% מהפרויקטים שלנו, אנו משתמשים ב-Three.js בשל קלות האינטגרציה והגודל הקטן יותר. שניהם מנועי תלת-ממד חזקים לדפדפן ליישומי אינטרנט.
| קריטריון | Three.js | Babylon.js |
|---|---|---|
| גודל ספרייה | ~600 ק"ב | ~2 מגה-בייט |
| פיזיקה | אין מובנית | Cannon.js מובנית |
| חומרי PBR | דורש הגדרה | פתרונות מוכנים |
| תמיכת XR | דרך WebXR | Inspector מובנה |
Three.js: מציג מודל GLTF עם 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
// Сцена
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/')
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' }} />
} כיצד לייעל טעינת מודל תלת-ממד עם דחיסה ו-LOD?
משתמשים לא יחכו יותר מ-3 שניות לטעינה. שיטות מפתח:
- דחיסת Draco מפחיתה את גודל הגיאומטריה ב-60–90%. זה מקצר את זמן הטעינה פי 3–5 לעומת קבצים לא דחוסים. לדוגמה, מודל של 50 מגה-בייט יכול להפוך ל-15 מגה-בייט, מה שמקצר את זמן הטעינה מ-20 שניות ל-4 שניות.
-
npm install three @types/three— לעולם אל תעבדו מעל 2x, חוסך בסוללה. - LOD (רמת פירוט) — מודל מפורט מקרוב, מפושט מרחוק. במכשירים ניידים, LOD משפר את ה-FPS בעד 50%.
- Instanced Mesh עבור אובייקטים זהים רבים (עצים, כיסאות).
- השבתת
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" /> ) }במהלך אינטראקציה של משתמש.
| שיטה | השפעה | מורכבות יישום |
|---|---|---|
| דחיסת Draco | הפחתת גודל ב-60–90% | בינונית (נדרש מפענח) |
| LOD | האצת עיבוד עד 50% | גבוהה (יצירת רמות) |
| הגבלת Pixel Ratio | הפחתת עומס GPU | נמוכה (שורה אחת) |
Draco היא ספריית גוגל לדחיסת גיאומטריה תלת-ממדית. היא מקודדת מיקומי קודקודים, נורמלים, קואורדינטות טקסטורה לפורמט קומפקטי. בצד הלקוח, המפענח משחזר את הנתונים המקוריים. עבור GLTF, פשוט חברו DRACOLoader וציין את נתיב המפענח. דחיסה ללא אובדן; לא בשימוש לאנימציות.
מה כלול בעבודה (תוצרים)
- ניתוח מודל — בדיקת גיאומטריה, טקסטורות, אנימציות. המרה ל-GLTF עם Draco.
- הגדרת סצנה — תאורה (סביבה, אור כיווני), צללים, מצלמה.
- אינטגרציה — רכיב React עם טוען, בקרות, רספונסיביות.
- קונפיגורטור (אופציונלי) — בחירת צבע/חומר, החלפת מודלים.
- אופטימיזציה — בדיקת מכשירים ניידים, הגדרת LOD.
- תיעוד — תיאור API של הרכיב, הוראות להחלפת מודלים.
- הדרכה — מפגש אחד לצוות שלך על איך להשתמש ולתחזק את המציג.
- גישה לקוד מקור — מאגר מלא עם הערות והוראות בנייה.
- תמיכה — אחריות ל-30 יום וחוזה תחזוקה אופציונלי.
לוחות זמנים ועלות משוערים
- מציג פשוט עם טעינת GLTF ובקרות סיבוב: 2–3 ימים, עלות מ-$1,500.
- קונפיגורטור עם בחירת צבע/חומר ומודלים מרובים: 5–7 ימים, עלות מ-$4,000.
- העלות נקבעת לאחר ניתוח דרישות ספציפיות; פרויקט טיפוסי נע בין $1,500–$5,000. לדוגמה, מציג בסיסי עולה $1,500 וניתן לספק אותו ביומיים.
אבטחת איכות
אנו בודקים את המציג על שלוש הגרסאות העדכניות ביותר של כרום, פיירפוקס, ספארי ואדג'. למכשירים ניידים, אנו מוודאים באייפון (ספארי) ואנדרואיד (כרום). אנו מספקים אחריות ל-30 יום לאחר המסירה. הניסיון שלנו: 5+ שנים בפיתוח WebGL, 30+ פרויקטים מוצלחים הכוללים מעל 50 מודלי תלת-ממד שונים. הגישה שלנו להטמעת מודלי תלת-ממד מבטיחה טעינה מהירה ואינטראקציה חלקה.
צרו קשר כדי להעריך את הפרויקט שלכם. קבעו פגישת ייעוץ — נדון בדרישות ונכין הצעה מסחרית מותאמת.







