"ليس هناك ما هو أسوأ من صورة واضحة لمفهوم ضبابي." - المصور انسل ادامز
في
الجزء الأول من المقالة ، أنشأنا أداة تتبع أشعة Whited ، قادرة على تتبع الانعكاسات الكاملة والظلال الحادة. لكننا نفتقر إلى آثار التشويش: انعكاس منتشر ، انعكاسات لامعة وظلال ناعمة.
استنادًا إلى
الشفرة التي لدينا
بالفعل ، سنحل بشكل متكرر
معادلة التقديم التي صاغها James Cajia في عام 1986 وسنحول العارض إلى أداة
تتبع مسار قادرة على نقل التأثيرات المذكورة أعلاه. سنستخدم مرة أخرى C # للنصوص البرمجية و HLSL للتظليل. يتم تحميل الرمز إلى
Bitbucket .
هذه المقالة رياضية أكثر بكثير من المقالة السابقة ، لكن لا تقلق. سأحاول شرح كل صيغة بأكبر قدر ممكن من الوضوح. هناك حاجة إلى الصيغ هنا لمعرفة ما يحدث
ولماذا يعمل العارض لدينا ، لذلك أوصي بمحاولة فهمها وإذا كان هناك شيء غير واضح ، اطرح أسئلة في التعليقات على المقالة الأصلية.
يتم تقديم الصورة أدناه باستخدام خريطة
Graffiti Shelter من موقع HDRI Haven. تم تقديم صور أخرى في هذه المقالة باستخدام بطاقة
كيارا 9 الغسق .
تقديم معادلة
من وجهة النظر الرسمية ، فإن مهمة العارض الواقعي هي حل معادلة التقديم ، والتي تتم كتابتها على النحو التالي:
L(x،\، vec omegao)=Le(x،\، vec omegao)+ int Omegafr(x،\، vec omegai، ، vec omegao) ،( vec omegai cdot vecn) ،L(x، ، vec omegai) ،d vec omegai
دعونا نحللها. هدفنا النهائي هو تحديد سطوع بكسل الشاشة. تعطينا معادلة التقديم مقدار الإضاءة
L(x،\، vec omegao) قادم من نقطة
x (نقطة حدوث الحزمة) في الاتجاه
vec omegao (الاتجاه الذي يسقط فيه الشعاع). قد يكون السطح نفسه مصدر ضوء ينبعث منه ضوء
Le(x،\، vec omegao) في اتجاهنا. معظم الأسطح لا تفعل ذلك ، لذا فهي تعكس الضوء من الخارج فقط. هذا هو السبب في استخدام التكامل. يتراكم الإضاءة القادمة من كل اتجاه ممكن لنصف الكرة الأرضية.
أوميغا حول الوضع الطبيعي (لذلك ، بينما نأخذ في الاعتبار الإضاءة المتساقطة على السطح
من الأعلى ، وليس
من الداخل ، والتي قد تكون ضرورية للمواد الشفافة)
الجزء الأول
fr تسمى دالة توزيع الانعكاس ثنائي الاتجاه (BRDF). تصف هذه الوظيفة بصريًا نوع المواد التي نتعامل معها: معدن أو عازل ، داكن أو مشرق ، لامع أو غير لامع. يحدد BRDF نسبة الإضاءة القادمة
vec omegai الذي ينعكس في الاتجاه
vec omegao . من الناحية العملية ، يتم تنفيذ ذلك باستخدام ناقل مكون من ثلاثة مكونات بقيم الأحمر والأخضر والأزرق في الفاصل الزمني
[0،1] .
الجزء الثاني -
( vec omegai cdot vecn) تعادل
1 cos theta أين
theta - الزاوية بين الضوء الساقط والسطح العادي
vecn . تخيل عمودًا من أشعة الضوء المتوازية تسقط على السطح بشكل عمودي. تخيل الآن نفس الشعاع الساقط على السطح بزاوية مسطحة. سيتم توزيع الضوء على مساحة أكبر ، ولكنه يعني أيضًا أن كل نقطة في هذه المنطقة ستبدو أغمق. هناك حاجة إلى جيب التمام لأخذ هذا بعين الاعتبار.
أخيرا ، تم الحصول على الإضاءة نفسها من
vec omegai يتم تحديده بشكل متكرر باستخدام نفس المعادلة. أي الإضاءة عند هذه النقطة
x يعتمد على الضوء الساقط من جميع الاتجاهات الممكنة في النصف العلوي من الكرة الأرضية. في كل من هذه الاتجاهات من نقطة
x هناك نقطة أخرى
x prime ، يعتمد سطوعها مرة أخرى على الضوء الساقط من جميع الاتجاهات الممكنة للنصف العلوي من هذه النقطة. يتم تكرار جميع الحسابات.
إليك ما يحدث هنا ، هذه معادلة تكاملية لا نهائية مع عدد لا نهائي من مناطق التكامل النصف كروية. لا يمكننا حل هذه المعادلة مباشرة ، ولكن هناك حل بسيط إلى حد ما.
1 لا تنسى ذلك! غالبًا ما نتحدث عن جيب التمام ، وسنضع دائمًا في الاعتبار المنتج العددي. منذ ذلك الحين
veca cdot vecb= | veca | | vecb | cos( theta) ، ونحن نتعامل مع
الاتجاهات (متجهات الوحدة) ، فإن المنتج القياسي
هو جيب التمام في معظم مهام رسومات الكمبيوتر.
يأتي مونتي كارلو للإنقاذ
إن Monte Carlo Integration هي تقنية تكامل رقمي تسمح لنا بحساب أي تكامل تقريبًا باستخدام عدد محدود من العينات العشوائية. علاوة على ذلك ، يضمن مونتي كارلو التقارب مع القرار الصحيح - كلما أخذنا عينات أكثر ، كان ذلك أفضل. هنا هو شكله المعمم:
FN تقريبا frac1N sumNn=0 fracf(xn)p(xn)
لذلك ، جزء لا يتجزأ من الوظيفة
f(xn) يمكن حسابها تقريبًا عن طريق حساب متوسط العينات العشوائية في مجال التكامل. كل عينة مقسومة على احتمال اختيارها.
p(xn) . ونتيجة لذلك ، سيكون للعينة المختارة في كثير من الأحيان وزن أكبر من العينة الأقل اختيارًا.
في حالة العينات المنتظمة في نصف الكرة الأرضية (يكون لكل اتجاه نفس احتمال الاختيار) ، يكون احتمال العينات ثابتًا:
p( omega)= frac12 pi (لأن
2 pi هي مساحة نصف الكرة الأرضية الواحدة). إذا جمعنا كل هذا معًا ، نحصل على ما يلي:
L(x،\، vec omegao) almostLe(x،\، vec omegao)+ frac1N sumNn=0 colorGreen2 pi\،fr(x،\، vec omegai،\، vec omegao)\،( vec omegai cdot vecn)\،L(x،\، vec omegai)
الإشعاع
Le(x،\، vec omegao) هي فقط القيمة التي يتم إرجاعها بواسطة دالة
Shade
.
frac1N تعمل بالفعل في وظيفة
AddShader
. الضرب بواسطة
L(x،\، vec omegai) يحدث عندما نعكس الشعاع ونتتبعه أكثر. مهمتنا هي إعطاء الحياة إلى الجزء الأخضر من المعادلة.
المتطلبات الأساسية
قبل الشروع في رحلة ، دعنا نتعامل مع بعض الجوانب: تراكم العينات والمشاهد القطعية وعشوائية التظليل.
تراكم
لسبب ما ، لا يمرر Unity لي نسيج HDR
destination
في
OnRenderImage
. كان التنسيق
R8G8B8A8_Typeless
لي ، لذا فإن الدقة تصبح منخفضة جدًا بحيث لا يمكن تجميع عدد كبير من العينات. للتعامل مع هذا ، دعنا نضيف
private RenderTexture _converged
إلى C #
private RenderTexture _converged
. سيكون هذا هو المخزن المؤقت لدينا ، حيث يتراكم بدقة عالية النتائج قبل عرضها على الشاشة. نقوم بتهيئة / تحرير النسيج بنفس طريقة
_target
في دالة
InitRenderTexture
. في وظيفة
Render
، ضاعف التبييض:
Graphics.Blit(_target, _converged, _addMaterial); Graphics.Blit(_converged, destination);
مشاهد حتمية
عند إجراء تغييرات على العرض لتقييم التأثير ، من المفيد المقارنة مع النتائج السابقة. حتى الآن ، مع كل إعادة تشغيل لوضع التشغيل أو إعادة تجميع النص ، سنحصل على مشهد عشوائي جديد. لتجنب ذلك ، أضف
public int SphereSeed
إلى
public int SphereSeed
C # والسطر التالي في بداية
SetUpScene
:
Random.InitState(SphereSeed);
الآن يمكننا ضبط مشاهد البذور يدويًا. أدخل أي رقم وقم بإيقاف
RayTracingMaster
/ تشغيل
RayTracingMaster
مرة أخرى حتى تحصل على المشهد الصحيح.
تم استخدام المعلمات التالية لصور العينة: بذور المجال 1223832719 ، نصف قطر المجال [5 ، 30] ، المجالات ماكس 10000 ، نصف قطر موضع المجال 100.
عشوائية شادر
قبل البدء في أخذ العينات العشوائية ، نحتاج إلى إضافة العشوائية إلى التظليل.
سأستخدم السلسلة الأساسية التي وجدتها على الشبكة ، تم تعديلها من أجل الراحة:
float2 _Pixel; float _Seed; float rand() { float result = frac(sin(_Seed / 100.0f * dot(_Pixel, float2(12.9898f, 78.233f))) * 43758.5453f); _Seed += 1.0f; return result; }
تهيئة
_Pixel
مباشرة في
CSMain
كـ
_Pixel = id.xy
بحيث يمكن لكل بكسل استخدام قيم عشوائية مختلفة.
_Seed
تهيئة
_Seed
من C # في دالة
SetShaderParameters
.
RayTracingShader.SetFloat("_Seed", Random.value);
جودة الأرقام العشوائية التي تم إنشاؤها هنا غير مستقرة. في المستقبل ، سيكون من الجدير استكشاف واختبار هذه الوظيفة من خلال تحليل تأثير المعلمات ومقارنتها مع المناهج الأخرى. ولكن في الوقت الحالي ، سنستخدمها ونأمل في الأفضل.
أخذ عينات من نصف الكرة الأرضية
لنبدأ مرة أخرى: نحتاج إلى اتجاهات عشوائية موزعة بشكل موحد في نصف الكرة الأرضية. تم وصف هذه المهمة غير التافهة للنطاق الكامل بالتفصيل في
هذه المقالة بواسطة Corey Simon. من السهل التكيف مع نصف الكرة الأرضية. إليك ما يبدو عليه رمز التظليل:
float3 SampleHemisphere(float3 normal) { // float cosTheta = rand(); float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta)); float phi = 2 * PI * rand(); float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); // return mul(tangentSpaceDir, GetTangentSpace(normal)); }
يتم إنشاء الاتجاهات لنصف الكرة الأرضية المتمحور حول المحور Z الموجب ، لذلك نحتاج إلى تحويلها بحيث تتمحور حول الوضع الطبيعي المطلوب. نحن نولد ظلًا وثنائيًا (متجهان متعامدان على العاديين ومتعامدين مع بعضنا البعض). أولاً ، نختار متجهًا مساعدًا لتوليد المماس. للقيام بذلك ، نأخذ المحور X الموجب ، ولن نعود إلى Z الموجب إلا إذا كان محاذاة عادةً (تقريبًا) مع المحور X. ثم يمكننا استخدام المنتج المتجه لتوليد المماس ، ومن ثم ثنائي الأبعاد.
float3x3 GetTangentSpace(float3 normal) {
تشتت لامبرت
الآن بعد أن أصبح لدينا اتجاهات عشوائية موحدة ، يمكننا المضي قدمًا في تنفيذ أول BRDF. بالنسبة للانعكاس المنتشر ، الأكثر استخدامًا هو Lambert BRDF ، وهو أمر بسيط بشكل مدهش:
fr(x،\، vec omegai،\، vec omegao)= frackd pi أين
kd - هذا سطح البياض. دعنا ندخلها في معادلة عرض مونت كارلو (لن أخوض في الاعتبار الابتعاث بعد) ونرى ما سيحدث:
L(x،\، vec omegao) almost frac1N sumNn=0 colorBlueViolet2kd\،( vec omegai cdot vecn)\،L(x،\، vec omegai)
دعونا ندرج هذه المعادلة في التظليل على الفور. في وظيفة
Shade
،
if (hit.distance < 1.#INF)
الرمز الموجود داخل
if (hit.distance < 1.#INF)
التالية:
// ray.origin = hit.position + hit.normal * 0.001f; ray.direction = SampleHemisphere(hit.normal); ray.energy *= 2 * hit.albedo * sdot(hit.normal, ray.direction); return 0.0f;
يتم تحديد الاتجاه الجديد للحزمة المنعكسة باستخدام وظيفتنا لعينات نصف الكرة المتجانسة. يتم ضرب طاقة الحزمة في الجزء المقابل من المعادلة الموضحة أعلاه. نظرًا لأن السطح لا ينبعث منه أي إضاءة (إنه يعكس فقط الضوء المستلم بشكل مباشر أو غير مباشر من السماء) ، فإننا نرجع 0. هنا ، لا تنس أن
AddShader
متوسط العينات ، لذلك لا داعي للقلق بشأن
frac1N sum . يحتوي
CSMain
بالفعل على الضرب في
L(x،\، vec omegai) (الشعاع المنعكس التالي) ، لذلك لم يبق لدينا الكثير من العمل.
sdot
هي وظيفة مساعدة حددتها لنفسي. تقوم ببساطة بإرجاع نتيجة المنتج الحجمي بمعامل إضافي ، ثم تقصره على الفاصل الزمني
[0،1] :
float sdot(float3 x, float3 y, float f = 1.0f) { return saturate(dot(x, y) * f); }
دعونا نلخص ما يفعله الكود حتى الآن. يولد
CSMain
الأشعة الأساسية للكاميرا ويدعو
Shade
. عند عبور السطح ، تولد هذه الوظيفة بدورها شعاعًا جديدًا (عشوائيًا بشكل موحد في نصف الكرة الأرضية حول العادي) وتأخذ في الاعتبار BRDF للمادة وجيب التمام في طاقة الشعاع. عند تقاطع الشعاع مع السماء ، نأخذ عينة من HDRI (مصدر الإضاءة الوحيد لدينا) ونعيد الإضاءة التي تضاعفتها طاقة الشعاع (أي نتيجة جميع التقاطعات السابقة ، بدءًا من الكاميرا). هذه عينة بسيطة تمتزج مع نتيجة متقاربة. ونتيجة لذلك ، يؤخذ التأثير في الاعتبار في كل عينة
frac1N .
حان الوقت للتحقق من كل شيء في العمل. نظرًا لعدم وجود انعكاس منتشر للمعادن ، فلنقم بإيقاف تشغيلها الآن في وظيفة
SetUpScene
نصي C # (ولكن لا يزال استدعاء
Random.value
هنا للحفاظ على حتمية المشهد):
bool metal = Random.value < 0.0f;
شغّل وضع التشغيل وانظر كيف يتم محو الصورة الصاخبة في البداية وتتحول إلى عرض جميل:
صورة مرآة فونغ
ليس سيئًا لعدد قليل من أسطر التعليمات البرمجية (وجزء صغير من الرياضيات). دعونا صقل الصورة بإضافة انعكاسات المرآة باستخدام BRDF من Phong. واجهت صياغة فونغ الأصلية مشاكلها (نقص العلاقات والحفاظ على الطاقة) ، لكن لحسن الحظ أزالها
الآخرون . يظهر BRDF المحسن أدناه.
vec omegar هو اتجاه الضوء المنعكس تماما ، و
alpha هو مؤشر Phong يتحكم في الخشونة:
fr(x،\، vec omegai،\، vec omegao)=ks\، frac alpha+22 pi\،( vec omegar cdot vec omegao) alpha
يوضح
الرسم البياني التفاعلي ثنائي الأبعاد كيف يبدو BRDF لـ Phong متى
alpha=15 لحادث شعاع بزاوية 45 درجة. حاول تغيير القيمة.
alpha .
الصق هذا في معادلة عرض مونت كارلو:
L(x،\، vec omegao) almost frac1N sumNn=0 colorbrownks\،( alpha+2)\،( vec omegar cdot vec omegao) alpha\،( vec omegai cdot vecn)\،L(x،\، vec omegai)
وأخيرًا ، دعنا نضيف هذا إلى Lambert BRDF الحالي:
L(x،\، vec omegao) almost frac1N sumNn=0[ colorBlueViolet2kd+ colorbrownks\،( alpha+2)\،( vec omegar cdot vec omegao) alpha]\،( vec omegai cdot vecn)\،L(x،\، vec omegai)
وهذه هي الطريقة التي يظهرون بها في الكود مع تشتت لامبرت:
لاحظ أننا استبدلنا المنتج الحجمي بمنتج مختلف قليلاً ، ولكنه مكافئ (منعكس
omegao بدلا من
omegai ) الآن
SetUpScene
المواد المعدنية إلى وظائف
SetUpScene
وتحقق من كيفية عملها.
تجربة قيم مختلفة
alpha ، قد تلاحظ مشكلة: حتى الأداء المنخفض يتطلب الكثير من الوقت للتقارب ، وفي ضوضاء عالية الأداء ملفتة للنظر بشكل خاص. حتى بعد بضع دقائق من الانتظار ، تكون النتيجة بعيدة عن المثالية ، وهو أمر غير مقبول لمثل هذا المشهد البسيط.
alpha=15 و
alpha=300 مع 8192 عينة تبدو كالتالي:
لماذا حدث هذا؟ بعد كل شيء ، قبل أن يكون لدينا مثل هذه الانعكاسات المثالية الجميلة (
alpha= infty )! .. المشكلة هي أننا
ننتج عينات
متجانسة ونعينها أوزانًا وفقًا لـ BRDF. مع قيم Phong العالية ، فإن BRDF صغير للجميع ، ولكن هذه الاتجاهات قريبة جدًا من الانعكاس المثالي ، ومن غير المحتمل جدًا أن نختارها عشوائيًا باستخدام عيناتنا
المتجانسة . من ناحية أخرى ،
إذا عبرنا بالفعل أحد هذه الاتجاهات ، فسيكون BRDF ضخمًا للتعويض عن جميع العينات الصغيرة الأخرى. والنتيجة تشتت كبير جداً. المسارات ذات الانعكاسات المرآوية المتعددة أسوأ وتؤدي إلى ضوضاء مرئية في الصور.
أخذ عينات محسنة
لجعل مسار التتبع عمليًا ، نحتاج إلى تغيير النموذج. بدلاً من إهدار عينات ثمينة في المناطق التي ينتهي بها الأمر إلى أن تكون غير مهمة (نظرًا لأنها تحصل على BRDF و / أو قيمة جيب التمام منخفضة جدًا) ، فلنولد
عينات مهمة .
كخطوة أولى ، سنعيد تأملاتنا المثالية ، ثم نرى كيف يمكن تعميم هذه الفكرة. للقيام بذلك ، نقسم منطق التظليل إلى انعكاس منتشر ورائع. لكل عينة ، سنختار عشوائيًا واحدًا أو آخر (اعتمادًا على النسبة
kd و
ks ) في حالة الانعكاس المنتشر ، سنلتزم بالعينات المتجانسة ، ولكن بالنسبة للمضاربة ، سنعكس بشكل واضح الحزمة في الاتجاه المهم الوحيد. نظرًا لأنه سيتم إنفاق عينات أقل على كل نوع من أنواع الانعكاس ، فإننا نحتاج إلى زيادة التأثير وفقًا لذلك ، من أجل الحصول على نفس القيمة الإجمالية:
energy
هي وظيفة مساعدة صغيرة متوسط قنوات الألوان:
float energy(float3 color) { return dot(color, 1.0f / 3.0f); }
لذلك أنشأنا أداة تتبع أشعة Whited الأكثر جمالًا من الجزء السابق ، ولكن الآن مع تظليل منتشر حقيقي (مما يعني الظلال الناعمة ، والانسداد المحيط ، والإضاءة العالمية المنتشرة):
عينة الأهمية
دعونا نلقي نظرة أخرى على صيغة مونت كارلو الأساسية:
FN تقريبا frac1N sumNn=0 fracf(xn)p(xn)
كما ترون ، نقسم تأثير كل عينة (عينة) على احتمال اختيار هذه العينة بعينها. حتى الآن ، استخدمنا عينات نصف الكرة المتجانسة ، لذلك كان لدينا ثابت
p( omega)= frac12 pi . كما رأينا أعلاه ، هذا أبعد ما يكون عن المثالية ، على سبيل المثال ، في حالة Phong BRDF ، وهو كبير في اتجاهات قليلة جدًا.
تخيل أنه يمكننا العثور على توزيع احتمالي يتطابق تمامًا مع الوظيفة القابلة للتكامل:
p(x)=f(x) . ثم سيحدث ما يلي:

الآن ليس لدينا عينات تقدم مساهمة قليلة جدًا. ستكون هذه العينات أقل احتمالا لاختيارها. سيؤدي ذلك إلى تقليل تباين النتيجة بشكل كبير وتسريع تقارب العرض.
من الناحية العملية ، من المستحيل العثور على هذا التوزيع المثالي ، لأن بعض أجزاء الوظيفة القابلة للتكامل (في حالتنا BRDF × جيب التمام × الضوء الساقط) غير معروفة (هذا هو الأكثر وضوحًا للضوء الساقط) ، ولكن توزيع العينات وفقًا لـ BRDF × جيب التمام أو حتى وفقًا لـ BRDF فقط سيساعد نحن. يسمى هذا المبدأ أخذ العينات حسب الأهمية.
عينة جيب التمام
في الخطوات التالية ، نحتاج إلى استبدال توزيعنا المتجانس للعينات بالتوزيع وفقًا لقاعدة جيب التمام. لا تنس ، بدلاً من ضرب عينات متجانسة في جيب التمام ،
مما يقلل من تأثيرها ، نريد توليد عدد
أصغر نسبيًا من العينات.
توضح هذه المقالة التي كتبها توماس بول كيفية القيام بذلك. سنضيف معلمة
alpha
إلى دالة
SampleHemisphere
. تحدد الوظيفة مؤشر اختيار جيب التمام: 0 لعينة موحدة ، 1 لاختيار التمام ، أو أعلى لقيم أعلى من Phong. في التعليمات البرمجية ، يبدو كما يلي:
float3 SampleHemisphere(float3 normal, float alpha) {
الآن احتمالية كل عينة متساوية
p( omega)= frac alpha+12 pi\،( vec omega cdot vecn) alpha . قد لا يبدو جمال هذه المعادلة واضحًا على الفور ، ولكن بعد ذلك بقليل ستفهمها.
عينة لامبرت حسب الأهمية
بالنسبة للمبتدئين ، سنقوم بتحسين عرض الانعكاس المنتشر. في توزيعنا المتجانس ، يتم استخدام ثابت Lambert BRDF بالفعل ، ولكن يمكننا تحسينه بإضافة جيب التمام. التوزيع الاحتمالي للعينة حسب جيب التمام (حيث
alpha=1 ) يساوي
frac( vec omegai cdot vecn) pi ، التي تبسط صيغة مونت كارلو للانعكاس المنتشر:
L(x،\، vec omegao) almost frac1N sumNn=0 colorBlueVioletkd\،L(x،\، vec omegai)
سيؤدي ذلك إلى تسريع التظليل المنتشر قليلاً. الآن دعنا ندخل في القضية الحقيقية.
أخذ العينات Fongov حسب الأهمية
بالنسبة لـ Phong BRDF ، الإجراء مشابه. هذه المرة لدينا ناتج جيب التمام: جيب التمام القياسي من معادلة التقديم (كما في حالة الانعكاس المنتشر) ، مضروبًا في جيب التمام BRDF المناسب. سنتعامل فقط مع الأخير.
دعونا ندرج التوزيع الاحتمالي من الأمثلة أعلاه في معادلة Phong. يمكن العثور على استنتاج مفصل في
Lafortune و Willems: استخدام نموذج انعكاس Phong المعدل للعرض الجسدي (1994) :
L(x،\، vec omegao) almost frac1N sumNn=0 colorbrownks\، frac alpha+2 alph a + 1 \ ،(vecomeg a i cdotvecn)\ ،L(x ، \ ،vecomeg a i )
هذه التغييرات كافية للقضاء على أي مشاكل مع الأداء العالي في Phong وجعل التقارب الخاص بنا يتقارب في وقت أكثر معقولية.
المواد
أخيرًا ، دعنا نوسع جيل المشاهد لدينا لخلق قيم متغيرة لسلاسة وانبعاث المجالات! في
struct Sphere
من البرنامج النصي C # ، أضف
public float smoothness
public Vector3 emission
. نظرًا لأننا قمنا بتغيير حجم البنية ، فنحن بحاجة إلى تغيير الخطوة عند إنشاء المخزن المؤقت للحساب (4 × عدد الأرقام العائمة ، تذكر؟). اجعل دالة
SetUpScene
بإدخال قيم من أجل النعومة
SetUpScene
.
في التظليل ، أضف كلا المتغيرين إلى
struct Sphere
struct RayHit
، ثم قم
CreateRayHit
في
CreateRayHit
. وأخيرًا ، قم بتعيين كلتا القيمتين في
IntersectGroundPlane
(مشفر ، والصق أي قيم) و
IntersectSphere
(الحصول على قيم من
Sphere
).
أريد استخدام قيم النعومة بنفس الطريقة كما في تظليل الوحدة القياسي ، والذي يختلف عن أس فونغ التعسفي إلى حد ما. فيما يلي تحويل جيد يمكن استخدامه في وظيفة
Shade
:
float SmoothnessToPhongAlpha(float s) { return pow(1000.0f, s * s); }
float alpha = SmoothnessToPhongAlpha(hit.smoothness);
يتم استخدام الابتعاث عن طريق إرجاع قيمة في
Shade
:
return hit.emission;
النتائج
خذ نفسًا عميقًا. استرخ وانتظر حتى تتحول الصورة إلى صورة جميلة:
مبروك! تمكنت من الحصول على مجموعة من التعبيرات الرياضية. قمنا بتطبيق مسار التتبع الذي يؤدي إلى تظليل منتشر وتظليل ، وتعلمنا عن أخذ العينات حسب الأهمية ، وتطبيق هذا المفهوم على الفور بحيث يتقارب العرض في دقائق ، وليس ساعات أو أيام.
مقارنة بالمقال السابق ، كانت هذه المقالة خطوة كبيرة من حيث التعقيد ، ولكنها أيضًا حسنت بشكل كبير جودة النتيجة. يستغرق العمل مع الحسابات الرياضية بعض الوقت ، ولكنه يبرر نفسه لأنه يمكن أن يعمق فهمك بشكل كبير لما يحدث وسيسمح لك بتوسيع الخوارزمية دون تدمير الموثوقية المادية.
شكرا للقراءة! في الجزء الثالث ، سنترك (لبعض الوقت) غابة أخذ العينات والتظليل ، ونعود إلى الحضارة لمقابلة السادة مولر وترومبور. سنحتاج للتحدث معهم حول المثلثات.
نبذة عن الكاتب: ديفيد كوري هو مطور لعبة ثري آيد ، مبرمج مختبر الهندسة الافتراضية من فولكس فاجن ، وباحث رسومات الحاسوب ، وموسيقي موسيقى المعادن الثقيلة.