گیمولوژی | رسانه تحلیلی بازی سازی | شیدر نویسی در یونیتی (بخش دوم)
قبل از شروع شیدر نویسی بهتر است به چند سوال متداول پاسخ دهیم:
شیدر چیست و چه لزومی دارد که از آنها استفاده کنیم؟
شیدر برنامهای است که به طور خاص روی یک GPU اجرا و از آنها، برای دستکاری آن چه روی صفحه نمایش توسط کارت گرافیک ارائه شده استفاده میشود. از آنجایی که این برنامههای کوچک در واقع روی کارت گرافیک اجرا میشوند، به این معنی است که آنها برای پردازش بسیار سریع هستند و لزوم استفادهی آنها، برای آزاد سازی چرخه ارزشمند CPU برای منطق بازی است.
چه نیازی به یادگیری شیدرنویسی داریم در صورتی که می توانیم از شیدر های آماده و پیش فرض استفاده کنیم؟
یادگیری شیدرنویسی به شما کمک میکند که علاوه بر نوشتن شیدر ، قدرت شخصیسازی شیدر های بازیتان را نیز داشته باشید و بنا بر نیازتان، ویژگیهایی را به آن اضافه و یا از آن کم کنید. هر چند شما میتوانید از شیدرهای پیشفرض و یا آماده استفاده کنید، اما گاهی لازم است که برای منحصربهفرد کردن بازیتان شیدری خاص بنویسید. همان طور که استفادهی بیش از حد کدهای آماده در بازی توصیه نمیشود، بهتر است خیلی زیاد از شیدرهای آماده استفاده نکنید. یکی از عواملی که باعث میشود بازیتان جذابتر شود، استفاده از شیدرها و افکتهای جدید است؛ پس بهتر است از کنار آن به آسانی عبور نکنید.
تعاریف شیدر، متریال و تسکچر
شیدر یک کد است که رفتار نورپردازی و ویژگیهای رندر آبجکت یا افکتهای دوربین را توصیف میکند اما متریال تعیین میکند که چگونه یک سطح باید رندر شود. متریالها میتوانند دادههای مختلفی را برحسب نیاز سایهزن در خود نگه دارند از جمله رفرنس بافت استفاده شده، اطلاعات کاشی کاری (Tiling Information)، رنگ و غیره .
بافتها (Textures) تصاویر Bitmap هستند. یک متریال ممکن است رفرنس به یک بافت داشته باشد به طوری که شیدرِ متریال در هنگام محاسبه میتواند رنگ سطحی یک شی را از بافتها استفاده کند. علاوه بر رنگ پایه (Albedo)، بافتها می توانند بسیاری از جنبههای دیگر سطح متریال مانند زبر و مات یا صیقلی و براق بودن را تعیین کنند.
نکات:
– گزینههای موجود در یک متریال بستگی به شیدر استفاده شده در متریال دارند. شیدر یک یا چند متغیر بافتی که انتظار میرود استفاده شود را مشخص میکند و Inspector Material در Unity به شما اجازه میدهد که ورودیهای بافت خود را به این متغیرهای بافت اختصاص دهید.
– اين نكته را مد نظر قرار دهید که شیدرها اغلب برای کنترل نور و اثرات سایهای استفاده میشوند اما هیچ دلیلی وجود ندارد که توانایی آنها به همین موارد محدود شود.
آیا دانستن برنامهنویسی برای نوشتن شیدر امری ضروری است؟ زبان شیدر نویسی در یونیتی چیست؟
برای شروع شیدر نویسی بهتر است مشکلی در برنامهنویسی نداشته باشید تا مسیر برایتان هموارتر گردد. یادگیری زبان شیدر مانند روال یادگیری زبان برنامهنویسی جدید است. مفاهیم زبانهای برنامهنویسی تقریباً یکسان است؛ بنابراین همان طور که برای یادگیری برنامهنویسی لازم است از قواعد دستوری (Syntax) و معناشناسی (Semantics) آن زبان پیروی کنید، برای شیدرنویسی هم باید همین روال را طی کنید.
تفاوت شیدر و متریال در چیست؟
شیدر یک کد است که رفتار نورپردازی و ویژگیهای رندر آبجکت و یا افکتهای دوربین را توصیف میکند. ولی متریال مجموعه مقداردهی برای تنظیمات یک شیدر است. این نکته را هم باید در نظر داشته باشیم که شیدرها اغلب برای کنترل نور و اثرات سایهای استفاده میشوند، اما هیچ دلیلی وجود ندارد که توانایی آنها به همین موارد محدود شود.
شاید شما علاقهای به کدنویسی نداشته یا دنبال راههای سادهتری برای نوشتن شیدر باشید، در این صورت باید این نکته را هم مدنظر قرار داد که شما میتوانید شیدر را به صورت بصری ایجاد کنید (Visual Programming ). این کار نه تنها آسانتر است بلکه دیگر نیاز به نوشتن کدهای طولانی ندارید. ابزارهایی نظیر ShaderForge و Amplify Shader Editor دارای محیطی گرافیکی هستند که شما را قادر میسازد تا با استفاده از اتصال یکسری Nodeهای مختلف، شیدر خود را به راحتی ایجاد کنید.
برنامهنویسی شیدر چه پیشنیازهایی دارد؟
در برنامهنویسی شیدر لازم است تا با مفاهیم اولیهی گرافیک کامپیوتری و ریاضی (ماتریسها، نسبتهای مثلثاتی و…) آشنا باشید تا درک بهتری از اصطلاحات گرافیکی ، توابع و الگوریتمهای ریاضی داشته باشید.
برای شروع شیدرنویسی در یونیتی بهتر است اول آشنایی اجمالی با زبانهای شیدرنویسی و زبان شیدرنویسی مورد استفاده در یونیتی را داشته باشید:
تصویر بالا سیر تکامل زبان شیدرنویسی را نشان میدهد
آناتومی شیدر
Unity3D از دو نوع مختلف شیدر پشتیبانی می کند: Surface Shader و Fragment and Shader Vertex. نوع سومی هم به نام شیدرهای تابع ثابت (Fixed Function Shaders) وجود دارد، اما آنها اکنون منسوخ شده و در این سری از پستها پوشش داده نخواهند شد. صرف نظر از این که کدام نوع متناسب با نیازهای شماست، آناتومی یک شیدر برای همه آنها یکسان است:
شما میتوانید چند بخش SubShader داشته باشید، یکی پس از دیگری. آنها حاوی دستورالعملهای واقعی برای GPU هستند. Unity3D سعی خواهد کرد تا آنها را به ترتیب اجرا کرده و تا زمانی آن را پیدا کند که سازگار با کارت گرافیک شما باشد. این در هنگام برنامهنویسی برای سیستمعاملهای مختلف مفید است، زیرا شما میتوانید نسخههای مختلف یک شیدر مشابه را در یک فایل متناسب کنید.
Properties
ویژگیهای (Properties) شیدر شما به نوعی معادل با فیلدهای Public در اسکریپت #C است؛ آنها در Inspector متریال ظاهر شده و به شما این امکان را میدهد تا آنها را تغییر بدهید.
بر خلاف آنچه که با یک اسکریپت اتفاق میافتد، شما میتوانید ویژگیهای متریال را در حالی که بازی در ادیتور در حال اجرا است تغییر دهید و حتی پس از اینکه از بازی خارج شدید، ویژگیهای متریال به حالت اول بر نمیگردد (یعنی مقادیر ویژگی متریال مثل مقادیر اسکریپت نیست که بعد از خارج شدن از بازی به حالت اولش برگردد).
در قطعه کد زیر تعریف همه انواع اساسی را که می توانید در یک شیدر داشته باشید پوشش می دهد:
نوع دوبعدی که در خطوط 3-4 استفاده شده، نشان میدهد که پارامترها بافت هستند. آنها میتوانند به سفید، سیاه یا خاکستری روشن مقداردهی اولیه شوند. شما همچنین میتوانید از Bump برای مقداردهی استفاده کنید تا تکسچر به عنوان نرمال مپ استفاده شود. در این مورد، آن به صورت خودکار به رنگ # 808080 (خاکستری) مقداردهی اولیه میشود. بردارها و رنگها همواره دارای چهار عنصر XYZW و RGBA هستند.
تصویر زیر نشان میدهد که چگونه این ویژگیها زمانی که شیدر به یک متریال متصل است در Inspector نمایش داده میشود.
متاسفانه تعریف ویژگیها به تنهایی کافی نیست. بخش Properties در واقع توسط Unity3D برای دسترسی از Inspector به متغیرهای پنهان در یک شیدر استفاده میشود. این متغیرها هنوز باید در قسمت اصلی شیدر تعریف شوند که در قسمت SubShader موجود است. یعنی هر ویژگیای که تعریف میشود باید یک متغیر متناظر هم برای آن تعریف کنیم.
نوع مورد استفاده برای بافت Sampler2D است. بردارها Float4 هستند و رنگها به طور کلی Half4 هستند که از 32 و 16 بیت استفاده میکنند.
با این حال، انواع داده لازم نیست: شما هیچ خطایی برای اعلام _MyRange به عنوان half به جای float نخواهید داشت. در حین کار شاید به مواردی نسبتاً گیجکننده برخورد کنید؛ به عنوان مثال اگر بتوانید یک ویژگی از نوع Vector را تعریف کنید که به متغیر float2 مرتبط است، دو ارزش اضافی توسط Unity3D نادیده گرفته میشود.
دستور رندر
همان طور که قبلا ذکر شد، بخش SubShader حاوی کد اصلی شیدر است که در Cg / HLSL نوشته شده و بسیار شبیه به C است.
به طور خلاصه، بدنهی یک شیدر برای هر پیکسل تصویرتان اجرا می شود. پرفورمنس در اینجا بسیار مهم است. با توجه به معماری GPUها، در تعداد دستورالعملهایی که میتوانید در یک شیدر انجام دهید محدودیتی وجود دارد. این مورد ممکن است از این تقسیم محاسبات در چند گذر جلوگیری کند، اما این موضوع در این آموزش پوشش نخواهد یافت.
بدنهی شیدر، به طور معمول به گونهی زیر تعریف میشود:
خطوط 8-11 شامل کد اصلی Cg است. بخش توسط دستورالعمل CGPROGRAM و ENDCG اعلام شده است.
خط 3 قبل از بدنهی واقعی، مفهوم برچسبها (Tags) را معرفی میکند. برچسبها یک راه برای گفتن ویژگیهای خاص شیدری است که آن را مینویسیم. به عنوان مثال، برچسب مربوط به ترتیب رندر شدن (Queue) و نحوه رندر کردن آن (RenderType) است.
نکته: RenderType برای Replacement Shaders استفاده میشود. به این صورت که شیدر جایگزین این برچسبها را خوانده و سپس از یک شیدر مناسب دیگر استفاده میکند. حتی اگر از شیدرهای جایگزین استفاده نکنید بهتر است که شیدرهای خود را برای استفاده در آینده برچسب دار کنید. لینک
GPU هنگام رندر معمولا آنها را با توجه به فاصله خود از دوربین مرتب میکند، به طوری که بیشتر آنها برای اولین بار رسم میشوند. این کار برای رندر اشیای جامد هندسی(Solid Geometries) کافی است اما اغلب اشیای شفاف با شکست مواجه میشود.
Background (پسزمینه) (1000): مورد استفاده برای پسزمینه و skyboxes.
Geometry (هندسه) (2000): برچسب به طور پیشفرض مورد استفاده برای بیشتر اجسام جامد.
Transparent (شفاف (3000):( مورد استفاده برای متریالهایی که ویژگی شفافیت دارند، مانند شیشه، آتش، پارتیکلها و آب.
Overlay (روکش) (4000): مورد استفاده برای جلوههایی مانند شعله ور شدن لنز، عناصر رابط کاربری گرافیکی و متون.
یونیتی همچنین اجازه میدهد تا دستورات مرتبط مانند Background+2 که ترتیب مقدار 1002 را نشان می دهد را مشخص کنید. از دست دادن ترتیب میتواند شرایط تند و زنندهای تولید کند که در آن یک شی همیشه رسم شده است. حتی زمانی که آب باید توسط مدلهای دیگر پوشش داده شود.
ZTEST
نکتهی مهمی که باید به یاد داشته باشید این است که یک شیء شفاف، لزوما همیشه زودتر از یک شیء هندسی ظاهر نمیشود.
GPU به طور پیشفرض، یک تست به نام ZTest را اجرا میکند که پیکسلهای پنهان را از رسم شدن باز میدارد. برای کار، آن با استفاده از یک بافر اضافی با همان اندازه صفحه نمایش رندر می کند. هر پیکسل شامل عمق (فاصله از دوربین) از جسم رسم شده در آن پیکسل است.
Surface versus vertex and fragment
آخرین بخش که باید پوشش داده شود، کد اصلی شیدر است. قبل از انجام این کار، باید تصمیم بگیریم که از کدام نوع شیدر استفاده کنیم. این بخش نگاهی اجمالی به نوشتن شیدر است.در مقالات بعدی نوشتن انواع شیدر را توضیح میدهیم. هرچند توضیحات بیشتر در این مقالات نمیگنجد، ولی برای شروع کفایت میکند.
The Surface Shader
هرگاه متریالی که میخواهید شبیهسازی کنید به تأثیرات نورپردازی واقعی نیاز داشت ، بهتر است از Surface Shader استفاده کنید.
Surface Shaders محاسبات چگونگی بازتاب نور را پنهان میکرده و اجازه میدهد تا ویژگیهای “بصری” مانند albedo ، normals ، بازتاب و غیره را در یک تابع به نام surf مشخص کنیم. این مقادیر سپس به یک مدل نورپردازی متصل میشوند که مقدار RGB نهایی را برای هر پیکسل نشان میدهد. همچنین شما میتوانید مدل نورپردازی خود را بنویسید، اما این فقط برای اثرات بسیار پیشرفته مورد نیاز است.
کد Cg یک surface shader معمولی به گونهی زیر تعریف میشود:
خط 5 یک بافت را وارد میکند و سپس به عنوان ویژگی Albedo متریال در خط 12 تعریف میشود. شیدر از مدل نورپردازی Lambertian (خط 3) استفاده میکند که روشی بسیار معمول برای مدلسازی چگونگی بازتاب نور روی یک شی است. شیدرهایی که فقط از ویژگیهای albedo استفاده میکنند معمولا diffuse نامیده میشوند.
The Vertex and Fragment Shader
شیدرهای Vertex and fragment خیلی نزدیک به کاری است که نحوه پردازش گرافیکی مثلثها (triangles) میکنند و هیچ مفهوم ساخته شده در مورد چگونگی رفتار نور را ندارد. هندسه مدل شما برای اولین بار از یک تابع به نام vert عبور داده می شود که میتواند رأسهای آن را تغییر دهد. سپس مثلثها به طور جداگانه از طریق تابع دیگری به نام frag عبور داده میشود که رنگ RGB نهایی را برای هر پیکسل تعیین میکند. این شیدرها برای اثرات دوبعدی، پس پردازش(postprocessing) و جلوههای ویژه سهبعدی مفیدند که در نظر گرفتن آنها به عنوان Surface Shaders بسیار پیچیده است(چون Surface Shaders مدلهای نورپردازی از قبل تعریف شده و کارهای طولانی را خلاصه کرده، لذا برای نوشتن شیدری متفاوت نیاز دارید تا از fragment shader استفاده کنید و شیدرتان را از صفر بنویسید).
vertex and fragment shader زیر به سادگی یک شی یکنواخت را قرمز و بدون نور میکند.
خطوط 15-17 خطوط را از فضای محلی بومی خود به حالت دوبعدی نهایی روی صفحه تبدیل میکنند.
یونیتی UNITY_MATRIX_MVP را برای این تعریف کرده است تا ریاضی پشت آن را پنهان کند. پس از این، خط 22 به هر پیکسل رنگ قرمز میدهد. فقط به یاد داشته باشید که بخش Cg در vertex and fragment shaders باید در بخش عبور قرار گیرد (ورتکس و فرگمنت شیدر را باید پاس بدهید، ولی سرفیس شیدر را بدون پاس همان طور در ساب شیدر مینویسید).این مورد برای شیدرهای سادهی surface shaders نیست، یعنی با آن یا بدون آن هم کار میکند.
نکته: mul(UNITY_MATRIX_MVP, float4(pos, 1.0)) با UnityObjectToClipPos معادل است. (لینک)
نکته: ماتریس مدل، مشاهده و طرح ریزی (Model View Projection) سه ماتریس جداگانه است که مدل را از فضای مختصات محلی (Local Position) جسم به فضای جهان(World Position)، نمایش (View) از فضای جهان به فضای دوربین و طرح ریزی از دوربین به صفحه نمایش را تعیین میکند.
Model Space, World Space, View Space
منابع :
Unity 5.x Shaders and Effects Cookbook
thebookofshaders
https://docs.unity3d.com/Manual/SL-ShadingLanguage.html
https://unity3d.com/learn/tutorials/topics/graphics/gentle-introduction-shaders
https://gamedevelopment.tutsplus.com/tutorials/a-beginners-guide-to-coding-graphics-shaders–cms-23313
https://docs.yoyogames.com/source/dadiospice/002_reference/shaders/index.html
https://en.wikipedia.org/wiki/Shader