Dance Tonite در WebVR

وقتی تیم Google Data Arts به من و مونیکر نزدیک شد تا با هم همکاری کنیم تا امکانات معرفی شده توسط WebVR را بررسی کنیم، هیجان‌زده شدم. من در طول سال‌ها کار تیم آنها را تماشا کرده‌ام و پروژه‌های آن‌ها همیشه برایم جذاب بوده است. همکاری ما منجر به Dance Tonite شد، یک تجربه رقص VR همیشه در حال تغییر با LCD Soundsystem و طرفداران آنها. در اینجا نحوه انجام آن است.

مفهوم

ما با توسعه یک سری نمونه های اولیه با استفاده از WebVR شروع کردیم، یک استاندارد باز که امکان ورود به VR را با بازدید از یک وب سایت با استفاده از مرورگر شما فراهم می کند. هدف این است که بدون توجه به دستگاهی که دارید، دسترسی به تجربه‌های VR را برای همه آسان‌تر کنید.

ما این را به دل گرفتیم. هر چیزی که به ذهن ما رسید باید روی همه انواع VR کار کند، از هدست‌های VR که با تلفن‌های همراه مانند Daydream View Google، Cardboard و Gear VR سامسونگ کار می‌کنند تا سیستم‌های در مقیاس اتاق مانند HTC VIVE و Oculus Rift که منعکس کننده فیزیکی شما هستند. حرکات در محیط مجازی شما شاید مهم‌تر از همه، ما احساس می‌کردیم که در روح وب است که چیزی بسازیم که برای همه کسانی که دستگاه VR ندارند نیز کار کند.

1. ضبط حرکت DIY

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

کسی که خودش را در Dance Tonite ضبط می کند. صفحه نمایش پشت سرشان   آنچه را که در هدست خود می بینند را نشان می دهد

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

با WebVR، توسعه دهنده از طریق شی VRPose به موقعیت سر و جهت گیری کاربر دسترسی دارد. این مقدار در هر فریم توسط سخت‌افزار VR به‌روزرسانی می‌شود تا کد شما بتواند فریم‌های جدید را از دیدگاه درست ارائه کند. از طریق GamePad API با WebVR ، ما همچنین می‌توانیم به موقعیت/جهت کنترل‌کننده‌های کاربران از طریق شی GamepadPose دسترسی داشته باشیم. ما به سادگی تمام این مقادیر موقعیت و جهت گیری را در هر فریم ذخیره می کنیم، بنابراین یک "ضبط" از حرکات کاربر ایجاد می کنیم.

2. مینیمالیسم و ​​لباس

با تجهیزات واقعیت مجازی مقیاس اتاق امروزی، می‌توانیم سه نقطه از بدن کاربر را ردیابی کنیم: سر و دو دست. در Dance Tonite می‌خواستیم در حرکت این 3 نقطه در فضا، تمرکز بر انسانیت را حفظ کنیم. برای رسیدن به این هدف، ما زیبایی شناسی را تا جایی که ممکن بود به حداقل رساندیم تا روی حرکت تمرکز کنیم. ما از ایده به کار انداختن مغز مردم خوشمان آمد.

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

از نظر بصری، ما از اتاق‌های رنگی و لباس‌های هندسی در این ضبط‌شده از بازسازی باله سه‌گانه اسکار شلمر در سال 1970 توسط مارگارت هستینگز الهام گرفتیم.

در حالی که دلیل شلمر برای انتخاب لباس‌های هندسی انتزاعی محدود کردن حرکات رقصندگانش به عروسک‌ها و عروسک‌ها بود، هدف ما برای Dance Tonite برعکس بود.

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

3. پدال حلقه برای حرکت

ما می خواستیم گروه های بزرگی از افراد ضبط شده را نشان دهیم که با یکدیگر می رقصند و حرکت می کنند. انجام این کار به صورت زنده امکان پذیر نیست، زیرا دستگاه های VR به تعداد کافی در دسترس نیستند. اما همچنان می‌خواستیم گروه‌هایی از مردم از طریق حرکت به یکدیگر واکنش نشان دهند. ذهن ما به اجرای بازگشتی نورمن مک‌کلارن در قطعه ویدیویی او «کانن» در سال 1964 رفت.

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

4. اتاق های به هم پیوسته

اتاق های به هم پیوسته

مانند بسیاری از موسیقی‌ها، آهنگ‌های LCD Soundsystem با استفاده از معیارهای دقیق زمان‌بندی شده ساخته می‌شوند. آهنگ آنها، Tonite، که در پروژه ما نشان داده شده است، دارای اقداماتی است که دقیقاً 8 ثانیه طول می کشد. ما می‌خواستیم از کاربران برای هر حلقه 8 ثانیه‌ای در مسیر عملکردی داشته باشیم. اگرچه ریتم این اقدامات تغییر نمی کند، محتوای موسیقی آنها تغییر می کند. با پیشرفت آهنگ، لحظاتی با سازها و آوازهای مختلف پیش می‌آید که اجراکنندگان می‌توانند به شیوه‌های متفاوتی نسبت به آن واکنش نشان دهند. هر یک از این معیارها به عنوان اتاقی بیان می شود که در آن افراد می توانند عملکردی متناسب با آن بسازند.

بهینه سازی برای عملکرد: فریم را رها نکنید

ایجاد یک تجربه VR چند پلتفرمی که بر روی یک پایگاه کد واحد با عملکرد مطلوب برای هر دستگاه یا پلتفرم اجرا می‌شود، کار ساده‌ای نیست.

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

1. هندسه بافر نمونه

از آنجایی که کل پروژه ما فقط از تعداد انگشت شماری از اشیاء سه بعدی استفاده می کند، ما توانستیم با استفاده از هندسه بافر نمونه، عملکرد بسیار خوبی داشته باشیم. اساساً به شما این امکان را می‌دهد که یک بار شیء خود را در GPU آپلود کنید و هر تعداد نمونه از آن شی را در یک فراخوانی ترسیم کنید. در Dance Tonite، ما فقط 3 شی مختلف داریم (یک مخروط، یک استوانه، و یک اتاق با سوراخ)، اما به طور بالقوه صدها کپی از آن اشیا. Instance Buffer Geometry بخشی از ThreeJS است، اما ما از فورک آزمایشی و در حال پیشرفت Dusan Bosnjak استفاده کردیم که THREE.InstanceMesh پیاده سازی می کند، که کار با Instanced Buffer Geometry را بسیار آسان تر می کند.

2. اجتناب از زباله جمع کن

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

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

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

برای مثال، در اینجا کد ما برای تبدیل ماتریس مکان سر و دست کاربر به آرایه مقادیر موقعیت/چرخشی است که در هر فریم ذخیره می‌کنیم. با استفاده مجدد از SERIALIZE_POSITION ، SERIALIZE_ROTATION ، و SERIALIZE_SCALE ، از تخصیص حافظه و جمع آوری زباله که در صورت ایجاد اشیاء جدید در هر بار فراخوانی تابع، انجام می شود، اجتناب می کنیم.

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. پخش متوالی حرکت و پیشرونده

برای ثبت حرکات کاربران در VR، باید موقعیت و چرخش هدست و کنترلرهای آن‌ها را سریال‌سازی کنیم و این داده‌ها را در سرورهای خود آپلود کنیم. ما شروع به گرفتن ماتریس های تبدیل کامل برای هر فریم کردیم. این کار به خوبی انجام شد، اما با ضرب 16 عدد در 3 موقعیت هر کدام با سرعت 90 فریم در ثانیه، منجر به فایل های بسیار بزرگ و در نتیجه انتظار طولانی هنگام آپلود و دانلود داده ها شد. فقط با استخراج داده های موقعیتی و چرخشی از ماتریس های تبدیل، توانستیم این مقادیر را از 16 به 7 کاهش دهیم.

از آنجایی که بازدیدکنندگان در وب اغلب بر روی یک پیوند کلیک می کنند بدون اینکه بدانند دقیقاً چه انتظاری دارند، ما باید محتوای بصری را به سرعت نشان دهیم وگرنه در عرض چند ثانیه آن را ترک می کنند.

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

برای اینکه پروژه ای مانند Dance Tonite در بالاترین نرخ فریم ممکن نمایش داده شود، مرورگر تنها زمان کمی برای محاسبات جاوا اسکریپت در هر فریم دارد. اگر زیاد طول بکشید، انیمیشن ها شروع به لکنت می کنند. در ابتدا، ما با لکنت مواجه بودیم زیرا این فایل‌های بزرگ JSON توسط مرورگر رمزگشایی می‌شدند.

ما با یک قالب داده جریانی مناسب بر اساس به نام NDJSON یا Newline محدود JSON مواجه شدیم. ترفند اینجا این است که یک فایل با یک سری رشته های JSON معتبر بسازید که هر کدام در خط خود هستند. این به شما امکان می‌دهد فایل را در حین بارگیری آن تجزیه کنید و به ما امکان می‌دهد عملکردها را قبل از بارگیری کامل نمایش دهیم.

در اینجا بخشی از یکی از ضبط های ما به نظر می رسد:

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

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

4. حرکت درون یابی

از آنجایی که امیدوار بودیم بین 30 تا 60 اجرا را همزمان نمایش دهیم، باید نرخ داده خود را حتی بیشتر از آنچه قبلاً داشتیم پایین بیاوریم. تیم Data Arts در پروژه Virtual Art Sessions خود با همین مشکل مقابله کرد، جایی که آنها ضبط‌های هنرمندانی را که در واقعیت مجازی با استفاده از Tilt Brush نقاشی می‌کنند، پخش می‌کنند. آنها این مشکل را با ساختن نسخه‌های میانی داده‌های کاربر با نرخ فریم پایین‌تر و درون‌یابی بین فریم‌ها در حین پخش آن‌ها حل کردند. ما متعجب شدیم که متوجه شدیم به سختی می‌توانیم تفاوت بین ضبط درون‌یابی که با سرعت 15 فریم بر ثانیه اجرا می‌شود در مقابل ضبط اصلی 90 فریم بر ثانیه تشخیص دهیم.

برای اینکه خودتان متوجه شوید، می توانید Dance Tonite را مجبور کنید که داده ها را با نرخ های مختلف با استفاده از رشته پرس و جو ?dataRate= پخش کند. می توانید از این برای مقایسه حرکت ضبط شده در 90 فریم در ثانیه ، 45 فریم در ثانیه یا 15 فریم در ثانیه استفاده کنید.

برای موقعیت، یک درون یابی خطی بین فریم کلیدی قبلی و بعدی انجام می دهیم، بر اساس اینکه چقدر از نظر زمانی بین فریم های کلیدی نزدیک هستیم (نسبت):

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

برای جهت‌یابی، یک درون‌یابی خطی کروی (slerp) بین فریم‌های کلیدی انجام می‌دهیم. جهت گیری به صورت کواترنیون ذخیره می شود.

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. همگام سازی حرکات با موسیقی

برای اینکه بدانیم کدام فریم از انیمیشن های ضبط شده را باید پخش کنیم، باید زمان فعلی موسیقی را تا میلی ثانیه بدانیم. به نظر می رسد که اگرچه عنصر صوتی HTML برای بارگیری و پخش تدریجی صدا عالی است، ویژگی زمانی که ارائه می دهد در هماهنگی با حلقه فریم مرورگر تغییر نمی کند. همیشه کمی خاموش است. گاهی کسری از میلی ثانیه خیلی زود، گاهی کسری خیلی دیر.

این منجر به لکنت در ضبط های زیبای رقص ما می شود که می خواهیم به هر قیمتی از آن اجتناب کنیم. برای رفع این مشکل، ما تایمر خود را در جاوا اسکریپت پیاده سازی کردیم. به این ترتیب می توانیم مطمئن شویم که مقدار زمان تغییر بین فریم ها دقیقاً مدت زمانی است که از آخرین فریم گذشته است. هر زمان که تایمر ما بیش از 10 میلی ثانیه با موسیقی هماهنگ نباشد، دوباره آن را همگام می کنیم.

6. تخریب و مه

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

در حالی که به عنوان یک بیننده این احساس عالی بود، برخی از موانع عملکرد را برای حل کردن معرفی کرد. دستگاه‌های واقعیت مجازی در مقیاس اتاق و دستگاه‌های بازی پیشرفته آن‌ها با 40 عملکرد اضافی عجیب و غریب مورد نیاز برای پایان جدید ما، عالی عمل کردند. اما نرخ فریم در برخی از دستگاه های تلفن همراه به نصف کاهش یافت.

برای مقابله با این موضوع مه را معرفی کردیم. پس از طی مسافت معینی همه چیز به آرامی سیاه می شود. از آنجایی که ما نیازی به محاسبه یا ترسیم چیزهایی نداریم که قابل مشاهده نیستند، عملکردها را در اتاق هایی که قابل مشاهده نیستند حذف می کنیم و این به ما امکان می دهد کار را برای CPU و GPU ذخیره کنیم. اما چگونه می توان در مورد فاصله مناسب تصمیم گرفت؟

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

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

چیزی برای همه: ساخت VR برای وب

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

1. گوی زرد

بنابراین کاربران VR در مقیاس اتاق ما اجراها را انجام می‌دهند، اما کاربران دستگاه‌های VR سیار (مانند Cardboard، Daydream View یا Samsung Gear) چگونه این پروژه را تجربه خواهند کرد؟ برای این کار، عنصر جدیدی را به محیط خود معرفی کردیم: گوی زرد.

گوی زرد
گوی زرد

وقتی پروژه را در VR تماشا می کنید، این کار را از نقطه نظر گوی زرد انجام می دهید. همانطور که از اتاقی به اتاق دیگر شناور می شوید، رقصندگان به حضور شما واکنش نشان می دهند. آنها به شما اشاره می کنند، دور شما می رقصند، حرکات خنده دار پشت سر شما انجام می دهند و به سرعت از مسیر شما خارج می شوند تا با شما برخورد نکنند. گوی زرد همیشه در مرکز توجه است.

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

2. دیدگاه دیگر

ما نمی‌خواستیم کاربران را بدون واقعیت مجازی کنار بگذاریم، به خصوص که آنها احتمالاً بزرگترین مخاطبان ما خواهند بود. به‌جای ایجاد یک تجربه واقعیت مجازی مصنوعی، می‌خواستیم به دستگاه‌های مبتنی بر صفحه‌نمایش تجربه خاص خود را بدهیم. ما این ایده را داشتیم که اجراها را از بالا از منظر ایزومتریک نشان دهیم. این دیدگاه در بازی های رایانه ای سابقه ای غنی دارد. این اولین بار در Zaxxon، یک بازی تیراندازی فضایی از سال 1982 استفاده شد. در حالی که کاربران VR در ضخامت آن هستند، پرسپکتیو ایزومتریک نمایی خداگونه به اکشن می دهد. ما تصمیم گرفتیم که مدل‌ها را کمی بزرگ‌تر کنیم و زیبایی‌شناسی خانه عروسکی را به ما نشان دهیم.

3. سایه ها: آن را جعل کنید تا زمانی که آن را بسازید

ما متوجه شدیم که برخی از کاربران ما برای دیدن عمق در دیدگاه ایزومتریک ما مشکل داشتند. من کاملاً مطمئن هستم که به همین دلیل است که Zaxxon همچنین یکی از اولین بازی های رایانه ای در تاریخ بود که سایه ای پویا را زیر اجسام پرنده خود قرار داد.

سایه ها

به نظر می رسد که ایجاد سایه به صورت سه بعدی سخت است. به خصوص برای دستگاه های فشرده مانند تلفن های همراه. در ابتدا مجبور بودیم تصمیم سختی بگیریم تا آنها را از معادله حذف کنیم، اما پس از مشورت از نویسنده Three.js و هکر آزمایشی با تجربه آقای doob ، او به ایده بدیع ... جعل آنها رسید.

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

برای ایجاد آنها، ما از این بافت با گرادیان سفید به سیاه ملایم (بدون شفافیت آلفا) استفاده کردیم. ما مواد را به عنوان شفاف تنظیم می کنیم و از ترکیب تفریقی استفاده می کنیم. این به آن‌ها کمک می‌کند وقتی روی هم قرار می‌گیرند به خوبی ترکیب شوند:

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. آنجا بودن

با کلیک بر روی سر یک مجری، بازدیدکنندگان بدون VR می توانند چیزها را از دید رقصنده تماشا کنند. از این زاویه، بسیاری از جزئیات کوچک آشکار می شوند. رقصندگان در تلاش برای حفظ اجرای خود به سرعت به یکدیگر نگاه می کنند. هنگامی که گوی وارد اتاق می شود، آنها را می بینید که عصبی به سمت آن نگاه می کنند. در حالی که به عنوان یک بیننده نمی توانید بر این حرکات تأثیر بگذارید، اما به طرز شگفت انگیزی احساس غوطه وری را به خوبی منتقل می کند. مجدداً، ما ترجیح دادیم این کار را به جای ارائه یک نسخه ساده واقعیت مجازی با کنترل ماوس به کاربران خود انجام دهیم.

5. به اشتراک گذاری ضبط

ما می دانیم که وقتی یک ضبط رقص پیچیده از 20 لایه از مجریان که به یکدیگر واکنش نشان می دهند، چقدر می توانید افتخار کنید. ما می دانستیم که کاربران ما احتمالاً می خواهند آن را به دوستان خود نشان دهند. اما یک تصویر ثابت از این شاهکار به اندازه کافی ارتباط برقرار نمی کند. در عوض، ما می‌خواستیم به کاربران خود اجازه دهیم تا ویدیوی اجراهای خود را به اشتراک بگذارند. در واقع، چرا GIF نیست؟ انیمیشن های ما دارای سایه صاف هستند که برای پالت های رنگی محدود فرمت مناسب است.

به اشتراک گذاری موارد ضبط شده

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

متأسفانه، با تعداد فریم هایی که برای انیمیشن ها نیاز داشتیم، روند رمزگذاری همچنان بسیار کند بود. GIF قادر است با استفاده از یک پالت رنگ محدود فایل های کوچک بسازد. ما متوجه شدیم که بیشتر زمان صرف یافتن نزدیک‌ترین رنگ برای هر پیکسل می‌شود. ما توانستیم این فرآیند را ده برابر با هک کردن در یک برش کوتاه کوچک بهینه کنیم: اگر رنگ پیکسل با رنگ قبلی یکی است، از همان رنگ پالت قبلی استفاده کنید.

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

6. زمین جامد: Google Cloud & Firebase

پشتیبان سایت «محتوای تولید شده توسط کاربر» اغلب می‌تواند پیچیده و شکننده باشد، اما ما به لطف Google Cloud و Firebase سیستمی ساده و قوی پیدا کردیم. هنگامی که یک اجرا کننده یک رقص جدید را در سیستم آپلود می کند، به طور ناشناس توسط Firebase Authentication احراز هویت می شود. به آن‌ها اجازه داده می‌شود تا با استفاده از Cloud Storage برای Firebase، ضبط خود را در یک فضای موقت آپلود کنند. هنگامی که آپلود کامل شد، دستگاه سرویس گیرنده با استفاده از توکن Firebase خود، یک تریگر Cloud Functions برای Firebase HTTP را فراخوانی می کند. این یک فرآیند سرور را راه اندازی می کند که ارسال را تأیید می کند، یک رکورد پایگاه داده ایجاد می کند و ضبط را به یک فهرست عمومی در Google Cloud Storage منتقل می کند.

زمین جامد

تمام محتوای عمومی ما در یک سری فایل های مسطح در یک سطل ذخیره سازی ابری ذخیره می شود. این بدان معناست که داده‌های ما به سرعت در سراسر جهان قابل دسترسی هستند و نیازی نیست نگران بارهای ترافیکی زیاد باشیم که به هیچ وجه بر دسترسی به داده‌ها تأثیر می‌گذارد.

ما از یک پایگاه داده بیدرنگ Firebase و نقاط پایانی Cloud Function برای ساختن یک ابزار تعدیل/مرور ساده استفاده کردیم که به ما امکان می‌دهد هر ارسال جدید را در VR تماشا کنیم و لیست‌های پخش جدید را از هر دستگاهی منتشر کنیم.

7. کارگران خدماتی

کارکنان خدمات یک نوآوری نسبتاً جدید هستند که به مدیریت ذخیره دارایی های وب سایت کمک می کنند. در مورد ما، کارکنان خدمات محتوای ما را به سرعت برای بازدیدکنندگان بازگشتی بارگیری می‌کنند و حتی به سایت اجازه می‌دهند به صورت آفلاین کار کند. اینها ویژگی های مهمی هستند زیرا بسیاری از بازدیدکنندگان ما از اتصالات تلفن همراه با کیفیت متفاوت استفاده می کنند.

افزودن یک سرویس‌کار به پروژه به لطف یک افزونه بسته وب مفید که بیشتر کارهای سنگین را برای شما انجام می‌دهد، آسان بود. در پیکربندی زیر، ما یک سرویس‌کار تولید می‌کنیم که به‌طور خودکار تمام فایل‌های استاتیک ما را کش می‌کند. در صورت موجود بودن، آخرین فایل لیست پخش را از شبکه می کشد، زیرا لیست پخش همیشه به روز می شود. تمام فایل‌های json ضبط شده در صورت موجود بودن باید از حافظه پنهان خارج شوند، زیرا هرگز تغییر نمی‌کنند.

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

در حال حاضر، این افزونه مانند فایل‌های موسیقی ما، دارایی‌های رسانه‌ای که به‌تدریج بارگذاری می‌شوند را مدیریت نمی‌کند، بنابراین ما با تنظیم هدر Cloud Storage Cache-Control روی این فایل‌ها به public, max-age=31536000 روی آن کار کردیم تا مرورگر فایل را کش کند. تا یک سال

نتیجه گیری

ما هیجان زده هستیم که ببینیم چگونه نوازندگان به این تجربه اضافه می کنند و از آن به عنوان ابزاری برای بیان خلاق با استفاده از حرکت استفاده می کنند. ما همه کدهای متن باز را منتشر کرده ایم که می توانید آنها را در https://github.com/puckey/dance-tonite بیابید. در این روزهای اولیه VR و به خصوص WebVR، ما مشتاقانه منتظریم تا ببینیم این رسانه جدید چه مسیرهای خلاقانه و غیرمنتظره جدیدی را در پیش خواهد گرفت. برقصید