UnityChan(Channel)
558 subscribers
2.07K photos
86 videos
38 files
88 links
کانال آموزش و نکات UnityChan

مالک کانال : @FProSensei

حمایت مالی (دونیت) : https://daramet.com/FProSensei

گروه یونیتی https://t.me/UnityChan
Download Telegram
در مجموعه آموزش های Anime Shader (NPR) قراره یک سری از دوره های خاص برای متخصص شدن در طراحی محیط فوق العاده استلایزد و خاص یاد بگیرید برای اینکه دوره دنبال کنید در کانال BlenderHints عضو شوید ❗️
2
FPro Sensei
در مجموعه آموزش های Anime Shader (NPR) قراره یک سری از دوره های خاص برای متخصص شدن در طراحی محیط فوق العاده استلایزد و خاص یاد بگیرید برای اینکه دوره دنبال کنید در کانال BlenderHints عضو شوید ❗️
در واقعه این دوره ها یک دوره طلایی هستن تا برای هنرمندان واقعی که یاد بگیرد چطوری اون صحنه های که توی انیمه که فوق العاده زیبا و استلایز هست رو چطوری میسازن جواب همشو توی این کانال پیدا خواهید کرد 👆
4
FPro Sensei
در واقعه این دوره ها یک دوره طلایی هستن تا برای هنرمندان واقعی که یاد بگیرد چطوری اون صحنه های که توی انیمه که فوق العاده زیبا و استلایز هست رو چطوری میسازن جواب همشو توی این کانال پیدا خواهید کرد 👆
در این آموزش به شما توضیح خواهم داد چطوری میتوانید از شکست نور واقعی در HDRP استفاده کنید ولی قبل از اینکه توضیح بدم جطوری از API استفادع کنید در داخل شیدر گراف این جدول را بخوبی دقت کنید : (آموزش تخصصی شیدر نویسی از FProSensei)
🔥1
در مسیر پکیج اگر به این :
"Packages/com.unity.render-pipelines.core/ShaderLibrary/Refraction.hlsl"


متوجه میشوید که در این مسیر شکست نور را محاسبه میکند خبر خوب اینکه این نه قفل مارک زد با Stencil دارد ونه اینکه حتما باید از Gbuffer استفاده بشه که توی Lit هست بلکه این توی بافر Forward+ هست پس براحتی میشه توی شیدر آب استلایزد استفاده بشه :
کد Custom Function برای شیدر گراف :


#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Refraction.hlsl"

void AniToonRefractionSS_float(
in float3 PositionWS, // Position node, فضای World
in float3 Normal, // نرمال موج‌دار -- همان که به SSR می‌دهی
in float3 ViewDir, // View Direction node, فضای World
in float4 ScreenPosition, // Screen Position, حالت Default
in float IOR, // ~1.333 برای آب
in float Thickness, // ضخامت بر حسب متر -- DepthFade * Depth Distance
in float Roughness, // 0 شفاف .. 1 تار
in float3 AbsorptionColor, // ضریب جذب به تفکیک کانال
in float AbsorptionStrength,
out float3 RefractedColor,
out float Weight)
{
#ifdef SHADERGRAPH_PREVIEW
RefractedColor = float3(0.0, 0.0, 0.0);
Weight = 0.0;
#else
float3 V = normalize(ViewDir);
float3 N = normalize(Normal);

float ior = max(IOR, 1.0001);
float thickness = max(Thickness, 0.0);

RefractionModelResult refraction =
RefractionModelBox(V, PositionWS, N, ior, thickness);

float2 hitNDC = ComputeNormalizedDeviceCoordinates(
refraction.positionWS, GetWorldToHClipMatrix());

float hitDeviceDepth = SampleCameraDepth(hitNDC);
float hitLinearDepth = LinearEyeDepth(hitDeviceDepth, _ZBufferParams);
float surfaceLinearDepth = LinearEyeDepth(PositionWS, GetWorldToViewMatrix());

float offsetMultiplier = (hitLinearDepth > surfaceLinearDepth) ? 1.0 : 0.0;
float2 samplingNDC = lerp(ScreenPosition.xy, hitNDC, offsetMultiplier);

float mipLevel = PositivePow(saturate(Roughness), 1.3)
* max((float)_ColorPyramidLodCount - 1.0, 0.0);

float3 color = SampleCameraColor(samplingNDC, mipLevel);
color *= GetInverseCurrentExposureMultiplier();

float3 absorption = exp(-max(AbsorptionColor, 0.0)
* max(AbsorptionStrength, 0.0)
* refraction.dist);
color *= absorption;

float2 coordCS = samplingNDC * 2.0 - 1.0;
float2 t = Remap10(abs(coordCS),
_SSRefractionInvScreenWeightDistance,
_SSRefractionInvScreenWeightDistance);
float edgeFade = Smoothstep01(t.x) * Smoothstep01(t.y);

RefractedColor = max(color, 0.0);
Weight = saturate(edgeFade);
#endif
}
FPro Sensei
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Refraction.hlsl"
خب توضیح خط به خط شروع میکنم بر خلاف GLSL ما در HLSL زبان شیدر نویسی مطرح C++ میتوانیم یک کتابخانه - Api یا هر چیزی که بشه ازش چیزی رو وارد کرد و از کلاس ها یا تابع هاش استفاده کرد رو به کار بگیریم متد #include همانند متد Import در پایتون هست که در مسیر :
 "Packages/com.unity.render-pipelines.core/ShaderLibrary/Refraction.hlsl" 


ماژول وارد کد نویسی ما میکند و ما حالا به کلاس و متد های داخلی ان درسترسی داریم ❗️
FPro Sensei
void AniToonRefractionSS_float(
متد void یک تابع برای ما تعریف میکند ریشه این متد از زبان C میاد که امروزه توی زبان های C++ - C# هم دیده میشه که استفاده از _float برای شیدر گراف مهمه یعنی شیدر گراف با این میشناسه که این قرار یک کاستوم نود باشع ❗️
🔥1
FPro Sensei
in float3 PositionWS, // Position node, فضای World
in float3 Normal, // نرمال موج‌دار -- همان که به SSR می‌دهی
in float3 ViewDir, // View Direction node, فضای World
in float4 ScreenPosition, // Screen Position, حالت Default
in float IOR, // ~1.333 برای آب
in float Thickness, // ضخامت بر حسب متر -- DepthFade * Depth Distance
in float Roughness, // 0 شفاف .. 1 تار
in float3 AbsorptionColor, // ضریب جذب به تفکیک کانال
in float AbsorptionStrength,
out float3 RefractedColor,
out float Weight)
این متغیر های ورودی هست که تعریف شده که اینظوری تعریف میشود :

A B C

A = از دو متد in و out میشه استفاده کرد

که اگر in باشد این متغیر ما ورودی هست یعنی داده میگیره و اگر out باشد باشد یعنی قراره خروجی بده

B = نوع داده را مشخص میکند برای مثال

float = عدد اعشاری که تک برداری هست

مثال = (1.3)

float2 = عدد اعشاری که بردار دو بعدی هست

مثال : (1.2658,2.565)

float3 = عدد اعشاری که بردار سه بعدی هست

برای مثال : (4.548,4.845.9)

و دیگر نوع داده : 2DUnityTexture و .....

C = اسم متغیر را تعریف میکنیم

برای مثال Normal

یک مثال کلی : in float3 PositionWS یعنی ورودی پوزیشین ابجکت را در فضای جهانی بگیر و out float3 RefractedColor یعنی خروجی شکست نور که در داخل شیدر محاسبه کردیم رو از نود بده بیرون
🔥1
FPro Sensei
#ifdef SHADERGRAPH_PREVIEW
این دو شرط #ifdef SHADERGRAPH_PREVIEW و #else تعریف شده بخاطر اینکه

میره ماکرو های تعریف شده رو بررسی میکنه اگر SHADERGRAPH_PREVIEW باشه داخل شرط اجرا میکنه نبود میره داخل else اجرا میکنه منظور از SHADERGRAPH_PREVIEW همون پیش نمایش شیدر هست
🔥1
FPro Sensei
RefractedColor = float3(0.0, 0.0, 0.0);
Weight = 0.0;
یک متعیر ساده تعریف میکنه که توضیح هم دادم چطوریه
🔥1
FPro Sensei
float3 V = normalize(ViewDir);
float3 N = normalize(Normal);
منظور از N نرمال سطح ابجکت و V همان زاویه دید دوربین هست که نرمالیز میکنه که مطمعن باشه خطا نباشه توی محاسبات
🔥1
FPro Sensei
float ior = max(IOR, 1.0001);
float thickness = max(Thickness, 0.0);
دو مقدار ior و thickness تعریف میکنه که با متد MAX میگه کمترین مقدار باید این باشه و زیر این نباید بره
🔥1
FPro Sensei
RefractionModelResult refraction =
RefractionModelBox(V, PositionWS, N, ior, thickness);
از همون تابع داخلی refraction استفاده میکنه و مقدایر (V, PositionWS, N, ior, thickness) میده به اون تا اون برام یک مدل RefractionModelBox برگشت بده و اما کار این تابع چیست :

بردار دید (V) را با نرمال سطح (N) و IOR شکست می‌دهد — refract(-V, N, 1/IOR) — یعنی زاویهٔ واقعی نوری که وارد آب می‌شود را حساب می‌کند.

فرض می‌کند سطح داخلی جسم (کف ظرف/کف آب) یک صفحهٔ موازی با سطح بالا و به فاصلهٔ thickness از آن است — یک «جعبه» ساده، نه انحنای واقعی.

پرتوی شکسته‌شده را با آن صفحهٔ داخلی تلاقی می‌دهد و نقطهٔ خروج (positionWS) و طول مسیر داخل ماده (dist) را برمی‌گرداند.
🔥1
FPro Sensei

float2 hitNDC = ComputeNormalizedDeviceCoordinates(
refraction.positionWS, GetWorldToHClipMatrix());
این خط نقطهٔ خروج پرتوی شکسته‌شده (که یک مختصات سه‌بعدی در فضای جهانی است) را به یک مختصات دوبعدی روی صفحهٔ نمایش (UV بین ۰ تا ۱) تبدیل می‌کند — یعنی «این نقطه از دنیای سه‌بعدی، دقیقاً کجای تصویر رندرشده قرار می‌گیرد؟» اما سوال این هست که چرا لازمه ؟

refraction.positionWS که از RefractionModelBox گرفتیم یک نقطه در فضای ۳بعدی جهان است (مثلاً «ته آب، ۲۰ سانتی‌متر پایین‌تر و کمی به یک طرف کج‌شده»). اما برای نمونه‌برداری از _ColorPyramidTexture (که یک تصویر ۲بعدی رندرشده از دوربین است) نیاز داریم بدانیم این نقطه در پیکسل‌بندی تصویر کجا می‌افتد. ❗️
FPro Sensei
float hitDeviceDepth = SampleCameraDepth(hitNDC);
float hitLinearDepth = LinearEyeDepth(hitDeviceDepth, _ZBufferParams);
float surfaceLinearDepth = LinearEyeDepth(PositionWS, GetWorldToViewMatrix());
خط اول: SampleCameraDepth(hitNDC)
در همان UV که از مرحلهٔ قبل (hitNDC) به‌دست آمد، به‌جای رنگ، مقدار عمق را از بافر عمق دوربین می‌خواند. خروجی‌اش یک عدد خام است (device depth، معمولاً بین ۰ و ۱، غیرخطی) — یعنی «چیزی که همین الان روی صفحه در این پیکسل رندر شده، چقدر از دوربین فاصله دارد؟»


خط دوم: LinearEyeDepth(hitDeviceDepth, _ZBufferParams)
عمق خام غیرخطی (device depth) را به یک عدد خطی به‌متر تبدیل می‌کند. بافر عمق در GPU معمولاً غیرخطی ذخیره می‌شود (دقت بیشتر نزدیک دوربین، کمتر دور) و برای مقایسهٔ مستقیم فاصله‌ها باید خطی‌اش کنیم. نتیجه: hitLinearDepth = «فاصلهٔ واقعی (متر) چیزی که در آن UV روی صفحه دیده می‌شود، از دوربین.»


خط سوم: LinearEyeDepth(PositionWS, GetWorldToViewMatrix())
این یکی overload متفاوتی از LinearEyeDepth است — به‌جای گرفتن device depth، مستقیم یک موقعیت world را می‌گیرد و با ماتریس world-to-view فاصلهٔ آن نقطه از دوربین را حساب می‌کند. اینجا PositionWS موقعیت خود سطح آب است (نه نقطهٔ شکسته‌شده). نتیجه: surfaceLinearDepth = «سطح آب خودش چقدر از دوربین فاصله دارد؟»
FPro Sensei
float offsetMultiplier = (hitLinearDepth > surfaceLinearDepth) ? 1.0 : 0.0;
float2 samplingNDC = lerp(ScreenPosition.xy, hitNDC, offsetMultiplier);
خط اول: offsetMultiplier
یک سوییچ صفر/یک می‌سازد:

اگر hitLinearDepth > surfaceLinearDepth (چیزی که پشت UV شکسته‌شده هست، از خود سطح آب دورتر است — یعنی واقعاً «پشت آب» است) → offsetMultiplier = 1.0
در غیر این صورت (چیزی که آنجاست جلوتر از آب است — یعنی یک جسم مثل پای کاراکتر جلوی آب ایستاده) → offsetMultiplier = 0.0

خط دوم: lerp(ScreenPosition.xy, hitNDC, offsetMultiplier)
بر اساس آن سوییچ، بین دو UV انتخاب می‌کند:

وقتی offsetMultiplier = 1.0 → نتیجه دقیقاً hitNDC می‌شود (همان UV شکسته‌شده که از RefractionModelBox + پروجکشن به‌دست آمد) — یعنی رفرکشن واقعی اعمال می‌شود.
وقتی offsetMultiplier = 0.0 → نتیجه دقیقاً ScreenPosition.xy می‌شود (UV خودِ پیکسل سطح آب، بدون هیچ آفستی) — یعنی رفرکشن خاموش می‌شود و همان‌جایی که هستیم را می‌خوانیم.

چون lerp با ضریب دقیقاً ۰ یا ۱ است (نه یک عدد میانی)، این عملاً یک if است نوشته‌شده به‌شکل بدون شاخه (branchless) — روی GPU این معمولاً سریع‌تر از if واقعی اجرا می‌شود.
🔥1