مرحبا يا هبر! تقدم هذه المقالة تطبيقًا بسيطًا
لخرائط الظل العاكسة (يتم وصف الخوارزمية في
مقال سابق ). بعد ذلك ، سوف أشرح كيف فعلت ذلك وما هي المخاطر. سيتم أيضًا النظر في بعض التحسينات المحتملة.
الشكل 1: من اليسار إلى اليمين: بدون RSM ، مع RSM ، الفرقالنتيجة
في
الشكل 1 ، يمكنك رؤية النتيجة التي تم الحصول عليها باستخدام
RSM . لإنشاء هذه الصور ، تم استخدام "Stanford Rabbit" وثلاثة رباعيات متعددة الألوان. في الصورة على اليسار ، يمكنك رؤية نتيجة التقديم بدون
RSM ، باستخدام
ضوء موضعي فقط. كل شيء في الظل أسود تماما. تُظهر الصورة في الوسط النتيجة باستخدام
RSM . الاختلافات التالية ملحوظة: في كل مكان هناك ألوان أكثر إشراقًا ، وردية اللون ، تغمر الأرض والأرانب ، التظليل ليس أسودًا تمامًا. تظهر الصورة الأخيرة الفرق بين الأول والثاني ، وبالتالي مساهمة
RSM . تكون الحواف والأدوات المشدودة مرئية في الصورة الوسطى ، ولكن يمكن حلها عن طريق ضبط حجم اللب ، وشدة الإضاءة غير المباشرة ، وعدد العينات.
التنفيذ
تم تنفيذ الخوارزمية على محركها الخاص. تتم كتابة التظليل في HLSL ، ويكون التجسيد في DirectX 11. قمت بالفعل بإعداد
التظليل المؤجل وتعيين
الظل للضوء الاتجاه (مصدر ضوء الاتجاه) قبل كتابة هذه المقالة. أولاً ، قمت بتطبيق
RSM للضوء الاتجاهي وفقط بعد إضافة الدعم
لخريطة الظل و
RSM للضوء الموضعي.
تمديد خريطة الظل
تقليديًا ، لا تعد
خرائط الظل (SM) سوى خريطة للعمق. هذا يعني أنك لا تحتاج حتى إلى تظليل بكسل / جزء لتجميع SM. ومع ذلك ، ستحتاج إلى عدد قليل من المخازن المؤقتة الإضافية لـ
RSM تحتاج إلى تخزين موقع مساحة العالم ، ومساحة مساحة العالم
وتدفقه (خرج الضوء). هذا يعني أنك تحتاج إلى تظليل بكسل / تجزئة ذو أهداف تجسيد متعددة. ضع في اعتبارك أنه من أجل هذه التقنية تحتاج إلى قطع
وجه الإعدام ، وليس الجزء الأمامي.
يعد استخدام الحواف الأمامية
للتخلص من الوجه طريقة مستخدمة على نطاق واسع لتجنب آثار الظل ، لكن هذا لا يعمل مع
RSM .
يمكنك تمرير المواقف والمساحات الطبيعية في العالم إلى تظليل البكسل وكتابتها إلى المخازن المؤقتة المناسبة. إذا كنت تستخدم
التعيين العادي ،
فقم أيضًا بحسابها في تظليل البكسل.
يتم احتساب
التدفق هناك ، بضرب مادة البياض حسب لون مصدر الضوء.
للضوء الموضعي ، تحتاج إلى ضرب القيمة الناتجة بزاوية الحدوث.
للضوء الاتجاهي ، يتم الحصول على صورة غير مظللة.
التحضير لحساب الإضاءة
هناك بعض الأشياء التي تحتاج إلى القيام بها للمرور الرئيسي. يجب ربط جميع المخازن المؤقتة المستخدمة في الظل بتمرير القوام. تحتاج أيضا إلى أرقام عشوائية. تقول
المقالة الرسمية أنك تحتاج إلى حساب هذه الأرقام مسبقًا وحفظها في مخزن مؤقت لتقليل عدد العمليات في تمرير أخذ العينات
RSM . نظرًا لأن الخوارزمية ثقيلة من حيث الأداء ، فأنا أتفق تمامًا مع المقالة الرسمية. يوصى أيضًا بالالتزام بالتماسك الزمني (استخدم نفس نمط أخذ العينات لجميع حسابات الإضاءة غير المباشرة). سيمنع هذا الخفقان عندما يستخدم كل إطار ظل مختلف.
تحتاج إلى رقمين عشريين عشوائيين في النطاق [0 ، 1] لكل عينة. سيتم استخدام هذه الأرقام العشوائية لتحديد إحداثيات العينة. ستحتاج أيضًا إلى المصفوفة نفسها التي تستخدمها لتحويل المواضع من مساحة العالم (مساحة العالم) إلى مساحة الظل (مساحة مصدر الضوء). ستحتاج أيضًا إلى مثل هذه المعلمات لأخذ العينات ، والتي ستعطي لونًا أسود إذا قمت بالعينة خارج حدود النسيج.
تمرير الإضاءة
الآن الجزء الصعب لفهم. نوصي بحساب الإضاءة غير المباشرة بعد حساب الإضاءة المباشرة لمصدر ضوء معين. هذا لأنك تحتاج إلى شاشة بملء الشاشة للحصول على
إضاءة اتجاهية . ومع ذلك ، بالنسبة
للضوء الموضعي والنقطي ، عادة ما تحتاج إلى استخدام شبكات ذات شكل معين مع
إعدام لملء وحدات بكسل أقل.
في الكود أدناه ، يتم حساب الإضاءة غير المباشرة للبكسل. بعد ذلك ، سأشرح ما يحدث هناك.
float3 DoReflectiveShadowMapping(float3 P, bool divideByW, float3 N) { float4 textureSpacePosition = mul(lightViewProjectionTextureMatrix, float4(P, 1.0)); if (divideByW) textureSpacePosition.xyz /= textureSpacePosition.w; float3 indirectIllumination = float3(0, 0, 0); float rMax = rsmRMax; for (uint i = 0; i < rsmSampleCount; ++i) { float2 rnd = rsmSamples[i].xy; float2 coords = textureSpacePosition.xy + rMax * rnd; float3 vplPositionWS = g_rsmPositionWsMap .Sample(g_clampedSampler, coords.xy).xyz; float3 vplNormalWS = g_rsmNormalWsMap .Sample(g_clampedSampler, coords.xy).xyz; float3 flux = g_rsmFluxMap.Sample(g_clampedSampler, coords.xy).xyz; float3 result = flux * ((max(0, dot(vplNormalWS, P – vplPositionWS)) * max(0, dot(N, vplPositionWS – P))) / pow(length(P – vplPositionWS), 4)); result *= rnd.x * rnd.x; indirectIllumination += result; } return saturate(indirectIllumination * rsmIntensity); }
الوسيطة الأولى للدالة هي
P ، وهي موضع مساحة العالم (في مساحة العالم) لوحدة بكسل محددة.
يتم استخدام
DivideByW للتقسيم المحتملين الضروري للحصول على القيمة
Z الصحيحة.
N هو عالم الفضاء الطبيعي.
float4 textureSpacePosition = mul(lightViewProjectionTextureMatrix, float4(P, 1.0)); if (divideByW) textureSpacePosition.xyz /= textureSpacePosition.w; float3 indirectIllumination = float3(0, 0, 0); float rMax = rsmRMax;
في هذا الجزء من الكود ، يتم حساب موضع الفضاء الخفيف (نسبة إلى مصدر الضوء) ، وتهيئة متغير الإضاءة غير المباشر ، حيث سيتم
جمع القيم المحسوبة من كل عينة ، ويتم
تعيين متغير
rMax من معادلة الإضاءة في
المقالة الرسمية ، التي
سأشرحها في القسم التالي.
for (uint i = 0; i < rsmSampleCount; ++i) { float2 rnd = rsmSamples[i].xy; float2 coords = textureSpacePosition.xy + rMax * rnd; float3 vplPositionWS = g_rsmPositionWsMap .Sample(g_clampedSampler, coords.xy).xyz; float3 vplNormalWS = g_rsmNormalWsMap .Sample(g_clampedSampler, coords.xy).xyz; float3 flux = g_rsmFluxMap.Sample(g_clampedSampler, coords.xy).xyz;
هنا نبدأ الدورة ونعد متغيراتنا للمعادلة. لأغراض التحسين ، تحتوي العينات العشوائية التي قمت بحسابها بالفعل على إزاحة إحداثيات ، أي للحصول على إحداثيات الأشعة فوق البنفسجية ، أحتاج فقط إلى إضافة
rMax * rnd إلى إحداثيات الفضاء الخفيف. إذا كانت الأشعة فوق البنفسجية الناتجة خارج النطاق [0.1] ، يجب أن تكون العينات سوداء. وهو أمر منطقي ، لأنه يتجاوز نطاق الإضاءة.
float3 result = flux * ((max(0, dot(vplNormalWS, P – vplPositionWS)) * max(0, dot(N, vplPositionWS – P))) / pow(length(P – vplPositionWS), 4)); result *= rnd.x * rnd.x; indirectIllumination += result; } return saturate(indirectIllumination * rsmIntensity);
هذا هو الجزء الذي يتم فيه حساب معادلة الإضاءة غير المباشرة (
الشكل 2 ) ، ويتم وزنها أيضًا وفقًا للمسافة من إحداثيات الفضاء الخفيف إلى العينة. تبدو المعادلة مخيفة ، ولا يساعد الكود في فهم كل شيء ، لذلك سأشرح بمزيد من التفصيل.
المتغير
ph (فاي) هو
تدفق الضوء ، وهو شدة الإشعاع.
المقالة السابقة تصف
التدفق بمزيد من التفاصيل.
جداول
التدفق مع اثنين من الأعمال الفنية العددية. الأول هو بين مصدر الضوء الطبيعي (texel) والاتجاه من مصدر الضوء إلى الموضع الحالي. والثاني هو بين التيار الطبيعي ومتجه الاتجاه من الموضع الحالي إلى موضع مصدر الضوء (texel). من أجل عدم تقديم مساهمة سلبية في الإضاءة (يتضح إذا لم يتم إضاءة البيكسل) ، تقتصر المنتجات العددية على النطاق [0 ،،]. في هذه المعادلة ، يتم التطبيع في النهاية ، أفترض ، لأسباب تتعلق بالأداء. من المقبول بنفس القدر تطبيع متجهات الاتجاه قبل أداء المنتجات العددية.
الشكل 2: معادلة إضاءة نقطة مع الموضع x ومصدر ضوء البكسل الاتجاهي العادي pيمكن خلط نتيجة هذا التمرير مع خلفية (إضاءة مباشرة) ، وستكون النتيجة كما في
الشكل 1 .
مطبات
عند تنفيذ هذه الخوارزمية ، واجهت بعض المشاكل. سأتحدث عن هذه المشكلات حتى لا تتدخل في نفس المشكلة.
العينات خاطئة
لقد أمضيت وقتًا طويلاً في اكتشاف السبب الذي جعل الإضاءة غير المباشرة تبدو متكررة. تكون قوام Crytek Sponza مخفية ، لذلك تحتاج إلى أداة لف ملفوفة لذلك. لكن بالنسبة لـ
RSM ، فهي ليست مناسبة جدًا.
أوبنجليقوم OpenGL بتعيين نسق RSM إلى GL_CLAMP_TO_BORDER
القيم المخصصة
لتحسين سير العمل ، من المهم أن تكون قادرًا على تغيير بعض المتغيرات بضغطة زر واحدة. على سبيل المثال ، شدة الإضاءة غير المباشرة ومجموعة أخذ العينات (
rMax ). يجب ضبط هذه المعلمات لكل مصدر ضوء. إذا كان لديك نطاق عينات كبير ، فستحصل على إضاءة غير مباشرة من كل مكان ، وهو أمر مفيد للمشاهد الكبيرة. لمزيد من الإضاءة المحلية غير المباشرة ، ستحتاج إلى نطاق أصغر. يوضح
الشكل 3 الإضاءة غير المباشرة العالمية والمحلية.
الشكل 3: مظاهرة التبعية rMax .مرور منفصل
في البداية ، اعتقدت أن بإمكاني إنتاج إضاءة غير مباشرة في ظلال ، حيث أفكر في الإضاءة المباشرة. بالنسبة
للإضاءة الاتجاهية ، يعمل هذا لأنك لا تزال ترسم رباعيًا بملء الشاشة. ومع ذلك ، بالنسبة
للضوء الموضعي والنقطي ، فأنت بحاجة إلى تحسين حساب الإضاءة غير المباشرة. لذلك ، اعتبرت الإضاءة غير المباشرة مرورًا منفصلاً ، وهو أمر ضروري إذا كنت تريد أيضًا إجراء
الاستيفاء في مساحة الشاشة .
مخبأ
هذه الخوارزمية ليست صديقة مع ذاكرة التخزين المؤقت على الإطلاق. ينفذ أخذ العينات في نقاط عشوائية في العديد من القوام. عدد العينات بدون تحسينات كبير أيضًا بشكل غير مقبول. بدقة 1280 * 720 وعدد عينات
RSM 400 ، ستجعل 1.105.920.000 عينة لكل مصدر ضوء.
إيجابيات وسلبيات
سوف أدرج إيجابيات وسلبيات هذه الخوارزمية حساب الإضاءة غير المباشرة.
ل | ضد |
من السهل أن نفهم الخوارزمية | ليس أصدقاء مع ذاكرة التخزين المؤقت على الإطلاق |
يتكامل بشكل جيد مع العارض المؤجل | الإعداد المتغير مطلوب |
يمكن استخدامها في الخوارزميات الأخرى ( LPV ) | الاختيار القسري بين الإضاءة غير المباشرة المحلية والعالمية |
التحسينات
لقد بذلت عدة محاولات لزيادة سرعة هذه الخوارزمية. كما هو موضح في
المقالة الرسمية ، يمكنك تنفيذ
الاستيفاء بين مساحة الشاشة . فعلت هذا ، وجعل أسرع قليلا. فيما يلي سوف أصف بعض التحسينات ، وأجري مقارنة (في الإطارات في الثانية الواحدة) بين التطبيقات التالية ، باستخدام مشهد به 3 جدران وأرنب: بدون
RSM ، تنفيذ ساذج لـ
RSM ، محرف بواسطة
RSM .
Z تحقق
أحد أسباب عمل جهاز
RSM الخاص بك بشكل غير فعال هو أنني حسبت أيضًا الإضاءة غير المباشرة لوحدات البكسل التي كانت جزءًا من صندوق السماء. Skybox بالتأكيد لا يحتاج إليها.
أخذ العينات وحدة المعالجة المركزية عشوائي
لن يوفر الحساب الأولي للعينات مزيدًا من التماسك الزمني فحسب ، بل يوفر لك أيضًا الحاجة إلى إعادة حساب هذه العينات في التظليل.
استيفاء مساحة الشاشة
يقترح
مقال رسمي استخدام هدف تقديم دقة منخفضة لحساب الإضاءة غير المباشرة. بالنسبة للمشاهد ذات العديد من القواعد السلس والجدران المستقيمة ، يمكن بسهولة تحريف معلومات الإضاءة بين النقاط بدقة أقل. لن أصف الاستيفاء بالتفصيل حتى تكون هذه المقالة أقصر قليلاً.
الخاتمة
فيما يلي النتائج لعدد مختلف من العينات. لدي بعض التعليقات بشأن هذه النتائج:
- منطقياً ، يظل FPS حوالي 700 لعدد مختلف من العينات عندما لا يتم تنفيذ حساب RSM .
- الاستيفاء يعطي بعض النفقات العامة وغير مفيد للغاية مع عدد قليل من العينات.
- حتى مع 100 عينة ، تبدو الصورة النهائية جيدة. قد يكون هذا بسبب الاستيفاء ، الذي "يطمس" الإضاءة غير المباشرة.
عدد العينات | FPS لـ No RSM | FPS لـ ساذج RSM | FPS لـ RSM محرف |
100 | ~ 700 | 152 | 264 |
200 | ~ 700 | 89 | 179 |
300 | ~ 700 | 62 | 138 |
400 | ~ 700 | 44 | 116 |