
الإضاءة المستندة إلى الصورة أو
IBL (
الإضاءة القائمة على الصورة ) هي فئة من أساليب الإضاءة التي لا تستند إلى حساب مصادر الضوء التحليلية (التي تمت مناقشتها في
الدرس السابق ) ، ولكن مع مراعاة البيئة الكاملة للأشياء المضاءة كمصدر ضوء مستمر واحد. في الحالة العامة ، يكمن الأساس التقني لهذه الطرق في معالجة خريطة مكعبة للبيئة (يتم إعدادها في العالم الحقيقي أو يتم إنشاؤها على أساس مشهد ثلاثي الأبعاد) بحيث يمكن استخدام البيانات المخزنة في الخريطة مباشرة في حسابات الإضاءة: في الواقع ، يعتبر كل مقطع من الخريطة المكعبة مصدرًا للضوء . بشكل عام ، يسمح لك هذا بالتقاط تأثير الإضاءة العالمية في المشهد ، وهو مكون مهم ينقل "النغمة" الإجمالية للمشهد الحالي ويساعد على أن تكون الأشياء المضيئة "مضمنة" بشكل أفضل فيه.
نظرًا لأن خوارزميات IBL تأخذ في الاعتبار الإضاءة من بيئة "عالمية" معينة ، فإن نتائجها تعتبر محاكاة أكثر دقة للإضاءة الخلفية أو حتى تقريبًا تقريبيًا للغاية للإضاءة العالمية. هذا الجانب يجعل طرق IBL مثيرة للاهتمام من حيث دمج PBR في النموذج ، لأن استخدام الضوء المحيط في نموذج الإضاءة يسمح للأشياء أن تبدو أكثر صحة جسديًا.
لدمج تأثير IBL في نظام PBR الموصوف بالفعل ، نعود إلى معادلة الانعكاس المألوفة:
L o ( p ، o m e g a o ) = i n t l i m i t s O m e g a ( k d f r a c c p i + k s f r a c D F G 4 ( o m e g a o c d o t n ) ( omegai cdotn))Li(p، omegai)n cdot omegaid omegai
كما هو موضح سابقًا ، فإن الهدف الرئيسي هو حساب التكامل لجميع اتجاهات الإشعاع الواردة
wi نصف الكرة الأرضية
أوميغا . في
الدرس الأخير ، لم يكن حساب التكامل مرهقًا ، حيث علمنا مسبقًا عدد مصادر الضوء ، وبالتالي ، كل هذه الاتجاهات العديدة لحدوث الضوء التي تتوافق معها. في الوقت نفسه ، لا يمكن حل التكامل مع المفاجئة:
أي ناقل متساقط
wi من البيئة يمكن أن تحمل سطوع الطاقة غير الصفرية. ونتيجة لذلك ، من أجل التطبيق العملي للطريقة ، يلزم تلبية المتطلبات التالية:
- تحتاج إلى التوصل إلى طريقة للحصول على سطوع الطاقة في المشهد من أجل ناقل اتجاه تعسفي wi ؛
- من الضروري أن يحدث حل التكامل في الوقت الحقيقي.
حسنًا ، يتم حل النقطة الأولى من تلقاء نفسها. هناك تلميح عن حل قد انزلق بالفعل هنا: إحدى الطرق لتمثيل تشعيع مشهد أو بيئة هي خريطة مكعبة خضعت لمعالجة خاصة. يمكن اعتبار كل texel في مثل هذه الخريطة كمصدر منفصل للانبعاث. عن طريق أخذ العينات من هذه الخريطة وفقًا لمتجه تعسفي
wi نحصل بسهولة على سطوع الطاقة للمشهد في هذا الاتجاه.
لذا ، نحصل على سطوع الطاقة للمشهد المتجه التعسفي
wi :
vec3 radiance = texture(_cubemapEnvironment, w_i).rgb;
ومع ذلك ، فإن حل التكامل يتطلب منا أن نصنع عينات من خريطة البيئة ليس من اتجاه واحد ، ولكن من كل ما هو ممكن في نصف الكرة الأرضية. وهكذا - لكل جزء مظلل. من الواضح أنه بالنسبة للمهام في الوقت الفعلي ، هذا غير عملي عمليًا. الطريقة الأكثر فعالية هي حساب جزء من عمليات الإندماج مقدمًا ، حتى خارج تطبيقنا. ولكن من أجل هذا ، سيكون عليك أن تشمر عن سواعدك وتتعمق في جوهر التعبير عن الانعكاسية:
Lo(p، omegao)= int limits Omega(kd fracc pi+ks fracDFG4( omegao cdotn)( omegai cdotn))Li(p، omegai)n cdot omegaid omegai
يمكن ملاحظة أن أجزاء التعبير تتعلق بالانتشار
kd ومرآة
ks مكونات BRDF مستقلة. يمكنك تقسيم التكامل إلى قسمين:
Lo(p، omegao)= int limits Omega(kd fracc pi)Li(p، omegai)n cdot omegaid omegai+ int limits Omega(ks fracDFG4( omegao cdotn)( omegai cdotn))Li(p، omegai)n cdot omegaid omegai
سيسمح لنا هذا التقسيم إلى أجزاء بالتعامل مع كل منها على حدة ، وفي هذا الدرس سنتعامل مع الجزء المسؤول عن الإضاءة المنتشرة.
بعد تحليل شكل التكامل على المكون المنتشر ، يمكننا أن نستنتج أن المكون المنتشر لامبرت ثابت في الأساس (اللون
s معامل الانكسار
kd و
pi تكون ثابتة في ظل ظروف Integrand) ولا تعتمد على متغيرات أخرى. بالنظر إلى هذه الحقيقة ، يمكننا وضع الثوابت وراء علامة التكامل:
Lo(p، omegao)=kd fracc pi int limits OmegaLi(p، omegai)n cdot omegaid omegai
لذلك نحصل على تكامل يعتمد فقط على
wi (من المفترض أن
p يتوافق مع مركز الخريطة المكعبة للبيئة). استنادًا إلى هذه الصيغة ، يمكنك حساب ، أو حتى أفضل ، حساب خريطة مكعبة جديدة مسبقًا تخزن نتيجة حساب تكامل المكون المنتشر لكل اتجاه للعينة (أو خريطة texel)
wo باستخدام عملية الالتفاف.
الالتفاف هو عملية تطبيق بعض الحسابات على كل عنصر في مجموعة بيانات ، مع مراعاة بيانات جميع العناصر الأخرى في المجموعة. في هذه الحالة ، هذه البيانات هي سطوع الطاقة للمشهد أو خريطة البيئة. وبالتالي ، لحساب قيمة واحدة في كل اتجاه للعينة في الخريطة المكعبة ، يجب أن نأخذ في الاعتبار القيم المأخوذة من جميع الاتجاهات الممكنة الأخرى للعينة في نصف الكرة الأرضية حول نقطة العينة.
لحل خريطة البيئة ، تحتاج إلى حل التكامل لكل اتجاه ناتج للعينة
wo عن طريق إجراء عينات منفصلة متعددة على طول الاتجاهات
wi تنتمي إلى نصف الكرة الأرضية
أوميغا ، ومتوسط سطوع الطاقة الإجمالية. نصف الكرة الأرضية ، الذي يتم على أساسه أخذ اتجاهات أخذ العينات
wi موجه على طول ناقلات
wo تمثل اتجاه الوجهة التي يتم حساب الالتفاف الحالي. انظر إلى الصورة لفهم أفضل:
مثل هذه الخريطة المكعبة المحسوبة مسبقًا التي تخزن نتيجة التكامل لكل اتجاه في العينة
wo يمكن أيضًا اعتباره نتيجة لتخزين تلخيص جميع الإضاءة المنتشرة غير المباشرة في المشهد ، الحادث على سطح معين موجه على طول الاتجاه
wo . بمعنى آخر ، تسمى هذه الخرائط المكعبة خرائط الإشعاع ، لأن خريطة البيئة التكعيبية ما قبل التلافيفية تسمح لك بتجربة حجم إشعاع المشهد مباشرة ، القادم من اتجاه تعسفي
wo ، بدون حسابات إضافية.
يعتمد التعبير الذي يحدد سطوع الطاقة أيضًا على موضع نقطة أخذ العينات p التي أخذناها مستلقية في وسط خريطة التشعيع. يفرض هذا الافتراض قيودًا بمعنى أن مصدر كل الإضاءة المنتشرة غير المباشرة سيكون أيضًا خريطة بيئية واحدة. في المشاهد غير المتجانسة في الإضاءة ، يمكن أن يدمر هذا الوهم الواقع (خاصة في المشاهد الداخلية). تحل محركات التقديم الحديثة هذه المشكلة عن طريق وضع أشياء مساعدة خاصة في المشهد - تحقيقات الانعكاس . ينخرط كل كائن من هذا القبيل في مهمة واحدة: فهو يشكل خريطة التشعيع الخاصة به لبيئته المباشرة. باستخدام هذه التقنية ، يكون الإشعاع (وسطوع الطاقة) عند نقطة تعسفية p سيتحدد بالاستكمال البسيط بين أقرب عينات الانعكاس. ولكن بالنسبة للمهام الحالية ، نتفق على أن خريطة البيئة تم أخذ عينات منها من مركزها ، وسنقوم بتحليل عينات انعكاس في المزيد من الدروس.
فيما يلي مثال لخريطة مكعبة للبيئة وخريطة تشعيع (بناءً على
محرك الأمواج ) مشتقة منها ، والتي تعد متوسط سطوع الطاقة للبيئة لكل اتجاه إخراج
wo .
لذلك ، تخزن هذه البطاقة نتيجة الالتفاف في كل حاوية (تتوافق مع الاتجاه
wo ) ، وتبدو مثل هذه الخريطة ظاهريًا وكأنها تخزين متوسط لون خريطة البيئة. ستعيد العينة في أي اتجاه من هذه الخريطة قيمة الإشعاع المنبثق من هذا الاتجاه.
PBR و HDR
في
الدرس السابق ، لوحظ بالفعل لفترة وجيزة أنه من أجل التشغيل الصحيح لنموذج الإضاءة PBR ، من المهم للغاية مراعاة نطاق سطوع HDR لمصادر الضوء الموجودة. نظرًا لأن نموذج PBR في المدخلات يقبل المعلمات بطريقة أو بأخرى بناءً على كميات وخصائص فيزيائية محددة للغاية ، فمن المنطقي أن يتطلب أن يتطابق سطوع الطاقة لمصادر الضوء مع نماذجها الأولية الحقيقية. لا يهم كيف نبرر القيمة المحددة لتدفق الإشعاع لكل مصدر: نقوم بعمل تقدير هندسي تقريبي أو ننتقل إلى
الكميات المادية - سيكون الفرق في الخصائص بين مصباح الغرفة والشمس كبيرًا على أي حال. بدون استخدام نطاق
HDR ، سيكون من المستحيل ببساطة تحديد السطوع النسبي لمجموعة متنوعة من مصادر الضوء بدقة.
لذا ، فإن PBR و HDR صديقان إلى الأبد ، وهذا أمر مفهوم ، ولكن كيف ترتبط هذه الحقيقة بطرق الإضاءة القائمة على الصور؟ في الدرس الأخير ، تبين أن تحويل PBR إلى نطاق عرض HDR أمر سهل. لا يزال هناك "لكن": نظرًا لأن الإضاءة غير المباشرة من البيئة تستند إلى خريطة مكعبة للبيئة ، هناك حاجة إلى طريقة للحفاظ على خصائص HDR الخاصة بإضاءة الخلفية هذه في خريطة البيئة.
حتى الآن ، استخدمنا خرائط البيئة التي تم إنشاؤها بتنسيق LDR (مثل
صناديق السماء ). استخدمنا عينة اللون منها في التقديم كما هو مقبول تمامًا للتظليل المباشر للكائنات. وهو غير مناسب تمامًا عند استخدام خرائط البيئة كمصادر للقياسات الموثوق بها ماديًا.
RGBE - تنسيق صورة HDR
تعرف على تنسيق ملف صورة RGBE. تُستخدم الملفات ذات الامتداد "
.hdr " لتخزين الصور ذات النطاق الديناميكي الواسع ، وتخصيص بايت واحد لكل عنصر من عناصر ثالوث اللون وبايت واحد
إضافي للأس . يتيح لك التنسيق أيضًا تخزين خرائط البيئة المكعبة مع نطاق كثافة ألوان يتجاوز نطاق LDR [0. ، 1.]. هذا يعني أن مصادر الضوء يمكن أن تحافظ على شدتها الحقيقية ، ويتم تمثيلها بخريطة بيئية كهذه.
تحتوي الشبكة على الكثير من خرائط البيئة المجانية بتنسيق RGBE ، تم تصويرها في ظروف حقيقية مختلفة. هنا مثال من موقع
أرشيف sIBL :
قد تفاجأ بما شاهدته: بعد كل شيء ، لا تبدو هذه الصورة المشوهة على الإطلاق خريطة مكعبة عادية مع انهيارها الواضح في 6 وجوه. التفسير بسيط: تم رسم خريطة البيئة هذه من كرة إلى مستوى -
تم تطبيق
مسح مستطيل متساوي . يتم ذلك لتتمكن من التخزين بتنسيق لا يدعم وضع التخزين للبطاقات المكعبة كما هو. بالطبع ، هذه الطريقة في الإسقاط لها عيوبها: الدقة الأفقية أعلى بكثير من الرأسي. في معظم حالات التطبيق في التقديم ، هذه نسبة مقبولة ، نظرًا لأن التفاصيل المثيرة للاهتمام حول البيئة والإضاءة عادة ما تكون موجودة بالضبط في المستوى الأفقي ، وليس في المستوى الرأسي. حسنًا ، بالإضافة إلى كل شيء ، نحتاج إلى رمز التحويل مرة أخرى إلى الخريطة المكعبة.
دعم تنسيق RGBE في stb_image.h
يتطلب تنزيل تنسيق الصورة هذا بنفسك معرفة
مواصفات التنسيق ، التي ليست صعبة ، ولكنها لا تزال شاقة. لحسن الحظ بالنسبة
لنا ، فإن مكتبة تحميل الصور
stb_image.h ، التي يتم تنفيذها في ملف رأس واحد ، تدعم تحميل ملفات RGBE ، وإرجاع مجموعة من أرقام
الفاصلة العائمة - ما نحتاجه لأغراضنا! من خلال إضافة مكتبة إلى مشروعك ، يعد تحميل بيانات الصورة أمرًا بسيطًا للغاية:
#include "stb_image.h" [...] stbi_set_flip_vertically_on_load(true); int width, height, nrComponents; float *data = stbi_loadf("newport_loft.hdr", &width, &height, &nrComponents, 0); unsigned int hdrTexture; if (data) { glGenTextures(1, &hdrTexture); glBindTexture(GL_TEXTURE_2D, hdrTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); stbi_image_free(data); } else { std::cout << "Failed to load HDR image." << std::endl; }
تقوم المكتبة تلقائيًا بتحويل القيم من تنسيق HDR الداخلي إلى أرقام 32 بت العادية العادية ، مع ثلاث قنوات ألوان افتراضيًا. يكفي حفظ بيانات صورة HDR الأصلية في نسيج عائم ثنائي الأبعاد عادي.
تحويل مسح متساوي الزاوية إلى خريطة مكعبة
يمكن استخدام مسح مستطيل بنفس القدر لاختيار عينات مباشرة من خريطة البيئة ، ومع ذلك ، فإن هذا يتطلب عمليات حسابية مكلفة ، في حين أن الجلب من خريطة مكعبة عادية سيكون عمليًا مجانيًا في الأداء. من هذه الاعتبارات بالتحديد ، سنتناول في هذا الدرس تحويل صورة مستطيلة الشكل إلى خريطة مكعبة ، سيتم استخدامها لاحقًا. ومع ذلك ، سيتم أيضًا عرض طريقة أخذ العينات المباشرة من خريطة مستطيلة بنفس الدرجة باستخدام متجه ثلاثي الأبعاد هنا ، بحيث يمكنك اختيار طريقة العمل التي تناسبك.
للتحويل ، تحتاج إلى رسم مكعب بحجم الوحدة ، ومراقبته من الداخل ، وإسقاط خريطة مستطيلة متساوية على وجوهها ، ثم استخراج ست صور من الوجوه كوجوه للخريطة المكعبة. إن تظليل الرأس في هذه المرحلة بسيط للغاية: فهو ببساطة يعالج رؤوس المكعب كما هو ، ويمرر أيضًا مواضعها التي لم يتم إصلاحها إلى تظليل القطع لاستخدامها كمتجه عينة ثلاثي الأبعاد:
#version 330 core layout (location = 0) in vec3 aPos; out vec3 localPos; uniform mat4 projection; uniform mat4 view; void main() { localPos = aPos; gl_Position = projection * view * vec4(localPos, 1.0); }
في تظليل الشظية ، نقوم بتظليل كل وجه من المكعب كما لو كنا نحاول لف المكعب بلطف بورقة مع خريطة مستطيلة بنفس القدر. للقيام بذلك ، يتم أخذ اتجاه العينة المنقولة إلى جهاز تظليل الشظية ، ومعالجته بواسطة سحر مثلثي خاص ، وفي النهاية ، يتم التحديد من خريطة مستطيلة متساوية كما لو كانت خريطة مكعبة بالفعل. يتم حفظ نتيجة التحديد مباشرة كلون جزء جزء الوجه المكعب:
#version 330 core out vec4 FragColor; in vec3 localPos; uniform sampler2D equirectangularMap; const vec2 invAtan = vec2(0.1591, 0.3183); vec2 SampleSphericalMap(vec3 v) { vec2 uv = vec2(atan(vz, vx), asin(vy)); uv *= invAtan; uv += 0.5; return uv; } void main() { // localPos vec2 uv = SampleSphericalMap(normalize(localPos)); vec3 color = texture(equirectangularMap, uv).rgb; FragColor = vec4(color, 1.0); }
إذا قمت بالفعل برسم مكعب باستخدام هذا التظليل وخريطة بيئة HDR المرتبطة ، فستحصل على شيء مثل هذا:
على سبيل المثال يمكننا أن نرى أنه في الواقع قمنا بإسقاط نسيج مستطيل على مكعب. رائع ، ولكن كيف سيساعدنا ذلك في إنشاء خريطة مكعبة حقيقية؟ لإنهاء هذه المهمة ، من الضروري تقديم نفس المكعب 6 مرات مع كاميرا تبحث في كل وجه ، أثناء كتابة الإخراج إلى كائن
مخزن مؤقت لإطار منفصل:
unsigned int captureFBO, captureRBO; glGenFramebuffers(1, &captureFBO); glGenRenderbuffers(1, &captureRBO); glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureRBO);
بالطبع ، لن ننسى تنظيم الذاكرة لتخزين كل من الوجوه الستة للخريطة المكعبة المستقبلية:
unsigned int envCubemap; glGenTextures(1, &envCubemap); glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); for (unsigned int i = 0; i < 6; ++i) {
بعد هذا التحضير ، يبقى فقط لإجراء نقل مباشر لأجزاء خريطة متساوية مستطيلة على حافة خريطة مكعبة.
لن نخوض في الكثير من التفاصيل ، خاصة وأن الكود يكرر الكثير في الدروس في
المخزن المؤقت للإطار والظلال الشامل . من حيث المبدأ ، يعود الأمر كله إلى إعداد ستة مصفوفات عرض منفصلة توجه الكاميرا بدقة إلى كل وجه من وجوه المكعب ، بالإضافة إلى مصفوفة إسقاط خاصة بزاوية رؤية 90 درجة لالتقاط وجه المكعب بالكامل. ثم ، ست مرات فقط ، يتم تنفيذ التقديم ، ويتم حفظ النتيجة في حاجز الإطار العائم:
glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); glm::mat4 captureViews[] = { glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)) };
هنا ، يتم إرفاق لون المخزن المؤقت للإطار ، وتغيير الوجه المتصل للخريطة المكعبة بالتناوب ، مما يؤدي إلى الإخراج المباشر للعرض إلى أحد وجوه خريطة البيئة. يلزم تنفيذ هذا الرمز مرة واحدة فقط ، وبعد ذلك
سيظل لدينا خريطة بيئة
envCubemap كاملة تحتوي على نتيجة تحويل النسخة الأصلية المتساوية المستطيل لخريطة بيئة HDR.
سنقوم باختبار الخريطة المكعبة الناتجة عن طريق رسم أبسط تظليل سكاي بوكس:
#version 330 core layout (location = 0) in vec3 aPos; uniform mat4 projection; uniform mat4 view; out vec3 localPos; void main() { localPos = aPos; // mat4 rotView = mat4(mat3(view)); vec4 clipPos = projection * rotView * vec4(localPos, 1.0); gl_Position = clipPos.xyww; }
انتبه إلى الحيلة باستخدام مكونات متجه
clipPos : نستخدم
xyww tetrad عند تسجيل إحداثيات الرأس المحولة لضمان أن جميع أجزاء صندوق السماء لها أقصى عمق 1.0 (تم استخدام النهج بالفعل في
الدرس المقابل ). لا تنس تغيير دالة المقارنة إلى
GL_LEQUAL :
glDepthFunc(GL_LEQUAL);
يختار جهاز تظليل الشظايا ببساطة من خريطة مكعبة:
#version 330 core out vec4 FragColor; in vec3 localPos; uniform samplerCube environmentMap; void main() { vec3 envColor = texture(environmentMap, localPos).rgb; envColor = envColor / (envColor + vec3(1.0)); envColor = pow(envColor, vec3(1.0/2.2)); FragColor = vec4(envColor, 1.0); }
يعتمد التحديد من الخريطة على الإحداثيات المحلية المستكملة لرؤوس المكعب ، وهو الاتجاه الصحيح للتحديد في هذه الحالة (مرة أخرى ، تمت مناقشته في الدرس على صناديق السماء ،
تقريبًا لكل. ). نظرًا لأن مكونات النقل في مصفوفة العرض تم تجاهلها ، فلن يعتمد تقديم صندوق السماء على موضع المراقب ، مما يخلق الوهم بخلفية بعيدة للغاية. نظرًا لأننا هنا نقوم بإخراج البيانات مباشرة من بطاقة HDR إلى عارض الإطارات الافتراضي ، وهو مستقبل LDR ، فمن الضروري استدعاء ضغط الدرجة اللونية. وأخيرًا ، يتم تخزين جميع بطاقات HDR تقريبًا في مساحة خطية ، مما يعني أنه يجب تطبيق
تصحيح جاما كوتر المعالجة النهائي.
لذلك ، عند إخراج skybox الذي تم الحصول عليه ، إلى جانب مجموعة المجالات المألوفة بالفعل ، يتم الحصول على شيء مشابه:
حسنًا ، تم بذل الكثير من الجهد ، ولكن في النهاية اعتدنا على قراءة خريطة بيئة HDR ، وتحويلها من خريطة متساوية إلى خريطة مكعبة ، وإخراج خريطة HDR التكعيبية كصندوق سماء في المشهد. علاوة على ذلك ، فإن رمز التحويل إلى خريطة مكعبة من خلال عرض ستة وجوه للخريطة المكعبة مفيد لنا أكثر في مهمة
الالتفاف لخريطة بيئية . رمز عملية التحويل بأكملها
هنا .
لف البطاقة المكعبة
كما قيل في بداية الدرس ، هدفنا الرئيسي هو حل متكامل لجميع الاتجاهات الممكنة للإضاءة المنتشرة غير المباشرة ، مع مراعاة التشعيع المحدد للمشهد في شكل خريطة مكعبة للبيئة. من المعروف أنه يمكننا الحصول على قيمة سطوع الطاقة في المشهد
L(p،wi) للتوجيه التعسفي
wi من خلال أخذ عينات من HDR خريطة مكعبة للبيئة في هذا الاتجاه. لحل التكامل ، سيكون من الضروري اختبار سطوع الطاقة للمشهد من جميع الاتجاهات الممكنة في نصف الكرة الأرضية
أوميغا كل جزء تمت مراجعته.
من الواضح أن مهمة أخذ عينات الإضاءة من البيئة من جميع الاتجاهات الممكنة في نصف الكرة الأرضية
أوميغا غير عملي من الناحية الحسابية - هناك عدد لا نهائي من هذه الاتجاهات. ومع ذلك ، من الممكن تطبيق التقريب عن طريق اتخاذ عدد محدود من التوجيهات التي يتم اختيارها عشوائيًا أو تقع بشكل موحد داخل نصف الكرة الأرضية.
سيسمح لنا هذا بالحصول على تقريب جيد إلى حد ما للإشعاع الحقيقي ، مما يحل بشكل أساسي جزءًا لا يتجزأ من اهتمامنا في شكل مبلغ محدود.ولكن بالنسبة للمهام في الوقت الفعلي ، حتى مثل هذا النهج لا يزال مفروضًا بشكل لا يصدق ، لأن العينات تؤخذ لكل جزء ، ويجب أن يكون عدد العينات مرتفعًا بما يكفي للحصول على نتيجة مقبولة. وبالتالي ، سيكون من الجيد إعداد البيانات مسبقًا لهذه الخطوة ، خارج عملية العرض. بما أن اتجاه نصف الكرة الأرضية يحدد أي منطقة من الفضاء نلتقطها للإشعاع ، فمن الممكن حساب الإشعاع مقدمًا لكل اتجاه ممكن لنصف الكرة بناءً على جميع الاتجاهات الصادرة المحتملةث س :
L o ( p ، ω o ) = ك د جπ ∫ΩLi(p،ωi)n⋅ωidωi
ونتيجة لذلك ، لناقل تعسفي معين w i ، يمكننا أخذ عينات من خريطة الإشعاع المحسوبة من أجل الحصول على الإشعاع المنتشر في هذا الاتجاه. لتحديد حجم الإشعاع المنتشر غير المباشر عند نقطة الجزء الحالي ، نأخذ التشعيع الكلي من نصف الكرة الموجه على طول السطح العادي إلى سطح الشظية. وبعبارة أخرى ، فإن الحصول على تشعيع مشهد ما يعود إلى اختيار بسيط: vec3 irradiance = texture(irradianceMap, N);
علاوة على ذلك ، لإنشاء خريطة تشعيع ، من الضروري تكوين خريطة البيئة ، المحولة إلى خريطة مكعبة. نحن نعلم أنه بالنسبة لكل جزء يعتبر نصف الكرة الموجه على طول العادي إلى السطحن .
في هذه الحالة ، يتم تقليل انحراف الخريطة التكعيبية إلى حساب متوسط مقدار سطوع الطاقة من جميع الاتجاهات w أنا داخل نصف الكرةΩ موجه بطول عادين :
لحسن الحظ ، فإن العمل التمهيدي الذي يستغرق وقتًا طويلًا والذي قمنا به في بداية الدرس سيجعل من السهل جدًا تحويل خريطة البيئة إلى خريطة مكعبة في تظليل جزء خاص ، والذي سيتم استخدام مخرجاته لتشكيل خريطة مكعبة جديدة. لهذا ، من المفيد استخدام قطعة التعليمات البرمجية نفسها لترجمة خريطة بيئة مستطيلة متساوية إلى خريطة مكعبة.يبقى فقط أن تأخذ تظليل معالجة آخر: #version 330 core out vec4 FragColor; in vec3 localPos; uniform samplerCube environmentMap; const float PI = 3.14159265359; void main() { // vec3 normal = normalize(localPos); vec3 irradiance = vec3(0.0); [...] // FragColor = vec4(irradiance, 1.0); }
هنا ، يُعد عارض بيئة environmentMap خريطة مكعبة للنطاق عالي الديناميكية للبيئة مشتقة سابقًا من متساوي الأضلاع.هناك العديد من الطرق لحل خريطة البيئة ، وفي هذه الحالة ، لكل مقطع من الخريطة المكعبة ، سننشئ العديد من ناقلات عينات نصف الكرة الأرضيةΩ موجّهة على طول اتجاه العينة ومتوسط النتائج. سيتم تحديد عدد ناقلات العينات ، وسيتم توزيع المتجهات نفسها بالتساوي داخل نصف الكرة الأرضية. ألاحظ أن Integrand هي وظيفة مستمرة ، وأن التقدير المنفصل لهذه الوظيفة سيكون تقريبًا فقط. وكلما أخذنا نواقل أكثر ، كلما اقتربنا من الحل التحليلي للتكامل الذي سنكون عليه. يعتمد تكامل تعبير الانعكاسية على الزاوية الصلبةد ث - القيم التي لا يناسبها العمل. بدلاً من الاندماج على زاوية صلبةد ث نغير التعبير ، مما يؤدي إلى التكامل عبر الإحداثيات الكرويةθ و
ϕ :
تمثل الزاوية Phi السمت في مستوى قاعدة نصف الكرة الأرضية ، والتي تتراوح من 0 إلى 2 π .
زاوية θ سيمثل زاوية الارتفاع التي تتراوح من 0 إلى12 π .
التعبير المعدل عن الانعكاسية في هذه المصطلحات هو كما يلي:L o ( p ، ϕ o ، θ o ) = ك د جπ ∫ 2 π ϕ = 0 ∫ 12 πθ=0Li(p،ϕi،θi)cos(θ)sin(θ)dϕdθ
سيتطلب حل هذا التكامل أخذ عدد محدود من العينات في نصف الكرة الأرضية Ω ومتوسط النتائج. معرفة عدد العيناترقم 1 و
n 2 لكل من الإحداثيات الكروية ، يمكننا ترجمة التكامل إلىالمجموع الريماني:L o ( p ، ϕ o ، θ o ) = ك د جπ 1n 1 n 2 n 1 ∑ ϕ=0 n 2 ∑ θ=0Li(p،ϕi،θi)cos(θ)sin(θ)dϕdθ
نظرًا لأن الإحداثيتين الكرويتين تختلفان بشكل منفصل ، في كل لحظة ، يتم إجراء أخذ العينات بمنطقة متوسطية معينة في نصف الكرة الأرضية ، كما يمكن رؤيته في الشكل أعلاه. نظرًا لطبيعة السطح الكروي ، يقل حجم منطقة أخذ العينات المنفصلة حتمًا مع زيادة زاوية الارتفاعθ والاقتراب من السمت. للتعويض عن هذا التأثير لتقليل المساحة ، أضفنا معامل الوزن للتعبيرs i n θ .
ونتيجة لذلك ، فإن تنفيذ أخذ العينات المنفصلة في نصف الكرة الأرضية بناءً على الإحداثيات الكروية لكل جزء في شكل رمز هو كما يلي: vec3 irradiance = vec3(0.0); vec3 up = vec3(0.0, 1.0, 0.0); vec3 right = cross(up, normal); up = cross(normal, right); float sampleDelta = 0.025; float nrSamples = 0.0; for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) { for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) { // . ( -) vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); // vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta); nrSamples++; } } irradiance = PI * irradiance * (1.0 / float(nrSamples));
تحدد العينة المتغيرة دلتا حجم الخطوة المنفصلة على طول سطح نصف الكرة. من خلال تغيير هذه القيمة ، يمكنك زيادة أو تقليل دقة النتيجة.داخل كلتا الدورتين ، يتم تكوين ناقل نموذجي ثلاثي الأبعاد منتظم من الإحداثيات الكروية ، يتم نقله من المماس إلى الفضاء العالمي ، ثم يتم استخدامه لاختبار عينة خريطة مكعبة من HDR. يتم تجميع نتائج العينات في متغير الإشعاع ، والذي سيتم تقسيمه في نهاية المعالجة على عدد العينات التي تم الحصول عليها للحصول على متوسط قيمة الإشعاع. لاحظ أن نتيجة أخذ العينات من النسيج يتم تعديلها بكميتين : cos (theta) - لمراعاة توهين الضوء عند زوايا كبيرة ، و sin (theta)- للتعويض عن الانخفاض في مساحة العينة عند الاقتراب من السمت.يبقى فقط للتعامل مع التعليمات البرمجية التي تعرض وتلتقط نتائج الالتفاف لخريطة بيئة envCubemap . أولاً ، أنشئ خريطة مكعبة لتخزين الإشعاع (ستحتاج إلى القيام بذلك مرة واحدة ، قبل دخول دورة التجسيد الرئيسية): unsigned int irradianceMap; glGenTextures(1, &irradianceMap); glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); for (unsigned int i = 0; i < 6; ++i) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, GL_RGB, GL_FLOAT, nullptr); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
نظرًا لأنه يتم الحصول على خريطة التشعيع عن طريق حساب متوسط العينات الموزعة بشكل موحد لسطوع الطاقة لخريطة البيئة ، فإنها لا تحتوي عمليًا على أجزاء وعناصر عالية التردد - بنية ذات دقة صغيرة إلى حد ما (32 × 32 هنا) وستكون التصفية الخطية الممكنة كافية لتخزينها.بعد ذلك ، قم بتعيين الالتقاط Framebuffer على هذا القرار: glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32);
يشبه رمز التقاط نتائج الالتفاف رمز نقل خريطة البيئة من متساوي الأضلاع إلى خريطة مكعب ، يتم استخدام تظليل الالتفاف فقط: irradianceShader.use(); irradianceShader.setInt("environmentMap", 0); irradianceShader.setMat4("projection", captureProjection); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap);
بعد الانتهاء من هذه المرحلة ، سيكون لدينا خريطة إشعاع محسوبة مسبقًا على أيدينا يمكن استخدامها مباشرة لحساب الإضاءة المنتشرة غير المباشرة. للتحقق من كيفية سير الالتفاف ، سنحاول استبدال نسيج skybox من خريطة البيئة بخريطة التشعيع:ونتيجة لذلك ، إذا رأيت شيئًا يشبه خريطة ضبابية للغاية للبيئة ، فمن المرجح أن الالتفاف كان ناجحًا.PBR والإضاءة غير المباشرة
يتم استخدام خريطة التشعيع الناتجة في الجزء المنتشر من التعبير المنعكس للانعكاسية وتمثل المساهمة المتراكمة من جميع الاتجاهات الممكنة للإضاءة غير المباشرة. نظرًا لأن الضوء في هذه الحالة لا يأتي من مصادر محددة ، ولكن من البيئة ككل ، فإننا نعتبر الإضاءة غير المباشرة المنتشرة والمرئية كخلفية ( محيطة ) ، لتحل محل القيمة الثابتة المستخدمة سابقًا.بادئ ذي بدء ، لا تنس أن تضيف عينة جديدة مع خريطة تشعيع: uniform samplerCube irradianceMap;
إن وجود خريطة تشعيع تخزن جميع المعلومات حول الإشعاع المنتشر غير المباشر من المشهد والطبيعي إلى السطح ، والحصول على بيانات عن تشعيع جزء معين أمر بسيط مثل صنع عينة واحدة من الملمس: // vec3 ambient = vec3(0.03); vec3 ambient = texture(irradianceMap, N).rgb;
ومع ذلك ، نظرًا لأن الإشعاع غير المباشر يحتوي على بيانات لكل من المكونات المنتشرة والمرآة (كما رأينا في النسخة المكونة للتعبير عن الانعكاسية) ، فإننا بحاجة إلى تعديل المكون المنتشر بطريقة خاصة. كما في الدرس السابق ، نستخدم تعبير فريسنل لتحديد درجة انعكاس الضوء لسطح معين ، حيث نحصل على درجة انكسار الضوء أو المعامل المنتشر: vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0); vec3 kD = 1.0 - kS; vec3 irradiance = texture(irradianceMap, N).rgb; vec3 diffuse = irradiance * albedo; vec3 ambient = (kD * diffuse) * ao;
نظرًا لأن الإضاءة الخلفية تقع من جميع الاتجاهات في نصف الكرة الأرضية استنادًا إلى الوضع الطبيعي على السطح N ، من المستحيل تحديد الوسيط الوحيد (فيمنتصف الطريق) متجه لحساب معامل فريسنل. من أجل محاكاة تأثير فريسنل في ظل هذه الظروف ، من الضروري حساب المعامل بناءً على الزاوية بين المتجه الطبيعي ومتجه المراقبة. ومع ذلك ، في وقت سابق ، كمعلمة لحساب معامل فريسنل ، استخدمنا المتجه الوسيط الذي تم الحصول عليه على أساس نموذج السطوح الدقيقة واعتمادًا على خشونة السطح. نظرًا لأنه في هذه الحالة ، لا يتم تضمين الخشونة في معلمات الحساب ، فسيتم دائمًا المبالغة في تقدير درجة انعكاس الضوء على السطح. يجب أن تتصرف الإضاءة غير المباشرة ككل مثل الإضاءة المباشرة ، أي من الأسطح الخشنة نتوقع درجة أقل من الانعكاس عند الحواف. ولكن بما أن الخشونة لا تؤخذ بعين الاعتبار ،ثم تبدو درجة الانعكاس المرآوي وفقًا لـ Fresnel للإضاءة غير المباشرة غير واقعية على الأسطح غير المعدنية الخشنة (في الصورة أدناه ، يتم المبالغة في التأثير الموضح لمزيد من الوضوح):يمكنك التغلب على هذا الإزعاج من خلال تقديم الخشونة في تعبير Fremlin-Schlick ، وهي عملية وصفها Sébastien Lagarde : vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) { return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); }
بالنظر إلى خشونة السطح عند حساب مجموعة فريسنل ، يأخذ رمز حساب مكون الخلفية الشكل التالي: vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); vec3 kD = 1.0 - kS; vec3 irradiance = texture(irradianceMap, N).rgb; vec3 diffuse = irradiance * albedo; vec3 ambient = (kD * diffuse) * ao;
كما اتضح ، فإن استخدام الإضاءة القائمة على الصورة يتلخص بطبيعته في عينة واحدة من خريطة مكعبة. ترتبط جميع الصعوبات بشكل أساسي بالتحضير الأولي لخريطة البيئة ونقلها إلى خريطة التشعيع.من خلال أخذ مشهد مألوف من درس حول مصادر الضوء التحليلية التي تحتوي على مجموعة من المجالات ذات المعدن والخشونة المتباينة ، وإضافة إضاءة خلفية منتشرة من البيئة ، يمكنك الحصول على شيء مثل هذا:لا يزال يبدو غريبًا ، نظرًا لأن المواد ذات درجة عالية من المعدن لا تزال تتطلب انعكاسًا من أجل النظر حقًا ، هم ، معدن (المعادن لا تعكس الإضاءة المنتشرة ، بعد كل شيء). وفي هذه الحالة ، الانعكاسات الوحيدة التي تم الحصول عليها من مصادر الضوء التحليلية النقطية. ومع ذلك ، يمكننا بالفعل أن نقول أن المجالات تبدو أكثر انغماسًا في البيئة (يمكن ملاحظتها بشكل خاص عند تبديل خرائط البيئة) ، نظرًا لأن الأسطح تستجيب الآن بشكل صحيح لإضاءة الخلفية من بيئة المشهد.كود المصدر الكامل للدرس هنا.. سنتناول في الدرس التالي أخيرًا النصف الثاني من تعبير الانعكاسية المسؤول عن الإضاءة المرآوية غير المباشرة. بعد هذه الخطوة ، ستشعر حقًا بقوة نهج PBR في الإضاءة.مواد إضافية
- مختبرات التشفير: العرض المادي : مقدمة لنموذج PBR مع شرح لكيفية بناء خريطة الإشعاع ولماذا.
- رياضيات التظليل : نظرة عامة موجزة من ScratchAPixel على بعض التقنيات الرياضية المستخدمة في هذا الدرس ، ولا سيما حول الإحداثيات القطبية والتكاملات.
ملاحظة : لدينا برقية أسيوط لتنسيق التحويلات. إذا كانت لديك رغبة جادة في المساعدة في الترجمة ، فأنت مرحب بك!