إنشاء كرة أرضية ثلاثية الأبعاد لعجائب الدنيا

مقدمة عن الكرة الأرضية الثلاثية الأبعاد لعجائب العالم

إذا اطّلعت على موقع عجائب الدنيا في Google الذي تم إطلاقه مؤخرًا على متصفح متوافق مع WebGL، ربما لاحظت كرة أرضية دوّارة في أسفل الشاشة. توضّح لك هذه المقالة آلية عمل الكرة الأرضية والأدوات التي استخدمناها لإنشائها.

إليك نظرة عامة سريعة: الكرة الأرضية "عجائب الدنيا" هي نسخة معدَّلة بشكل كبير من الكرة الأرضية WebGL من قِبل فريق "فنون البيانات" في Google. لقد أخذنا الكرة الأرضية الأصلية وأزلنا أجزاء الرسم البياني الشريطي وغيرنا تأثيرات التظليل وأضفنا علامات HTML رائعة قابلة للنقر وهندسة قارات Natural Earth من العرض التجريبي لتطبيق GlobeTweeter من Mozilla (شكرًا جزيلاً لكريسيان بينسون). كل ذلك لإنشاء كرة أرضية متحركة جميلة تتناسب مع نظام ألوان الموقع الإلكتروني وتضيف لمسة إضافية من الرقي إلى الموقع الإلكتروني.

كان الغرض من تصميم الكرة الأرضية هو إنشاء خريطة متحركة ذات مظهر جميل تتضمّن علامات قابلة للنقر فوقها فوق المواقع التراثية العالمية. مع وضع ذلك في الاعتبار، بدأت أبحث عن حلّ مناسب. كان أول ما خطر ببالنا هو الكرة الأرضية WebGL التي أنشأها فريق "فنون البيانات" في Google. إنها كرة أرضية وتبدو رائعة. هل لديك أي استفسار آخر؟

إعداد الكرة الأرضية WebGL

كانت الخطوة الأولى في إنشاء تطبيق مصغّر للكرة الأرضية هي تنزيل تطبيق WebGL Globe وإعداده وتشغيله. يمكنك العثور على الكرة الأرضية WebGL على الإنترنت في Google Code، ومن السهل تنزيلها وتشغيلها. نزِّل ملف zip واستخرِجه، ثم انتقِل إلى الدليل الذي يحتوي عليه وشغِّل خادم ويب أساسيًا: python -m SimpleHTTPServer. (يُرجى العِلم أنّ ترميز UTF-8 غير مفعَّل تلقائيًا في هذا الإعداد، ولكن يمكنك استخدامه). الآن، إذا انتقلت إلى http://localhost:8000/globe/globe.html، من المفترض أن تظهر لك الكرة الأرضية WebGL.

بعد تشغيل الكرة الأرضية WebGL، كان علينا إزالة كل الأجزاء غير الضرورية. عدّلت ملف HTML لإزالة أجزاء واجهة المستخدم وأزلت إعدادات الرسم البياني الشريطي للكرة الأرضية من دالة إعداد الكرة الأرضية. في نهاية هذه العملية، ظهر لي على الشاشة نموذج بسيط جدًا لكوكب باستخدام WebGL. يمكنك تدويرها وتبدو رائعة، ولكن هذا كل ما في الأمر.

لإزالة العناصر غير الضرورية، حذفتُ جميع عناصر واجهة المستخدم من ملف index.html الخاص بخريطة الكرة الأرضية وعدّلتُ النص البرمجي لإعداد النموذج ليظهر على النحو التالي:

if(!Detector.webgl){
  Detector.addGetWebGLMessage();
} else {
  var container = document.getElementById('container');
  var globe = new DAT.Globe(container);
  globe.animate();
}

إضافة هندسة القارة

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

بالنسبة إلى هندسة اليابسة، لجأت إلى الإصدار التجريبي من GlobeTweeter المفتوح المصدر وحمّلت التصميم الثلاثي الأبعاد في ذلك إلى Three.js. بعد تحميل النموذج وعرضه، حان وقت تحسين مظهر الكرة الأرضية. كانت المشكلة الأولى هي أنّ نموذج اليابسة في الكرة الأرضية لم يكن كرويًا بما يكفي ليتناسب مع الكرة الأرضية في WebGL، لذا انتهيت من كتابة خوارزمية سريعة لتقسيم الشبكة جعلت نموذج اليابسة أكثر كروية.

باستخدام نموذج أرضي كروي، تمكّنت من وضعه قليلاً بعيدًا عن سطح الكرة الأرضية، ما أدى إلى إنشاء قارات عائمة تم تحديدها بخط أسود بحجم 2 بكسل تحتها لإنشاء ظلال من أنواع مختلفة. جرّبتُ أيضًا استخدام خطوط خارجية بألوان النيون لإنشاء مظهر يشبه أسلوب فيلم Tron.

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

من الأفكار الأخرى التي خطرت لي في وقت مبكر لتصميم الكرة الأرضية هي جعلها تبدو مثل الخزف المزجّج. لم نتمكّن من تجربة هذا الخيار لأنّنا لم نتمكّن من كتابة تأثيرات ضوئية لتطبيق مظهر البورسلين (كان من الأفضل أن يتوفّر لنا محرِّر مواد مرئية). أقرب ما حاولتُه هو هذا الكوكب الأبيض المتوهج مع الكتل الأرضية السوداء. إنه تصميم أنيق إلى حدٍ ما، ولكنّه يتميز بتباين عالٍ جدًا. ولا يبدو أنّه جيد جدًا. هكذا، حان وقت التخلص من جهاز آخر.

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

إليك شكل تأثير المحيط على الكرة الأرضية السوداء. برنامج بسيط جدًا لظلال الرأس وبرنامج "حسنًا، يبدو هذا رائعًا تعديل تعديل" غير الرسمي لظلال الأجزاء

    'ocean' : {
      uniforms: {
        'texture': { type: 't', value: 0, texture: null }
      },
      vertexShader: [
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
          'vNormal = normalize( normalMatrix * normal );',
          'vUv = uv;',
        '}'
      ].join('\n'),
      fragmentShader: [
        'uniform sampler2D texture;',
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'vec3 diffuse = texture2D( texture, vUv ).xyz;',
          'float intensity = pow(1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 4.0);',
          'float i = 0.8-pow(clamp(dot( vNormal, vec3( 0, 0, 1.0 )), 0.0, 1.0), 1.5);',
          'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * intensity;',
          'float d = clamp(pow(max(0.0,(diffuse.r-0.062)*10.0), 2.0)*5.0, 0.0, 1.0);',
          'gl_FragColor = vec4( (d*vec3(i)) + ((1.0-d)*diffuse) + atmosphere, 1.0 );',
        '}'
      ].join('\n')
    }

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

إنشاء العلامات باستخدام CSS

وعندما اكتملت الكرة الأرضية واليابسة، بدأت العمل على علامات الأماكن. قرّرت استخدام عناصر HTML بأسلوب CSS للعلامات، لتسهيل إنشاء العلامات وتصميمها، وإمكانية إعادة استخدام العلامات في الخريطة ثنائية الأبعاد التي كان يعمل عليها الفريق. في ذلك الوقت، لم أكن أعرف أيضًا طريقة سهلة لجعل علامات WebGL قابلة للنقر ولم أرد كتابة رمز إضافي لتحميل نماذج العلامات أو إنشائها. بعد النظر في الأمر، تبيّن أنّ علامات CSS كانت تعمل بشكل جيد، ولكنّها كانت تواجه أحيانًا مشاكل في الأداء عندما كانت أدوات تركيب الصور وأدوات العرض في المتصفّح في مراحل تغيُّر. من وجهة نظر الأداء، كان استخدام WebGL لإنشاء العلامات خيارًا أفضل. ومع ذلك، وفّرت علامات CSS قدرًا كبيرًا من وقت المطوّرين.

تتكوّن علامات CSS من عنصرَي div تم وضعهما في موضع مطلق باستخدام خاصية التحويل في CSS. خلفية العلامات هي تدرج ألوان CSS، والجزء المثلث من العلامة هو div مُدار. تحتوي العلامات على ظلّ صغير لإبرازها من الخلفية. كانت المشكلة الأكبر في العلامات هي جعلها تحقّق أداءً جيدًا بما يكفي. على الرغم من أنّه قد يبدو الأمر محزنًا، إلا أنّ رسم بضع عشرات من عناصر div التي تتحرك وتغيّر فهرس z في كل إطار هو طريقة جيدة جدًا لإطلاق جميع أنواع المشاكل المتعلّقة بعرض المتصفّح.

إنّ طريقة مزامنة العلامات مع المشهد الثلاثي الأبعاد ليست معقّدة جدًا. يحتوي كلّ علامة على عنصر ثلاثي الأبعاد مناظر لها في مشهد Three.js، ويتم استخدامه لتتبُّع العلامات. للحصول على إحداثيات مساحة الشاشة، أستخدِم مصفوفات Three.js للكرة الأرضية والعلامة، وأضرب متجهًا صفريًا بها. ومن ذلك، أحصل على موضع العلامة في المشهد. للحصول على موضع العلامة على الشاشة، أُعرِض موضع المشهد من خلال الكاميرا. يحتوي المتجه المسقط الناتج على إحداثيات مساحة الشاشة للعلامة، وهو جاهز للاستخدام في CSS.

var mat = new THREE.Matrix4();
var v = new THREE.Vector3();

for (var i=0; i<locations.length; i++) {
  mat.copy(scene.matrix);
  mat.multiplySelf(locations[i].point.matrix);
  v.set(0,0,0);
  mat.multiplyVector3(v);
  projector.projectVector(v, camera);
  var x = w * (v.x + 1) / 2; // Screen coords are between -1 .. 1, so we transform them to pixels.
  var y = h - h * (v.y + 1) / 2; // The y coordinate is flipped in WebGL.
  var z = v.z;
}

في النهاية، تبيّن أنّ أسرع طريقة هي استخدام عمليات التحويل في CSS لنقل العلامات، وليس استخدام تلاشي الشفافية لأنّه كان يؤدي إلى مسار بطيء على Firefox والاحتفاظ بجميع العلامات في DOM، وليس إزالتها عندما تذهب خلف الكرة الأرضية. جرّبنا أيضًا استخدام عمليات التحويل الثلاثية الأبعاد بدلاً من الفواصل الزمنية z، ولكن لم تنجح هذه الطريقة في التطبيق لأي سبب (مع أنّها نجحت في حالة اختبار مُبسّطة)، وكان إطلاق التطبيق على وشك البدء، لذا اضطررنا إلى ترك هذا الجزء للصيانة بعد الإطلاق.

عند النقر على علامة، يتم توسيعها إلى قائمة بأسماء أماكن يمكن النقر عليها. هذه هي جميع عناصر HTML DOM العادية، لذا كان من السهل جدًا كتابتها. تعمل جميع الروابط وعمليات عرض النصوص بدون أيّ عمل إضافي من جانبنا.

تقليل حجم الملف

بعد أن أصبح العرض التجريبي يعمل وأصبح متصلاً ببقية موقع "عجائب الدنيا" الإلكتروني، بقيت هناك مشكلة كبيرة يجب حلّها. كان حجم شبكة تنسيق JSON للقارات في العالم حوالي 3 ميغابايت. غير مناسب للصفحة الرئيسية لموقع إلكتروني ترويجي. من حسن الحظ أنّ ضغط الشبكة باستخدام gzip قلّل حجمها إلى 350 كيلوبايت. ولكن لا يزال حجم 350 كيلوبايت كبيرًا بعض الشيء. بعد إرسال رسالتَي إلكترونيتَين، تمكّنا من الاستعانة بـ "وون تشون"، الذي عمل على ضغط شبكات Google Body الضخمة، لمساعدتنا في ضغط الشبكة. ضغط شبكة الأسطح من قائمة كبيرة ومستوية من المثلثات المقدَّمة كإحداثيات JSON إلى إحداثيات مضغوطة من 11 بت مع المثلثات المفهرَسة، وقلّل حجم الملف إلى 95 كيلوبايت مضغوطًا.

لا يؤدي استخدام الشبكات المضغوطة إلى توفير عرض النطاق فحسب، بل يؤدي أيضًا إلى تحليل الشبكات بشكل أسرع. إنّ تحويل 3 ميغابايت من الأرقام التي تم تحويلها إلى سلاسل إلى أرقام أصلية يتطلّب قدرًا كبيرًا من العمل أكثر من تحليل 100 كيلوبايت من البيانات الثنائية. إنّ تقليل حجم الصفحة بمقدار 250 كيلوبايت هو أمر رائع، كما أنّ وقت التحميل الأولي يقلّ عن ثانية واحدة عند الاتصال بسرعة 2 ميغابت في الثانية. أسرع وأصغر حجمًا، رائع!

في الوقت نفسه، كنت أحاول تحميل ملفات Shapefiles الأصلية من Natural Earth التي تم اشتقاق شبكة GlobeTweeter منها. لقد تمكّنت من تحميل ملفات Shapefiles، ولكن عرضها ككتل أرضية مسطّحة يتطلّب إنشاء مثلثات لها (مع إضافة ثقوب للبحيرات). لقد حصلت على الأشكال الثلاثية باستخدام أدوات THREE.js ولكن ليس الثقوب. وكانت الشبكات الناتجة ذات حواف طويلة جدًا، ما تطلّب تقسيم الشبكة إلى مثلثات أصغر. باختصار، لم أتمكّن من تشغيله في الوقت المناسب، ولكن الشيء الجميل هو أنّ تنسيق Shapefile المضغوط بشكل أكبر كان سيتيح لك الحصول على نموذج مساحة أرضية بحجم 8 كيلوبايت. حسنًا، ربما في المرة القادمة.

الإجراءات المستقبلية

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

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

ملخّص

في هذه المقالة، وصفت كيفية إنشاء الكرة الأرضية الثلاثية الأبعاد لمشروع "عجائب العالم" من Google. نأمل أن تكون قد استمتعت بالأمثلة وسنحاول إنشاء تطبيق مصغّر مخصّص لك للكرة الأرضية.

المراجع