ساخت عجایب جهان سه بعدی گلوب

Ilmari Heikkinen

مقدمه ای بر جهان عجایب سه بعدی

اگر سایت Google World Wonders را که اخیراً راه اندازی شده است را در یک مرورگر دارای WebGL مشاهده کرده باشید، ممکن است یک کره در حال چرخش فانتزی را در پایین صفحه مشاهده کرده باشید. این مقاله به شما اجازه می دهد تا در مورد نحوه عملکرد کره زمین و آنچه که ما برای ساخت آن استفاده کرده ایم آشنا شوید.

برای ارائه یک نمای کلی سریع، World Wonders globe یک نسخه به شدت تغییر یافته از WebGL Globe توسط تیم Google Data Arts است. کره اصلی را برداشتیم، بیت‌های نمودار میله‌ای را حذف کردیم، سایه‌زن‌ها را تغییر دادیم، نشانگرهای HTML قابل کلیک و هندسه طبیعی قاره زمین را از نسخه نمایشی GlobeTweeter موزیلا اضافه کردیم (با تشکر بزرگ از سدریک پینسون!) همه اینها برای ساختن یک کره متحرک زیبا که با سایت مطابقت دارد. طرح رنگ و لایه ای از پیچیدگی را به سایت اضافه می کند.

خلاصه طراحی برای جهان این بود که یک نقشه متحرک با ظاهر زیبا با نشانگرهای قابل کلیک در بالای سایت های میراث جهانی قرار داده شود. با در نظر گرفتن این موضوع، شروع به جستجوی چیزی مناسب کردم. اولین چیزی که به ذهن رسید WebGL Globe بود که توسط تیم Google Data Arts ساخته شد. این یک کره است و به نظر جالب می رسد. چه چیز دیگری نیاز دارید، نه؟

راه اندازی WebGL Globe

اولین قدم در ساخت ویجت globe دانلود WebGL Globe و راه اندازی آن بود. WebGL Globe در Google Code آنلاین است و دانلود و اجرا آن ساده است. فایل فشرده ، سی دی را در آن دانلود و استخراج کنید و یک وب سرور اصلی را اجرا کنید: python -m SimpleHTTPServer . (توجه داشته باشید، این UTF-8 به طور پیش‌فرض روشن نیست؛ می‌توانید از آن استفاده کنید .) حالا اگر به http://localhost:8000/globe/globe.html بروید، باید WebGL Globe را ببینید.

با راه‌اندازی WebGL Globe، زمان آن رسیده بود که تمام قطعات غیرضروری را قطع کنیم. من HTML را ویرایش کردم تا بیت‌های رابط کاربری را حذف کنم و چیزهای تنظیم گراف globe bar را از تابع مقداردهی اولیه کره حذف کردم. در پایان آن فرآیند، من یک WebGL Globe بسیار ساده روی صفحه نمایشم داشتم. می توانید آن را دور بچرخانید و به نظر جالب می رسد، اما همین.

برای برش چیزهای غیر ضروری، تمام عناصر رابط کاربری را از index.html globe حذف کردم و اسکریپت اولیه را به شکل زیر ویرایش کردم:

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

اضافه کردن هندسه قاره

ما می خواستیم دوربین را نزدیک به سطح کره زمین داشته باشیم، اما زمانی که کره زمین را آزمایش کردیم، زوم کردن کره زمین در عدم وضوح بافت آشکار شد. با بزرگنمایی، بافت WebGL Globe مسدود و تار می شود. ما می‌توانستیم از یک تصویر بزرگ‌تر استفاده کنیم، اما این باعث می‌شود دانلود و اجرا کره زمین کندتر شود، بنابراین ما تصمیم گرفتیم که از یک نمایش برداری از توده‌های خشکی و مرزها استفاده کنیم.

برای هندسه خشکی، به نسخه ی نمایشی GlobeTweeter منبع باز روی آوردم و مدل سه بعدی را در Three.js بارگذاری کردم. با بارگذاری و رندر شدن مدل، زمان شروع به جلا دادن ظاهر جهان فرا رسیده بود. اولین مشکل این بود که مدل خشکی کره به اندازه کافی کروی نبود که با WebGL Globe همسطح شود، بنابراین در نهایت یک الگوریتم تقسیم سریع مش را نوشتم که مدل خشکی را کروی‌تر کرد.

با یک مدل خشکی کروی، من توانستم آن را کمی با فاصله کمی از سطح کره زمین قرار دهم و قاره‌های شناور را با یک خط سیاه 2 پیکسلی در زیر آن‌ها برای نوعی سایه ایجاد کنم. من همچنین با طرح های نئون رنگی آزمایش کردم تا ظاهری شبیه ترون داشته باشم.

با رندر شدن کره زمین و خشکی، شروع به آزمایش ظاهرهای مختلف برای کره زمین کردم. همانطور که می خواستیم با یک ظاهر تک رنگ کم رنگ جلو برویم، من به کره خاکستری و توده های خشکی چسبیدم. علاوه بر طرح‌های نئونی فوق، من یک کره تاریک با زمین‌های تیره روی پس‌زمینه‌ای روشن را امتحان کردم که در واقع بسیار جالب به نظر می‌رسد. اما کنتراست آن خیلی کم بود و به راحتی قابل خواندن نبود و با احساس پروژه مطابقت نداشت، بنابراین آن را کنار گذاشتم.

یکی دیگر از فکرهای اولیه من برای ظاهر کره زمین این بود که آن را شبیه به چینی لعابدار کنم. آن یکی را که نتوانستم امتحان کنم، زیرا نتوانستم سایه بان بنویسم تا ظاهر چینی را انجام دهم (ویرایشگر مواد بصری خوب است). نزدیک ترین چیزی که من امتحان کردم این کره سفید درخشان با خشکی های سیاه بود. کمی تمیز است اما کنتراست بسیار بالایی دارد. و خیلی خوب به نظر نمی رسد. بنابراین یکی دیگر برای ضایعات.

سایه بان ها در کره های سیاه و سفید از نوعی نورپردازی تقلبی با نور پس زمینه استفاده می کنند. سبکی کره به فاصله سطح نرمال تا صفحه صفحه بستگی دارد. بنابراین پیکسل های وسط کره که به سمت صفحه نمایش هستند تاریک و پیکسل های لبه های کره زمین روشن هستند. در ترکیب با پس‌زمینه‌ای روشن، نگاهی خواهید داشت که در آن کره زمین در حال بازتاب پس‌زمینه روشن پراکنده است و ظاهری کلاسیک را ایجاد می‌کند. کره سیاه همچنین از بافت WebGL Globe به عنوان نقشه براق استفاده می کند، به طوری که قفسه های قاره (مناطق آب کم عمق) در مقایسه با سایر نقاط کره زمین براق به نظر می رسند.

در اینجا سایه بان اقیانوس برای کره سیاه به نظر می رسد. سایه‌زن رأس بسیار ابتدایی و سایه‌زن قطعه‌ای «اوه که به نظر می‌رسد یک نیشگون گرفتن و کشیدن کمی تمیز» است.

    '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-index خود را در هر فریم تغییر می دهند، راه بسیار خوبی برای ایجاد انواع تله های رندر مرورگر است.

نحوه همگام سازی نشانگرها با صحنه سه بعدی خیلی پیچیده نیست. هر نشانگر دارای یک Object3D مربوطه در صحنه 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 برای جابجایی نشانگرها بود، نه استفاده از محو شدن کدورت، زیرا باعث ایجاد یک مسیر آهسته در فایرفاکس می‌شد و همه نشانگرها را در DOM نگه می‌داشت، نه حذف آنها هنگام رفتن به پشت کره زمین. ما همچنین استفاده از تبدیل‌های سه بعدی را به‌جای شاخص‌های z آزمایش کردیم، اما به دلایلی در برنامه به درستی کار نکرد (اما در یک مورد آزمایشی کاهش یافته کار می‌کرد)، و چند روزی از راه‌اندازی آن فاصله داشتیم. در آن نقطه، بنابراین مجبور شد آن بخش را به تعمیر و نگهداری پس از راه اندازی بسپارد.

هنگامی که روی یک نشانگر کلیک می کنید، به لیستی از نام مکان های قابل کلیک تبدیل می شود. اینها همه چیزهای معمولی HTML DOM هستند، بنابراین نوشتن آن بسیار آسان بود. همه پیوندها و رندر متن فقط بدون کار اضافی از طرف ما کار می کنند.

فشرده کردن اندازه فایل

با کارکرد نسخه ی نمایشی و اتصال به بقیه سایت عجایب جهان، هنوز یک مشکل بزرگ برای حل وجود داشت. اندازه مش با فرمت JSON برای خشکی های کره زمین حدود 3 مگ بود. برای صفحه اول یک سایت ویترین خوب نیست. نکته خوب این بود که فشرده سازی مش با gzip آن را به 350 کیلوبایت کاهش داد. اما هی، 350 کیلوبایت هنوز کمی بزرگ است. چند ایمیل بعد، موفق شدیم وون چون را استخدام کنیم -- که روی فشرده سازی مش های بزرگ Google Body کار می کرد -- تا به ما در فشرده سازی مش کمک کند. او مش را از یک لیست مسطح بزرگ از مثلث‌هایی که به‌عنوان مختصات JSON ارائه می‌شد، به هم‌خوان‌های 11 بیتی فشرده‌شده با مثلث‌های نمایه‌شده فشار داد و اندازه فایل را به 95 کیلوبایت گیزیپ‌کرده کاهش داد.

استفاده از مش های فشرده نه تنها باعث صرفه جویی در پهنای باند می شود، بلکه مش ها نیز سریعتر تجزیه می شوند. تبدیل 3 مگ اعداد رشته ای به اعداد بومی کار بسیار بیشتری نسبت به تجزیه صد کیلوبایت داده باینری می طلبد. و کاهش اندازه 250 کیلوبایتی برای صفحه بسیار خوب است و همچنین زمان بارگذاری اولیه را در یک اتصال 2 مگابیت بر ثانیه کمتر از یک ثانیه می کند. سریعتر و کوچکتر، سس عالی!

در همان زمان، من در حال بارگذاری فایل‌های شکل طبیعی طبیعی زمین بودم که مش GlobeTweeter از آن مشتق شده است. من موفق شدم Shapefiles را بارگیری کنم اما رندر کردن آنها به عنوان خشکی های مسطح نیاز به مثلث کردن آنها دارد (با سوراخ هایی برای دریاچه ها، ناچ). و مش های به دست آمده دارای لبه های بسیار طولانی بودند که نیاز به تقسیم مش به تریس های کوچکتر داشت. خلاصه، من نتوانستم آن را به موقع کار کنم، اما نکته جالب این بود که فرمت Shapefile فشرده‌تر، یک مدل 8 کیلوبایتی را برای شما به ارمغان می‌آورد. آه، خب، شاید دفعه بعد.

کار آینده

یکی از مواردی که می تواند از کمی کار اضافی استفاده کند، زیباتر کردن انیمیشن های نشانگر است. حالا وقتی آنها به افق می روند، اثر کمی چسبنده است. علاوه بر این، داشتن یک انیمیشن جالب برای باز شدن نشانگر خوب خواهد بود.

از نظر عملکرد، دو موردی که وجود ندارد، بهینه سازی الگوریتم تقسیم مش و سریعتر کردن نشانگرها است. جدای از آن، همه چیز شیک است. هورا!

خلاصه

در این مقاله توضیح دادم که چگونه کره‌ی سه‌بعدی را برای پروژه Google World Wonders ساختیم. امیدوارم از نمونه ها لذت برده باشید و سعی کنید ویجت گلوب سفارشی خود را بسازید.

منابع