الجزء الأول من الترجمة
هنا . في هذا الجزء ، سنتحدث عن تأثير الحدة ومتوسط السطوع ومراحل القمر والظواهر الجوية أثناء المطر.
الجزء 6. شحذ
في هذا الجزء ، سوف نلقي نظرة فاحصة على تأثير آخر بعد المعالجة من The Witcher 3 - Sharpen.
شحذ يجعل الصورة الناتج أكثر وضوحا قليلا. هذا التأثير معروف لنا من خلال Photoshop ومحرري الرسوم الآخرين.
في The Witcher 3 ، فإن التوضيح لديه خياران: منخفض وعالي. سأتحدث عن الفرق بينهما أدناه ، ولكن الآن ، دعونا نلقي نظرة على لقطات الشاشة:
الخيار "منخفض" - ما يصل إلىالخيار "منخفض" - بعدخيار عالي - ما يصل إلىالخيار "عالي" - بعدإذا كنت تريد إلقاء نظرة على مقارنات (تفاعلية) أكثر تفصيلاً ، فراجع
القسم في دليل أداء Nvidia The Witcher 3 . كما ترون ، فإن التأثير ملحوظ بشكل خاص على العشب وأوراق الشجر.
في هذا الجزء من المنشور ، سوف ندرس الإطار منذ بداية اللعبة: لقد اخترت ذلك عن قصد ، لأننا هنا نرى الإغاثة (مسافة الرسم الطويلة) وقبة السماء.
فيما يتعلق بالإدخال ، يتطلب التوضيح وجود مخزن مؤقت للون
t0 (LDR بعد تصحيح النغمات وتوهج العدسات)
وتخزين مؤقت للعمق
t1 .
دعنا نفحص رمز المجمّع لتظليل البكسل:
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb3[3], immediateIndexed
dcl_constantbuffer cb12[23], immediateIndexed
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_input_ps_siv v0.xy, position
dcl_output o0.xyzw
dcl_temps 7
0: ftoi r0.xy, v0.xyxx
1: mov r0.zw, l(0, 0, 0, 0)
2: ld_indexable(texture2d)(float,float,float,float) r0.x, r0.xyzw, t1.xyzw
3: mad r0.x, r0.x, cb12[22].x, cb12[22].y
4: mad r0.y, r0.x, cb12[21].x, cb12[21].y
5: max r0.y, r0.y, l(0.000100)
6: div r0.y, l(1.000000, 1.000000, 1.000000, 1.000000), r0.y
7: mad_sat r0.y, r0.y, cb3[1].z, cb3[1].w
8: add r0.z, -cb3[1].x, cb3[1].y
9: mad r0.y, r0.y, r0.z, cb3[1].x
10: add r0.y, r0.y, l(1.000000)
11: ge r0.x, r0.x, l(1.000000)
12: movc r0.x, r0.x, l(0), l(1.000000)
13: mul r0.z, r0.x, r0.y
14: round_z r1.xy, v0.xyxx
15: add r1.xy, r1.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
16: div r1.xy, r1.xyxx, cb3[0].zwzz
17: sample_l(texture2d)(float,float,float,float) r2.xyz, r1.xyxx, t0.xyzw, s0, l(0)
18: lt r0.z, l(0), r0.z
19: if_nz r0.z
20: div r3.xy, l(0.500000, 0.500000, 0.000000, 0.000000), cb3[0].zwzz
21: add r0.zw, r1.xxxy, -r3.xxxy
22: sample_l(texture2d)(float,float,float,float) r4.xyz, r0.zwzz, t0.xyzw, s0, l(0)
23: mov r3.zw, -r3.xxxy
24: add r5.xyzw, r1.xyxy, r3.zyxw
25: sample_l(texture2d)(float,float,float,float) r6.xyz, r5.xyxx, t0.xyzw, s0, l(0)
26: add r4.xyz, r4.xyzx, r6.xyzx
27: sample_l(texture2d)(float,float,float,float) r5.xyz, r5.zwzz, t0.xyzw, s0, l(0)
28: add r4.xyz, r4.xyzx, r5.xyzx
29: add r0.zw, r1.xxxy, r3.xxxy
30: sample_l(texture2d)(float,float,float,float) r1.xyz, r0.zwzz, t0.xyzw, s0, l(0)
31: add r1.xyz, r1.xyzx, r4.xyzx
32: mul r3.xyz, r1.xyzx, l(0.250000, 0.250000, 0.250000, 0.000000)
33: mad r1.xyz, -r1.xyzx, l(0.250000, 0.250000, 0.250000, 0.000000), r2.xyzx
34: max r0.z, abs(r1.z), abs(r1.y)
35: max r0.z, r0.z, abs(r1.x)
36: mad_sat r0.z, r0.z, cb3[2].x, cb3[2].y
37: mad r0.x, r0.y, r0.x, l(-1.000000)
38: mad r0.x, r0.z, r0.x, l(1.000000)
39: dp3 r0.y, l(0.212600, 0.715200, 0.072200, 0.000000), r2.xyzx
40: dp3 r0.z, l(0.212600, 0.715200, 0.072200, 0.000000), r3.xyzx
41: max r0.w, r0.y, l(0.000100)
42: div r1.xyz, r2.xyzx, r0.wwww
43: add r0.y, -r0.z, r0.y
44: mad r0.x, r0.x, r0.y, r0.z
45: max r0.x, r0.x, l(0)
46: mul r2.xyz, r0.xxxx, r1.xyzx
47: endif
48: mov o0.xyz, r2.xyzx
49: mov o0.w, l(1.000000)
50: ret
50 سطر من رمز المجمع تبدو وكأنها مهمة ممكنة. دعنا ننكب على حلها.
شحذ توليد القيمة
الخطوة الأولى هي تحميل المخزن المؤقت للعمق (السطر 1). تجدر الإشارة إلى أن "The Witcher 3" يستخدم عمق مقلوب (1.0 - إغلاق ، 0.0 - بعيد). كما تعلم ، فإن عمق الجهاز مرتبط بشكل غير خطي (انظر
هذه المقالة للحصول على التفاصيل ).
توفر الخطوط 3-6 طريقة مثيرة للاهتمام للغاية لربط عمق الأجهزة هذا [1.0 - 0.0] بقيم [عن بُعد] (وضعناها في مرحلة MatrixPerspectiveFov). خذ بعين الاعتبار القيم من المخزن المؤقت ثابت:
بعد أن بلغت قيمة "الإغلاق" 0.2 ، والقيمة "far" 5000 ، يمكننا حساب قيم cb12_v21.xy كما يلي:
cb12_v21.y = 1.0 / near
cb12_v21.x = - (1.0 / near) + (1.0 / near) * (near / far)
هذه الشفرة شائعة جدًا في تظليل TW3 ، لذلك أعتقد أنها مجرد وظيفة.
بعد الحصول على "عمق هرم الرؤية" ، يستخدم الخط 7 المقياس / التشويه لإنشاء معامل الاستيفاء (هنا نستخدم
التشبع لتقصر القيم على الفاصل الزمني [0-1]).
cb3_v1.xy و cb3_v2.xy - هذا هو سطوع تأثير التوضيح على مسافات قصيرة وطويلة. دعنا نسميها شحادةقرب وشحذ. وهذا هو الفرق الوحيد بين الخيارات "المنخفضة" و "عالية" لهذا التأثير في The Witcher 3.
الآن حان الوقت لاستخدام النسبة الناتجة. لا تقوم الخطوط من 8 إلى 9 بفعل
lerp(sharpenNear, sharpenFar, interpolationCoeff)
. ما هذا؟ بفضل هذا ، نحصل على سطوع مختلف بالقرب من جيرالت وبعيدًا عنه. ألقِ نظرة:
ربما يكون هذا ملحوظًا بالكاد ، لكن هنا قمنا بتحريفها استنادًا إلى المسافة التي يكون فيها سطوع التوضيح بجانب اللاعب (2.177151) ودرجة سطوع التأثير بعيدة جدًا (1.91303). بعد هذا الحساب ، نضيف 1.0 إلى السطوع (السطر 10). لماذا هذا مطلوب؟ لنفترض أن العملية lerp الموضحة أعلاه أعطتنا 0.0. بعد إضافة 1.0 ، نحصل على 1.0 بشكل طبيعي ، وهذه قيمة لن تؤثر على البيكسل عند القيام بالوضوح. قراءة المزيد عن هذا أدناه.
أثناء التوضيح ، لا نريد التأثير على السماء. يمكن تحقيق ذلك عن طريق إضافة فحص مشروط بسيط:
// sharpen
float fSkyboxTest = (fDepth >= 1.0) ? 0 : 1;
في The Witcher 3 ، تبلغ قيمة عمق البكسل في السماء 1.0 ، لذلك نستخدمها للحصول على نوع من "المرشح الثنائي" (حقيقة مثيرة: في هذه الحالة ، لن تعمل
الخطوة بشكل صحيح).
الآن يمكننا مضاعفة السطوع المحرف بواسطة "مرشح السماء":
يتم تنفيذ هذا الضرب على السطر 13.
مثال على رمز Shader:
// sharpen
float fSharpenAmount = fSharpenIntensity * fSkyboxTest;
مركز أخذ العينات بكسل
يحتوي
SV_Position على مظهر مهم هنا:
إزاحة نصف بكسل . اتضح أن هذا بكسل في الزاوية اليسرى العليا (0 ، 0) لديه إحداثيات ليس (0 ، 0) من حيث SV_Position.xy ، ولكن (0.5 ، 0.5). واو!
هنا نريد أن نأخذ عينة في وسط البكسل ، لذلك دعونا نلقي نظرة على الأسطر 14-16. يمكنك كتابتها في HLSL:
// .
// "" SV_Position.xy.
float2 uvCenter = trunc( Input.Position.xy );
// ,
uvCenter += float2(0.5, 0.5);
uvCenter /= g_Viewport.xy
ولاحقًا ، نأخذ عينات من نسيج الألوان المدخلة من texcoords "uvCenter". لا تقلق ، ستكون نتيجة أخذ العينات هي نفسها كما في الطريقة "العادية" (SV_Position.xy / ViewportSize.xy).
لشحذ أو لا لشحذ
يعتمد القرار بشأن استخدام شحذ على fSharpenAmount.
//
float3 colorCenter = TexColorBuffer.SampleLevel( samplerLinearClamp, uvCenter, 0 ).rgb;
//
float3 finalColor = colorCenter;
if ( fSharpenAmount > 0 )
{
// sharpening...
}
return float4( finalColor, 1 );
شحذ
لقد حان الوقت لإلقاء نظرة على الدوافع الخوارزمية نفسها.
بشكل أساسي ، ينفذ الإجراءات التالية:
- عينات أربعة أضعاف نسيج لون الإدخال في زوايا البيكسل ،
- يضيف عينات ويحسب متوسط القيمة ،
- يحسب الفرق بين "الوسط" و "الزاوية متوسط" ،
- يجد الحد الأقصى للمكون المطلق للفرق ،
- يصحح كحد أقصى. القيمة المطلقة مكون باستخدام قيم مقياس + التحيز ،
- يحدد حجم التأثير باستخدام الحد الأقصى. القيمة المطلقة عنصر
- يحسب قيمة السطوع (luma) لـ "centerColor" و "averageColor" ،
- يقسم colorCenter إلى لوما ،
- يحسب قيمة لوما جديدة محرفة بناءً على حجم التأثير ،
- يضاعف colorCenter بواسطة قيمة luma الجديدة.
الكثير من العمل ، وكان من الصعب علي معرفة ذلك ، لأنني لم أجرب مطلقًا مرشحات شحذ.
لنبدأ بنمط أخذ العينات. كما ترون في رمز المجمع ، يتم تنفيذ أربع قراءات الملمس.
سيتم عرض ذلك بأفضل صورة باستخدام مثال لصورة البكسل (مستوى مهارة الفنان
خبير ):
تستخدم جميع القراءات الموجودة في التظليل أخذ عينات ثنائية الخط (D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT).
الإزاحة من المركز إلى كل من الزوايا هي (± 0.5 ، ± 0.5) ، اعتمادًا على الزاوية.
انظر كيف يمكن تنفيذ ذلك على HLSL؟ لنرى:
float2 uvCorner;
float2 uvOffset = float2( 0.5, 0.5 ) / g_Viewport.xy; // remember about division!
float3 colorCorners = 0;
//
// -0,5, -0.5
uvCorner = uvCenter - uvOffset;
colorCorners += TexColorBuffer.SampleLevel( samplerLinearClamp, uvCorner, 0 ).rgb;
//
// +0.5, -0.5
uvCorner = uvCenter + float2(uvOffset.x, -uvOffset.y);
colorCorners += TexColorBuffer.SampleLevel( samplerLinearClamp, uvCorner, 0 ).rgb;
//
// -0.5, +0.5
uvCorner = uvCenter + float2(-uvOffset.x, uvOffset.y);
colorCorners += TexColorBuffer.SampleLevel( samplerLinearClamp, uvCorner, 0 ).rgb;
//
// +0.5, +0.5
uvCorner = uvCenter + uvOffset;
colorCorners += TexColorBuffer.SampleLevel( samplerLinearClamp, uvCorner, 0 ).rgb;
لذلك ، يتم الآن تلخيص جميع العينات الأربعة في متغير "colorCorners". دعنا نتبع هذه الخطوات:
//
float3 averageColorCorners = colorCorners / 4.0;
//
float3 diffColor = colorCenter - averageColorCorners;
// . . RGB-
float fDiffColorMaxComponent = max( abs(diffColor.x), max( abs(diffColor.y), abs(diffColor.z) ) );
//
float fDiffColorMaxComponentScaled = saturate( fDiffColorMaxComponent * sharpenLumScale + sharpenLumBias );
// .
// "1.0" - fSharpenIntensity 1.0.
float fPixelSharpenAmount = lerp(1.0, fSharpenAmount, fDiffColorMaxComponentScaled);
// "" .
float lumaCenter = dot( LUMINANCE_RGB, finalColor );
float lumaCornersAverage = dot( LUMINANCE_RGB, averageColorCorners );
// "centerColor"
float3 fColorBalanced = colorCenter / max( lumaCenter, 1e-4 );
//
float fPixelLuminance = lerp(lumaCornersAverage, lumaCenter, fPixelSharpenAmount);
//
finalColor = fColorBalanced * max(fPixelLuminance, 0.0);
}
return float4(finalColor, 1.0);
يتم تنفيذ التعرف على الحافة عن طريق حساب الحد الأقصى. القيمة المطلقة عنصر الفرق. خطوة ذكية! تحقق من التصور:
تصور الحد الأقصى للمكون المطلق للفرق.عظيم تظليل HLSL النهائي متوفر
هنا . آسف للتنسيق الفقراء جدا. يمكنك استخدام برنامج
HLSLexplorer الخاص بي وتجربة الرمز.
أستطيع أن أقول لحسن الحظ أن الكود أعلاه ينشئ نفس كود التجميع كما في اللعبة!
لتلخيص: مكتوب بشكل جيد للغاية تظليل Witcher 3 (لاحظ أن fPixelSharpenAmount أكبر من 1.0! هذا مثير للاهتمام ...). بالإضافة إلى ذلك ، فإن الطريقة الرئيسية لتغيير سطوع التأثير هي سطوع الكائنات القريبة / البعيدة. في هذه اللعبة ، ليسوا ثوابت ؛ لقد جمعت عدة أمثلة للقيم:
Skellige:
| شحذقرب | شحذ | شحذالمقاومة | شحذالمقاومة | sharpenLumScale | sharpenLumBias |
---|
منخفضة |
عالية | 2.0 | 1.8 | 0.025
| -0.25
| -13.33333
| 1.33333 |
كاير مورهان:
| شحذقرب
| شحذ
| شحذالمقاومة
| شحذالمقاومة
| sharpenLumScale
| sharpenLumBias
|
---|
منخفضة
| 0.57751
| 0.31303
| 0.06665
| -0.33256
| -1.0
| 2.0
|
عالية
| 2.17751
| 1.91303
| 0.06665
| -0.33256
| -1.0
| 2.0 |
الجزء 7. متوسط السطوع
يمكن العثور على عملية حساب متوسط سطوع الإطار الحالي في أي لعبة فيديو حديثة تقريبًا. غالبًا ما تستخدم هذه القيمة لاحقًا لتأثير تكيف العين وتصحيح النغمة (انظر في
الجزء السابق من المنشور). في الحلول البسيطة ، يستخدم حساب السطوع ، على سبيل المثال ، الملمس 512
2 ، ثم حساب مستويات mip وتطبيقه. هذا يعمل عادة ، ولكن يحد كثيرا من الاحتمالات. تستخدم حلول أكثر تعقيدًا تظليل حسابي يؤدي ، على سبيل المثال ،
الاختزال المتوازي .
دعنا نتعرف على كيفية قيام فريق CD Projekt Red بحل هذه المشكلة في The Witcher 3. في الجزء السابق ، درست بالفعل تصحيح النغمة وتكيفها ، وبالتالي فإن الجزء الوحيد المتبقي من اللغز هو متوسط السطوع.
بادئ ذي بدء ، يتكون حساب السطوع المتوسط لـ The Witcher 3 من تمريرين. من أجل الوضوح ، قررت تقسيمها إلى أجزاء منفصلة ، وننظر أولاً في المقطع الأول - "توزيع السطوع" (حساب الرسم البياني للسطوع).
توزيع السطوع
من السهل العثور على هذين التمريرين في أي محلل إطار. هذه هي المكالمات
Dispatch بالترتيب الصحيح قبل إجراء التكيف العين:
دعونا ننظر إلى المدخلات لهذا المرور. انه يحتاج الى اثنين من القوام:
1) عازلة ألوان HDR ، يتم تقليل مقياسها إلى 1/4 × 1/4 (على سبيل المثال ، من 1920 × 1080 إلى 480 × 270) ،
2) عمق ملء الشاشة العازلة
1/4 × 1/4 HDR لون عازلة. لاحظ الخدعة صعبة - هذا المخزن المؤقت هو جزء من مخزن مؤقت أكبر. إعادة استخدام المخازن المؤقتة هي ممارسة جيدة.ملء الشاشة عمق العازلةلماذا التصغير العازلة اللون؟ أعتقد أن الأمر كله يتعلق بالأداء.
أما بالنسبة لإخراج هذا المرور ، فهو مخزن مؤقت منظم. 256 عنصر من 4 بايت لكل منهما.
ليس لدى Shaders معلومات تصحيح الأخطاء هنا ، لذلك لنفترض أنها مجرد مخزن مؤقت لقيم int غير الموقعة.
هام: تستدعي الخطوة الأولى في حساب متوسط السطوع
ClearUnorderedAccessViewUint لإعادة تعيين كافة عناصر المخزن المؤقت المنظم إلى صفر.
دعنا ندرس كود المجمّع للتظليل الحسابي (هذا هو أول تظليل حسابي في تحليلنا بأكمله!)
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb0[3], immediateIndexed
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_uav_structured u0, 4
dcl_input vThreadGroupID.x
dcl_input vThreadIDInGroup.x
dcl_temps 6
dcl_tgsm_structured g0, 4, 256
dcl_thread_group 64, 1, 1
0: store_structured g0.x, vThreadIDInGroup.x, l(0), l(0)
1: iadd r0.xyz, vThreadIDInGroup.xxxx, l(64, 128, 192, 0)
2: store_structured g0.x, r0.x, l(0), l(0)
3: store_structured g0.x, r0.y, l(0), l(0)
4: store_structured g0.x, r0.z, l(0), l(0)
5: sync_g_t
6: ftoi r1.x, cb0[2].z
7: mov r2.y, vThreadGroupID.x
8: mov r2.zw, l(0, 0, 0, 0)
9: mov r3.zw, l(0, 0, 0, 0)
10: mov r4.yw, l(0, 0, 0, 0)
11: mov r1.y, l(0)
12: loop
13: utof r1.z, r1.y
14: ge r1.z, r1.z, cb0[0].x
15: breakc_nz r1.z
16: iadd r2.x, r1.y, vThreadIDInGroup.x
17: utof r1.z, r2.x
18: lt r1.z, r1.z, cb0[0].x
19: if_nz r1.z
20: ld_indexable(texture2d)(float,float,float,float) r5.xyz, r2.xyzw, t0.xyzw
21: dp3 r1.z, r5.xyzx, l(0.212600, 0.715200, 0.072200, 0.000000)
22: imul null, r3.xy, r1.xxxx, r2.xyxx
23: ld_indexable(texture2d)(float,float,float,float) r1.w, r3.xyzw, t1.yzwx
24: eq r1.w, r1.w, cb0[2].w
25: and r1.w, r1.w, cb0[2].y
26: add r2.x, -r1.z, cb0[2].x
27: mad r1.z, r1.w, r2.x, r1.z
28: add r1.z, r1.z, l(1.000000)
29: log r1.z, r1.z
30: mul r1.z, r1.z, l(88.722839)
31: ftou r1.z, r1.z
32: umin r4.x, r1.z, l(255)
33: atomic_iadd g0, r4.xyxx, l(1)
34: endif
35: iadd r1.y, r1.y, l(64)
36: endloop
37: sync_g_t
38: ld_structured r1.x, vThreadIDInGroup.x, l(0), g0.xxxx
39: mov r4.z, vThreadIDInGroup.x
40: atomic_iadd u0, r4.zwzz, r1.x
41: ld_structured r1.x, r0.x, l(0), g0.xxxx
42: mov r0.w, l(0)
43: atomic_iadd u0, r0.xwxx, r1.x
44: ld_structured r0.x, r0.y, l(0), g0.xxxx
45: atomic_iadd u0, r0.ywyy, r0.x
46: ld_structured r0.x, r0.z, l(0), g0.xxxx
47: atomic_iadd u0, r0.zwzz, r0.x
48: ret
ومخزن مؤقت ثابت:
نحن نعلم بالفعل أن الإدخال الأول هو مخزن مؤقت للون HDR. مع جهاز FullHD ، تبلغ دقة الشاشة 480 × 270. دعونا نلقي نظرة على دعوة الإرسال.
إرسال (270 ، 1 ، 1) - وهذا يعني أننا ندير 270 مجموعة من سلاسل الرسائل. ببساطة ، نحن ندير مجموعة واحدة من الخيوط في كل سطر من لون المخزن المؤقت.
كل مجموعة مؤشر ترابط ينفذ سطر واحد من HDR لون المخزن المؤقتالآن بعد أن أصبح لدينا هذا السياق ، دعونا نحاول معرفة ما يفعله التظليل.
تحتوي كل مجموعة مؤشرات ترابط على 64 مؤشر ترابط في الاتجاه X (dcl_thread_group 64 ، 1 ، 1) ، بالإضافة إلى الذاكرة المشتركة ، 256 عنصرًا مع 4 بايت لكل (dcl_tgsm_structured g0 ، 4 ، 256).
لاحظ أننا في التظليل نستخدم
SV_GroupThreadID (vThreadIDInGroup.x) [0-63] و
SV_GroupID (vThreadGroupID.x) [0-269].
1) نبدأ بتعيين جميع قيم القيم المشتركة للذاكرة المشتركة. نظرًا لأن إجمالي الذاكرة يحتوي على 256 عنصرًا و 64 مؤشر ترابط لكل مجموعة ، يمكن القيام بذلك بسهولة من خلال حلقة بسيطة:
// - .
// 64 , 4 .
[unroll] for (uint idx=0; idx < 4; idx++)
{
const uint offset = threadID + idx*64;
shared_data[ offset ] = 0;
}
2) بعد ذلك ، قمنا بتعيين الحاجز باستخدام
GroupMemoryBarrierWithGroupSync (sync_g_t). نقوم بهذا لضمان إعادة تعيين جميع سلاسل العمليات في الذاكرة المشتركة للمجموعات إلى الصفر قبل المتابعة إلى الخطوة التالية.
3) ننفذ الآن حلقة ، والتي يمكن كتابتها تقريبًا مثل هذا:
// cb0_v0.x - . 1920x1080 1920/4 = 480;
float ViewportSizeX = cb0_v0.x;
[loop] for ( uint PositionX = 0; PositionX < ViewportSizeX; PositionX += 64 )
{
...
هذا هو بسيط للحلقة مع زيادة قدرها 64 (هل فهمت بالفعل لماذا؟).
الخطوة التالية هي حساب موضع البكسل المحمّل.
دعونا نفكر في ذلك.
بالنسبة إلى إحداثي Y ، يمكننا استخدام SV_GroupID.x لأننا أطلقنا 270 مجموعة سلاسل رسائل.
للتنسيق X ، يمكننا ... الاستفادة من تدفق المجموعة الحالي! دعنا نحاول القيام بذلك.
نظرًا لوجود 64 مؤشر ترابط في كل مجموعة ، سيتجاوز هذا الحل جميع وحدات البكسل.
النظر في مجموعة الخيط (0 ، 0 ، 0).
- سيعالج الدفق (0 ، 0 ، 0) وحدات البكسل (0 ، 0) ، (64 ، 0) ، (128 ، 0) ، (192 ، 0) ، (256 ، 0) ، (320 ، 0) ، (384 ، 0) ، (448،0).
- يعالج مؤشر الترابط (1 ، 0 ، 0) وحدات البكسل (1 ، 0) ، (65 ، 0) ، (129 ، 0) ، (193 ، 0) ، (257 ، 0) ، (321 ، 0) ، (385 ، 0) ، (449 ، 0) ...
- سيعالج الدفق (63 ، 0 ، 0) البيكسلات (63 ، 0) ، (127 ، 0) ، (191 ، 0) ، (255 ، 0) ، (319 ، 0) ، (383 ، 0) ، (447 ، 0)
وبالتالي ، سيتم معالجة جميع بكسل.
نحتاج أيضًا إلى التأكد من عدم تحميل البكسل من خارج مخزن الألوان المؤقت:
// X. Y GroupID.
uint CurrentPixelPositionX = PositionX + threadID;
uint CurrentPixelPositionY = groupID;
if ( CurrentPixelPositionX < ViewportSizeX )
{
// HDR- .
// HDR- , .
uint2 colorPos = uint2(CurrentPixelPositionX, CurrentPixelPositionY);
float3 color = texture0.Load( int3(colorPos, 0) ).rgb;
float luma = dot(color, LUMA_RGB);
انظر؟ انها بسيطة جدا!
أنا أيضا حساب السطوع (السطر 21 من رمز المجمع).
رائع ، لقد قمنا بالفعل بحساب السطوع من بكسل اللون. والخطوة التالية هي تحميل (وليس عينة!) قيمة عمق المقابلة.
ولكن هنا لدينا مشكلة ، لأننا ربطنا عازلة أعماق حل كامل. ماذا تفعل حيال ذلك؟
هذا بسيط بشكل مدهش - فقط قم بضرب colorPos بواسطة بعض الثابت (cb0_v2.z). قمنا بتخفيض المخزن المؤقت للون HDR أربع مرات. وبالتالي فإن القيمة 4!
const int iDepthTextureScale = (int) cb0_v2.z;
uint2 depthPos = iDepthTextureScale * colorPos;
float depth = texture1.Load( int3(depthPos, 0) ).x;
حتى الآن جيد جدا! ولكن ... وصلنا إلى خطوط 24-25 ...
24: eq r2.x, r2.x, cb0[2].w
25: and r2.x, r2.x, cb0[2].y
اذن أولاً ، لدينا
مقارنة بالمساواة بين النقاط العائمة ، والنتيجة مكتوبة بـ r2.x ، وبعد ذلك مباشرة ... ماذا؟ Bitwise
و ؟؟ حقا؟ للحصول على قيمة النقطة العائمة؟ ماذا بحق الجحيم ؟؟
مشكلة 'eq + و'اسمحوا لي أن أقول أنه بالنسبة لي كان الجزء الأصعب من التظليل. حاولت حتى مجموعات asint / asfloat غريبة ...
وإذا كنت تستخدم نهج مختلف قليلا؟ لنقم فقط بإجراء المقارنة المعتادة في نظام HLSL.
float DummyPS() : SV_Target0
{
float test = (cb0_v0.x == cb0_v0.y);
return test;
}
وهنا هو الإخراج في رمز المجمع:
0: eq r0.x, cb0[0].y, cb0[0].x
1: and o0.x, r0.x, l(0x3f800000)
2: ret
مثيرة للاهتمام ، أليس كذلك؟ لم أكن أتوقع أن أرى "و" هنا.
0x3f800000 هي 1.0f فقط ... هذا منطقي لأننا نحصل على 1.0 و 0.0 وإلا إذا نجحت المقارنة.
ولكن ماذا لو "استبدلنا" 1.0 بقيمة أخرى؟ على سبيل المثال ، مثل هذا:
float DummyPS() : SV_Target0
{
float test = (cb0_v0.x == cb0_v0.y) ? cb0_v0.z : 0.0;
return test;
}
نحصل على النتيجة التالية:
0: eq r0.x, cb0[0].y, cb0[0].x
1: and o0.x, r0.x, cb0[0].z
2: ret
ها! لقد نجحت. هذا هو مجرد سحر المترجم HLSL. ملاحظة: إذا استبدلت 0.0 بشيء آخر ، فستحصل على movc.
دعنا نعود إلى التظليل الحسابي. الخطوة التالية هي التحقق من أن العمق يساوي cb0_v2.w. يساوي دائمًا 0.0 - وبعبارة أخرى ، نتحقق مما إذا كان البيكسل في طائرة بعيدة (في السماء). إذا كان الأمر كذلك ، فنحن نعيّن هذا المعامل بعض القيمة ، حوالي 0.5 (لقد راجعت على عدة إطارات).
يتم استخدام هذا المعامل المحسوب للتحريف بين سطوع اللون وسطوع "السماء" (قيمة cb0_v2.x ، والتي غالباً ما تساوي 0.0). أفترض أن هذا ضروري للتحكم في أهمية السماء في حساب متوسط السطوع. عادة ما يتم تقليل الأهمية. فكرة ذكية جدا.
// , ( ). , ,
// .
float value = (depth == cb0_v2.w) ? cb0_v2.y : 0.0;
// 'value' 0.0, lerp 'luma'. 'value'
// ( 0.50), luma . (cb0_v2.x 0.0).
float lumaOk = lerp( luma, cb0_v2.x, value );
نظرًا لأن لدينا lumaOk ، فإن الخطوة التالية هي حساب اللوغاريتم الطبيعي لإنشاء توزيع جيد. ولكن مهلا ، دعنا نقول lumaOk هو 0.0. نحن نعلم أن قيمة السجل (0) غير محددة ، لذلك نضيف 1.0 لأن log (1) = 0.0.
بعد ذلك ، نقيس اللوغاريتم المحسوب على 128 لتوزيعه في 256 خلية. ذكي جدا!
ومن هنا يتم أخذ هذه القيمة 88.722839. هذا هو
128 * (2)
.
هذه مجرد طريقة لحساب HLSL اللوغاريتمات.
هناك وظيفة واحدة فقط في تعليمة برمجية HLSL تقوم بحساب اللوغاريتمات:
log ، ولها قاعدة 2.
// , lumaOk 0.0.
// log(0) undefined
// log(1) = 0.
//
lumaOk = log(lumaOk + 1.0);
// 128
lumaOk *= 128;
أخيرًا ، نقوم بحساب فهرس الخلية من السطوع الموزع لوغاريتميًا ونضيف 1 إلى الخلية المقابلة في الذاكرة المشتركة.
// . Uint, 256 ,
// , .
uint uLuma = (uint) lumaOk;
uLuma = min(uLuma, 255);
// 1 .
InterlockedAdd( shared_data[uLuma], 1 );
ستكون الخطوة التالية مرة أخرى تعيين حاجز لضمان معالجة جميع وحدات البكسل في الصف.
والخطوة الأخيرة هي إضافة قيم من الذاكرة المشتركة إلى المخزن المؤقت المهيكل. يتم ذلك بنفس الطريقة ، من خلال حلقة بسيطة:
// ,
GroupMemoryBarrierWithGroupSync();
// .
[unroll] for (uint idx = 0; idx < 4; idx++)
{
const uint offset = threadID + idx*64;
uint data = shared_data[offset];
InterlockedAdd( g_buffer[offset], data );
}
بعد ملء جميع مؤشرات الترابط 64 في مجموعة مؤشر الترابط البيانات الشائعة ، يضيف كل مؤشر ترابط 4 قيم إلى المخزن المؤقت الإخراج.
النظر في المخزن المؤقت الإخراج. دعونا نفكر في ذلك. مجموع جميع القيم في المخزن المؤقت يساوي إجمالي عدد البكسل! (في 480 × 270 = 129،600). أي أننا نعرف عدد البكسلات التي لها قيمة سطوع محددة.
إذا كنت ضليعًا في التظليل الحسابي (مثلي) ، فقد لا يكون ذلك واضحًا في البداية ، لذلك اقرأ المنشور عدة مرات ، وأخذ الورق وقلم رصاص ، وحاول فهم المفاهيم التي بنيت عليها هذه التقنية.
هذا كل شئ! هذه هي الطريقة التي يحسب Witcher 3 رسم بياني للسطوع. شخصيا ، لقد تعلمت الكثير عند كتابة هذا الجزء. مبروك للاعبين من CD Projekt Red على عملهم الممتاز!
إذا كنت مهتمًا بتظليل HLSL كامل ، فهو متوفر
هنا . أسعى دائمًا للحصول على رمز التجميع في أقرب وقت ممكن من اللعبة وأنا سعيد تمامًا لأنني نجحت مرة أخرى!
حساب متوسط السطوع
هذا هو الجزء الثاني من تحليل حسابات السطوع المتوسط في "The Witcher 3: Wild Hunt".
قبل الدخول في معركة مع تظليل حسابي آخر ، دعنا نكرر بإيجاز ما حدث في الجزء الأخير: لقد عملنا مع مخزن مؤقت للون HDR مع مقياس يصل إلى 1 / 4x1 / 4. بعد التمريرة الأولى ، حصلنا على رسم بياني سطوع (مخزن مؤقت منظم يحتوي على 256 قيمة عدد صحيح غير موقعة). حسبنا لوغاريتم سطوع كل بكسل ، وزعناه على 256 خلية وزادنا القيمة المقابلة للمخزن المؤقت المهيكل بمقدار 1 لكل بكسل. لهذا السبب ، يكون إجمالي كل القيم في هذه الخلايا 256 مساوياً لعدد البكسل.
مثال على إخراج التمريرة الأولى. هناك 256 عناصر.على سبيل المثال ، يبلغ حجم المخزن المؤقت ملء الشاشة 1920 × 1080. بعد التصغير ، استخدم التمرير الأول مخزنًا مؤقتًا 480 × 270. مجموع قيم 256 في المخزن المؤقت سيكون مساوي 480 * 270 = 129 600.
بعد هذه المقدمة الموجزة ، نحن على استعداد للانتقال إلى الخطوة التالية: إلى الحوسبة.
هذه المرة يتم استخدام مجموعة مؤشر ترابط واحد فقط (إرسال (1 ، 1 ، 1)).
دعونا نلقي نظرة على رمز المجمع للتظليل الحسابي:
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb0[1], immediateIndexed
dcl_uav_structured u0, 4
dcl_uav_typed_texture2d (float,float,float,float) u1
dcl_input vThreadIDInGroup.x
dcl_temps 4
dcl_tgsm_structured g0, 4, 256
dcl_thread_group 64, 1, 1
0: ld_structured_indexable(structured_buffer, stride=4)(mixed,mixed,mixed,mixed) r0.x, vThreadIDInGroup.x, l(0), u0.xxxx
1: store_structured g0.x, vThreadIDInGroup.x, l(0), r0.x
2: iadd r0.xyz, vThreadIDInGroup.xxxx, l(64, 128, 192, 0)
3: ld_structured_indexable(structured_buffer, stride=4)(mixed,mixed,mixed,mixed) r0.w, r0.x, l(0), u0.xxxx
4: store_structured g0.x, r0.x, l(0), r0.w
5: ld_structured_indexable(structured_buffer, stride=4)(mixed,mixed,mixed,mixed) r0.x, r0.y, l(0), u0.xxxx
6: store_structured g0.x, r0.y, l(0), r0.x
7: ld_structured_indexable(structured_buffer, stride=4)(mixed,mixed,mixed,mixed) r0.x, r0.z, l(0), u0.xxxx
8: store_structured g0.x, r0.z, l(0), r0.x
9: sync_g_t
10: if_z vThreadIDInGroup.x
11: mul r0.x, cb0[0].y, cb0[0].x
12: ftou r0.x, r0.x
13: utof r0.y, r0.x
14: mul r0.yz, r0.yyyy, cb0[0].zzwz
15: ftoi r0.yz, r0.yyzy
16: iadd r0.x, r0.x, l(-1)
17: imax r0.y, r0.y, l(0)
18: imin r0.y, r0.x, r0.y
19: imax r0.z, r0.y, r0.z
20: imin r0.x, r0.x, r0.z
21: mov r1.z, l(-1)
22: mov r2.xyz, l(0, 0, 0, 0)
23: loop
24: breakc_nz r2.x
25: ld_structured r0.z, r2.z, l(0), g0.xxxx
26: iadd r3.x, r0.z, r2.y
27: ilt r0.z, r0.y, r3.x
28: iadd r3.y, r2.z, l(1)
29: mov r1.xy, r2.yzyy
30: mov r3.z, r2.x
31: movc r2.xyz, r0.zzzz, r1.zxyz, r3.zxyz
32: endloop
33: mov r0.w, l(-1)
34: mov r1.yz, r2.yyzy
35: mov r1.xw, l(0, 0, 0, 0)
36: loop
37: breakc_nz r1.x
38: ld_structured r2.x, r1.z, l(0), g0.xxxx
39: iadd r1.y, r1.y, r2.x
40: utof r2.x, r2.x
41: utof r2.w, r1.z
42: add r2.w, r2.w, l(0.500000)
43: mul r2.w, r2.w, l(0.011271)
44: exp r2.w, r2.w
45: add r2.w, r2.w, l(-1.000000)
46: mad r3.z, r2.x, r2.w, r1.w
47: ilt r2.x, r0.x, r1.y
48: iadd r2.w, -r2.y, r1.y
49: itof r2.w, r2.w
50: div r0.z, r3.z, r2.w
51: iadd r3.y, r1.z, l(1)
52: mov r0.y, r1.z
53: mov r3.w, r1.x
54: movc r1.xzw, r2.xxxx, r0.wwyz, r3.wwyz
55: endloop
56: store_uav_typed u1.xyzw, l(0, 0, 0, 0), r1.wwww
57: endif
58: ret
يوجد مخزن مؤقت واحد ثابت:
ألقِ نظرة سريعة على رمز المجمع: يتم إرفاق اثنين من الطائرات بدون طيار (u0: المخزن المؤقت للإدخال من الجزء الأول و u1: نسيج الخرج بتنسيق 1x1 R32_FLOAT). نرى أيضًا أن هناك 64 مؤشر ترابط لكل مجموعة و 256 عنصرًا من ذاكرة المجموعة المشتركة ذات 4 بايت.
نبدأ بملء الذاكرة المشتركة بالبيانات من مخزن الإدخال المؤقت. لدينا 64 مؤشر ترابط ، لذلك سيكون عليك فعل نفس الشيء كما كان من قبل.
للتأكد من أن جميع البيانات قد تم تحميلها لمزيد من المعالجة ، بعد ذلك وضعنا حاجزًا.
// - .
// 64 , 4
// .
[unroll] for (uint idx=0; idx < 4; idx++)
{
const uint offset = threadID + idx*64;
shared_data[ offset ] = g_buffer[offset];
}
// , ,
// .
GroupMemoryBarrierWithGroupSync();
يتم تنفيذ جميع الحسابات في مؤشر ترابط واحد فقط ، ويتم استخدام جميع الحسابات الأخرى ببساطة لتحميل القيم من المخزن المؤقت في الذاكرة المشتركة.
يحتوي دفق "الحوسبة" على مؤشر 0. لماذا؟ من الناحية النظرية ، يمكننا استخدام أي تيار من الفاصل الزمني [0-63] ، ولكن بفضل المقارنة مع 0 ، يمكننا تجنب مقارنات عدد صحيح صحيح إضافي (تعليمات
ieq ).
تستند الخوارزمية إلى إشارة الفاصل الزمني للبكسلات التي سيتم أخذها في الاعتبار في العملية.
في السطر 11 ، نقوم بضرب العرض * الارتفاع للحصول على إجمالي عدد البيكسلات وضربها برقمين من الفاصل الزمني [0.0f-1.0f] ، مما يشير إلى بداية ونهاية الفاصل الزمني. يتم استخدام قيود إضافية لضمان
0 <= Start <= End <= totalPixels - 1
:
// 0.
[branch] if (threadID == 0)
{
//
uint totalPixels = cb0_v0.x * cb0_v0.y;
// (, , ),
// .
int pixelsToConsiderStart = totalPixels * cb0_v0.z;
int pixelsToConsiderEnd = totalPixels * cb0_v0.w;
int pixelsMinusOne = totalPixels - 1;
pixelsToConsiderStart = clamp( pixelsToConsiderStart, 0, pixelsMinusOne );
pixelsToConsiderEnd = clamp( pixelsToConsiderEnd, pixelsToConsiderStart, pixelsMinusOne );
كما ترون ، هناك دورتان أدناه. المشكلة معهم (أو مع رمز المجمع الخاص بهم) هي أن هناك انتقالات شرطية غريبة في نهايات الحلقات. كان من الصعب جدًا بالنسبة لي إعادة إنشائها. انظر أيضًا إلى السطر 21. لماذا يوجد "-1"؟ ساوضح ذلك قليلا أدناه.
تتمثل مهمة الدورة الأولى في تجاهل
pixelsToConsiderStart ومنحنا فهرس الخلية العازلة التي
يوجد فيها
pixelsToConsiderStart +1 (بالإضافة إلى عدد جميع وحدات البكسل في الخلايا السابقة).
دعنا نقول أن
pixelsToConsiderStart تساوي 30،000 تقريبًا ، وفي المخزن المؤقت يوجد 37000 بكسل في الخلية "صفر" (يحدث هذا في اللعبة ليلًا). لذلك ، نريد أن نبدأ في تحليل السطوع باستخدام بكسل 30001 تقريبًا ، الموجود في الخلية "صفر". في هذه الحالة ، نخرج فورًا من الحلقة ، ونحصل على مؤشر البداية '0' والصفر بكسل المهملة.
ألق نظرة على كود HLSL:
//
int numProcessedPixels = 0;
// [0-255]
int lumaValue = 0;
//
bool bExitLoop = false;
// - "pixelsToConsiderStart" .
// lumaValue, .
[loop]
while (!bExitLoop)
{
// .
uint numPixels = shared_data[lumaValue];
// , lumaValue
int tempSum = numProcessedPixels + numPixels;
// , pixelsToConsiderStart, .
// , lumaValue.
// , pixelsToConsiderStart - "" , , .
[flatten]
if (tempSum > pixelsToConsiderStart)
{
bExitLoop = true;
}
else
{
numProcessedPixels = tempSum;
lumaValue++;
}
}
يرتبط الرقم الغامض "-1" من السطر 21 من رمز المجمّع بشرط Boolean لتنفيذ الحلقة (اكتشفت هذا عن طريق الصدفة تقريبًا).
بعد تلقي عدد البكسل من خلايا
lumaValue و
lumaValue نفسها ، يمكننا الانتقال إلى الدورة الثانية.
مهمة الدورة الثانية هي حساب تأثير البكسل ومتوسط السطوع.
نبدأ بـ
lumaValue المحسوبة في الحلقة الأولى.
float finalAvgLuminance = 0.0f;
//
uint numProcessedPixelStart = numProcessedPixels;
// - .
// , , lumaValue.
// [0-255], , , ,
// pixelsToConsiderEnd.
// .
bExitLoop = false;
[loop]
while (!bExitLoop)
{
// .
uint numPixels = shared_data[lumaValue];
//
numProcessedPixels += numPixels;
// , [0-255] (uint)
uint encodedLumaUint = lumaValue;
//
float numberOfPixelsWithCurrentLuma = numPixels;
// , [0-255] (float)
float encodedLumaFloat = encodedLumaUint;
في هذه المرحلة ، حصلنا على قيمة السطوع المشفرة في الفاصل الزمني [0.0f-255.f].
عملية فك التشفير بسيطة للغاية - تحتاج إلى عكس حساب مرحلة الترميز.
تكرار موجز لعملية الترميز:
float luma = dot( hdrPixelColor, float3(0.2126, 0.7152, 0.0722) );
...
float outLuma;
// log(0) undef, log(1) = 0
outLuma = luma + 1.0;
//
outLuma = log( outLuma );
// 128, log(1) * 128 = 0, log(2,71828) * 128 = 128, log(7,38905) * 128 = 256
outLuma = outLuma * 128
// uint
uint outLumaUint = min( (uint) outLuma, 255);
لفك تشفير السطوع ، نعكس عملية الترميز ، على سبيل المثال ، مثل هذا:
// 0.5f ( , )
float fDecodedLuma = encodedLumaFloat + 0.5;
// :
// 128
fDecodedLuma /= 128.0;
// exp(x), log(x)
fDecodedLuma = exp(fDecodedLuma);
// 1.0
fDecodedLuma -= 1.0;
بعد ذلك نحسب التوزيع بضرب عدد البكسلات مع سطوع معين بالسطوع الذي تم فك ترميزه ،
ونقوم بتلخيصها حتى نصل إلى معالجة
pixelsToConsiderEnd بكسل.
بعد ذلك ، نقسم التأثير الكلي على عدد وحدات البكسل التي تم تحليلها.هذا هو الجزء المتبقي من الحلقة (والتظليل): يتوفر التظليل الكامل هنا . وهو متوافق تمامًا مع برنامج HLSLexplorer الخاص بي ، والذي بدونه لن أكون قادرًا على إعادة إنشاء حساب السطوع المتوسط بشكل فعال في The Witcher 3 (وجميع التأثيرات الأخرى أيضًا!). في الختام ، بعض الأفكار. من حيث حساب متوسط السطوع ، كان من الصعب إعادة إنشاء هذا التظليل. الأسباب الرئيسية: 1) اختبارات "معلقة" غريبة على تنفيذ الدورة ، استغرق الأمر الكثير من الوقت أكثر مما اعتقدت سابقا. 2) مشاكل مع تصحيح هذا التظليل الحسابي في RenderDoc (v. 1.2).//
float fCurrentLumaContribution = numberOfPixelsWithCurrentLuma * fDecodedLuma;
// () .
float tempTotalContribution = fCurrentLumaContribution + finalAvgLuminance;
[flatten]
if (numProcessedPixels > pixelsToConsiderEnd )
{
//
bExitLoop = true;
// , .
//
int diff = numProcessedPixels - numProcessedPixelStart;
//
finalAvgLuminance = tempTotalContribution / float(diff);
}
else
{
// lumaValue
finalAvgLuminance = tempTotalContribution;
lumaValue++;
}
}
//
g_avgLuminance[uint2(0,0)] = finalAvgLuminance;
العمليات "ld_structured_indexable" غير مدعومة بالكامل ، على الرغم من أن نتيجة القراءة من الفهرس 0 تعطي القيمة الصحيحة ، بينما ترجع جميع الأصفار الأخرى ، وهذا هو السبب في استمرار الدورات إلى أجل غير مسمى.على الرغم من أنني لم أتمكن من تحقيق نفس رمز التجميع كما في النسخة الأصلية (انظر لقطة الشاشة أدناه لمعرفة الاختلافات) ، باستخدام RenderDoc تمكنت من ضخ هذا التظليل في خط الأنابيب - وكانت النتائج هي نفسها!نتيجة المعركة. على اليسار هو تظليلي ، على اليمين هو رمز المجمع الأصلي.الجزء 8. القمر ومراحله
في الجزء الثامن من المقال ، قمت بفحص تظليل القمر من The Witcher 3 (وبشكل أكثر تحديداً ، من Blood and Wine extension).القمر عنصر مهم في سماء الليل ، وقد يكون من الصعب للغاية جعله مُصدقًا ، لكن بالنسبة لي المشي ليلا في TW3 كان من دواعي سروري الحقيقي.مجرد إلقاء نظرة على هذا المشهد!قبل أن نتناول تظليل البكسل ، سأقول بضع كلمات حول الفروق الدقيقة في التقديم. من وجهة نظر هندسية ، القمر هو مجرد كرة (انظر أدناه) ، والتي تحتوي على إحداثيات الملمس ، وناقلات طبيعية ومماسية. يقوم تظليل قمة الرأس بحساب الموضع في الفضاء العالمي ، وكذلك المتجهات الطبيعية للعادي ، المماس والمماس إلى نقطتين (باستخدام منتج متجه) ، مضروبًا بالمصفوفة العالمية.لضمان أن القمر تقع بالكامل على متن الطائرة بعد والحقول وMinDepth MaxDepth هيكل D3D11_VIEWPORT تعيين قيمة 0.0 (نفس الخدعة التي كانت تستخدم لقبة السماء). يتم تقديم القمر مباشرة بعد السماء.الكرة المستخدمة لرسم القمر، حسناً ، كل شيء ، كما أعتقد ، يمكنك المتابعة. دعنا نلقي نظرة على تظليل البكسل: السبب الرئيسي وراء اختيار التظليل من Blood and Wine بسيط - إنه أقصر. أولاً ، نحسب الإزاحة لأخذ عينات من النسيج. يستخدم cb0 [0] .w كإزاحة على طول المحور X. مع هذه الحيلة البسيطة يمكننا محاكاة دوران القمر حول محوره.ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb0[1], immediateIndexed
dcl_constantbuffer cb2[3], immediateIndexed
dcl_constantbuffer cb12[267], immediateIndexed
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_input_ps linear v1.w
dcl_input_ps linear v2.xyzw
dcl_input_ps linear v3.xy
dcl_input_ps linear v4.xy
dcl_output o0.xyzw
dcl_temps 3
0: mov r0.x, -cb0[0].w
1: mov r0.y, l(0)
2: add r0.xy, r0.xyxx, v2.xyxx
3: sample_indexable(texture2d)(float,float,float,float) r0.xyzw, r0.xyxx, t0.xyzw, s0
4: add r0.xyz, r0.xyzx, l(-0.500000, -0.500000, -0.500000, 0.000000)
5: log r0.w, r0.w
6: mul r0.w, r0.w, l(2.200000)
7: exp r0.w, r0.w
8: add r0.xyz, r0.xyzx, r0.xyzx
9: dp3 r1.x, r0.xyzx, r0.xyzx
10: rsq r1.x, r1.x
11: mul r0.xyz, r0.xyzx, r1.xxxx
12: mul r1.xy, r0.yyyy, v3.xyxx
13: mad r0.xy, v4.xyxx, r0.xxxx, r1.xyxx
14: mad r0.xy, v2.zwzz, r0.zzzz, r0.xyxx
15: mad r0.z, cb0[0].y, l(0.033864), cb0[0].w
16: mul r0.z, r0.z, l(6.283185)
17: sincos r1.x, r2.x, r0.z
18: mov r2.y, r1.x
19: dp2_sat r0.x, r0.xyxx, r2.xyxx
20: mul r0.xyz, r0.xxxx, cb12[266].xyzx
21: mul r0.xyz, r0.xyzx, r0.wwww
22: mul r0.xyz, r0.xyzx, cb2[2].xyzx
23: add_sat r0.w, -v1.w, l(1.000000)
24: mul r0.w, r0.w, cb2[2].w
25: mul o0.xyz, r0.wwww, r0.xyzx
26: mov o0.w, l(0)
27: ret

أمثلة على القيم من المخزن المؤقت الثابت ،يتم إرفاق مادة واحدة (1024 × 512) كمدخل. يتم تشفير الخريطة العادية في قنوات RGB ولون سطح القمر في قناة ألفا. ذكي!قناة ألفا من نسيج هو لون سطح القمر.قنوات RGB الملمس هي خريطة طبيعية.بعد تلقي إحداثيات الملمس الصحيحة ، نقوم بتجربة قنوات RGBA. نحتاج إلى فك الخريطة العادية وإجراء تصحيح غاما للون السطح. حاليًا ، يمكن كتابة تظليل HLSL بهذا الشكل ، على سبيل المثال: الخطوة التالية هي إجراء الربط العادي ، ولكن فقط في مكونات XY. (في The Witcher 3 ، يكون المحور Z صعوديًا ، والقناة Z بأكملها من الملمس 1.0). يمكننا أن نفعل ذلك بهذه الطريقة: حان الوقت لجزءي المفضل من هذا التظليل. انظر مرة أخرى إلى السطور 15-16: ما هو هذا الغامض 0.033864؟ في البداية ، يبدو أنه لا معنى له ، لكن إذا قمنا بحساب القيمة معكوسة ، فسوف نحصل على حوالي 29.53 ، أي ما يعادل مدة الشهر السينودسيfloat4 MoonPS(in InputStruct IN) : SV_Target0
{
// Texcoords
float2 uvOffsets = float2(-cb0_v0.w, 0.0);
// texcoords
float2 uv = IN.param2.xy + uvOffsets;
//
float4 sampledTexture = texture0.Sample( sampler0, uv);
// - -
float moonColorTex = pow(sampledTexture.a, 2.2 );
// [0,1] [-1,1].
// : sampledTexture.xyz * 2.0 - 1.0
float3 sampledNormal = normalize((sampledTexture.xyz - 0.5) * 2);
//
float3 Tangent = IN.param4.xyz;
float3 Normal = float3(IN.param2.zw, IN.param3.w);
float3 Bitangent = IN.param3.xyz;
// TBN
float3x3 TBN = float3x3(Tangent, Bitangent, Normal);
// XY
// TBN float3x2: 3 , 2
float2 vNormal = mul(sampledNormal, (float3x2)TBN).xy;
15: mad r0.z, cb0[0].y, l(0.033864), cb0[0].w
16: mul r0.z, r0.z, l(6.283185)
في الايام! هذا ما أسميه الانتباه إلى التفاصيل!يمكننا أن نفترض بشكل موثوق أن cb0 [0] .y هو عدد الأيام التي مرت أثناء اللعب. يتم استخدام انحراف إضافي هنا ، يستخدم كإزاحة على طول المحور السيني للنسيج.بعد تلقي هذا المعامل ، نضربه في 2 * Pi.ثم باستخدام sincos نحسب ناقل 2D آخر.من خلال حساب المنتج القياسي بين المتجه العادي وناقل "القمر" ، يتم محاكاة مرحلة من القمر. إلقاء نظرة على لقطات مع مراحل مختلفة من القمر:// .
// days/29.53 + bias.
float phase = cb0_v0.y * (1.0 / SYNODIC_MONTH_LENGTH) + cb0_v0.w;
// 2*PI. , 29.53
// sin/cos.
phase *= TWOPI;
// .
float outSin = 0.0;
float outCos = 0.0;
sincos(phase, outSin, outCos);
//
float lunarPhase = saturate( dot(vNormal, float2(outCos, outSin)) );
الخطوة الأخيرة هي إجراء سلسلة من عمليات الضرب لحساب اللون النهائي. من المحتمل أنك لا تفهم سبب إرسال هذا التظليل قيمة ألفا البالغة 0.0 إلى الإخراج. هذا بسبب تقديم القمر مع مزج:// .
// cb12_v266.xyz , .
// (1.54, 2.82, 4.13)
float3 moonSurfaceGlowColor = cb12_v266.xyz;
float3 moonColor = lunarPhase * moonSurfaceGlowColor;
moonColor = moonColorTex * moonColor;
// cb_v2.xyz - , , , (1.0, 1.0, 1.0)
moonColor *= cb2_v2.xyz;
// , , . - .
// , ,
// .
float paramHorizon = saturate(1.0 - IN.param1.w);
paramHorizon *= cb2_v2.w;
moonColor *= paramHorizon;
//
return float4(moonColor, 0.0);
تتيح لك هذه الطريقة الحصول على لون الخلفية (السماء) إذا عادت هذه التظليل إلى اللون الأسود.إذا كنت مهتمًا بتظليل كامل ، فيمكنك أخذها هنا . يحتوي على مخازن مؤقتة كبيرة ويجب أن يكون جاهزًا بالفعل للحقن في RenderDoc بدلاً من التظليل الأصلي (ما عليك سوى إعادة تسمية "MoonPS" إلى "EditedShaderPS").والأخير: أردت مشاركة النتائج معك:على اليسار هو تظليلي ، على اليمين هو التظليل الأصلي من اللعبة.الفرق ضئيل ولا يؤثر على النتائج.كما ترون ، كان هذا التظليل من السهل جدًا إعادة إنشائه.الجزء 9. G- العازلة
في هذا الجزء ، سأكشف عن بعض تفاصيل gbuffer في The Witcher 3.وسوف نفترض أنك تعرف أساسيات التظليل المؤجل.التكرار المختصر: إن فكرة التأجيل لا تتمثل في حساب كل الإضاءة والتظليل النهائي في آن واحد ، ولكن تقسيم الحسابات إلى مرحلتين.في المرحلة الأولى (الممر الهندسي) نملأ GBuffer ببيانات السطح (الموضع ، والأوضاع الطبيعية ، والألوان براق ، إلخ ...) ، وفي الثانية (ممر الإضاءة) نجمع كل شيء ونحسب الإضاءة.التظليل المؤجل هو أسلوب شائع للغاية لأنه يسمح لك بالحساب في تمريرة بملء الشاشة من خلال تقنيات مثل التظليل المؤجل للتجانب ، مما يحسن الأداء إلى حد كبير.ببساطة ، GBuffer هي مجموعة من القوام مع خصائص الهندسة. من المهم جدًا إنشاء الهيكل المناسب لذلك. كمثال على الحياة الحقيقية ، يمكنك دراسة تقنية تقديم Crysis 3 .بعد هذه المقدمة الموجزة ، دعونا نلقي نظرة على إطار مثال من The Witcher 3: Blood and Wine:يتكون أحد الفنادق العديدة الموجودة في Toussent Basic GBuffer من ثلاثة أهداف تجسيد بملء الشاشة بتنسيق DXGI_FORMAT_R8G8B8A8_UNORM وتخزين مؤقت للعمق + الاستنسل بتنسيق DXGI_FORMAT_D24_UNORM_S8_UINT.هنا لقطاتهم:تقديم الهدف 0 - قنوات RGB ، لون السطحتقديم الهدف 0 - قناة ألفا. بصراحة ، ليس لدي أي فكرة عن ماهية هذه المعلومات.تقديم الهدف 1 - قنوات RGB. يتم تسجيل المتجهات الطبيعية في الفاصل الزمني [0-1] هنا.تقديم الهدف 1 - قناة ألفا. يشبه الانعكاسية!تقديم الهدف 2 - قنوات RGB. يشبه اللون براق!في هذا المشهد ، تكون قناة ألفا سوداء (ولكن يتم استخدامها لاحقًا).أعماق العازلة. لاحظ أن عمق المقلوب يستخدم هنا.يستخدم المخزن المؤقت للاستنسل في تحديد نوع معين من البكسل (على سبيل المثال ، الجلد والغطاء النباتي ، إلخ.)هذا ليس GBuffer بأكمله. يستخدم مسار الإضاءة أيضًا مجسات الإضاءة وغيرها من المخازن المؤقتة ، لكنني لن أناقشها في هذه المقالة.قبل الانتقال إلى الجزء "الرئيسي" من المنشور ، سأقدم ملاحظات عامة:ملاحظات عامة
1) المخزن المؤقت الوحيد المراد تنظيفه هو المخزن المؤقت للعمق / الاستنسل.إذا قمت بتحليل القوام المذكور أعلاه في محلل إطار جيد ، فستفاجأ قليلاً ، لأنها لا تستخدم مكالمة "مسح" ، باستثناء العمق / الاستنسل.هذا ، في الواقع ، يبدو RenderTarget1 بهذا الشكل (لاحظ وحدات البكسل "الضبابية" على المستوى البعيد):هذا هو تحسين بسيط وذكي.درس مهم: تحتاج إلى إنفاق الموارد على مكالمات ClearRenderTargetView ، لذا استخدمها فقط عند الضرورة.2) مقلوب عمق - هو تهدئةفي العديد من المواد بالفعل مكتوبة حول دقة عازلة بعمق مع النقطة العائمة. ويتشر 3 يستخدم عكس ض. هذا هو الخيار الطبيعي لمثل هذه اللعبة المفتوحة في العالم مع مسافات طويلة.لن يكون التبديل إلى DirectX أمرًا صعبًا:أ) نقوم بمسح ذاكرة التخزين المؤقت للعمق من خلال كتابة "0" وليس "1".في النهج التقليدي ، تم استخدام القيمة البعيدة "1" لمسح المخزن المؤقت للعمق. بعد انقلاب العمق ، أصبحت القيمة "البعيدة" الجديدة 0 ، لذلك تحتاج إلى تغيير كل شيء.ب) تبديل الحدود القريبة والبعيدة عند حساب مصفوفة الإسقاطج) تغيير فحص العمق من "أقل" إلى "أكثر"بالنسبة لبرنامج OpenGL ، هناك حاجة إلى مزيد من العمل (انظر المقالات المذكورة أعلاه) ، ولكن الأمر يستحق ذلك.3) نحن لا نحتفظ بمكانتنا في العالمنعم ، كل شيء بسيط للغاية. في مرور الإضاءة ، نقوم بإعادة إنشاء موقع في العالم من الأعماق.بكسل تظليل
في هذا الجزء ، أردت أن أعرض بالضبط تظليل البكسل الذي يوفر بيانات السطح لـ GBuffer.حتى الآن نحن نعرف بالفعل كيفية تخزين اللون ، والأوضاع الطبيعية والبراق.بالطبع ، ليس كل شيء بهذه البساطة كما تظن.المشكلة في تظليل بكسل هو أنه يحتوي على العديد من الخيارات. وهي تختلف في عدد القوام المنقولة إليها وعدد المعلمات المستخدمة من المخزن المؤقت الثابت (ربما من المخزن المؤقت الثابت الذي يصف المادة).للتحليل ، قررت استخدام هذا البرميل الجميل:برميلنا البطولي!يرجى نرحب القوام:لذلك لدينا albedo ، خريطة طبيعية ، ولون براق. حالة قياسية جميلة.قبل أن نبدأ ، بضع كلمات حول إدخالالهندسة : يتم نقل الهندسة مع الموضع ، texcoords ، المخازن المؤقتة العادية والماسية.ينتج تظليل قمة الرأس ما لا يقل عن texcoords ، نواقل الظلال الطبيعية / المماسية الطبيعية إلى نقطتين ، مضروبة سابقًا في المصفوفة العالمية. بالنسبة للمواد الأكثر تعقيدًا (على سبيل المثال ، مع خريطتين منتشرتين أو خريطتين عاديتين) ، يمكن لتظليل قمة الرأس إخراج بيانات أخرى ، لكنني أردت عرض مثال بسيط هنا.تظليل بكسل في رمز المجمّع: يتكون تظليل من عدة خطوات. سأصف كل جزء رئيسي من هذا التظليل بشكل منفصل.ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb4[3], immediateIndexed
dcl_sampler s0, mode_default
dcl_sampler s13, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_resource_texture2d (float,float,float,float) t2
dcl_resource_texture2d (float,float,float,float) t13
dcl_input_ps linear v0.zw
dcl_input_ps linear v1.xyzw
dcl_input_ps linear v2.xyz
dcl_input_ps linear v3.xyz
dcl_input_ps_sgv v4.x, isfrontface
dcl_output o0.xyzw
dcl_output o1.xyzw
dcl_output o2.xyzw
dcl_temps 3
0: sample_indexable(texture2d)(float,float,float,float) r0.xyzw, v1.xyxx, t1.xyzw, s0
1: sample_indexable(texture2d)(float,float,float,float) r1.xyz, v1.xyxx, t0.xyzw, s0
2: add r1.w, r1.y, r1.x
3: add r1.w, r1.z, r1.w
4: mul r2.x, r1.w, l(0.333300)
5: add r2.y, l(-1.000000), cb4[1].x
6: mul r2.y, r2.y, l(0.500000)
7: mov_sat r2.z, r2.y
8: mad r1.w, r1.w, l(-0.666600), l(1.000000)
9: mad r1.w, r2.z, r1.w, r2.x
10: mul r2.xzw, r1.xxyz, cb4[0].xxyz
11: mul_sat r2.xzw, r2.xxzw, l(1.500000, 0.000000, 1.500000, 1.500000)
12: mul_sat r1.w, abs(r2.y), r1.w
13: add r2.xyz, -r1.xyzx, r2.xzwx
14: mad r1.xyz, r1.wwww, r2.xyzx, r1.xyzx
15: max r1.w, r1.z, r1.y
16: max r1.w, r1.w, r1.x
17: lt r1.w, l(0.220000), r1.w
18: movc r1.w, r1.w, l(-0.300000), l(-0.150000)
19: mad r1.w, v0.z, r1.w, l(1.000000)
20: mul o0.xyz, r1.wwww, r1.xyzx
21: add r0.xyz, r0.xyzx, l(-0.500000, -0.500000, -0.500000, 0.000000)
22: add r0.xyz, r0.xyzx, r0.xyzx
23: mov r1.x, v0.w
24: mov r1.yz, v1.zzwz
25: mul r1.xyz, r0.yyyy, r1.xyzx
26: mad r1.xyz, v3.xyzx, r0.xxxx, r1.xyzx
27: mad r0.xyz, v2.xyzx, r0.zzzz, r1.xyzx
28: uge r1.x, l(0), v4.x
29: if_nz r1.x
30: dp3 r1.x, v2.xyzx, r0.xyzx
31: mul r1.xyz, r1.xxxx, v2.xyzx
32: mad r0.xyz, -r1.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), r0.xyzx
33: endif
34: sample_indexable(texture2d)(float,float,float,float) r1.xyz, v1.xyxx, t2.xyzw, s0
35: max r1.w, r1.z, r1.y
36: max r1.w, r1.w, r1.x
37: lt r1.w, l(0.200000), r1.w
38: movc r2.xyz, r1.wwww, r1.xyzx, l(0.120000, 0.120000, 0.120000, 0.000000)
39: add r2.xyz, -r1.xyzx, r2.xyzx
40: mad o2.xyz, v0.zzzz, r2.xyzx, r1.xyzx
41: lt r1.x, r0.w, l(0.330000)
42: mul r1.y, r0.w, l(0.950000)
43: movc r1.x, r1.x, r1.y, l(0.330000)
44: add r1.x, -r0.w, r1.x
45: mad o1.w, v0.z, r1.x, r0.w
46: dp3 r0.w, r0.xyzx, r0.xyzx
47: rsq r0.w, r0.w
48: mul r0.xyz, r0.wwww, r0.xyzx
49: max r0.w, abs(r0.y), abs(r0.x)
50: max r0.w, r0.w, abs(r0.z)
51: lt r1.xy, abs(r0.zyzz), r0.wwww
52: movc r1.yz, r1.yyyy, abs(r0.zzyz), abs(r0.zzxz)
53: movc r1.xy, r1.xxxx, r1.yzyy, abs(r0.yxyy)
54: lt r1.z, r1.y, r1.x
55: movc r1.xy, r1.zzzz, r1.xyxx, r1.yxyy
56: div r1.z, r1.y, r1.x
57: div r0.xyz, r0.xyzx, r0.wwww
58: sample_l(texture2d)(float,float,float,float) r0.w, r1.xzxx, t13.yzwx, s13, l(0)
59: mul r0.xyz, r0.wwww, r0.xyzx
60: mad o1.xyz, r0.xyzx, l(0.500000, 0.500000, 0.500000, 0.000000), l(0.500000, 0.500000, 0.500000, 0.000000)
61: mov o0.w, cb4[2].x
62: mov o2.w, l(0)
63: ret
لكن أولاً ، كالعادة - لقطة شاشة تحتوي على قيم من المخزن المؤقت الثابت:البيدو
سنبدأ مع الأشياء المعقدة. إنها ليست مجرد "OutputColor.rgb = Texture.Sample (uv) .rgb"بعد أخذ عينات من ملون RGB (السطر 1) ، فإن الأسطر الـ 14 التالية هي ما أسميه "المخزن المؤقت لتقليل التشبع". اسمحوا لي أن أريك رمز HLSL: بالنسبة لمعظم الكائنات ، لا يعمل هذا الرمز سوى إرجاع اللون الأصلي من النسيج. يتم تحقيق ذلك من خلال القيم "cbuffer المادية" المقابلة. يحتوي cb4_v1.x على قيمة 1.0 ، والتي تُرجع قناعًا 0.0 وتُرجع لون الإدخال من تعليمة lerp . ومع ذلك ، هناك بعض الاستثناءات. أكبر desaturationFactor وجدته هو 4.0 (وهو أقل من 1.0) ، و desaturatedColorfloat3 albedoColorFilter( in float3 color, in float desaturationFactor, in float3 desaturationValue )
{
float sumColorComponents = color.r + color.g + color.b;
float averageColorComponentValue = 0.3333 * sumColorComponents;
float oneMinusAverageColorComponentValue = 1.0 - averageColorComponentValue;
float factor = 0.5 * (desaturationFactor - 1.0);
float avgColorComponent = lerp(averageColorComponentValue, oneMinusAverageColorComponentValue, saturate(factor));
float3 desaturatedColor = saturate(color * desaturationValue * 1.5);
float mask = saturate( avgColorComponent * abs(factor) );
float3 finalColor = lerp( color, desaturatedColor, mask );
return finalColor;
}
يعتمد على المواد. يمكن أن يكون شيء مثل (0.2 ، 0.3 ، 0.4) ؛ لا توجد قواعد صارمة. وبطبيعة الحال، لم أستطع مساعدة ولكن تحقيق هذا في بلده DX11-الإطار، وهنا النتائج، حيث كل القيم desaturatedColor يساوي float3 (0.25، 0.3، 0.45)desaturationFactor = 1.0 (ليس له أي تأثير)desaturationFactor = 2.0desaturationFactor = 3.0desaturationFactor = 4.0أنا متأكد من أن هذا مجرد تطبيق لمعلمات المواد ، ولكن لا يتم تنفيذه في نهاية الجزء albedo.تضيف الخطوط 15-20 اللمسات الأخيرة: v0.z هو الناتج من تظليل قمة الرأس ، وهي صفر. لا تنسَ ذلك ، لأنه سيتم استخدام الإصدار v0.z لاحقًا عدة مرات. يبدو أنه نوع من المعامل ، ويبدو أن الكود بأكمله يشبه البياض الخفيف ، لكن بما أن v0.z تساوي 0 ، فإن اللون يظل بلا تغيير. HLSL : بخصوص RT0.a ، كما نرى ، يتم أخذها من المخزن المؤقت الثابت للمادة ، ولكن نظرًا لأن التظليل لا يحتوي على معلومات تصحيح الأخطاء ، فمن الصعب تحديد ماهية ذلك. ربما الشفافية؟ لقد انتهينا من هدف العرض الأول!15: max r1.w, r1.z, r1.y
16: max r1.w, r1.w, r1.x
17: lt r1.w, l(0.220000), r1.w
18: movc r1.w, r1.w, l(-0.300000), l(-0.150000)
19: mad r1.w, v0.z, r1.w, l(1.000000)
20: mul o0.xyz, r1.wwww, r1.xyzx
/* ALBEDO */
// (?)
float3 albedoColor = albedoColorFilter( colorTex, cb4_v1.x, cb4_v0.rgb );
float albedoMaxComponent = getMaxComponent( albedoColor );
// ,
// "paramZ" 0
float paramZ = Input.out0.z; // , 0
// , 0.70 0.85
// lerp, .
float param = (albedoMaxComponent > 0.22) ? 0.70 : 0.85;
float mulParam = lerp(1, param, paramZ);
//
pout.RT0.rgb = albedoColor * mulParam;
pout.RT0.a = cb4_v2.x;
عادي
سنبدأ بتفريغ الخريطة العادية ، وبعد ذلك ، كالمعتاد ، سنربط القواعد الطبيعية: حتى الآن ، لا شيء يثير الدهشة. انظر إلى الأسطر 28-33: يمكننا كتابتها تقريبًا كما يلي: لست متأكدًا مما إذا كانت الكتابة صحيحة. إذا كنت تعرف ماهية هذه العملية الرياضية ، فأعلمني بذلك. نرى أن تظليل البكسل يستخدم SV_IsFrontFace./* */
float3 sampledNormal = ((normalTex.xyz - 0.5) * 2);
// TBN
float3 Tangent = Input.TangentW.xyz;
float3 Normal = Input.NormalW.xyz;
float3 Bitangent;
Bitangent.x = Input.out0.w;
Bitangent.yz = Input.out1.zw;
// ; , , normal-tbn
// 'mad' 'mov'
Bitangent = saturate(Bitangent);
float3x3 TBN = float3x3(Tangent, Bitangent, Normal);
float3 normal = mul( sampledNormal, TBN );
28: uge r1.x, l(0), v4.x
29: if_nz r1.x
30: dp3 r1.x, v2.xyzx, r0.xyzx
31: mul r1.xyz, r1.xxxx, v2.xyzx
32: mad r0.xyz, -r1.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), r0.xyzx
33: endif
[branch] if (bIsFrontFace <= 0)
{
float cosTheta = dot(Input.NormalW, normal);
float3 invNormal = cosTheta * Input.NormalW;
normal = normal - 2*invNormal;
}
ما هذا
تأتي الوثائق للمساعدة (أردت أن أكتب "msdn" ، لكن ...):لتحديد ما إذا كان المثلث ينظر إلى الكاميرا. للخطوط والنقاط ، IsFrontFace صحيح. الاستثناء هو الخطوط المرسومة من المثلثات (وضع الإطار السلكي) ، والتي تحدد IsFrontFace بشكل مشابه لتنقيط المثلث في الوضع الصلب. يمكن أن يتم الكتابة إليه بواسطة تظليل هندسي ، والقراءة منه بواسطة تظليل بكسل.
أردت التحقق من ذلك بمفردي. وفي الواقع ، فإن التأثير ملحوظ فقط في وضع الإطار السلكي. أعتقد أن هذا الجزء من الشفرة ضروري من أجل الحساب الصحيح للحالات الطبيعية (وبالتالي الإضاءة) في وضع الإطار السلكي.فيما يلي مقارنة: كلا ألوان إطار المشهد النهائي مع تشغيل / إيقاف هذه الخدعة ، بالإضافة إلى نسيج gbuffer [0-1] الطبيعي مع وجود / إيقاف تشغيل الخدعة:لون المشهد بدون خدعةمشهد ملون مع حيلةعادي [0-1] لا حيلةعادي [0-1] باستخدام خدعةهل لاحظت أن كل هدف تجسيد في GBuffer له التنسيق R8G8B8A8_UNORM؟ هذا يعني أن هناك 256 قيمة ممكنة لكل مكون. هل هذا يكفي لتخزين الحالات الطبيعية؟تعد تخزين القواعد الطبيعية عالية الجودة مع ما يكفي من البايتات في Gbuffer مشكلة معروفة ، ولكن لحسن الحظ ، هناك العديد من المواد المختلفة التي يمكن التعلم منها .ربما يعرف بعضكم بالفعل الطريقة المستخدمة هنا. يجب أن أقول أنه في المقطع الكامل للهندسة هناك مادة إضافية واحدة ملحقة بالشريحة 13 ...:ها!
يستخدم Witcher 3 تقنية تسمى " Best Fit Normals ". هنا لن أشرح ذلك بالتفصيل (انظر العرض). تم اختراعه في الفترة ما بين 2009-2010 من قِبل Crytek ، وبما أن CryEngine مفتوح المصدر ، فإن BFN هي أيضًا مفتوحة المصدر .BFN يعطي نسيج من الحياة الطبيعية نظرة "محبب".بعد توسيع النطاقات الطبيعية باستخدام BFN ، نقوم بإعادة ترميزها من الفاصل الزمني [-1 ؛ 1] إلى [0 ، 1].براق
لنبدأ من السطر 34 وأخذ عينات من نسيج براق: كما ترون ، هناك مرشح "يعتم" نعرفه من البيدو: نحسب المكون بحد أقصى. القيمة ، ثم احسب اللون "الغامق" وقم بتحريفه باللون الأصلي المرتب ، مع أخذ المعلمة من تظليل vertex ... وهو 0 ، لذلك عند الإخراج نحصل على اللون من النسيج. HLSL:34: sample_indexable(texture2d)(float,float,float,float) r1.xyz, v1.xyxx, t2.xyzw, s0
35: max r1.w, r1.z, r1.y
36: max r1.w, r1.w, r1.x
37: lt r1.w, l(0.200000), r1.w
38: movc r2.xyz, r1.wwww, r1.xyzx, l(0.120000, 0.120000, 0.120000, 0.000000)
39: add r2.xyz, -r1.xyzx, r2.xyzx
40: mad o2.xyz, v0.zzzz, r2.xyzx, r1.xyzx
/* SPECULAR */
float3 specularTex = texture2.Sample( samplerAnisoWrap, Texcoords ).rgb;
// , Albedo. . ,
// - "".
// paramZ 0,
// .
float specularMaxComponent = getMaxComponent( specularTex );
float3 specB = (specularMaxComponent > 0.2) ? specularTex : float3(0.12, 0.12, 0.12);
float3 finalSpec = lerp(specularTex, specB, paramZ);
pout.RT2.xyz = finalSpec;
الانعكاس
ليس لدي أي فكرة عما إذا كان هذا الاسم مناسبًا لهذه المعلمة ، لأنني لا أعرف كيف يؤثر على مرور الإضاءة. الحقيقة هي أن قناة ألفا للخريطة العادية للإدخال تحتوي على بيانات إضافية:نسيج قناة ألفا "خريطة طبيعية".رمز المجمع: قل مرحباً لصديقنا القديم - v0.z! معناها يشبه البياض والمضارب:41: lt r1.x, r0.w, l(0.330000)
42: mul r1.y, r0.w, l(0.950000)
43: movc r1.x, r1.x, r1.y, l(0.330000)
44: add r1.x, -r0.w, r1.x
45: mad o1.w, v0.z, r1.x, r0.w
/* REFLECTIVITY */
float reflectivity = normalTex.a;
float reflectivity2 = (reflectivity < 0.33) ? (reflectivity * 0.95) : 0.33;
float finalReflectivity = lerp(reflectivity, reflectivity2, paramZ);
pout.RT1.a = finalReflectivity;
عظيم!
هذه هي نهاية تحليل الإصدار الأول من تظليل بكسل.في ما يلي مقارنة بين تظليلي (يسار) مع الأصل (يمين):هذه الاختلافات لا تؤثر على الحسابات ، لذلك انتهى عملي هنا.بكسل شادر: Albedo + خيار عادي
قررت أن أعرض خيارًا واحدًا ، الآن فقط باستخدام خرائط albedo والخرائط العادية ، بدون نسيج براق. رمز المجمّع أطول قليلاً: الفرق بين هذا والخيارات السابقة كما يلي: أ) الأسطر 1 ، 19 : يتم ضرب المعلمة الاستيفاء v0.z ب cb4 [0] .x من المخزن المؤقت الثابت ، لكن هذا المنتج يستخدم فقط في ألبيدو الاستيفاء على السطر 19. بالنسبة للإخراج الآخر ، يتم استخدام القيمة "العادية" لـ v0.z. ب) الأسطر 54-55 : o2.w تم ضبطها الآن بشرط (cb4 [7] .x> 0.0) لقد أدركنا بالفعل هذا النموذج "نوع من المقارنة - AND" من حساب الرسم البياني للسطوع. يمكن كتابتها على النحو التالي: ج) الأسطر 34-42 : عملية حسابية مختلفة تمامًا.ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb4[8], immediateIndexed
dcl_sampler s0, mode_default
dcl_sampler s13, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_resource_texture2d (float,float,float,float) t13
dcl_input_ps linear v0.zw
dcl_input_ps linear v1.xyzw
dcl_input_ps linear v2.xyz
dcl_input_ps linear v3.xyz
dcl_input_ps_sgv v4.x, isfrontface
dcl_output o0.xyzw
dcl_output o1.xyzw
dcl_output o2.xyzw
dcl_temps 4
0: mul r0.x, v0.z, cb4[0].x
1: sample_indexable(texture2d)(float,float,float,float) r1.xyzw, v1.xyxx, t1.xyzw, s0
2: sample_indexable(texture2d)(float,float,float,float) r0.yzw, v1.xyxx, t0.wxyz, s0
3: add r2.x, r0.z, r0.y
4: add r2.x, r0.w, r2.x
5: add r2.z, l(-1.000000), cb4[2].x
6: mul r2.yz, r2.xxzx, l(0.000000, 0.333300, 0.500000, 0.000000)
7: mov_sat r2.w, r2.z
8: mad r2.x, r2.x, l(-0.666600), l(1.000000)
9: mad r2.x, r2.w, r2.x, r2.y
10: mul r3.xyz, r0.yzwy, cb4[1].xyzx
11: mul_sat r3.xyz, r3.xyzx, l(1.500000, 1.500000, 1.500000, 0.000000)
12: mul_sat r2.x, abs(r2.z), r2.x
13: add r2.yzw, -r0.yyzw, r3.xxyz
14: mad r0.yzw, r2.xxxx, r2.yyzw, r0.yyzw
15: max r2.x, r0.w, r0.z
16: max r2.x, r0.y, r2.x
17: lt r2.x, l(0.220000), r2.x
18: movc r2.x, r2.x, l(-0.300000), l(-0.150000)
19: mad r0.x, r0.x, r2.x, l(1.000000)
20: mul o0.xyz, r0.xxxx, r0.yzwy
21: add r0.xyz, r1.xyzx, l(-0.500000, -0.500000, -0.500000, 0.000000)
22: add r0.xyz, r0.xyzx, r0.xyzx
23: mov r1.x, v0.w
24: mov r1.yz, v1.zzwz
25: mul r1.xyz, r0.yyyy, r1.xyzx
26: mad r0.xyw, v3.xyxz, r0.xxxx, r1.xyxz
27: mad r0.xyz, v2.xyzx, r0.zzzz, r0.xywx
28: uge r0.w, l(0), v4.x
29: if_nz r0.w
30: dp3 r0.w, v2.xyzx, r0.xyzx
31: mul r1.xyz, r0.wwww, v2.xyzx
32: mad r0.xyz, -r1.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), r0.xyzx
33: endif
34: add r0.w, -r1.w, l(1.000000)
35: log r1.xyz, cb4[3].xyzx
36: mul r1.xyz, r1.xyzx, l(2.200000, 2.200000, 2.200000, 0.000000)
37: exp r1.xyz, r1.xyzx
38: mad r0.w, r0.w, cb4[4].x, cb4[5].x
39: mul_sat r1.xyz, r0.wwww, r1.xyzx
40: log r1.xyz, r1.xyzx
41: mul r1.xyz, r1.xyzx, l(0.454545, 0.454545, 0.454545, 0.000000)
42: exp r1.xyz, r1.xyzx
43: max r0.w, r1.z, r1.y
44: max r0.w, r0.w, r1.x
45: lt r0.w, l(0.200000), r0.w
46: movc r2.xyz, r0.wwww, r1.xyzx, l(0.120000, 0.120000, 0.120000, 0.000000)
47: add r2.xyz, -r1.xyzx, r2.xyzx
48: mad o2.xyz, v0.zzzz, r2.xyzx, r1.xyzx
49: lt r0.w, r1.w, l(0.330000)
50: mul r1.x, r1.w, l(0.950000)
51: movc r0.w, r0.w, r1.x, l(0.330000)
52: add r0.w, -r1.w, r0.w
53: mad o1.w, v0.z, r0.w, r1.w
54: lt r0.w, l(0), cb4[7].x
55: and o2.w, r0.w, l(0.064706)
56: dp3 r0.w, r0.xyzx, r0.xyzx
57: rsq r0.w, r0.w
58: mul r0.xyz, r0.wwww, r0.xyzx
59: max r0.w, abs(r0.y), abs(r0.x)
60: max r0.w, r0.w, abs(r0.z)
61: lt r1.xy, abs(r0.zyzz), r0.wwww
62: movc r1.yz, r1.yyyy, abs(r0.zzyz), abs(r0.zzxz)
63: movc r1.xy, r1.xxxx, r1.yzyy, abs(r0.yxyy)
64: lt r1.z, r1.y, r1.x
65: movc r1.xy, r1.zzzz, r1.xyxx, r1.yxyy
66: div r1.z, r1.y, r1.x
67: div r0.xyz, r0.xyzx, r0.wwww
68: sample_l(texture2d)(float,float,float,float) r0.w, r1.xzxx, t13.yzwx, s13, l(0)
69: mul r0.xyz, r0.wwww, r0.xyzx
70: mad o1.xyz, r0.xyzx, l(0.500000, 0.500000, 0.500000, 0.000000), l(0.500000, 0.500000, 0.500000, 0.000000)
71: mov o0.w, cb4[6].x
72: ret
pout.RT2.w = (cb4_v7.x > 0.0) ? (16.5/255.0) : 0.0;
لا يوجد نسيج براق هنا. دعونا نلقي نظرة على رمز المجمع المسؤول عن هذا الجزء: لاحظ أننا استخدمنا هنا (1 - القدرة المنعكسة). لحسن الحظ ، فإن كتابة هذا في HLSL بسيط جدًا: سأضيف أنه في هذا الإصدار يكون المخزن المؤقت الثابت مع بيانات المواد أكبر قليلاً. هنا ، يتم استخدام هذه القيم الإضافية لمحاكاة اللون المضارب. ما تبقى من تظليل هو نفسه كما في الإصدار السابق. هناك 72 سطرًا من رمز المجمّع أكثر من اللازم لعرضها في WinMerge ، لذا خذ كلمتي إليها: لقد تحولت الكود الخاص بي إلى ما هو عليه كما هو الحال في النص الأصلي. أو يمكنك تنزيل HLSLexplorer الخاص بي وانظر بنفسك!34: add r0.w, -r1.w, l(1.000000)
35: log r1.xyz, cb4[3].xyzx
36: mul r1.xyz, r1.xyzx, l(2.200000, 2.200000, 2.200000, 0.000000)
37: exp r1.xyz, r1.xyzx
38: mad r0.w, r0.w, cb4[4].x, cb4[5].x
39: mul_sat r1.xyz, r0.wwww, r1.xyzx
40: log r1.xyz, r1.xyzx
41: mul r1.xyz, r1.xyzx, l(0.454545, 0.454545, 0.454545, 0.000000)
42: exp r1.xyz, r1.xyzx
float oneMinusReflectivity = 1.0 - normalTex.a;
float3 specularTex = pow(cb4_v3.rgb, 2.2);
oneMinusReflectivity = oneMinusReflectivity * cb4_v4.x + cb4_v5.x;
specularTex = saturate(specularTex * oneMinusReflectivity);
specularTex = pow(specularTex, 1.0/2.2);
// ...
float specularMaxComponent = getMaxComponent( specularTex );
...
لتلخيص
... وإذا قرأته هنا ، فأنت على الأرجح تريد أن تعمق قليلاً.ما يبدو بسيطًا في الحياة الواقعية ليس غالبًا ما يكون كذلك ، ونقل البيانات إلى gbuffer لم يكن Witcher 3 استثناءً. لقد عرضت عليك أبسط الإصدارات من تظليل البكسل المسؤول عنها ، كما قدمت ملاحظات عامة تتعلق بالتظليل المؤجل بشكل عام.بالنسبة إلى أكثر المرضى ، هناك خياران لتظليل البيكسلات في pastebin:الخيار 1 - مع نسيج براقالخيار 2 - بدون نسيج براقالجزء 10. الستائر المطر في المسافة
في هذا الجزء سوف نلقي نظرة على التأثير الجوي الرائع الذي يعجبني حقًا - ستائر المطر / الضوء البعيدة بالقرب من الأفق. في اللعبة ، من الأسهل لقاءهم في جزر Skellig.أنا شخصياً أحب هذه الظاهرة في الغلاف الجوي وكنت أشعر بالفضول حول كيفية قيام مبرمجي الرسومات CD Projekt Red بتنفيذها. دعونا معرفة ذلك!فيما يلي شاشتان قبل وبعد تطبيق ستائر المطر:للستائر المطربعد ستائر المطرالهندسة
أولاً ، سوف نركز على الهندسة. الفكرة هي استخدام اسطوانة صغيرة:أسطوانة في الفضاء المحليمن وجهة نظر موضعها في المساحة المحلية ، فهي صغيرة جدًا - موقعها في النطاق (0.0 - 1.0).تبدو دائرة الإدخال لمكالمة السحب هذه ...التالي مهم بالنسبة لنا هنا: Texcoords و Instance_Transform.يتم تغليف Texcoords بكل بساطة: U من القواعد العليا والسفلية في الفترة [0.02777 - 1.02734]. V في القاعدة السفلية هي 1.0 ، وفي الأعلى - 0.0. كما ترون ، يمكنك ببساطة إنشاء هذه الشبكة حتى إجرائياً.بعد تلقي هذه الأسطوانة الصغيرة في المساحة المحلية ، قمنا بضربها في المصفوفة العالمية المقدمة لكل مثيل لعنصر الإدخال INSTANCE_TRANSFORM. دعونا نتحقق من قيم هذه المصفوفة:تبدو مخيفة جدا ، أليس كذلك؟ ولكن لا تقلق ، فسوف نحلل هذه المصفوفة ونرى ما يخفيه! النتائج مثيرة جدًا للاهتمام: من المهم معرفة موضع الكاميرا في هذا الإطار المحدد: (-116.5338 ، 234.8695 ، 2.09) كما ترون ، قمنا بتوسيع الأسطوانة لجعلها كبيرة جدًا في مساحة العالم (في المحور TW3 ، ارتفع المحور Z) ، ، وتحولت. إليك ما تبدو عليه الاسطوانة بعد التحويل باستخدام تظليل قمة الرأس:XMMATRIX mat( -227.7472, 159.8043, 374.0736, -116.4951,
-194.7577, -173.3836, -494.4982, 238.6908,
-14.16466, -185.4743, 784.564, -1.45565,
0.0, 0.0, 0.0, 1.0 );
mat = XMMatrixTranspose( mat );
XMVECTOR vScale;
XMVECTOR vRotateQuat;
XMVECTOR vTranslation;
XMMatrixDecompose( &vScale, &vRotateQuat, &vTranslation, mat );
// ...
XMMATRIX matRotate = XMMatrixRotationQuaternion( vRotateQuat );
vRotateQuat: (0.0924987569, -0.314900011, 0.883411944, -0.334462732)
vScale: (299.999969, 300.000000, 1000.00012)
vTranslation: (-116.495102, 238.690796, -1.45564997)
اسطوانة بعد التحويل بواسطة تظليل قمة الرأس. انظر كيف تقع نسبة إلى هرم الرؤية.فيرتكس شادر
تعتمد هندسة الإدخال وتظليل قمة الرأس بشكل صارم على بعضها البعض.دعنا نلقي نظرة فاحصة على رمز المجمّع لتظليل قمة الرأس: إلى جانب تيككووردس البسيطة المارة (السطر 0) و Instance_LOD_Params (السطر 8) ، هناك حاجة إلى عنصرين إضافيين للإخراج: SV_Position (هذا واضح) والارتفاع (المكون .z) للموضع في العالم. تذكر أن المساحة المحلية في النطاق [0-1]؟ لذلك ، قبل تطبيق المصفوفة العالمية ، يستخدم تظليل قمة الرأس مقياسًا وانحرافًا لتغيير الوضع المحلي. خطوة ذكية! في هذه الحالة ، يكون المقياس = float3 (4 ، 4 ، 2) ، والتحيز = float3 (-2 ، -2 ، -1). < النمط الملموس بين الأسطر 9 و 28 هو ضرب مصفوفة رئيسية صفين. دعنا فقط نلقي نظرة على تظليل قمة الرأس النهائي على HLSL:vs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb1[7], immediateIndexed
dcl_constantbuffer cb2[6], immediateIndexed
dcl_input v0.xyz
dcl_input v1.xy
dcl_input v4.xyzw
dcl_input v5.xyzw
dcl_input v6.xyzw
dcl_input v7.xyzw
dcl_output o0.xyz
dcl_output o1.xyzw
dcl_output_siv o2.xyzw, position
dcl_temps 2
0: mov o0.xy, v1.xyxx
1: mul r0.xyzw, v5.xyzw, cb1[6].yyyy
2: mad r0.xyzw, v4.xyzw, cb1[6].xxxx, r0.xyzw
3: mad r0.xyzw, v6.xyzw, cb1[6].zzzz, r0.xyzw
4: mad r0.xyzw, cb1[6].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw
5: mad r1.xyz, v0.xyzx, cb2[4].xyzx, cb2[5].xyzx
6: mov r1.w, l(1.000000)
7: dp4 o0.z, r1.xyzw, r0.xyzw
8: mov o1.xyzw, v7.xyzw
9: mul r0.xyzw, v5.xyzw, cb1[0].yyyy
10: mad r0.xyzw, v4.xyzw, cb1[0].xxxx, r0.xyzw
11: mad r0.xyzw, v6.xyzw, cb1[0].zzzz, r0.xyzw
12: mad r0.xyzw, cb1[0].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw
13: dp4 o2.x, r1.xyzw, r0.xyzw
14: mul r0.xyzw, v5.xyzw, cb1[1].yyyy
15: mad r0.xyzw, v4.xyzw, cb1[1].xxxx, r0.xyzw
16: mad r0.xyzw, v6.xyzw, cb1[1].zzzz, r0.xyzw
17: mad r0.xyzw, cb1[1].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw
18: dp4 o2.y, r1.xyzw, r0.xyzw
19: mul r0.xyzw, v5.xyzw, cb1[2].yyyy
20: mad r0.xyzw, v4.xyzw, cb1[2].xxxx, r0.xyzw
21: mad r0.xyzw, v6.xyzw, cb1[2].zzzz, r0.xyzw
22: mad r0.xyzw, cb1[2].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw
23: dp4 o2.z, r1.xyzw, r0.xyzw
24: mul r0.xyzw, v5.xyzw, cb1[3].yyyy
25: mad r0.xyzw, v4.xyzw, cb1[3].xxxx, r0.xyzw
26: mad r0.xyzw, v6.xyzw, cb1[3].zzzz, r0.xyzw
27: mad r0.xyzw, cb1[3].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw
28: dp4 o2.w, r1.xyzw, r0.xyzw
29: ret
cbuffer cbPerFrame : register (b1)
{
row_major float4x4 g_viewProjMatrix;
row_major float4x4 g_rainShaftsViewProjMatrix;
}
cbuffer cbPerObject : register (b2)
{
float4x4 g_mtxWorld;
float4 g_modelScale;
float4 g_modelBias;
}
struct VS_INPUT
{
float3 PositionW : POSITION;
float2 Texcoord : TEXCOORD;
float3 NormalW : NORMAL;
float3 TangentW : TANGENT;
float4 InstanceTransform0 : INSTANCE_TRANSFORM0;
float4 InstanceTransform1 : INSTANCE_TRANSFORM1;
float4 InstanceTransform2 : INSTANCE_TRANSFORM2;
float4 InstanceLODParams : INSTANCE_LOD_PARAMS;
};
struct VS_OUTPUT
{
float3 TexcoordAndZ : Texcoord0;
float4 LODParams : LODParams;
float4 PositionH : SV_Position;
};
VS_OUTPUT RainShaftsVS( VS_INPUT Input )
{
VS_OUTPUT Output = (VS_OUTPUT)0;
//
Output.TexcoordAndZ.xy = Input.Texcoord;
Output.LODParams = Input.InstanceLODParams;
//
float3 meshScale = g_modelScale.xyz; // float3( 4, 4, 2 );
float3 meshBias = g_modelBias.xyz; // float3( -2, -2, -1 );
float3 PositionL = Input.PositionW * meshScale + meshBias;
// instanceWorld float4s:
float4x4 matInstanceWorld = float4x4(Input.InstanceTransform0, Input.InstanceTransform1,
Input.InstanceTransform2 , float4(0, 0, 0, 1) );
// (.z)
float4x4 matWorldInstanceLod = mul( g_rainShaftsViewProjMatrix, matInstanceWorld );
Output.TexcoordAndZ.z = mul( float4(PositionL, 1.0), transpose(matWorldInstanceLod) ).z;
// SV_Posiiton
float4x4 matModelViewProjection = mul(g_viewProjMatrix, matInstanceWorld );
Output.PositionH = mul( float4(PositionL, 1.0), transpose(matModelViewProjection) );
return Output;
}
مقارنة ظلالي (يسار) والأصل (يمين):الاختلافات لا تؤثر على الحسابات. أنا حقنت شادر بلدي في الإطار وكان كل شيء لا يزال بخير!بكسل تظليل
اخيرا
للبدء ، سوف أعرض عليك المدخلات:يتم استخدام نسيجين هنا: نسيج الضوضاء ومخزن مؤقت للعمق:القيم من مخازن ثابتة:ورمز المجمّع لتظليل البكسل:ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb0[8], immediateIndexed
dcl_constantbuffer cb2[3], immediateIndexed
dcl_constantbuffer cb12[23], immediateIndexed
dcl_constantbuffer cb4[8], immediateIndexed
dcl_sampler s0, mode_default
dcl_sampler s15, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t15
dcl_input_ps linear v0.xyz
dcl_input_ps linear v1.w
dcl_input_ps_siv v2.xy, position
dcl_output o0.xyzw
dcl_temps 1
0: mul r0.xy, cb0[0].xxxx, cb4[5].xyxx
1: mad r0.xy, v0.xyxx, cb4[4].xyxx, r0.xyxx
2: sample_indexable(texture2d)(float,float,float,float) r0.x, r0.xyxx, t0.xyzw, s0
3: add r0.y, -cb4[2].x, cb4[3].x
4: mad_sat r0.x, r0.x, r0.y, cb4[2].x
5: mul r0.x, r0.x, v0.y
6: mul r0.x, r0.x, v1.w
7: mul r0.x, r0.x, cb4[1].x
8: mul r0.yz, v2.xxyx, cb0[1].zzwz
9: sample_l(texture2d)(float,float,float,float) r0.y, r0.yzyy, t15.yxzw, s15, l(0)
10: mad r0.y, r0.y, cb12[22].x, cb12[22].y
11: mad r0.y, r0.y, cb12[21].x, cb12[21].y
12: max r0.y, r0.y, l(0.000100)
13: div r0.y, l(1.000000, 1.000000, 1.000000, 1.000000), r0.y
14: add r0.y, r0.y, -v0.z
15: mul_sat r0.y, r0.y, cb4[6].x
16: mul_sat r0.x, r0.y, r0.x
17: mad r0.y, cb0[7].y, r0.x, -r0.x
18: mad r0.x, cb4[7].x, r0.y, r0.x
19: mul r0.xyz, r0.xxxx, cb4[0].xyzx
20: log r0.xyz, r0.xyzx
21: mul r0.xyz, r0.xyzx, l(2.200000, 2.200000, 2.200000, 0.000000)
22: exp r0.xyz, r0.xyzx
23: mul r0.xyz, r0.xyzx, cb2[2].xyzx
24: mul o0.xyz, r0.xyzx, cb2[2].wwww
25: mov o0.w, l(0)
26: ret
واو!
كمية كبيرة جدا ، ولكن في الواقع ، كل شيء ليس سيئا للغاية.ما الذي يحدث هنا؟ أولاً ، نحسب الأشعة فوق البنفسجية المتحركة باستخدام الوقت المنقضي من cbuffer (cb0 [0] .x) والمقياس / الإزاحة. تستخدم هذه texcoords لأخذ عينات من نسيج الضوضاء (السطر 2).بعد تلقي قيمة الضوضاء من النسيج ، نقوم بتحريف بين قيم min / max (عادةً 0 و 1).ثم نقوم بالضرب ، على سبيل المثال ، عن طريق إحداثيات الملمس V (تذكر أن الإحداثي V ينتقل من 1 إلى 0؟) - السطر 5.وهكذا ، قمنا بحساب "قناع السطوع" - يبدو كما يلي:لاحظ أن الكائنات البعيدة (المنارة ، الجبال ...) قد اختفت. حدث هذا لأن الأسطوانة تجتاز اختبار العمق - الأسطوانة ليست على المستوى البعيد ويتم رسمها فوق هذه الكائنات:اختبار العمقنريد أن نحاكي أن ستارة المطر أبعد (ولكن ليس بالضرورة في الطائرة البعيدة). للقيام بذلك ، نقوم بحساب قناع آخر ، "قناع الكائنات البعيدة".يتم حسابها بواسطة الصيغة التالية:farObjectsMask = saturate( (FrustumDepth - CylinderWorldSpaceHeight) * 0.001 );
(يتم أخذ 0.001 من المخزن المؤقت) ، والذي يعطينا القناع المطلوب:(في الجزء الخاص بتأثير Sharpen ، لقد أوضحت بالفعل سطحيًا كيف يتم استخراج عمق هرم الرؤية من المخزن المؤقت للعمق).شخصيًا ، يبدو لي أن هذا التأثير يمكن تحقيقه أقل تكلفة دون حساب الارتفاع في مساحة العالم عن طريق ضرب عمق هرم الرؤية برقم أصغر ، على سبيل المثال 0.0004.عند مضاعفة القناع ، يتم الحصول على القناع الأخير:بعد تلقي هذا القناع النهائي (السطر 16) ، نقوم بإجراء استيفاء آخر ، لا يفعل شيئًا تقريبًا (على الأقل في الحالة المختبرة) ، ثم نضرب القناع النهائي بلون الستائر (السطر 19) ، ونقوم بتصحيح غاما (الخطوط 20) -22) والضرب النهائي (23-24).في النهاية ، نرجع لونًا بقيمة صفر ألفا. هذا بسبب تمكين الخلط على هذا المسار:FinalColor = SourceColor * 1.0 + (1.0 - SourceAlpha) * DestColor
إذا كنت لا تفهم تمامًا كيف يعمل الاختلاط ، فإليك توضيحًا موجزًا:SourceColor هو إخراج RGB من تظليل البكسل ، و DestColor هو لون RGB الحالي للبكسل في هدف التجسيد . منذ SourceAlpha يساوي دائما إلى 0.0، والمعادلة أعلاه يقلل إلى: FinalColor = SourceColor + DestColor
.ببساطة ، هنا نقوم بإجراء خلط المضافة. إذا تم إرجاع تظليل البكسل (0 ، 0 ، 0) ، فسيظل اللون كما هو.إليك رمز HLSL النهائي - أعتقد أنه بعد التوضيح سيكون فهمه أسهل كثيرًا: يمكنني أن أقول لحسن الحظ أن تظليل البكسل الخاص بي يقوم بإنشاء نفس رمز التجميع كما في النص الأصلي. أتمنى أن تستمتعوا بالمقال. شكرا للقراءة!struct VS_OUTPUT
{
float3 TexcoordAndWorldspaceHeight : Texcoord0;
float4 LODParams : LODParams; // float4(1,1,1,1)
float4 PositionH : SV_Position;
};
float getFrustumDepth( in float depth )
{
// from [1-0] to [0-1]
float d = depth * cb12_v22.x + cb12_v22.y;
// special coefficents
d = d * cb12_v21.x + cb12_v21.y;
// return frustum depth
return 1.0 / max(d, 1e-4);
}
float4 EditedShaderPS( in VS_OUTPUT Input ) : SV_Target0
{
// * Input from Vertex Shader
float2 InputUV = Input.TexcoordAndWorldspaceHeight.xy;
float WorldHeight = Input.TexcoordAndWorldspaceHeight.z;
float LODParam = Input.LODParams.w;
// * Inputs
float elapsedTime = cb0_v0.x;
float2 uvAnimation = cb4_v5.xy;
float2 uvScale = cb4_v4.xy;
float minValue = cb4_v2.x; // 0.0
float maxValue = cb4_v3.x; // 1.0
float3 shaftsColor = cb4_v0.rgb; // RGB( 147, 162, 173 )
float3 finalColorFilter = cb2_v2.rgb; // float3( 1.175, 1.296, 1.342 );
float finalEffectIntensity = cb2_v2.w;
float2 invViewportSize = cb0_v1.zw;
float depthScale = cb4_v6.x; // 0.001
// sample noise
float2 uvOffsets = elapsedTime * uvAnimation;
float2 uv = InputUV * uvScale + uvOffsets;
float disturb = texture0.Sample( sampler0, uv ).x;
// * Intensity mask
float intensity = saturate( lerp(minValue, maxValue, disturb) );
intensity *= InputUV.y; // transition from (0, 1)
intensity *= LODParam; // usually 1.0
intensity *= cb4_v1.x; // 1.0
// Sample depth
float2 ScreenUV = Input.PositionH.xy * invViewportSize;
float hardwareDepth = texture15.SampleLevel( sampler15, ScreenUV, 0 ).x;
float frustumDepth = getFrustumDepth( hardwareDepth );
// * Calculate mask covering distant objects behind cylinder.
// Seems that the input really is world-space height (.z component, see vertex shader)
float depth = frustumDepth - WorldHeight;
float distantObjectsMask = saturate( depth * depthScale );
// * calculate final mask
float finalEffectMask = saturate( intensity * distantObjectsMask );
// cb0_v7.y and cb4_v7.x are set to 1.0 so I didn't bother with naming them :)
float paramX = finalEffectMask;
float paramY = cb0_v7.y * finalEffectMask;
float effectAmount = lerp(paramX, paramY, cb4_v7.x);
// color of shafts comes from contant buffer
float3 effectColor = effectAmount * shaftsColor;
// gamma correction
effectColor = pow(effectColor, 2.2);
// final multiplications
effectColor *= finalColorFilter;
effectColor *= finalEffectIntensity;
// return with zero alpha 'cause the blending used here is:
// SourceColor * 1.0 + (1.0 - SrcAlpha) * DestColor
return float4( effectColor, 0.0 );
}