در این پست قصد دارم روش هایی برای بهینه سازی شیدر رو مطرح کنم.
از if-else statement استفاده نکنید!
چون GPU در حالت عادی به صورت موازی عمل می کنه اما اگر از if-else statement استفاده کنید دیگه موازی عمل نمی کنه!
یکسری از Thread ها از بلوک IF عبور می کنند و باقی اونا از بلوک ELSE به خاطر همین دیگه مثل قبل با هم پیش نمیرن و باعث میشه سرعتشون کند بشه
یک لشکر سرباز رو در نظر بگیرید که به دو قسمت تقسیم میشه دیگه قدرت این لشکر دیگه مثل سابق نیست!
تو کتاب The Book of shader نوشته به جای اینکه از if-else استفاده کنید تابع های Built-in مثل step و smooth step استفاده بشه.
تا جایی که ممکنه محاسبات رو در vertex shader انجام بدید!
تعداد پیکسل های صفحه خیلی زیاده اگر قرار باشه یک محاسبه روی تک تک پیکسل ها انجام بشه ممکنه خیلی گرون تموم بشه.
پس سعی کنید محاسبات رو داخل vertex shader انجام بدید.چون تعداد ورتکس ها نسبت به پیکسل ها کمتره!
استفاده از قابلیت multi-compile
به جای اینکه 10 تا شیدر مشابه تو پروژه داشته باشید می تونید از یک شیدر همه کاره استفاده کنید!
که با یکسری Toggle فعال یا غیر فعال میشن!
تو شیدر گراف از قسمت keywords می تونید چنین قابلیتی رو استفاده کنید.
استفاده از Shader Variant Collection
تعریف: shader varient ها شیدر هایی هستند که کد های مشترک رو share می کنند ، اما در جاهای مختلف فرق دارند.شیدر ها با تعداد زیادی varient به اصطلاح mega shader یا uber shader نامیده می شوند.
نکته: ShaderVariantCollection یک asset است که درواقع لیستی از شیدر ها را نگه میدارد.و برای هر یک از آنها ، لیستی از انواع Pass و ترکیب کلمات کلیدی shader برای بارگذاری وجود دارد.
نکته : ShaderVariantCollection ثبت می کند که کدام نوع shader varient در در هر شیدر استفاده می شود. این مورد برای پیش بارگذاری شیدر یا به اصطلاح ("warm up") استفاده می شود ، به طوری که یک بازی می تواند اطمینان حاصل کند که انواع شیدر "در واقع مورد نیاز" از زمان بارگذاری (یا زمان بارگذاری سطح) بارگیری می شوند ، که برای جلوگیری از hiccup های ناشی از کامپایل شیدر در بازی استفاده می شود.
سعی کنید زیاد از نود های Procedural شیدر گراف استفاده نکنید!
نود های Procedural خیلی خوبه بهش یک UV میدی و مثلاً یک نویز تولید می کنه.
اما دقت کنید که پشت پرده محاسبات داره.
به جاش می تونید از چندتا تکسچر از قبل Generate شده استفاده کنید.
از Channel های زیاد استفاده نکنید!
به جای ساختن چندتا ماسک از یک ماسک استفاده کنید!
برای اینکه یک ماسک داشته باشید می تونید از چنل های RGBA استفاده کنید که همون طور که می بینید حالت Linear Dodge میگیره!
در این پست قصد داریم با SRP Batcher را توضیح دهیم:
تعریف SRP Batcher
یک Rendering loop است که سرعت رندر CPU را در Scene با متریال هایی که Shader Variant یکسانی دارند افزایش میدهد.
چگونه SRP Batcher را فعال کنیم؟
برای استفاده از SRP Batcher باید از یک Rendering Pipeline مثل URP ، LWRP یا HDRP استفاده کنید.
بعد وارد Scriptable Object بشوید و SRP Batcher را فعال کنید.
متریال در یونیتی
یونیتی rendering engine انعطاف پذیری دارد.
شما می توانید در هر زمان و در طول هر فریمی خصوصیات متریال را تغییر بدید!
علاوه بر این ، یونیتی از لحاظ تاریخی برای بافرهای غیر ثابت(non-constant buffers) ساخته شده ، و از API های گرافیکی مثل DirectX9 پشتیبانی می کند.
با این حال چنین ویژگی های خوبی اشکالاتی هم دارد!
به عنوان مثال وقتی یک Draw call از متریال جدیدی استفاده می کند کار های بیشتری باید انجام بشه.
بنابراین وقتی متریال های بیشتری در یک Scene داشته باشید پردازش CPU بیشتری برای تنظیم کردن اطلاعات GPU نیاز دارید.
Standard Unity rendering workflow
در طول render loop داخلی ، وقتی متریالی جدید تشخیص داده می شود، CPU تمام خصوصیات آن را جمع می کند و بافرهای ثابت مختلف را در حافظه GPU تنظیم می کند. تعداد بافرهای GPU بستگی به این داره که شیدر چطور CBUFFER های خودش را اعلام کند.
نحوه کار کردن SRP Batcher
وقتی تکنولوژی SRP را می ساختند ، مجبور شدند بعضی از قسمت های سطح پایین engine را بازنویسی کنند.این فرصت خوبی بود تا بعضی از الگو ها مثل داده های پایدار GPU را به صورت native ادغام کنند.
هدفشون این بود که موارد کلی را سرعت ببخشند و تو یک Scene از متریال های مختلفی با تعداد کمی Shader Varients استفاده شود.
اکنون render loop می تواند متریال هایی با داده های پایدار داخل حافظه GPU ایجاد کند.
اگر محتوای متريال عوض نشود هیچ نیازی به تنظیم و بارگذاری به GPU نیست.
به علاوه از یک روش اختصاصی کد استفاده می شود تا سریعاً خصوصیات Built-in engine در یک GPU Buffer بزرگ آپدیت شود.
فلوچارت جدید به صورت زیر است:
SRP Batcher rendering workflow.
اینجا CPU فقط خصوصیات built-in engine و آبجکت های دارای برچسب تبدیل ماتریس را هندل می کند.
همه متریال ها CBUFFERs های پایداری دارند که داخل حافظه GPU که قرار داه و آماده استفاده هستند.
به طور خلاصه ، سرعت حاصل از دو چیز متفاوت است:
هر محتوای متریال اکنون داخل GPU پایدار است.
یک کد اختصاصی مدیریت GPU CBUFFER بزرگ "برای هر آبجکت" را کنترل می کند.
چگونه چک کنیم SRP Batcher بهینه است؟
فهمیدن Batch در متن "SRP Batcher" مهم است!
مردم از روی عادت سعی می کنند draw call ها را کاهش بدهند تا هزینه رندر CPU را کاهش بدهند
دلیل اصلی آن این است که engine قبل از صدور draw call باید موارد زیادی را تنظیم کند.
در صورتی که هزینه واقعی CPU از اون تنظیمه میاد ، نه از خود GPU DrawCall (که فقط چند بایت برای ارسال فرمان به بافر GPU است).
درواقع SRP Batcher تعدادDrawCall ها را کاهش نمی دهد! بلکه هزینه تنظیم GPU بین DrawCall ها را کاهش میدهد.
که می تونید تو work flow زیر مشاهده کنید:
سمت چپ SRP rendering loop استاندارد و سمت راست SRP Batcher است.در متن SRP Batcher کلمه Batch فقط دنباله ای از “Bind”, “Draw”, “Bind”, Draw” دستورات GPU است.
در standard SRP متریال های جدید slow SetShaderPass نامیده می شوند.
در متن SRP Batcher هر shader varient جدید SetShaderPass نامیده می شود.
برای داشتن ماکزیمم Performance باید Batch ها را تا جایی که می تونید بزرگ نگه دارید.
بنابراین از تغییر هر shader varient باید پرهیز کنید.اما در عوض شما می تونید از هر متریال مختلفی که می خواید در صورتی که شیدرشون یکی باشد استفاده کنید.