אנימציות WebGL ואפקטי תלת-ממד: פיתוח ואופטימיזציה
כשמנסים להוסיף אנימציית תלת-ממד באמצעות Canvas 2D, הביצועים צונחים ל-15 FPS במכשירים ניידים — ה-GPU יושב בטל. WebGL פותר זאת על ידי מתן גישה ישירה למעבד הגרפי. כשאנחנו צריכים לרנדר אלפי חלקיקים, לעוות גאומטריה על בסיס אות שמע, או לבנות סצנות תלת-ממד אינטראקטיביות ללא תוספים — אנחנו משתמשים ב-WebGL 2.0 (נתמך על ידי 95%+ מהדפדפנים) דרך Three.js או ישירות דרך ממשק ה-WebGL API למשימות מותאמות אישית. לדוגמה, בפרויקט דף נחיתה פרימיום החלפנו אנימציית CSS ברקע שיידר — LCP ירד מ-4.2 ל-1.8 שניות, בעוד המורכבות החזותית שולשה. צרו קשר כדי שנעריך את הפרויקט שלכם.
למה WebGL הוא התקן לתלת-ממד באינטרנט?
WebGL הוא הטכנולוגיה היחידה שמספקת גרפיקת תלת-ממד מלאה ללא תוספים. בניגוד לאנימציות CSS או Canvas 2D, WebGL משתמש בשיידרים על ה-GPU, ומספק ביצועים שאי אפשר להשיג באמצעים אחרים. לוויזואליזציית נתונים, גימיפיקציה או עיצוב פרימיום, זה התקן. מפרט ה-WebGL של קבוצת Khronos נתמך על ידי כל הדפדפנים המודרניים. לפי גוגל, אלמנטים תלת-ממדיים אינטראקטיביים מעלים המרות ב-30–40% במסחר אלקטרוני — אבל רק עם אופטימיזציה נכונה.
איך אנחנו מיישמים אפקטי תלת-ממד
אנחנו יוצרים רקעי שיידר (רעש מונפש, גרדיאנטים שמגיבים לעכבר), מערכות חלקיקים (100k+ חלקיקים מונפשים על ה-GPU), עיוות תמונה בריחוף, מודלים תלת-ממדיים של מוצרים עם קונפיגורטורי חומרים, וסצנות אינטראקטיביות. להלן טבלה של אפקטים טיפוסיים.
| אפקט | כלי | ביצועים | זמן אב-טיפוס |
|---|---|---|---|
| רקע שיידר | WebGL גולמי / Three.js | 60fps במחשב שולחני | 1–2 ימים |
| מערכת חלקיקים (100k) | Three.js עם BufferGeometry | 30–60fps במובייל | 2–3 ימים |
| עיוות תמונה | Three.js עם מפת תזוזה | 60fps | יום אחד |
| מודל מוצר תלת-ממדי | Three.js + GLTF | תלוי במספר פוליגונים | 5–7 ימים |
יישום טכני: שיידרים, חלקיקים, עיוותים
Three.js ו-WebGL גולמי
Three.js הוא התקן דה פקטו לרוב פרויקטי האינטרנט. הוא מפשט שיידרים ובופרים, ומספק סצנה, מצלמה ותאורה. גרסה r169+ תומכת ב-WebGPU כרנדרר חלופי. WebGL גולמי משמש כשצריך שליטה מלאה: פרימיטיבים גאומטריים מותאמים אישית, מצבי מיזוג לא סטנדרטיים, גודל חבילה מינימלי. שיידרי GLSL נכתבים ידנית לכל אפקט.
// Вертексный шейдер — деформация плоскости по шуму
uniform float uTime;
uniform float uAmplitude;
varying vec2 vUv;
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
void main() {
vUv = uv;
vec3 pos = position;
float noise = snoise(vec2(pos.x * 0.5 + uTime * 0.3, pos.y * 0.5));
pos.z += noise * uAmplitude;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
} // Three.js — инициализация сцены с постпроцессингом
import * as THREE from 'three'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#webgl'),
antialias: true,
alpha: true,
})
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.toneMapping = THREE.ACESFilmicToneMapping
const composer = new EffectComposer(renderer)
composer.addPass(new RenderPass(scene, camera))
composer.addPass(new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
0.8, // strength
0.4, // radius
0.85 // threshold
)) מערכת חלקיקים
עבור 100k+ חלקיקים, אנחנו משתמשים ב-BufferGeometry עם תכונות ב-Float32Array. האנימציה רצה כולה בשיידר הקודקודים — ה-CPU לא מעורב בזמן ריצה.
const COUNT = 150000
const positions = new Float32Array(COUNT * 3)
const randoms = new Float32Array(COUNT)
for (let i = 0; i < COUNT; i++) {
positions[i * 3 + 0] = (Math.random() - 0.5) * 10
positions[i * 3 + 1] = (Math.random() - 0.5) * 10
positions[i * 3 + 2] = (Math.random() - 0.5) * 10
randoms[i] = Math.random()
}
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1))
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uSize: { value: 3.0 * renderer.getPixelRatio() },
},
vertexShader: particleVertexShader,
fragmentShader: particleFragmentShader,
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending,
}) עיוות תמונה בריחוף
מרקם התמונה מעוות באמצעות מפת תזוזה המבוססת על מיקום הסמן. אפקט ריחוף 'נוזלי'.
// Uniforms для передачи в шейдер
const uniforms = {
uTexture: { value: texture },
uDisplacement: { value: displacementTexture },
uMouse: { value: new THREE.Vector2(0, 0) },
uVelo: { value: 0 },
}
// Отслеживание скорости движения мыши
let lastMouse = new THREE.Vector2()
let currentVelo = 0
window.addEventListener('mousemove', (e) => {
const current = new THREE.Vector2(
e.clientX / window.innerWidth,
1.0 - e.clientY / window.innerHeight
)
const delta = current.distanceTo(lastMouse)
currentVelo = Math.min(delta * 10, 1.0)
lastMouse.copy(current)
uniforms.uMouse.value.copy(current)
}) טעינת נכסים
מודלים תלת-ממדיים בפורמט .glb (GLTF בינארי). דחיסה באמצעות Draco (גאומטריה) + KTX2 (מרקמים).
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
const ktx2Loader = new KTX2Loader()
ktx2Loader.setTranscoderPath('/basis/')
ktx2Loader.detectSupport(renderer)
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
loader.setKTX2Loader(ktx2Loader)
loader.load('/models/scene.glb', (gltf) => {
scene.add(gltf.scene)
}, (progress) => {
const pct = (progress.loaded / progress.total * 100).toFixed(0)
onProgress(pct)
}) אופטימיזציית ביצועים ואדפטיביות
יעד קצב הפריימים הוא 60 FPS במחשב שולחני, 30 FPS במובייל עם הפחתת איכות אוטומטית. נקבע באמצעות // Вертексный шейдер — деформация плоскости по шуму uniform float uTime; uniform float uAmplitude; varying vec2 vUv; vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } void main() { vUv = uv; vec3 pos = position; float noise = snoise(vec2(pos.x * 0.5 + uTime * 0.3, pos.y * 0.5)); pos.z += noise * uAmplitude; gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); } ו-benchmark ברינדור הראשון. כללים מרכזיים: drawcall אחד במקום אלף (InstancedMesh), // Three.js — инициализация сцены с постпроцессингом import * as THREE from 'three' import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js' import { RenderPass } from 'three/addons/postprocessing/RenderPass.js' import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js' const renderer = new THREE.WebGLRenderer({ canvas: document.querySelector('#webgl'), antialias: true, alpha: true, }) renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) renderer.setSize(window.innerWidth, window.innerHeight) renderer.toneMapping = THREE.ACESFilmicToneMapping const composer = new EffectComposer(renderer) composer.addPass(new RenderPass(scene, camera)) composer.addPass(new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 0.8, // strength 0.4, // radius 0.85 // threshold )) , dispose בעת הסרת רכיב, פוסט-פרוסינג רק כאשר const COUNT = 150000 const positions = new Float32Array(COUNT * 3) const randoms = new Float32Array(COUNT) for (let i = 0; i < COUNT; i++) { positions[i * 3 + 0] = (Math.random() - 0.5) * 10 positions[i * 3 + 1] = (Math.random() - 0.5) * 10 positions[i * 3 + 2] = (Math.random() - 0.5) * 10 randoms[i] = Math.random() } const geometry = new THREE.BufferGeometry() geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)) geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1)) const material = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uSize: { value: 3.0 * renderer.getPixelRatio() }, }, vertexShader: particleVertexShader, fragmentShader: particleFragmentShader, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, }) . השוואת גישות מופיעה להלן.
| פרמטר | Three.js | WebGL גולמי |
|---|---|---|
| מהירות פיתוח | גבוהה (רכיבים מוכנים) | נמוכה (שליטה ידנית) |
| ביצועים | מספיק ל-90% מהמשימות | מקסימליים עם אופטימיזציה מותאמת |
| גודל חבילה | ~600 KB (מינימום) | ~50 KB (רק שיידרים) |
| מתי לבחור | אפקטים סטנדרטיים, לוחות זמנים קריטיים | סצנות ייחודיות, גודל מינימלי |
איך לייעל את WebGL למכשירים חלשים?
בדקו אוטומטית את כוח ה-GPU באמצעות benchmark בטעינה. אם ה-FPS יורד מתחת ליעד, הפחיתו את יחס הפיקסלים, מספר החלקיקים, ובטלו פוסט-פרוסינג. למובייל, השתמשו ב-InstancedMesh והימנעו מחומרים שקופים. תמיכה ב-// Uniforms для передачи в шейдер const uniforms = { uTexture: { value: texture }, uDisplacement: { value: displacementTexture }, uMouse: { value: new THREE.Vector2(0, 0) }, uVelo: { value: 0 }, } // Отслеживание скорости движения мыши let lastMouse = new THREE.Vector2() let currentVelo = 0 window.addEventListener('mousemove', (e) => { const current = new THREE.Vector2( e.clientX / window.innerWidth, 1.0 - e.clientY / window.innerHeight ) const delta = current.distanceTo(lastMouse) currentVelo = Math.min(delta * 10, 1.0) lastMouse.copy(current) uniforms.uMouse.value.copy(current) }) מבטלת את האנימציה לחלוטין. גישה זו חוסכת עד 40% מתעבורה ומבטיחה חוויה חלקה בכל מכשיר.
מה כלול בפרויקט?
- אב-טיפוס של האפקט עם שיידרי מקור
- אינטגרציה לפריימוורק שלכם (React, Vue, Angular, vanilla)
- אופטימיזציה ל-Core Web Vitals ולמכשירים ניידים
- תיעוד להתקנה ותחזוקה
- גישה למאגר הקוד
קבלו ייעוץ בתוך יום עסקים אחד — צרו קשר כדי לדון במשימה שלכם.
שלבי עבודה ולוחות זמנים משוערים
- בריף וניתוח דרישות — 1–2 ימים
- אב-טיפוס של האפקט המרכזי — 3–5 ימים
- פיתוח ואופטימיזציה — 5–10 ימים
- בדיקות מכשירים — 2–3 ימים
- פריסה ותיעוד — 2 ימים
סה"כ: בין 10 ל-20 ימי עסקים תלוי במורכבות. המחיר מחושב באופן אישי. צרו קשר כדי שנעריך את הפרויקט שלכם.







