اضغط كل إطار من لعبتك في المتصفح
Squeezing Every Frame Out of Your Browser Game
دليل عملي لتشخيص وإصلاح مشاكل الأداء في الألعاب المبنية بـ Three.js والـ Gaussian Splats، من ميزانية العرض إلى ضغط الـ Splat.
A practical guide to diagnosing and fixing performance bottlenecks in games built with Three.js and Gaussian Splats, from render budgets to splat compression.
01 ليش الأداء أهم في المتصفح؟ Why Performance Matters More in the Browser
محركات الألعاب الأصلية مثل Unity وUnreal تعطيك وصول مباشر للذاكرة والأصول والـ GPU. المتصفح يضيف طبقة إضافية بين كودك والهاردوير. جافاسكريبت تدير ذاكرتها بنفسها تلقائياً، وهذا ممكن يسبب توقفات قصيرة في أوقات غير متوقعة. WebGL يعاقبك على أوامر الـ GPU المتكررة. وملفات الـ Gaussian Splat، بقدر ما تبدو رائعة، هي من أثقل الأصول اللي ممكن تحمّلها في مشهد متصفح.
Native game engines like Unity and Unreal give you direct access to memory, assets, and the GPU. The browser adds an extra layer between your code and the hardware. JavaScript manages its own memory automatically, which can cause brief pauses at unpredictable moments. WebGL penalizes you for repetitive GPU commands. And Gaussian Splat files, as impressive as they look, are among the largest assets you will ever load into a browser scene.
هذا كله ما يعني إنك ما تقدر تحقق ٦٠ إطاراً في الثانية. آلاف ألعاب وتجارب Three.js تحققها. لكنه يعني إنك لازم تكون متعمّد. لعبة تشتغل بسلاسة في Chrome على MacBook Pro ممكن تتعثّر على لابتوب Windows متوسط أو ترتجف بشدة على جوال. تقنيات التحسين في هذا المقال مو نظرية، هي تغييرات ملموسة وقابلة للقياس تصنع الفرق بين لعبة تقدر تلعبها وواحدة ما تقدر.
None of this means you can't hit 60 frames per second. Thousands of Three.js games and experiences do. But it does mean you have to be deliberate. A game that runs smoothly in Chrome on a MacBook Pro might crawl on a mid-range Windows laptop or stutter badly on a phone. The optimization techniques in this article are not theoretical; they are the concrete, measurable changes that make the difference between a game that's playable and one that isn't.
أهم تحوّل في التفكير هو: قِس قبل ما تحسّن. أدوات المطوّر المدمجة في المتصفح وأدوات الأداء الخاصة بـ Three.js راح تخبرك بالضبط وين يروح وقت الإطار. ما تخمّن. قِس أولاً، ثم أصلح.
The most important shift in mindset is this: measure before you optimize. The browser's built-in developer tools and Three.js's own performance utilities will tell you exactly where your frame time is going. Don't guess. Profile first, then fix.
«قِس أولاً، ثم أصلح. أكبر مكاسب الأداء دايماً تجي من إصلاح المشكلة الصح، مو المشكلة الواضحة.»
"Profile first, then fix. The biggest performance wins always come from fixing the right problem, not the obvious one."
02 افهم ميزانية الإطار Understanding Your Frame Budget
عند ٦٠ إطاراً في الثانية، عندك ١٦.٦٧ ميلي ثانية لتكمّل كل شيء: تحديث منطق اللعبة، تشغيل الفيزياء، إصدار أوامر الرسم، وترك الـ GPU يرسم الإطار. هذه الميزانية مشتركة بين جافاسكريبت والعمل على CPU بـ Three.js وعرض الـ GPU. عرض الـ Gaussian Splat يضيف فوق هذا عملية فرز في كل إطار، لأن شفافية الـ Splat تحتاج ترتيب من الخلف للأمام في كل مرة.
At 60 frames per second, you have 16.67 milliseconds to complete everything: update game logic, run physics, issue draw calls, and let the GPU render the frame. That budget is shared across JavaScript, the CPU-side Three.js work, and the GPU rasterization. Gaussian Splat rendering adds a sorting pass on top of that, because splat transparency requires back-to-front ordering every frame.
أول أداة تمدّ إيدك لها هي تبويب Performance في المتصفح. افتح DevTools، سجّل بضع ثوان من اللعب، وشوف وين يروح وقت الإطار. تبحث عن مهام جافاسكريبت طويلة، توقفات تنظيف ذاكرة مفرطة، وتعطّلات الـ GPU. Three.js كذلك تتكامل مع مكتبة stats.js، اللي تعطيك عداد FPS ووقت الإطار على الشاشة خلال التطوير.
The first tool to reach for is the browser's Performance tab. Open DevTools, record a few seconds of gameplay, and look at where your frame time is going. You're looking for long JavaScript tasks, excessive garbage collection pauses, and GPU stalls. Three.js also integrates with the stats.js library, which gives you an on-screen FPS and frame time counter you can display during development.
import Stats from 'three/addons/libs/stats.module.js';
const stats = new Stats();
stats.showPanel(0); // 0: fps, 1: ms per frame, 2: mb memory
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// your update and render code here
renderer.render(scene, camera);
stats.end();
requestAnimationFrame(animate);
}
animate();
لمّا تكون عندك رؤية واضحة لوقت الإطار، تقدر تاخذ قرارات مبنية على معطيات. مشهد يصرف ١٢ ميلي ثانية على الـ GPU و٢ على جافاسكريبت يحتاج إصلاحات على جانب الـ GPU: أوامر رسم أقل، شيدرات أبسط، دقة أقل. مشهد يصرف ١٠ ميلي ثانية في جافاسكريبت على الأرجح عنده حلقة منطق تشتغل أكثر من اللازم في كل إطار.
Once you have visibility into your frame time, you can make informed decisions. A scene spending 12ms on the GPU and 2ms on JavaScript needs GPU-side fixes: fewer draw calls, simpler shaders, lower resolution. A scene spending 10ms in JavaScript likely has a logic loop that's doing too much work per frame, or objects being created and destroyed in a way that triggers garbage collection.
03 تحسين عرض الـ Gaussian Splat Optimizing Gaussian Splat Rendering
الـ Gaussian Splats هي أكبر متغيّر أداء في لعبة متصفح مبنية بالـ Splats. مشهد Splat محمّل ببساطة يمكن يحتوي من ثلاثة إلى خمسة ملايين Gaussian، كل واحدة تحتاج فرز وعملية رسم. هذا الرقم يحتاج ينزل قبل أي شيء ثاني.
Gaussian Splats are the single biggest performance variable in a splat-based browser game. A naively loaded splat scene can contain three to five million Gaussians, each requiring a sort and a compositing draw. That number needs to come down before anything else.
قلّل عدد الـ Splats أثناء التدريب Reduce Splat Count at Training Time
أفعل تحسين يصير قبل ما يوصل الملف للعبتك. لمّا تدرّب بـ gsplat أو الـ repo الرسمي، تقدر تتحكم في الحد الأقصى لعدد الـ Gaussians عبر معاملات التدريب. تقليل densify_grad_threshold وتحديد سقف لـ max_num_splats ينتج ملفات أصغر بتأثير بصري أقل مما تتوقع، لأن المحسّن يركّز التفاصيل تلقائياً حيث تهم أكثر.
The most effective optimization happens before the file ever reaches your game. When training with gsplat or the official repo, you can control the maximum number of Gaussians through training hyperparameters. Reducing densify_grad_threshold and capping max_num_splats produces smaller files with less visual impact than you might expect, because the optimizer naturally concentrates detail where it matters most.
بعد التدريب، استخدم SuperSplat أو أداة مثل splat-transform لقصّ مشهدك وتنظيفه. احذف الـ Gaussians اللي هي خارج منطقة اللعب أو طافية كبقايا إعادة بناء. مشهد داخلي مقصوص جيداً يمكن يفقد ٤٠ إلى ٦٠ بالمئة من الـ Gaussians مع ما يكاد يُلاحَظ أي فرق في الجودة من الزوايا اللي يراها اللاعب فعلاً.
After training, use SuperSplat or a command-line tool like splat-transform to crop and clean your scene. Remove Gaussians outside the playable area or floating as reconstruction artifacts. A well-cropped interior splat can lose 40 to 60 percent of its Gaussians with almost no visible quality loss from the angles a player actually sees.
التحميل التدريجي والبث Progressive Loading and Streaming
ملف Splat بـ ١٥٠ ميغابايت ما يقدر يُحمّل فوراً. بدل ما تحجب اللعبة حتى يكتمل الملف، ابثّ الـ Splat تدريجياً. مكتبة @mkkellogg/gaussian-splats-3d تدعم العرض التدريجي بشكل مدمج: يظهر الـ Splat بدقة منخفضة ويتحسّن مع وصول المزيد من البيانات.
A 150 MB splat file cannot load instantly. Rather than blocking the game until the full file is ready, stream the splat progressively. The @mkkellogg/gaussian-splats-3d library supports progressive rendering out of the box: the splat appears at low fidelity and fills in detail as more data arrives.
const viewer = new GaussianSplats3D.Viewer({
renderer, camera,
// Render as soon as partial data is available
progressiveLoad: true,
splatSortDistanceMapPrecision: 32,
gpuAcceleratedSort: true,
});
await viewer.addSplatScene('./scene.splat', {
streamLoadData: true,
onProgress: (percent) => {
updateLoadingBar(percent);
if (percent >= 30) hideLoadingScreen();
}
});
الفرز المسرَّع بالـ GPU GPU-Accelerated Sorting
الفرز في كل إطار هو أكثر جزء مكثّف على الـ CPU في عرض الـ Splat. بشكل افتراضي يصير على الـ CPU في جافاسكريبت، وهذا ممكن وحده يأخذ عدة ميلي ثوان في الإطار. خيار gpuAcceleratedSort ينقل هذا لعملية WebGL compute، ويحرّر الـ thread الرئيسي بشكل ملحوظ.
The per-frame sort is the most CPU-intensive part of splat rendering. By default, sorting happens on the CPU in JavaScript, which can consume several milliseconds per frame on its own. The gpuAcceleratedSort option offloads this to a WebGL compute pass, freeing the main thread significantly. Enable it when targeting devices with decent GPU support, which covers the vast majority of modern desktop and mobile browsers.
04 تقليل أوامر الرسم في Three.js Three.js Draw Call Reduction
كل كائن تضيفه لمشهد Three.js يستخدم مادة فريدة يولّد على الأقل أمر رسم واحد في كل إطار. أوامر الرسم مكلفة مو بسبب الهندسة نفسها بل بسبب تغييرات الحالة اللي تفرضها على الـ GPU. مشهد بـ ٥٠٠ أمر رسم سيؤدي أسوأ بكثير من مشهد بـ ٥٠ أمر رسم يعرض نفس التعقيد البصري.
Every object you add to a Three.js scene that uses a unique material generates at least one draw call per frame. Draw calls are expensive not because of the geometry itself but because of the state changes they force on the GPU. A scene with 500 draw calls will perform far worse than a scene with 50 draw calls rendering the same visual complexity, simply because of the overhead of switching GPU state 450 extra times.
الأداة الأساسية لتقليل أوامر الرسم هي الـ Instancing. إذا عندك عشر أشجار أو أربعين عملة أو مئة وحدة عدو تشترك في نفس الهندسة، لازم تستخدم InstancedMesh. الـ Instancing يخلّيك ترسم كل نسخ الكائن في أمر رسم واحد بتمرير بيانات التحويل لكل نسخة كـ buffer attribute.
The primary tool for reducing draw calls is instancing. If you have ten trees, forty coins, or a hundred enemy units that share the same geometry, you should be using InstancedMesh. Instancing lets you draw all copies of an object in a single draw call by passing per-instance transformation data as a buffer attribute.
const geometry = new THREE.SphereGeometry(0.3, 8, 8);
const material = new THREE.MeshStandardMaterial({ color: '#1a52b8' });
// 100 collectibles as a single draw call
const count = 100;
const mesh = new THREE.InstancedMesh(geometry, material, count);
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
matrix.setPosition(
(Math.random() - 0.5) * 20, 0.5,
(Math.random() - 0.5) * 20
);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
بعيداً عن الـ Instancing، دمج الهندسة الثابتة خيار قوي آخر. إذا عندك مجموعة من العناصر الزخرفية اللي ما تتحرك، ادمجها في BufferGeometry واحدة باستخدام BufferGeometryUtils.mergeGeometries(). هذا يطوي كائنات متعددة في أمر رسم واحد بدون أي تكلفة وقت تشغيل.
Beyond instancing, merging static geometry is another strong option. If you have a collection of props that never move, merge them into a single BufferGeometry using BufferGeometryUtils.mergeGeometries(). This collapses multiple objects into one draw call with no runtime cost.
05 تحسين المواد والـ Shaders Material and Shader Optimization
المادة اللي تعيّنها للشبكة تحدد كم يكلف عرضها. Three.js توفّر مواد بمستويات تكلفة مختلفة، واختيار الصح لكل حالة استخدام مكسب سهل يغفل عنه كثير من المطوّرين.
The material you assign to a mesh determines how expensive it is to render. Three.js provides materials at different cost levels, and choosing the right one for each use case is an easy win that many developers overlook.
MeshBasicMaterial
Lowest Costما فيه أي حسابات إضاءة. استخدمه لعناصر الواجهة، الأسطح المضيئة، السماوات، والـ Wireframes. أرخص بكثير من Phong أو Standard.
No lighting calculations at all. Use for UI elements, emissive surfaces, skyboxes, and wireframes. Dramatically cheaper than Phong or Standard.
MeshLambertMaterial
Low Costإضاءة منتشرة بدون بريق. مناسب للأسطح الغير لامعة مثل التضاريس والخشب والقماش. أسرع بكثير من MeshStandardMaterial.
Diffuse lighting without specular highlights. Good for matte surfaces like terrain, wood, and fabric. Much faster than MeshStandardMaterial.
MeshStandardMaterial
Medium Cost · PBRعرض فيزيائي مع خشونة ومعدنية. استخدمه بحذر، فقط للكائنات اللي تحتاج استجابة إضاءة واقعية فعلاً. مو لكل شبكة.
Physically based rendering with roughness and metalness. Use sparingly, only for objects that genuinely need realistic light response. Not for every mesh.
Custom ShaderMaterial
Variable Costاكتب شيدرات GLSL خاصتك لمّا المواد المدمجة إما غالية جداً أو ما تحقق المظهر المطلوب. شيدر مكتوب بشكل جيد يقدر يتفوق على MeshStandardMaterial.
Write your own GLSL shaders when built-in materials are either too expensive or can't achieve the look you need. A well-written custom shader can outperform MeshStandardMaterial significantly.
حدّد عدد الإضاءات الفورية في مشهدك. كل ضوء يلقي ظلالاً يضاعف تكلفة العرض بشكل كبير، لأن المشهد يجب أن يُرسم مرة لكل ضوء من منظور ذلك الضوء. في معظم ألعاب المتصفح، ضوء اتجاهي واحد مع ضوء محيط هو التوازن الصح. اخبز كل شيء ثاني في القوام.
Limit the number of real-time lights in your scene. Each light that casts shadows multiplies render cost significantly, because the scene must be rendered once per shadow-casting light from that light's perspective. In most browser games, one directional light with shadows plus ambient light is the right balance. Bake everything else into textures.
06 الهندسة واستراتيجيات الـ LOD Geometry and LOD Strategies
عدد المضلعات يهم أقل مما يعتقد معظم الناس على مستوى الكائنات الفردية، لكنه يتراكم بسرعة لما عندك مئات الكائنات في مشهد. القاعدة العامة هي استخدام الحد الأدنى من المضلعات اللي يحافظ على مظهر السيلويت المرئي على المسافة النموذجية للمشاهدة. عملة يلتقطها اللاعب من مترين ما تحتاج ١٠٠٠٠ مثلث.
Polygon count matters less than most people think at the level of individual objects, but it adds up fast when you have hundreds of objects in a scene. The general rule is to use the minimum polygon count that preserves the silhouette and surface detail visible at the typical viewing distance. A coin a player picks up from two meters away does not need 10,000 triangles.
Three.js لها فئة LOD مدمجة (Level of Detail) تخلّيك تتبادل بين نسخ عالية ومتوسطة ومنخفضة الدقة بناءً على مسافة الكاميرا. الكائنات البعيدة ترسم بدقة منخفضة؛ مع اقترابها تزيد التفاصيل. هذه من أفعل التقنيات للحفاظ على ميزانيات المضلعات تحت السيطرة.
Three.js has a built-in LOD (Level of Detail) class that lets you swap between high, medium, and low resolution versions of a mesh based on camera distance. Objects far from the camera render at low resolution; as they approach, the detail increases. This is one of the most effective techniques for keeping polygon budgets under control without sacrificing visual quality up close.
const lod = new THREE.LOD();
// High detail: visible within 10 units
const highGeo = new THREE.SphereGeometry(1, 32, 32);
lod.addLevel(new THREE.Mesh(highGeo, material), 0);
// Medium detail: visible from 10 to 30 units
const midGeo = new THREE.SphereGeometry(1, 12, 12);
lod.addLevel(new THREE.Mesh(midGeo, material), 10);
// Low detail: beyond 30 units
const lowGeo = new THREE.SphereGeometry(1, 6, 6);
lod.addLevel(new THREE.Mesh(lowGeo, material), 30);
scene.add(lod);
// Call update each frame with your camera
lod.update(camera);
07 إدارة القوام والذاكرة Texture and Memory Management
القوام هو أكثر مصدر شائع لضغط ذاكرة الـ GPU في ألعاب المتصفح. كل قوام تحمّله يبقى في ذاكرة الـ GPU حتى تتخلص منه صراحةً. في لعبة بانتقالات مستويات أو محتوى محمّل ديناميكياً، الفشل في التخلص من قوام المشاهد السابقة يستهلك تدريجياً كل الـ VRAM المتاحة ويسبب ارتجاجاً أو أعطالاً.
Textures are the most common source of GPU memory pressure in browser games. Every texture you load stays in GPU memory until you explicitly dispose of it. In a game with level transitions or dynamically loaded content, failing to dispose of textures from previous scenes will gradually consume all available VRAM and cause stuttering or crashes.
اضغط دائماً القوام قبل تحميله. الصيغ الأصلية للويب هي .basis و.ktx2، اللي تستخدم صيغ ضغط أصلية للـ GPU مثل BCn وETC2 وASTC. هذه الصيغ تُفكّ ضغطها مباشرةً بالـ GPU بدل جافاسكريبت، وتستهلك جزءاً بسيطاً من الـ VRAM مقارنة بـ PNG أو JPEG عادية.
Always compress textures before loading them. The web-native formats are .basis and .ktx2, which use GPU-native compression formats like BCn, ETC2, and ASTC. These formats are decompressed directly by the GPU rather than in JavaScript, and they consume a fraction of the VRAM of a standard PNG or JPEG.
// When a scene or level is no longer needed
function disposeScene(scene) {
scene.traverse((object) => {
if (object.isMesh) {
object.geometry.dispose();
const mat = object.material;
if (mat.map) mat.map.dispose();
if (mat.normalMap) mat.normalMap.dispose();
if (mat.roughnessMap) mat.roughnessMap.dispose();
mat.dispose();
}
});
}
08 تحسين جافاسكريبت وحلقة اللعبة JavaScript and Game Loop Optimization
حلقة العرض تشتغل ٦٠ مرة في الثانية. أي عمل تسوّيه داخلها ما يحتاج يصير في كل إطار هو هدر. تكامل الفيزياء، إيجاد مسار الذكاء الاصطناعي، وتحديثات الشبكة غالباً ما تحتاج تشتغل بـ ٦٠ هرتز. شغّلها بخطوة زمنية ثابتة أو بتردد أقل وتداخل النتائج للعرض.
The render loop runs 60 times per second. Any work you do inside it that doesn't need to happen every frame is waste. Physics integration, AI pathfinding, and network updates often don't need to run at 60hz. Run them on a fixed timestep or at a lower frequency and interpolate the results for rendering.
تجميع الكائنات ضروري للألعاب اللي تولّد وتحذف كائنات بكثرة، مثل الرصاصات أو الجسيمات أو الأعداء. إنشاء كائن جافاسكريبت جديد يخصّص ذاكرة. لمّا يُنظّف ذلك الكائن لاحقاً، المنظّف يوقف مؤقتاً الـ thread الرئيسي لاسترداده. في حلقة اللعبة هذا ينتج الارتجاجات والانقطاعات اللي تبدو كإطارات مفقودة.
Object pooling is critical for games that spawn and destroy objects frequently, like bullets, particles, or enemies. Creating a new JavaScript object allocates memory. When that object is garbage collected later, the collector pauses your main thread to reclaim it. In a game loop, this produces the stutters and hitches that feel like dropped frames.
class BulletPool {
constructor(size) {
this.pool = [];
const geo = new THREE.SphereGeometry(0.05, 4, 4);
const mat = new THREE.MeshBasicMaterial({ color: '#ffffff' });
for (let i = 0; i < size; i++) {
const mesh = new THREE.Mesh(geo, mat);
mesh.visible = false;
scene.add(mesh);
this.pool.push({ mesh, active: false });
}
}
get() { return this.pool.find(b => !b.active) || null; }
release(bullet) {
bullet.active = false;
bullet.mesh.visible = false;
}
}
تجنّب كذلك إنشاء vectors أو quaternions أو matrices جديدة داخل حلقة اللعبة. خصّصها مرة واحدة في أعلى الـ module وأعد استخدامها. new THREE.Vector3() داخل animate() ينشئ كائناً جديداً في كل إطار، ٦٠ مرة في الثانية. خزّن كل ما تقدر.
Also avoid creating new vectors, quaternions, or matrices inside the game loop. Allocate them once at the top of your module and reuse them. new THREE.Vector3() inside animate() creates a new object every frame, 60 times per second, all of which become garbage. Cache everything you can.
09 إعداد المُصيّر وتحجيم الدقة Renderer Configuration and Resolution Scaling
WebGLRenderer في Three.js له عدة إعدادات تؤثر مباشرةً على الأداء وسهل تغليطها. الـ Antialiasing مكلف على الموبايل وغالباً غير ضروري لما عندك FXAA أو SMAA كبديل. تحجيم نسبة البكسل هو أكبر رافعة فردية لأداء الموبايل.
Three.js's WebGLRenderer has several settings that directly impact performance and are easy to misconfigure. Antialiasing is expensive on mobile and often unnecessary when you have FXAA or SMAA as a post-process alternative. Pixel ratio scaling is the biggest single lever for mobile performance.
حدّد سقف نسبة البكسل
Cap Pixel Ratio
Mobile Criticalشاشات Retina والـ High-DPI عندها نسب بكسل ٢ أو ٣. العرض بالدقة الكاملة يضاعف عمل الـ GPU بـ ٤ أو ٩ أضعاف. حدّد سقفاً بـ ١.٥ للموبايل و٢ للديسك توب.
Retina and high-DPI screens have pixel ratios of 2 or 3. Rendering at full resolution multiplies GPU work by 4x or 9x. Cap at 1.5 for mobile, 2 for desktop. Most players won't notice the difference.
عطّل الميزات غير المستخدمة
Disable Unused Features
Easy Winاضبط antialias: false إذا تستخدم AA ما بعد المعالجة. عطّل physicallyCorrectLights إلا إذا احتجت دقة PBR. كل ميزة ما تستخدمها لا تكلف شيئاً.
Set antialias: false on the renderer if you use post-processing AA. Disable physicallyCorrectLights unless you need PBR accuracy. Every feature you don't use costs nothing if you never enable it.
دقة ديناميكية
Dynamic Resolution
Advancedراقب وقت الإطار وقلّل دقة المُصيّر ديناميكياً إذا انخفضت عن ٥٠ إطار. ارتفع مرة أخرى لمّا يتوفر هامش. يحافظ على سلاسة اللعبة على الهاردوير الأضعف.
Monitor frame time each frame and reduce the renderer resolution dynamically if you fall below 50fps. Scale back up when headroom returns. This keeps the game smooth on weaker hardware without a fixed quality drop.
Frustum Culling
Built InThree.js تحجب الكائنات خارج حقل رؤية الكاميرا تلقائياً. تأكّد إن object.frustumCulled = true مضبوطة على كل الشبكات. للمشاهد الكبيرة، أضف مكتبة حجب إضافية فوقها.
Three.js culls objects outside the camera frustum automatically. Make sure object.frustumCulled = true is set on all meshes (it is by default). For large scenes, add an occlusion culling library on top.
const renderer = new THREE.WebGLRenderer({
antialias: false, // use post-process AA instead
powerPreference: 'high-performance',
stencil: false, // disable if not needed
depth: true,
});
// Cap pixel ratio for mobile performance
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
10 قِس النتائج Measuring the Results
التحسين بدون قياس هو تخمين. بعد أي تغيير، شغّل اللعبة مجدداً وقارن أوقات الإطار. تبويب Performance في المتصفح يعطيك تفصيل دقيق لعمل الـ CPU في كل إطار. امتداد توقيت الـ GPU، المتاح عبر renderer.info، يريك كم أمر رسم ومثلث وبرنامج شيدر نشط في أي إطار معيّن.
Optimization without measurement is guesswork. After making any change, run the game again and compare frame times. The browser's Performance tab gives you a detailed breakdown of CPU work per frame. The GPU timing extension, available via renderer.info, shows you how many draw calls, triangles, and shader programs are active in any given frame.
تحقق من renderer.info.render بانتظام أثناء التطوير. يكشف عدد الاستدعاءات والمثلثات المرسومة في كل إطار. إذا كانت أوامر الرسم في المئات، الـ Instancing ودمج الهندسة يجب أن تكون أولويتك الأولى. إذا كان عدد المثلثات في عشرات الملايين، الـ LOD وتبسيط الهندسة يجيء أولاً. إذا كان استخدام الذاكرة في renderer.info.memory يرتفع مع الوقت، عندك تسريب في التخلص من الموارد.
Check renderer.info.render regularly during development. It exposes the number of calls, triangles, points, and lines being rendered each frame. If draw calls are in the hundreds, instancing and geometry merging should be your first priority. If triangle count is in the tens of millions, LOD and geometry simplification should come first. If memory usage in renderer.info.memory keeps climbing over time, you have a disposal leak.
اختبر على الهاردوير الحقيقي المستهدف، مو فقط جهاز التطوير. مشهد يشتغل بـ ١٢٠ إطار على لابتوب المطوّر ممكن يشتغل بـ ٢٨ إطار على الجوال المتوسط اللي يستخدمه لاعبوك فعلاً. قرارات الأداء اللي تاخذها يجب أن تبنيها على أضعف جهاز تبي تشغّل عليه، مو أسرع جهاز تملكه.
Test on real target hardware, not just your development machine. A scene that runs at 120fps on a developer laptop may run at 28fps on the mid-range phone your players actually use. Browser-based games have an unusually wide hardware range to support, and the performance decisions you make should be informed by the weakest device you want to run on, not the fastest one you own.
تحسين الأداء مو مهمة واحدة تسوّيها في نهاية التطوير. هو انضباط تطبّقه طوال الرحلة. قِس مبكراً، أصلح تدريجياً، واختبر على نطاق واسع. الألعاب اللي تحس إنها سريعة ومصقولة في المتصفح هي تلك اللي مطوّروها تعلّموا وين تذهب الميزانية وصرفوها بحكمة.
Performance optimization is not a single task you do at the end of development. It is a discipline you apply throughout. Profile early, fix incrementally, and test broadly. The games that feel fast and polished in the browser are not the ones with the least ambition; they are the ones whose developers learned where the budget was going and spent it wisely.