التحسين
يمكن أن تكون المواقع الإلكترونية ثلاثية الأبعاد مطالبة من حيث الأداء. من المهم أن نتذكر أن ليس جميع المستخدمين لديهم نفس الجهاز.
بعضهم لديهم أجهزة متطورة مع وحدة معالجة رسومية قوية و شاشة عالية الدقة بينما الآخرون لديهم أجهزة ضعيفة مع وحدة معالجة رسومية ضعيفة و شاشة منخفضة الدقة.
لضمان أن يتمكن أكبر عدد ممكن من المستخدمين من الاستمتاع بتطبيقاتك ثلاثية الأبعاد بأفضل شكل، تحتاج إلى تحسينها وتجنب الحسابات غير الضرورية.
في هذا الدرس، سنرى تقنيات مختلفة لتحسين مشاريعنا React Three Fiber وبعض الفخاخ الشائعة التي يجب تجنبها.
عمليات الرسم
في الرسوم البيانية ثلاثية الأبعاد، تعتبر عملية الرسم أمر يتم إرساله إلى وحدة المعالجة الرسومية لكي تخبر واجهة برمجة الرسوميات ما الذي يجب رسمه وكيفية رسمه.
كلما زادت عمليات الرسم لديك، زاد العمل الذي يجب على وحدة المعالجة الرسومية القيام به.
لمراقبة عدد عمليات الرسم في مشاريعك باستخدام React Three Fiber يمكنك استخدام ملحق SpectorJS لمتصفح كروم.
بمجرد تثبيت الملحق، يمكنك فتح لوحة SpectorJS بالنقر على أيقونة Spector.js في شريط أدوات كروم.
انقر فوق زر الدائرة الحمراء لبدء تسجيل عمليات الرسم. انتظر لبضع ثوان وستفتح لوحة SpectorJS.
بشكل افتراضي، يفتح في علامة تبويب الأوامر. يعرض لك جميع الأوامر المرسلة إلى وحدة المعالجة الرسومية. في العمود الأيسر، يمكنك مشاهدة الصورة المصغرة لكل عملية رسم.
الأوامر الأولى للرسم هي للظلال. ثم يمكنك رؤية عمليات الرسم للشخصية ثم واحدة لكل مكعب (ويجب تحسين الأخيرة).
تعطي علامة التبويب المعلومات أيضًا بعض المعلومات المفيدة مثل عدد الرؤوس وعدد عمليات الرسم.
الآن سنرى كيف يمكننا تحسين مشاريع React Three Fiber لتقليل عدد عمليات الرسم.
التكرار
مفهوم التكرار يُستخدم بشكل واسع في الرسومات ثلاثية الأبعاد. يسمح هذا المفهوم بإعادة استخدام الـ geometry والـ material نفسها لأكثر من كائن.
إنه حل رائع عندما ترغب في عرض نفس الكائن عدة مرات ولكن بمواقف position
أو scale
أو rotation
أو color
مختلفة...
يقوم بدمج جميع الرسائل المرسومة في واحدة فقط مما يؤدي إلى تحسين الأداء بشكل كبير.
يقدم مكتبة Drei مكون Instances لمساعدتنا في القيام بذلك ببساطة.
نحتاج إلى تغليف الـ geometry والـ material التي نريد تكرارها باستخدام مكون Instances
.
// ... import { Instances } from "drei"; // ... export const Experience = () => { const boxes = Array.from({ length: 40 }, () => ({ position: [ THREE.MathUtils.randFloat(2, 20) * (THREE.MathUtils.randInt(0, 1) ? -1 : 1), THREE.MathUtils.randFloat(0.2, 10), THREE.MathUtils.randFloat(10, 50), ], scale: THREE.MathUtils.randFloat(0.2, 1.2), color: new THREE.Color().setHSL(Math.random(), 1, 0.5), speed: THREE.MathUtils.randFloat(0.08, 0.42), })); return ( <> <OrbitControls maxPolarAngle={Math.PI / 2} minPolarAngle={0} maxDistance={12} minDistance={8} /> <NinjaMale scale={1.4} /> <ContactShadows opacity={0.5} /> <Instances> <boxGeometry /> <meshStandardMaterial side={THREE.DoubleSide} /> {boxes.map(({ scale, position, color, speed }, i) => ( <Box key={i} scale={scale} position={position} color={color} speed={speed} /> ))} </Instances> </> ); };
ثم داخل مكون Box
(والذي هو داخل مكون Instances
) يمكننا استبدال الـ mesh بـ instancedMesh باستخدام مكون Instance
والتخلص من الـ material وgeometry الخاصة بكل Box
:
// ... import { Instance } from "drei"; const Box = ({ scale, position, color, speed }) => { const ref = useRef(); useFrame(() => { ref.current.position.z -= speed; if (ref.current.position.z < -50) ref.current.position.z = 10; }); return <Instance ref={ref} scale={scale} position={position} color={color} />; }; // ...
بنفس عدد الـ boxes، انخفضنا من 50 draw calls إلى إجمالي 18 draw call.
إذا تساءلت لماذا لم تكن 39 draw calls أقل، فهذا لأننا كان لدينا أحد الـ draw call لكل box مرئي وبعضها لم يكن داخل frustum الكاميرا.
الآن يمكننا زيادة عدد الصناديق دون التأثير على الأداء:
// ... const boxes = Array.from({ length: 1000 }, () => ({ position: [ THREE.MathUtils.randFloat(2, 20) * (THREE.MathUtils.randInt(0, 1) ? -1 : 1), THREE.MathUtils.randFloat(0.2, 10), THREE.MathUtils.randFloat(10, 50), ], scale: THREE.MathUtils.randFloat(0.2, 1.2), color: new THREE.Color().setHSL(Math.random(), 1, 0.5), speed: THREE.MathUtils.randFloat(0.08, 0.42), })); // ...
لدينا الآن 1000 صندوق متحرك مقابل إجمالي فقط 18 draw calls.
إذا كنت فضوليًا، يمكنك محاولة رسم 1000 صندوق بدون استخدام التكرار ورؤية كيف ستؤثر على الأداء. (لست مسؤولًا عن اشتعال حاسوبك 🤭)
مراقبة الأداء
نريد أن تعمل تطبيقات الويب ثلاثية الأبعاد بسلاسة على جميع الأجهزة. للتأكد من تحقيق ذلك، نحتاج إلى مراقبة أداء تطبيقاتنا وتعديله بما يتناسب.
لنبدأ بإلغاء التعليق عن المكون Effects واستيراده في مكون App.jsx
:
import { Environment } from "@react-three/drei"; import { Canvas } from "@react-three/fiber"; import { Effects } from "./components/Effects"; import { Experience } from "./components/Experience"; function App() { return ( <> <Canvas camera={{ position: [0, 2, 10], fov: 42 }}> <color attach="background" args={["#ffffff"]} /> <fog attach="fog" args={["#ffffff", 10, 50]} /> <group position-y={-2}> <Experience /> </group> <Environment preset="sunset" /> <Effects /> </Canvas> </> ); } export default App;
الآن، لدينا تأثير blur جميل وعمق المجال مطبق على مشهدنا.
بينما تعمل بشكل ممتاز على جهاز MacBook Pro بمعالج M1 Max، فإن ذلك لا يعني أنها ستعمل بنفس الكفاءة على جهاز أكثر تواضعًا.
أنا أيضًا أستخدم شاشة جهاز الكمبيوتر المحمول الخاص بي، لكن ماذا لو كان هناك شخص يستخدم شاشة كبيرة بدقة 4K؟ المزيد من البكسلات المراد عرضها يعني المزيد من العمل على GPU.
على أجهزة الجوال، تكون الشاشة أصغر ولكن GPU يكون أيضًا أقل قوة.
لهذه الأسباب كلها، من المهم اختبار تطبيقاتك على أجهزة مختلفة والتحقق مما إذا كانت تعمل بشكل جيد.
لتمكين مراقبة أداء تطبيقاتنا، يمكننا استخدام مكون PerformanceMonitor من مكتبة Drei.
لقد اكتشفنا سابقًا مكون Stats
لعرض FPS وMS. يشير إلى PerformanceMonitor
لضبط جودة تطبيقاتنا وفقًا للأداء.
في App.jsx
دعنا نستورد مكون PerformanceMonitor
ونضيفه إلى مكون Canvas
:
// ... import { PerformanceMonitor } from "@react-three/drei"; function App() { return ( <> <Canvas camera={{ position: [0, 2, 10], fov: 42 }}> <PerformanceMonitor onChange={(api) => { console.log("Performance Monitor (FPS)", api.fps); console.log("Performance Monitor (Factor)", api.factor); }} onIncline={() => { console.log("Performance Monitor (Inclined)"); }} onDecline={() => { console.log("Performance Monitor (Declined)"); }} /> {/* ... */} </Canvas> </> ); } export default App;
- يتم استدعاء
onChange
كلما تغير الأداء. يمنحنا الوصول إلى العديد من المعلومات مثل FPS وfactor. - يتم استدعاء وظيفة
onIncline
وonDecline
عندما يرتفع أو ينخفض المتوسط FPS عن عائق محدد.
سنحتفظ بالإعدادات الافتراضية، ولكن هنا بعض props
(وقيمها الافتراضية) التي يمكننا استخدامها لضبط متى يتم استدعاء وظائف onIncline
وonDecline
:
/** النسبة المئوية للتكرارات التي تتطابق مع الحدود الدنيا والعليا، 0.75 */ threshold?: number /** وظيفة تتلقى أعلى معدل تحديث للجهاز لتحديد الحدود الدنيا والعليا التي تُنشئ حاجز حيث لا يجب أن يحدث ميل أو هبوط، (refreshrate) => (refreshrate > 90 ? [50, 90] : [50, 60]) */ bounds: (refreshrate: number) => [lower: number, upper: number]
للحصول على القائمة الكاملة لـ props، يمكنك التحقق من PerformanceMonitor documentation.
مع 1000 صندوق الحالية، الـ FPS على جهازي حول 95 ويتم استدعاء onIncline
.
دعونا نزود عدد الصناديق إلى 20000:
// ... export const Experience = () => { const boxes = Array.from({ length: 20000 }, () => ({ position: [ THREE.MathUtils.randFloat(2, 20) * (THREE.MathUtils.randInt(0, 1) ? -1 : 1), THREE.MathUtils.randFloat(0.2, 10), THREE.MathUtils.randFloat(10, 50), ], scale: THREE.MathUtils.randFloat(0.2, 1.2), color: new THREE.Color().setHSL(Math.random(), 1, 0.5), speed: THREE.MathUtils.randFloat(0.08, 0.42), })); // ... };
جهازي قوي جدًا، إذا لم يستطع عرض 20000 حاول تقليل العدد.
تنخفض الـ FPS إلى أقل من 50 ويتم استدعاء onDecline
!
يمكننا التفاعل وفقًا لذلك وتعديل أي شيء يمكن أن يؤثر على الأداء.
لنبدأ بتعطيل تأثيرات Postprocessing:
import { Environment, PerformanceMonitor } from "@react-three/drei"; import { Canvas } from "@react-three/fiber"; import { useState } from "react"; import { Effects } from "./components/Effects"; import { Experience } from "./components/Experience"; function App() { const [effect, setEffect] = useState(true); return ( <> <Canvas camera={{ position: [0, 2, 10], fov: 42 }}> <PerformanceMonitor onChange={(api) => { console.log("Performance Monitor (FPS)", api.fps); console.log("Performance Monitor (Factor)", api.factor); }} onIncline={() => { console.log("Performance Monitor (Inclined)"); }} onDecline={() => { setEffect(false); console.log("Performance Monitor (Declined)"); }} /> <color attach="background" args={["#ffffff"]} /> <fog attach="fog" args={["#ffffff", 10, 50]} /> <group position-y={-2}> <Experience /> </group> <Environment preset="sunset" /> {effect && <Effects />} </Canvas> </> ); } export default App;
الآن يتم عرض التأثيرات بشكل شرطي ويتم تعطيلها عندما يتدهور الأداء.
على جهازي، يزداد الـ FPS قليلاً لكنه لا يزال أقل من 50. بما أننا نعرف أن عدد الصناديق هو السبب الرئيسي في تدني الأداء، دعونا نقلله إلى النصف عندما يتم استدعاء onDecline
.
أولاً نحتاج إلى تمرير عدد الصناديق كـ prop إلى مكون Experience
:
import { useMemo, useRef } from "react"; // ... export const Experience = ({ nbBoxes }) => { const boxes = useMemo( () => Array.from({ length: nbBoxes }, () => ({ position: [ THREE.MathUtils.randFloat(2, 20) * (THREE.MathUtils.randInt(0, 1) ? -1 : 1), THREE.MathUtils.randFloat(0.2, 10), THREE.MathUtils.randFloat(10, 50), ], scale: THREE.MathUtils.randFloat(0.2, 1.2), color: new THREE.Color().setHSL(Math.random(), 1, 0.5), speed: THREE.MathUtils.randFloat(0.08, 0.42), })), [nbBoxes] );
لقد استخدمت أيضًا memoized لمصفوفة boxes
لتجنب إعادة توليدها في كل عملية إعادة رسم.
ثم يمكننا تحديث عدد الصناديق في مكون App.jsx
:
import { Environment, PerformanceMonitor } from "@react-three/drei"; import { Canvas } from "@react-three/fiber"; import { useState } from "react"; import { Effects } from "./components/Effects"; import { Experience } from "./components/Experience"; function App() { const [effect, setEffect] = useState(true); const [nbBoxes, setNbBoxes] = useState(20000); return ( <> <Canvas camera={{ position: [0, 2, 10], fov: 42 }}> <PerformanceMonitor onChange={(api) => { console.log("Performance Monitor (FPS)", api.fps); console.log("Performance Monitor (Factor)", api.factor); }} onIncline={() => { console.log("Performance Monitor (Inclined)"); }} onDecline={() => { setEffect(false); setNbBoxes(nbBoxes / 2); console.log("Performance Monitor (Declined)"); }} /> <color attach="background" args={["#ffffff"]} /> <fog attach="fog" args={["#ffffff", 10, 50]} /> <group position-y={-2}> <Experience nbBoxes={nbBoxes} /> </group> <Environment preset="sunset" /> {effect && <Effects />} </Canvas> </> ); } export default App;
الآن عندما يتدهور الأداء، يتم تقليل عدد الصناديق إلى النصف.
الأداء عاد إلى الطبيعي وتم استدعاء onIncline
مرة أخرى.
يمكننا إعادة التأثيرات إلى مكانها:
<PerformanceMonitor onChange={(api) => { console.log("Performance Monitor (FPS)", api.fps); console.log("Performance Monitor (Factor)", api.factor); }} onIncline={() => { setEffect(true); console.log("Performance Monitor (Inclined)"); }} onDecline={() => { setEffect(false); setNbBoxes(nbBoxes / 2); console.log("Performance Monitor (Declined)"); }} />
يقود تنفيذ ذلك إلى حلقة حيث يتم تمكين وتعطيل التأثيرات باستمرار. (هذا ليس حالتنا لأن عدد الصناديق فقط هو الذي يسبب المشكلة)
ولكن لتجنب هذا، يمكننا استخدام flipflops
وonFallback
لـ props من مكون PerformanceMonitor
:
/** كم من المرات يمكنه الميلان أو الهبوط قبل استدعاء onFallback، Infinity */ flipflops?: number /** يتم استدعاؤها عندما يتم الوصول إلى عدد flipflops، تشير إلى عدم الاستقرار، استخدم الدالة لتعيين أساس ثابت */ onFallback?: (api: PerformanceMonitorApi) => void
بينما في هذا المثال لعبنا مع عدد الصناديق والتأثيرات، بشكل عام يمكنك النظر في الخيارات التالية لتحسين مشاريعك في React Three Fiber:
- تعطيل الظلال
- استخدام خامات/نصوص ثلاثية البُعد وأقل وضوحاً
- تقليل DPR (Device Pixel Ratio) لمكون
Canvas
- تقليل FOV للكاميرا (تقليل FOV يعني أقل ما تحتاج GPU لعرضه)
- تقليل القريب والبعيد للكاميرا (سيؤدي ذلك إلى تقليل عدد الرؤوس التي يتم عرضها)
- أي شيء يمكن أن يتم تقليله أو تعطيله دون التأثير على تجربة المستخدم
الخَبْز
لقد قدمنا بالفعل مفهوم الخَبْز في فصل الظلال. إنه عملية الحساب المسبق للـ الضوء والـ الظلال في مشهد.
ينتج عن ذلك نسيج يمكن تطبيقه على المشهد بدلاً من الحسابات في الوقت الفعلي. يساهم ذلك في تحسين الأداء وتقليل استدعاءات الرسم.
UseState
كما رأينا بالفعل في فصل React Hooks، لا ينبغي استخدام الـ useState
في الحلقات سريعة التغيير لأنها تسبب إعادة التقديم التي يمكن أن تؤثر بشدة على الأداء.
أضعه هنا مرة أخرى لأنه خطأ شائع يمكن أن يكون من الصعب اكتشافه.
التذكير
يمكن استخدام useMemo
وuseCallback
لتخزين القيم مؤقتًا وتجنب الحسابات غير الضرورية. فهي مفيدة بشكل خاص عند التعامل مع حسابات معقدة.
تحقق من فصل React Hooks لمزيد من التفاصيل حول متى وكيفية استخدامها.
الخاتمة
في هذا الدرس، رأينا كيفية تحسين مشاريع React Three Fiber الخاصة بنا.
للمزيد، يمكنك الاطلاع على قسم Scaling performance وقسم Performance pitfalls في وثائق React Three Fiber.
React Three Fiber: The Ultimate Guide to 3D Web Development
✨ You have reached the end of the preview ✨
Go to the next level with Three.js and React Three Fiber!
Get full access to this lesson and the complete course when you enroll:
- 🔓 Full lesson videos with no limits
- 💻 Access to the final source code
- 🎓 Course progress tracking & completion
- 💬 Invite to our private Discord community
One-time payment. Lifetime updates included.