تسريع WebGL / Three.js مع OffscreenCanvas وعمال الويب

تسريع WebGL / Three.js مع OffscreenCanvas وعمال الويب

في هذا البرنامج التعليمي ، سأوضح لك كيفية استخدام OffscreenCanvas للحصول على جميع التعليمات البرمجية للعمل مع WebGL و Three.js في سلسلة رسائل منفصلة على الويب. هذا تسارع عمل الموقع وعلى الأجهزة الضعيفة اختفت أفاريز أثناء تحميل الصفحة.

تستند المقالة إلى التجربة الشخصية ، عندما أضفت تدوير الأرض ثلاثية الأبعاد إلى موقعي واستغرق الأمر 5 نقاط من الإنتاجية في Google Lighthouse - أكثر من اللازم لسهولة المواجهة.

المشكلة


تخفي Three.js الكثير من مشكلات WebGL المعقدة ، لكن لها سعرًا كبيرًا - تضيف المكتبة 563 كيلوبايت إلى بنية JS الخاصة بك للمتصفحات (ولا تسمح بنية المكتبة بالتريشينج بكفاءة).

قد يقول البعض أن الصور غالبًا ما تزن 500 كيلو بايت - وستكون خاطئة جدًا. كل كيلوبايت من البرنامج النصي أقوى بكثير في الأداء من كيلوبايت في الصورة. لكي يكون الموقع سريعًا ، يجب عليك التفكير ليس فقط في عرض القناة ووقت التأخير - أنت بحاجة أيضًا إلى التفكير في وقت تشغيل وحدة المعالجة المركزية بالكمبيوتر لمعالجة الملفات. على الهواتف وأجهزة الكمبيوتر المحمولة الضعيفة ، قد تستغرق المعالجة وقتًا أطول من التحميل.

170K معالجة JS تستغرق 3.5 ثانية مقابل 0.1 ثانية لصورة 170K
تستغرق معالجة JS 170K 3.5 ثانية مقابل 0.1 ثانية لصور 170K - Eddie Osmani

بينما سيقوم المستعرض بتنفيذ 500 كيلوبايت Three.js ، سيتم حظر تدفق الصفحة الرئيسية وسيشاهد المستخدم إفريز الواجهة.

عمال الويب و Offscreen Canvas


لقد كان لدينا منذ فترة طويلة حل بعدم إزالة الإفريز خلال المدى الطويل لـ JS - العاملون على الويب الذين يقومون بتشغيل الشفرة في خيط منفصل.

بحيث لا يتحول العمل مع العاملين على شبكة الإنترنت إلى جحيم من البرمجة ذات مؤشرات الترابط المتعددة ، ولا يستطيع عامل الويب الوصول إلى DOM. يعمل الخيط الرئيسي فقط مع صفحة HTML. ولكن كيف تبدأ Three.js دون الوصول إلى DOM ، الأمر الذي يتطلب الوصول المباشر إلى <canvas> ؟

للقيام بذلك ، هناك OffscreenCanvas - يسمح لك بتمرير <canvas> إلى عامل ويب. من أجل عدم فتح أبواب الجحيم متعدد مؤشرات الترابط ، بعد النقل ، يفقد مؤشر الترابط الرئيسي الوصول إلى هذا <canvas> - سوف يعمل معه مؤشر ترابط واحد فقط.

يبدو أننا قريبون من الهدف ، لكن اتضح أن Chrome هو الوحيد الذي يدعم OffscreenCanvas .

يدعم Chrome فقط OffscreenCanvas
دعم OffscreenCanvas لشهر أبريل 2019 وفقًا لـ Can I Use

ولكن حتى هنا ، في مواجهة العدو الرئيسي لمطور الويب ، ودعم المتصفح ، يجب ألا نستسلم. نلتقي ونجد العنصر الأخير من اللغز - هذه هي الحالة المثالية "للتحسين التدريجي". في Chrome والمتصفحات المستقبلية ، سنقوم بإزالة الإفريز ، وستعمل المتصفحات الأخرى كما كان من قبل.

نتيجة لذلك ، سوف نحتاج إلى كتابة ملف واحد يمكن أن يعمل في بيئتين مختلفتين في آن واحد - في عامل ويب وفي دفق JS رئيسي منتظم.

قرار


لإخفاء الاختراقات تحت طبقة من السكر ، قمت بإنشاء مكتبة JS صغيرة خارج الشاشة التي تحتوي على 400 بايت (!). في الأمثلة ، سوف تستخدمها الشفرة ، لكنني سأخبرك كيف تعمل "تحت الغطاء".

لنبدأ بتثبيت المكتبة:

 npm install offscreen-canvas 

نحتاج إلى ملف JS منفصل لموظف الويب - قم بإنشاء ملف تجميع منفصل في Webpack أو Parcel:

  entry: { 'app': './src/app.js', + 'webgl-worker': './src/webgl-worker.js' } 

ستعمل المجمعات على تغيير اسم الملف باستمرار أثناء النشر بسبب مخبئ ذاكرة التخزين المؤقت - سنحتاج إلى كتابة الاسم في HTML باستخدام علامة التحميل المسبق . هنا المثال سيكون مجردة ، لأن الكود الفعلي سيعتمد إلى حد كبير على ميزات التجميع الخاص بك.

  <link type="preload" as="script" href="./webgl-worker.js"> </head> 

نحتاج الآن إلى الحصول على عقدة DOM لـ <canvas> ومحتويات علامة التحميل المسبق في ملف JS الرئيسي.

 import createWorker from 'offscreen-canvas/create-worker' const workerUrl = document.querySelector('[rel=preload][as=script]').href const canvas = document.querySelector('canvas') const worker = createWorker(canvas, workerUrl) 

createWorker إذا كان هناك canvas.transferControlToOffscreen بتحميل ملف JS إلى عامل الويب. وفي غياب هذه الطريقة - مثل <script> منتظم.

أنشئ هذا webgl-worker.js للعامل:

 import insideWorker from 'offscreen-canvas/inside-worker' const worker = insideWorker(e => { if (e.data.canvas) { //       <canvas> } }) 

يتحقق insideWorker لمعرفة ما إذا كان قد تم تحميله داخل أحد عمال الويب. اعتمادًا على البيئة ، ستطلق أنظمة اتصال مختلفة باستخدام الخيط الرئيسي.

ستقوم المكتبة بتشغيل وظيفة تم تمريرها إلى insideWorker لكل رسالة جديدة من سلسلة الرسائل الرئيسية. مباشرة بعد التحميل ، createWorker أول رسالة { canvas, width, height } لرسم الإطار الأول على <canvas> .

 + import { + WebGLRenderer, Scene, PerspectiveCamera, AmbientLight, + Mesh, SphereGeometry, MeshPhongMaterial + } from 'three' import insideWorker from 'offscreen-canvas/inside-worker' + const scene = new Scene() + const camera = new PerspectiveCamera(45, 1, 0.01, 1000) + scene.add(new AmbientLight(0x909090)) + + let sphere = new Mesh( + new SphereGeometry(0.5, 64, 64), + new MeshPhongMaterial() + ) + scene.add(sphere) + + let renderer + function render () { + renderer.render(scene, camera) + } const worker = insideWorker(e => { if (e.data.canvas) { + // canvas  -    —    ,     Three.js + if (!canvas.style) canvas.style = { width, height } + renderer = new WebGLRenderer({ canvas, antialias: true }) + renderer.setPixelRatio(pixelRatio) + renderer.setSize(width, height) + + render() } }) 

عند نقل الكود القديم الخاص بـ Three.js إلى أحد العاملين على الويب ، قد تشاهد أخطاء ، نظرًا لأن عامل الويب ليس لديه DOM API. على سبيل المثال ، لا يوجد document.createElement لتحميل مواد SVG. لذلك ، في بعض الأحيان سنحتاج إلى برامج تحميل مختلفة في عامل الويب وداخل برنامج نصي عادي. للتحقق من نوع البيئة ، لدينا worker.isWorker :

  renderer.setPixelRatio(pixelRatio) renderer.setSize(width, height) + const loader = worker.isWorker ? new ImageBitmapLoader() : new ImageLoader() + loader.load('/texture.png', mapImage => { + sphere.material.map = new CanvasTexture(mapImage) + render() + }) render() 

ووجهنا الإطار الأول. لكن يجب أن تستجيب معظم مشاهد WebGL لإجراءات المستخدم. على سبيل المثال ، قم بتدوير الكاميرا عندما يتحرك المؤشر أو ارسم إطارًا عند تغيير حجم النافذة. لسوء الحظ ، لا يمكن لعامل الويب الاستماع إلى أحداث DOM. نحتاج إلى الاستماع إليهم في ساحة المشاركات الرئيسية وإرسال رسائل إلى عامل الويب.

  import createWorker from 'offscreen-canvas/create-worker' const workerUrl = document.querySelector('[rel=preload][as=script]').href const canvas = document.querySelector('canvas') const worker = createWorker(canvas, workerUrl) + window.addEventListener('resize', () => { + worker.post({ + type: 'resize', width: canvas.clientWidth, height: canvas.clientHeight + }) + }) 

  const worker = insideWorker(e => { if (e.data.canvas) { if (!canvas.style) canvas.style = { width, height } renderer = new WebGLRenderer({ canvas, antialias: true }) renderer.setPixelRatio(pixelRatio) renderer.setSize(width, height) const loader = worker.isWorker ? new ImageBitmapLoader() : new ImageLoader() loader.load('/texture.png', mapImage => { sphere.material.map = new CanvasTexture(mapImage) render() }) render() - } + } else if (e.data.type === 'resize') { + renderer.setSize(width, height) + render() + } }) 

يؤدي


مع OffscreenCanvas هزمت أفاريز على موقعي وحصلت على 100٪ من نقاط Google Lighthouse. ويعمل WebGL في جميع المتصفحات ، حتى بدون دعم OffscreenCanvas .

يمكنك إلقاء نظرة على الموقع المباشر والرمز المصدر للخيط الرئيسي أو العامل .


باستخدام OffscreenCanvas ، ارتفعت نظارات Google Lighthouse من 95 إلى 100

Source: https://habr.com/ru/post/ar446682/


All Articles