با توجه به این که ما در این مقاله میخواهیم مبحث شروع شده در مقالهی قبلی را ادامه دهیم، بهتر است قبل از انجام هر کاری در ابتدا مقالهی آشنایی با رندرینگ و بهینهسازی آن را مطالعه کرده و با تعریفها و توضیحاتی که در آن دادهایم به خوبی آشنا شوید. دقت کنید با این که مبنای کاری ما پاسخ به تمامی سوالات است شما دوستان عزیز است اما شما به راحتی میتوانید از بخش انتهایی هر مقاله به فهرست تمامی مقالات نوشته شده دسترسی پیدا کرده و بدون اتلاف وقت پاسخ بسیاری از سوالات خود را پیدا کنید.
اما به طور خلاصه در مقالهی گذشته پس از صحبت در مورد فرآیند رندرینگ و بیان گام به گام مراحل مرتبط با CPU و GPU، در این رابطه صحبت کردیم که اگر پردازندهی مرکزی سیستم، نقطهی گلوگاه فرآیند باشد عمده کاری که از جانب ما برمیآید مدیریت میزان تعداد دستوراتی است که باید برای رندر فریمهای بازی مورد نظر ما برای پردازندهی گرافیکی ارسال شود که مدیریت این مسئله هم در چند زیر موضوع مختلف صورت میگیرد. اما قبل از شروع موضوع جدید باید به این نکته هم اشاره کنیم که وظایفی که پردازندهی مرکزی در اجرای یک بازی بر عهده دارد محدود به فرآیند رندرینگ نیست و از اجرای کدهای بازی گرفته تا انجام محاسبات فیزیکی درون محیط همگی بر عهدهی این قطعه از رایانه است. حتی به صورت دقیقتر در موتوری همانند یونیتی هم با این که وظایف مشخصی از مسئلهی رندرینگ بر عهدهی ترد (Thread) اصلی است اما این ترد وظایف بسیار دیگری از جمله اجرای کدهای اسکریپتهای بازی را هم بر عهده دارد. برای همین اگر بازی ما به خاطر این ترد در پردازندهی مرکزی دچار گلوگاه شده باشد، باید در کنار توجه به نکات مربوط به فرآیند رندرینگ، گزینههای بهینهسازی دیگر از جمله بهینهسازی کدهای اسکریپت را هم مورد توجه قرار دهیم.
اگر پردازندهی گرافیکی گلوگاه باشد
مثل دیگر موارد اولین کاری که در این زمینه باید صورت بگیرید شناسایی دلیل بهوجود آمدن گلوگاه است. کارایی پردازندههای گرافیکی معمولا به خصوصیتی به نام Fill Rate آنها محدود میشود که در ادامه در مورد آن صحبت خواهیم کرد. اما در کنار این مورد دو مسئلهی پهنای باند حافظه (Memory Bandwidth) و Vertex Processing هم میتوانند نقاطی برای ایجاد گلوگاه باشند که البته تاثیر آنها معمولا کمتر از مورد اول است.
نرخ پرکردن یا Fill Rate
تعریف این ویژگی بسیار ساده است و اشاره به تعداد پیکسلهایی دارد که یک پردازندهی گرافیکی میتواند در ثانیه رندر کند. این که این میزان چگونه حساب میشود خارج از بحث ما است اما کاملا مشخص است که میزان وضوح تصویر و همچنین نرخ فریم بازی به طور مستقیم روی تعداد پیکسلهایی که باید در ثانیه حساب شوند تاثیر گذار هستند و برای همین به راحتی میتوان از طریق آنها فهمید که آیا فرآیند رندرینگ شما به خاطر همین ویژگی دچار مشکل شده است یا خیر. برای دانستن این مسئله کافی است سه گام زیر را انجام دهید:
- بازی خود را پروفایل کرده و زمان مربوط به پردازندهی گرافیکی را ثبت کنید
- از طریق گزینهی Player Setting وضوح تصویر بازی را کاهش دهید (مثلا از فولاچدی به اچدی)
- دوباره بازی را پروفایل کنید. اگر عملکرد بازی بهتر شد احتمالا مشکل از همین ویژگی است
اگر بازی شما به خاطر این ویژگی دچار مشکل شده است، احتمالا این مسئله به خاطر یکی از سه دلیل Fragment Shaders، Overdraw یا Image Effect خواهد بود.
Fragment Shaders
Fragment shaders بخشی از کدهای سایهزنی هستند که به پردازندهی گرافیکی میگویند که یک پیکسل تنها را چگونه ترسیم کند. دقت کنید این بخش از کدها برای هر پیکسلی که باید ترسیم شود توسط پردازندهی گرافیکی اجرا میشود و برای همین کوچکترین مشکل در این بخش میتواند تاثیر زیادی روی عملکرد نهایی داشته باشد. جدای از این بحث به طور کلی در رابطه با شیدرها چند نکتهی مهم وجود دارد. اولین نکته این است که تا میتوانید بهتر است هم برای افزایش سرعت تولید و هم دستیابی به عملکرد بهتر از شیدرهای آمادهی خود موتور استفاده کنید. چرا که معمولا آنها توسط تیم سازنده به بهترین شکل ممکن بهینه شدهاند و برای همین نگرانی بابت عملکرد آنها وجود ندارد. Unity’s Standard Shader از این نمونه هستند که میتوانید روی لینک آن کلیک کرده و اطلاعات بیشتر را در مورد آنها کسب کنید.
از طرفی کاملا طبیعی است که بهای جزییات بیشتر مصرف بیشتر منابع سختافزاری است و برای همین تا میتوانیم باید اصل خود را روی سادگی شیدرها و سپس بهینه بودن آنها بگذاریم که اگر شما خودتان شیدرهای مورد نیازتان را آماده میکنید بهتر است دو مقالهی مرتبط با موتور یونیتی را که در زیر آوردهایم مطالعه کنید.
نکتهی آخری که در این بخش وجود دارد این است که اگر جنبهی ظاهری بازی شما دچار مشکل نمیشود، هیچ مشکلی ندارد که مثلا از شیدرهایی استفاده کنید که برای دستگاههای موبایل آماده و بهینه شدهاند. به دلیل سبک بودن این گونه شیدرها استفاده از آنها میتواند منابع سختافزاری قابل توجهی را برای ما در پلتفرمهای دیگر آزاد کند.
Overdraw
این اصطلاح برای زمانهایی به کار میرود که یک پیکسل مشخص برای چندین مرتبه درون فریم نهایی رندر میشود. مثلا فرض کنید ما در بخشی از تصویر خود چندین شی را در یک ردیف پشتسر هم داریم. در چنین حالتی با این که کاملا عقلانی است که فقط آن شی نزدیک به تصویر که دیگر اشیا را پوشانده رندر کنیم اما در شرایطی نمیتوانیم به این روش عمل کنیم. به روش توضیح داده شده اصطلاحا روش بالا به پایین میگویند که در آن ترتیب اشیا مشخص شده و صرفا شی جلویی رندر میشود. اما مثلا در شرایطی که شی جلویی ما یک شی شفاف (Transparent) باشد، در چنین حالتی موتوری همانند یونیتی ترتیب رندر را عوض کرده و به صورت پایین به بالا فرآیند رندر را انجام میدهد تا بتواند حالت تصویر در پشت یک شی شفاف را به خوبی ایجاد کند. بنابراین همان گونه که مشخص است در چنین حالتی یک پیکسل چندین نوبت رندر میشود که عمل هزینهبری است.
Overdraw مبحث پیچیدهای است و این گونه نیست که با یک راهکار مشخص بتوانیم تمامی مشکلات مربوط به آن را حل کنیم با این حال اگر بتوانیم به روشی از تعداد اشیایی که روی هم قرار میگیرند و موتور یونیتی هم نمیتواند آنها برای رندر ترتیببندی کند کم کنیم، میتوانیم به مقدار زیادی به هدف خودمان برسیم. بهترین نقطه برای فهمیدن این که ما چه تعداد اشیا را در پشتسر هم در صحنه داریم این است که همانند تصویر زیر از بخش نوار کنترلی منظره، گزینهي Overdraw را انتخاب کنیم. در چنین حالتی به صورت خودکار و در هر نمایی که تصویر را قرار بدهید، موتور بازی به طور خودکار میزان اشیایی را که در پشتسر هم قرار گرفتهاند با استفاده از بازهی رنگ سیاه (عدم وجود هیچ گونه روی هم قرار گرفتگی) تا سفید (شدت بالایی از روی هم قرار گرفتن اشیا) به شما نشان میدهد.
دقت کنید با این که تعداد اشیای درون صحنه هیچ تغییری نمیکند ولی گذر احتمالا گذر نمای دوربین از جهتی که شدت این مسئله در آن کمتر است هم میتواند روی عملکرد بازی شما تاثیر گذار باشد. با این حال اولین توصیه کمکردن این است که تا میتوانید با رعایت ظاهر گرافیکی بازی اشیایی که در پشت هم قرار میگیرند را کم کنید.
البته بد نیست این را بدانید که مهمترین متهمان رخدادن مشکل Overdraw، متریالهای شفاف، ذرات بهینهنشده و عناصر رابط کاربری هستند که روی دیگر اشیا قرار گفتهاند. به عنوان مثال شما میتوانید در رابطه با مسائل مرتبط با رابطکاربری از این لینک مطالب مفیدی را به دست آورید.
Image Effects
افکتهای تصویری هم یکی از مهمترین دلایل مشکلات مربوط به Fill Rate در بازیهای ویدیویی هستند به خصوص اگر ما به طور همزمان از چندین افکت تصویری در بازی خود استفاده میکنیم. جدای از این که مثل همیشه باید سعی کنیم این عنصر را در بهینهترین حالت خود مورد استفاده قرار دهیم، اما اگر بازی ما از چندین افکت تصویری به صورت همزمان روی یک دوربین استفاده میکند بهتر است در یک زمان اعمال این افکتها روی تصویر را صادر کنیم. اگر یادتان باشد در مباحث مرتبط با پردازندهی مرکزی هم ترتیب و تعداد صادرکردن دستورات به طور مستقیم روی کارایی مجموعه تاثیر گذار بود. البته باز هم پیش میآید که با رعایت تمامی این نکات بازی شما از جنبهی Fill Rate همچنان دچار مشکل باشد که بهترین گزینه در این نقطه غیرفعال کردن افکتهای تصویری در سیستمهای پایینرده است.
پهنای باند حافظه (Memory Bandwidth)
منظور از پهنای باند حافظه میزان سرعت خواندن و نوشتن پردازنده (چه مرکزی و چه گرافیکی) روی حافظهی اختصاصی مربوط به آن است. دقت کنید که همانند پردازندهی مرکزی که به حافظهی رم برای انجام کارهای خود نیاز دارد، در هر کارت گرافیکی هم به منظور تامین نیازهای پردازندهی گرافیکی، حافظههایی در نظر گرفته شده است که معمولا ما همانند مثال GTX 1060Ti 6GB میزان ظرفیت حافظهی هر کارتی را در نام آن مشاهد میکنیم. اما اگر بازی شما از این مسئله رنج میبرد نحوهی شناسایی آن بسیار شبیه حالت قبل است.
- بازی خود را پروفایل کرده و زمان مربوط به پردازندهی گرافیکی را ثبت کنید
- از طریق گزینهی Project Setting و گزینهی Quality میزان کیفیت بافتها (Textures) را کاهش دهید (مثلا از Full Res به Half Res)
- دوباره بازی را پروفایل کنید. اگر عملکرد بازی بهتر شد احتمالا مشکل از همین ویژگی است
البته ممکن است همانند تصویر بالا هیچ تفاوتی ایجاد نشود که این مسئله نشاندهندهی عدم مشکل در این زمینه و وجود مشکل در میزان Fill Rate است چرا که تمامی این تصاویر روی یک سیستم و به صورت اختصاصی برای این مقاله ضبط شده است. به هر جهت اگر مشکل از پهنایباند بود باید بدانید که این مشکل معمولا به خاطر این رخ میدهد که ما در حال استفاده از بافتهایی در بازی خود هستیم که بیش از حد کشش منابع سختافزاری ما بزرگ هستند.
راهحل کاملا مشخص است و ما باید به طریقی بافتهای به کار رفته در بازی خود را بهینه کنیم. اولین راهکار در این زمینه فشرده کردن بافتها یا همان Texture Compression است. دقت کنید که فرمتهای مختلفی برای فشردهسازی بافتها وجود دارد که ما به طور خلاصه به این مسئله در بخش مربوط به موبایل اشاره کردیم و صرفا در زمان بستهبندی بازی و البته با توجه به محدودیتهای هر کدام از اشکال فشردهسازی میتوانیم از این تکنیک در بازی خود استفاده کنیم. شما میتوانید اطلاعات بیشتر در این زمینه را از طریق این لینک به دست آورید.
اما راهکار دیگری که در این زمینه وجود دارد استفاده از Mipmaps در بازی است. Mipmaps نسخههای کم کیفیتتر از بافتهای شما هستند که موتور یونیتی با استفاده از آنها سعی میکند در زمانهای که بعضی از اشیا از دوبین فاصله دارند، با به کار گیری این بافتهای کمکیفیتتر روی اشیا، بار رندر کردن آنها درون صحنه را کمتر کنند. شما به راحتی میتوانید همانند حالت Overdraw از منوی کنترلی صحنه و انتخاب گزینهی Mipmaps میزان بهرهی اشیا درون صحنه از این ویژگی را مشاهده کنید.
Vertex Processing
منظور از Vertex Processing کارهایی است که پردازندهی گرافیکی باید برای رندر کردن هر راس در یک مش (Mesh) انجام دهد. دقت کنید که هر مدل سهبعدی در ابتدا از شبکهای توری مانند به نام مش همانند تصویر زیر تشکیل شده است که این شبکه هم از راسهای متعددی تشکیل شده است.
اگر مشکل بازی ما به خاطر Fill Rate و پهنایباند حافظه نبود، احتمالا باید مشکل را در Vertex Processing یا همان فرآیند پردازش رئوس جست و جو کنیم. این فرآیند به طور مستقیم تحت تاثیر دو عامل تعداد رئوس و همچنین تعداد عملیاتهایی است که باید روی هر راس صورت بگیرد. برای همین راهکار موجود هم پیرامون کمکردن تعداد عاملهای گفته شده قرار دارد.
اولین و مهمترین مسئله کاهش پیچیدگیهای غیرضروری در مشها است که نمونهی آن را در مطالب قبلی و در رابطه با نمونهی آماده شدهی یکی از دوستان بیان کردیم. بهتر است از همان ابتدا و در نرمافزار مدلسازی فرآیند کاهش جزییات صورت بگیرد. همچنین در کنار این مورد استفاده از تکنیک Level of Detail یا به اختصار LOD هم میتواند در کاهش پیچیدگی مشها موثر باشد. در این تکنیک موتور به صورت خودکار یا حتی به صورتی که شما باید مقدماتش را فراهم کنید، اشیای دورتر به دوربین را در شکلی سادهتر رندر میکند که این مسئله به حذف پیچیدگیهای غیرقابل مشاهده مشها کمک میکند. شما میتوانید از این لینک اطلاعات بیشتری را در این زمینه به دست آورید.
اما اگر علاقمندید در کنار حفظ عملکرد بازی در حد قابل قبولی هم جزییات روی مشهای خود را داشته باشید بهتر است از تکنیکی به نام نرمال مپینگ (Normal Mapping) استفاده کنید که البته در صورت عدم رعایت نکات مربوط به این تکنیک خود این تکنیک هم میتواند مشکلاتی را برای کارایی پردازندهی گرافیکی شما ایجاد کند. اطلاعات بیشتر برای این به کارگیری این تکنیک را میتوانید از این لینک به دست آورید.
اگر به هر جهت مش مورد نظر ما از نرمالها بهتره نمیبرد میتوانیم در زمان وارد کردن آن به موتور بازی گزینهی Tangents را برای آن غیرفعال کنیم و این گونه میزان اطلاعاتی را که برای رندرکردن آن مش برای پردازندهی گرافیکی ارسال میشود کاهش دهیم.
به عنوان آخرین نکته در این بخش باید به این مسئله توجه کنید که شیدرهایی که برای رئوس نوشته میشوند، چگونگی رندرشدن هر راس را به پردازندهی گرافیکی نشان میدهند. برای همین اگر بازی ما از جنبهی Vertex Processing دچار مشکل است، ممکن است کاهش پیچیدگی شیدرهای نوشته شده برای این رئوس بتواند در بهبود این مشکل مفید باشد. همچنین ما نکات مربوط به استفاده از شیدرها را در قسمت مربوط به Fragment Shaders تا حدودی برای شما بیان کردهایم که کاملا برای این مسئله هم قابل استفاده هستند.
جمعبندی
در مجموع در این مقاله و مقالهی قبلی ما نکاتی را در رابطه با فرآیند پروفایل کردن فرآیند رندرینگ یک بازی و رفع مشکلات مربوط به آن بیان کردیم. دقت کنید که با این که فرآیند رندرینگ یکی از مهمترین بخشهای فنی یک بازی را تشکیل میدهد ولی تمامی آن را در برنمیگیرد. مثلا در رابطه با همین موتور یونیتی که مبانی تمامی صحبتهای ما بود، مقالههایی در زمینهی بهینهسازی کدهای اسکریپت و یا حتی مدیریت حافظهی رم وجود دارد. برای همین همواره سعی کنید از این اصل کلی پیروی کنید که هر بخشی از فرآیند تولید یک بازی ویدیویی را که یاد میگیرید، در قدم بعدی در ابتدا سعی کنید با تمرین و آزمایش روی آن مسلط شده و سپس بعد از آن بخشهای مرتبط با بهینهسازی آن را هم یاد بگیرید. دقت کنید مادامی که مثلا شما درست و اصولی فرآیند رندرینگ را درک نکرده باشید نخواهید توانست نکات مربوط به بهینهسازی آن را هم به خوبی یاد بگیرید. به همین خاطر در ابتدا اصل روی یادگیری کامل یک مبحث و سپس یادگیری نکات مربوط به بهینهسازی آن است.