گیمولوژی | رسانه تحلیلی بازی سازی | مدلسازی با Raymarching و میادین فاصله در یونیتی
Raymarching یک روش برای ارائه گرافیک کامپیوتری است، اما هنوز به پتانسیل کامل نرسیده است. در Raymarching نیازی به خط لوله گرافیکی نیست و از آن معمولا برای رندر کردن بافت حجمی(Volume Textures) و Heightmaps و سطوح تحلیلی(Analytic Surfaces) استفاده میشود. امروزه اکثر بازیها از OpenGL یا Direct3D (DirectX) استفاده میکنند تا به وسیلهی شتاب سختافزاری کارت گرافیکی، چندضلعیها را رسم کنند. رایانهها میتوانند میلیونها مثلث را به صورت 60 فریم در ثانیه را پردازش کنند که این حیرت آور است! Raymarching به خوبی APIهای گرافیکی شناخته نمیشود. اما چقدر جزئیات را میتوان فقط با 2 مثلث به دست آورد؟
هدف من از این مطلب این است که به شما نشان دهم این روش قدیمیتر رندر، میتواند به بازی بازگردد و با استفاده از پردازش موازی و تکنیکهای محاسبه شده بهینهسازی شود! GPU در حال حاضر بسیار قدرتمند است و از آن برای شتاب فیزیک و حتی یادگیری هوش مصنوعی عمیق استفاده میشود. حال وقت آن است که از آن برای استفاده بیش از فقط خط لوله گرافیکی استفاده شود!
https://github.com/smkplus/UnityRayMarching
دو مثلث، با جزئیات بینهایت!!!
Raymarching روش ریاضی برای رندر کردن است. با دایرههای فاصله (فاصله از یک نقطه به یک ابتدایی)، مراحل ثابت (معمولا با رندر حجمی استفاده میشود) و Root-Finders (یک رویکرد ریاضی) انجام میشود.
https://www.shadertoy.com/view/ld3Gz2
به طور معمول ایجاد یک صحنه مانند تصویر بالا، به ابزار مدلسازی (Maya و Blender و 3D Max) و ابزار بافتدهی (Substance Designer ، Painter) نیاز دارد؛ اما این صحنه با ریاضی ایجاد و با Raymarching رندر شده است. در این روش شما محدود به این نیستید که چند مثلث میتوانید رندر کنید. با این حال Raymarching راهحل همه مشکلات ما نیست. این روش به سبک خود آهسته است و من فکر میکنم که باید در کنار چندضلعیها استفاده شود. اجازه بدهید در کد توضیح بدهم.
در اینجا تابعی برای برگرداندن فاصلهی بافر عمق دوربین وجود دارد.
چگونه Raymarching را به بازیهایمان اضافه کنیم؟
ترکیب دو روش رندر سخت نیست، اما اول شما باید تفاوتشان را درک کنید.
- Raymarching صد در صد دقیق نیست. با استفاده از میدانهای فاصله ما را به سطحی که میخواهیم رندر کنیم، نزدیک میکند؛ اما تقریبا هرگز به درستی نمیتوانیم به دنبال فاصلهی درست آن بگردیم.
- رندر چند ضلعی (با perspective) شامل استفاده از ماتریس طرحریزی(projection matrix) است. این عمق است، فاصله نیست.
به منظور ترکیب دو روش رندر ، معمولا سادهترین راه شروع با چندضلعیها است و بنا به دلایل مختلف با Raymarching به پایان میرسد. آزمایش عمیق با یک بافر فاصله سخت است و شما خود را به اشیای جامد محدود میکنید. پس از پایان دادن به تمام رندر، مرحله فاز Raymarching باید انجام شود. (شبیه به نحوه چیدن اجسام جامد قبل از آن که شفاف باشد). بیایید در مورد آمادهسازی عمق بافر صحبت کنیم و آن را به یک بافر فاصله بدهیم.
آن چه که در اینجا رخ میدهد، این است که من از معکوس ماتریس طرحریزی استفاده میکنم تا بفهمم که کدام مختصات UV (تبدیل شده به ( [-1,-1] -> [1,1] ) در فاصله نزدیک plane قرار دارد (1-،x،y). در این مرحله، من از ماتریس دوربین استفاده نکردهام، بنابراین فرض بر این است که دوربین در origin ([0,0,0]) است. طول این مختصات با مختصات UV متفاوت است.فاصله نزدیک plane باید با مختصات UV [0.5،0.5] مساوی باشد.
پس از گرفتن این اعداد، جهت متغیر Ray را تنظیم میکنیم .این کار لازم است چون Raymarching توسط پرتاب اشعه کار میکند.
Raymarching چطور کار میکند
در حال حاضر همه چیزهایی را که لازم است شروع کنیم داریم. با فاصله از بافر عمق، میتوانیم تقاطعات را اداره کنیم. با ماتریس طرحریزی معکوس، اشعههای صحیح را محاسبه کردیم تا با میدان دید دوربینمان منطبق باشد. چه چیز باقی مانده است؟ موقعیت دوربین و سپس روند انجام میشود. خوشبختانه این آسان است.
این کد به موقعیت دوربینهای فعلی ضمیمه شده است.
همان طور که میبینید تابع گرفتن فاصله از عمق را به رنگ بنفش هایلایت کردم. با این تابع فاصله را میگیریم و در متغیر اعشاری به نام dist ذخیره میکنیم. در نهایت ما یک float3 را به عنوان یک متغیر خروجی پاس دادیم، یعنی همان جهت اشعه (RayDirection). بنابراین از این تابع با FOV مناسب خارج میشود. با این وجود فاقد چرخش دوربین است.ما میتوانیم موقعیت را با یک متغیر یکسان استاندارد (_cameraPos) به دست آوریم و RayDirection را در View matrix ضرب کنیم. به همین دلیل من از 0.0 به عنوان پارامتر w استفاده میکنم؛ زیرا ما نمیخواهیم موقعیت دوربین را در این متغیر ذخیره کنیم. ما فقط آن را چرخاندهایم.
تصویر زیر پیادهسازی کد بالا را نشان میدهد، همان طور که در تصویر زیر مشاهده میکنید، دو کره زرد و یک مکعب مقیاسپذیر وجود دارد. یکی از آنها (در سمت راست) از چندضلعی(polygon) استفاده میکند. مکعب دقیقا همان طور که انتظار میرود، کره را تقسیم میکند. در سمت چپ، از تنظیمات ما برای محاسبه درست اطلاعات FOV و موقعیت و چرخش دوربین استفاده میکند.
همچنین توجه کنید که لبههای face مقابل مکعب چقدر صاف است. جایی که با کره تقسیم شده، با Raymarching رندر شده است. آن را با کره نسبتا high-poly در سمت راست مقایسه کنید.
رسم اشکال هندسی با استفاده از توابع فاصله
رندرینگ با Raymarching نیازمند درک جدی ریاضی یا یک Cheat واقعا خوب است. متاسفانه منابع زیادی وجود ندارد. دراینجا یک مقاله با توابع فاصله برای هر شکل قابل مشاهده است:
بیایید به جای کره از شکل متفاوتی استفاده کنیم مانند دونات.
این یک تابع فاصلهی شکل دونات است. اگر شما ناآشنا هستید، به پایین بروید تا یک دونات را ببینید. تابع فاصله، فاصله را از یک نقطه، به نزدیکترین نقطه روی شکل اولیه (Primitive) بر میگرداند. این چیزی است که برای رندر کردن دوناتمان استفاده میکنیم. در تصویر زیر، میتوانید شکلهای سیاه، دایرههای قرمز، نقاط آبی و خطوط قرمز را مشاهده کنید. نقطه آبی در پایین سمت چپ دوربین است. نقطه آبی در بالا سمت راست نقطهای است که به آن نگاه میشود. ما اطلاعاتی در مورد هر چیزی به جز فاصله تا نزدیکترین سطح (خط سیاه در مرکز پایین) نداریم. بنابراین ما از این فاصله استفاده میکنیم تا به جلو حرکت و روند را تکرار کنیم، تا زمانی که به سطحی میرسیم که ما میخواهیم.
بیایید اول نگاهی به آن چه برای پیادهسازی نیاز داریم بیندازیم. در اینجا لیست TODO وجود دارد:
– دریافت مبدا پرتو (موقعیت دوربین)
– جهت گیری پرتو (دوربین FOV، نسبت ابعاد، و چرخش)
– اضافه کردن یک تابع فاصله کد اضافه (Torus)
– پرتاب کردن پرتو به سمت شکل
– دریافت فاصله از سطح شکل، در آن پرتو
آن چه ما برای اولین بارانجام خواهیم داد محاسبه یک نقطه است. برای این کار از معادله نقطه در امتداد یک بردار استفاده میکنیم تا فاصلهای خاص را در امتداد اشعه حرکت دهیم. سپس فاصله را از شکل اولیه(Primitive) بررسی میکنیم. ما فاصلهای را که در حال حرکت در امتداد اشعه بود، با استفاده از فاصلهای که محاسبه کردیم افزایش میدهیم . سپس ما روند را تکرار میکنیم. بنابراین این روند را در یک حلقه FOR کنترل میکنیم.
بیایید دوناتمان را Raymarching کنیم.
شاید وقت آن است که بعضی موارد را روشن کنیم؟
آنچه که ما در کد بالا انجام دادیم، تکرار حرکت در امتداد یک پرتو تا زمانی که فاصله ای برای کار کردن داشتیم است.اگر فقط حلقه را یک بار تکرار کنید، شما فقط اولین تخمین فاصله را بازمیگردانید.به همین دلیل است که ما باید کد را تکرار کنیم. در حال حاضر، همه چیزهایی که رندر شده زرد است. ما با موفقیت یک Torus ایجاد کردیم و شما میتوانید هر کدام از توابع فاصله را در اینجا مشاهده کنید تا ببینید چگونه کار میکنند. در نهایت، ما چیزی کمی پیشرفته تر را پوشش خواهیم داد.
گرفتن اطلاعات سبک G-Buffer
برای استفاده از هر مدل نورپردازی، نیاز به اطلاعات بیشتری داریم. در حال حاضر، ما فقط فاصلهای را در امتداد یک اشعه قرار دادهایم و هیچ جزئیاتی برای دوناتمان در نظر نگرفتیم. برای کار کردن بیشتر روی شکل، به موارد زیر نیاز داریم:
– مختصات سه بعدی (3D Coordinates)
– نرمال سطوح (Surface Normals)
خوشبختانه، هر دوی آنها بسیار آسان بدست میآیند.
چگونه موقعیت و Normals را بدست آوریم:
به منظور روشن شدن آن، ما فقط از معادله نورپردازی استاندارد برای Phong که می توان در اینجا در ویکی پدیا پیدا کرد، استفاده می کنیم.
خب تا اینجا همه چیز خوب به نظر میرسد اما کار بافت دهی هنوز باقی مانده است. متاسفانه هیچ راهی برای گرفتن UV وجود ندارد. برای بافت دهی باید از Projection Mapping استفاده کنیم.
وقفه
قبل از ادامه دادن کار بر روی دونات با خود گفتم که یک دموی دیگر از Shadertoy را به اشتراک بگذارم تا خستگیتان در رود. در اینجا صحنه دیگری است که میتوانید با آن ارتباط برقرار کنید. این شکلهای اولیه با Raymarching رسم شدهاند که شامل ویژگیهایی نظیر شکست نور و برخی کارهای دیگر که Raymarching قادر است انجام دهد، میباشد.
Projection Mapping
بیایید دوناتمان را تزئین کنیم. در اینجا بافت خمیری و در اینجا بافتی روی دونات که استفاده خواهیم کرد، وجود دارد. خب آخرین مرحله کدنویسی را انجام میدهیم.
پراجکشن مپینگ سه بعدی مسطح برای دوناتمان
نتیجه
در پستهای بعدی، رندر حجمی را پیگیری خواهم کرد که در حال حاضر بیشترین پتانسیل را برای برای اثرات دود، شبیه سازی ذرات عظیم و ابرها دارد. دلیل آن این است که یونیتی Raymarching را پوشش میدهد. هر چیزی که ماهیت حجم دارد، میتواند با تکنیکهای بسیار مشابه به آنچه که در بالا توضیح داده شد، رندر شود.
منبع: Gamasutra