
رسم الخرائط العادية
تتكون جميع المشاهد التي نستخدمها من مضلعات ، والتي تتكون بدورها من مئات وآلاف المثلثات المسطحة تمامًا. لقد تمكنا بالفعل من زيادة واقعية المشاهد قليلاً بسبب التفاصيل الإضافية التي توفر تطبيق القوام ثنائي الأبعاد على هذه المثلثات المسطحة. يساعد التركيب في إخفاء حقيقة أن جميع الكائنات في المشهد ليست سوى مجموعة من العديد من المثلثات الصغيرة. تقنية رائعة ، لكن إمكانياتها ليست غير محدودة: عند الاقتراب من أي سطح ، يصبح كل واحد واضحًا أنه يتكون من أسطح مستوية. معظم الأشياء الحقيقية ليست مسطحة تمامًا وتظهر الكثير من تفاصيل الإغاثة.
على سبيل المثال ، خذ البناء بالطوب. سطحه خشن للغاية ، ومن الواضح أنه لا يمثله طائرة: يوجد عليه تجاويف بالأسمنت والعديد من التفاصيل الصغيرة مثل الثقوب والشقوق. إذا قمنا بتحليل مشهد بتقليد من الطوب في وجود الإضاءة ، فإن الوهم بتخفيف السطح يتدمر بسهولة. فيما يلي مثال لمثل هذا المشهد الذي يحتوي على طائرة ذات نسيج بناء ومصدر ضوء نقطة واحدة:
كما ترون ، لا تأخذ الإضاءة على الإطلاق في الاعتبار تفاصيل الإغاثة المفترضة لهذا السطح: جميع الشقوق الصغيرة والتجاويف مع الأسمنت لا يمكن تمييزها أيضًا عن بقية السطح. يمكن للمرء استخدام خريطة اللمعان من أجل الحد من إضاءة بعض التفاصيل الموجودة في تجاويف السطح. لكن هذا يبدو وكأنه اختراق قذر وليس حلًا عمليًا. ما نحتاجه هو طريقة لتزويد معادلات الإضاءة ببيانات عن السطح الصغير.
في سياق معادلات الإضاءة المعروفة لنا ، ضع في اعتبارك هذا السؤال: في أي ظروف سيتم إضاءة السطح بشكل مسطح تمامًا؟ الجواب متعلق بالطبيعي على السطح. من وجهة نظر خوارزمية الإضاءة ، يتم إرسال المعلومات حول شكل السطح فقط من خلال الناقل العادي. نظرًا لأن الناقل الطبيعي ثابت في كل مكان على السطح المعروض أعلاه ، فإن الإضاءة تخرج أيضًا موحدة ، تتوافق مع المستوى. ولكن ماذا لو مررنا إلى خوارزمية الإضاءة ليس الثابت العادي الوحيد لجميع الأجزاء التي تنتمي إلى الكائن ، ولكن الفريد العادي لكل جزء؟ وبالتالي ، فإن المتجه العادي سيتغير قليلاً بناءً على التضاريس السطحية ، مما سيخلق وهمًا أكثر إقناعًا بتعقيد السطح:
من خلال استخدام المعايير المختلفة بشكل جزئي ، ستعتبر خوارزمية الإضاءة السطح مؤلفًا من العديد من المستويات المجهرية متعامدة مع ناقلها الطبيعي. ونتيجة لذلك ، سيضيف هذا ملمسًا كبيرًا إلى الكائن. تقنية تطبيق المعايير المعيارية الفريدة على جزء ، وليس على السطح بالكامل - هذا هو
رسم الخرائط العادي أو
رسم الخرائط النتوء . كما هو مطبق على مشهد مألوف بالفعل:
يمكنك أن ترى الزيادة المثيرة للإعجاب في التعقيد البصري بسبب التكلفة المتواضعة للأداء. نظرًا لأن جميع التغييرات في نموذج الإضاءة لا يتم توفيرها إلا في حالة طبيعية فريدة في كل جزء ، فلا يتم تغيير صيغ الحساب. فقط عند الإدخال ، بدلاً من الوضع الطبيعي المحرف ، يأتي الجزء الطبيعي للجزء الحالي إلى السطح. كل معادلات الإضاءة نفسها تقوم ببقية العمل لخلق الوهم بالراحة.
رسم الخرائط العادية
لذلك ، اتضح أننا بحاجة إلى تزويد خوارزمية الإضاءة بالمعايير الطبيعية الفريدة لكل جزء. سوف نستخدم الطريقة المألوفة بالفعل في مواد الانعكاس المنتشر والمرئي ونستخدم الملمس الثنائي الأبعاد المعتاد لتخزين البيانات العادية في كل نقطة على السطح. لا تفاجأ ، فالقوام رائع أيضًا لتخزين المتجهات العادية. ثم علينا فقط الاختيار من الملمس ، واستعادة المتجه العادي وإجراء حسابات الإضاءة.
للوهلة الأولى ، قد لا يكون من الواضح جدًا كيفية حفظ بيانات المتجه في مادة عادية ، والتي تُستخدم عادةً لتخزين معلومات الألوان. ولكن فكر لثانية واحدة: ثلاثي الألوان RGB هو في الأساس ناقل ثلاثي الأبعاد. بطريقة مشابهة ، يمكنك حفظ مكونات ناقل XYZ العادي في مكونات اللون المقابلة. تقع قيم مكونات المتجه العادي في الفاصل الزمني [-1 ، 1] وبالتالي تتطلب تحويلًا إضافيًا إلى الفاصل الزمني [0 ، 1]:
vec3 rgb_normal = normal * 0.5 + 0.5;
سيسمح لنا هذا الانخفاض في المتجه العادي لمساحة مكونات الألوان RGB بحفظ المتجه العادي في الملمس ، الذي تم الحصول عليه على أساس الإغاثة الحقيقية للكائن المنمذج وفريد لكل جزء. مثال على مثل هذا الملمس - الخرائط العادية - لنفس الطوب:
من المثير للاهتمام ملاحظة اللون الأزرق لهذه الخريطة العادية (تحتوي جميع الخرائط العادية تقريبًا على لون مماثل). يحدث هذا لأن جميع القيم الطبيعية يتم توجيهها تقريبًا على طول محور الأوز ، والذي يمثله إحداثيات ثلاثية (0 ، 0 ، 1) ، أي على شكل ثالوث اللون - أزرق نقي. التغييرات الطفيفة في الصبغة هي نتيجة لانحراف المعايير الطبيعية عن شبه المحور الإيجابي أوز في بعض المناطق ، والتي تتوافق مع التضاريس غير المستوية. لذا ، يمكنك أن ترى أنه على الحواف العلوية لكل لبنة ، يأخذ النسيج لونًا أخضر. وهذا منطقي: على الوجوه العلوية للطوب ، يجب أن يتم توجيه المعايير بشكل أكبر نحو محور oY (0 ، 1 ، 0) ، الذي يتوافق مع اللون الأخضر.
بالنسبة لمشهد الاختبار ، خذ طائرة موجّهة نحو المحور شبه الموجب الموجب OZ واستخدم
الخريطة المنتشرة التالية
والخريطة العادية لذلك.
يرجى ملاحظة أن الخريطة العادية على الرابط وفي الصورة أعلاه مختلفة. في المقال ، ذكر المؤلف بشكل عرضي أسباب الاختلافات ، وحصر نفسه في تقديم المشورة بشأن تحويل الخرائط العادية إلى طريقة يشير فيها المكون الأخضر إلى "أسفل" بدلاً من "أعلى" في النظام المحلي إلى مستوى النسيج.
إذا نظرت بمزيد من التفاصيل ، فعندئذ يتفاعل عاملان هنا:
- الفرق هو كيفية معالجة texels في ذاكرة العميل وفي ذاكرة نسيج OpenGL
- وجود تدوينين للخرائط العادية. تقليديا ، معسكرين: نمط DirectX و OpenGL
فيما يتعلق برموز الخريطة العادية ، من المعروف تاريخياً وجود معسكرين: DirectX و OpenGL.
يبدو أنها غير متوافقة. ومع قليل من التفكير ، يمكنك أن تفهم أن DirectX تعتبر المساحة المظلمة أعسر وأن OpenGL يدوياً. سيؤدي تمرير خريطة X العادية لتطبيقنا دون أي تغييرات إلى إضاءة غير صحيحة ، وليس من الواضح دائمًا على الفور أنها غير صحيحة. والأهم من ذلك ، أن الانتفاخات في تنسيق OpenGL تصبح مسافات بادئة لـ DirectX والعكس صحيح.
فيما يتعلق بالعنونة: تحميل البيانات من ملف نسيج في الذاكرة ، نفترض أن texel الأول هو texel العلوي الأيسر للصورة. لتمثيل بيانات النسيج في ذاكرة التطبيق ، يكون هذا صحيحًا بشكل عام. لكن OpenGL يستخدم نظام إحداثيات نسيج مختلف: بالنسبة له ، فإن النسيج الأول هو أسفل اليسار. من أجل التركيب الصحيح ، عادة ما يتم قلب الصور على طول المحور Y في رمز محمل ملف صورة أو آخر. بالنسبة إلى Stb_image المستخدم في الدروس ، تحتاج إلى إضافة مربع اختيار
stbi_set_flip_vertically_on_load(1);
الشيء المضحك هو أن خيارين يتم عرضهما بشكل صحيح من حيث الإضاءة: خريطة عادية في تدوين OpenGL مع تشغيل انعكاس Y أو خريطة عادية في تدوين DirectX مع إيقاف انعكاس Y. تعمل الإضاءة في كلتا الحالتين بشكل صحيح ، وسيبقى الفرق فقط في عكس النسيج على طول المحور ذ.
ملاحظة عبر.
لذا ، قم بتحميل كل من القوام ، وربط كتل النسيج وجعل المستوى المعد ، مع مراعاة التعديلات التالية لرمز تظليل الجزء:
uniform sampler2D normalMap; void main() {
هنا نطبق التحول العكسي من مساحة قيمة RGB إلى ناقل عادي كامل ثم نستخدمه ببساطة في نموذج الإضاءة Blinn-Fong المعروف.
الآن ، إذا قمت بتغيير موضع مصدر الضوء في المشهد ببطء ، يمكنك الشعور بوهم تخفيف السطح الذي توفره الخريطة العادية:
ولكن لا تزال هناك مشكلة واحدة تضيق بشكل كبير نطاق الاستخدام المحتمل للخرائط العادية. كما لوحظ بالفعل ، ألمح اللون الأزرق للخريطة العادية إلى أن جميع المتجهات في النسيج موجهة في المتوسط على طول المحور الإيجابي oZ. في مشهدنا ، لم يخلق هذا مشاكل ، لأن الوضع الطبيعي لسطح الطائرة كان محاذاة أيضًا مع الأوزون. ومع ذلك ، ماذا يحدث إذا قمنا بتغيير موضع الطائرة في المشهد بحيث يتماشى الوضع الطبيعي لها مع المحور الإيجابي oY؟
تبين أن الإضاءة خاطئة تمامًا! والسبب بسيط: عينات من المعايير الطبيعية من الخريطة لا تزال ترجع ناقلات موجهة على طول نصف المحور الموجب oZ ، على الرغم من أنه في هذه الحالة يجب أن يتم توجيهها في اتجاه نصف المحور الموجب للسطح العادي. في الوقت نفسه ، حساب الإضاءة كما لو كانت المعايير على السطح تقع كما لو كانت الطائرة لا تزال موجهة نحو شبه المحور الإيجابي OZ ، مما يعطي نتيجة غير صحيحة. يوضح الشكل أدناه بشكل أوضح اتجاه المعايير الطبيعية التي يتم قراءتها من الخريطة نسبة إلى السطح:
يمكن ملاحظة أن القيم الطبيعية يتم محاذاة بشكل عام على طول شبه المحور الإيجابي oZ ، على الرغم من أنه كان ينبغي محاذاة على طول العادي إلى السطح الذي يتم توجيهه على طول شبه المحور الموجب oY.
قد يكون الحل الممكن هو إعداد خريطة عادية منفصلة لكل اتجاه للسطح قيد النظر. بالنسبة للمكعب ، قد تكون هناك حاجة إلى ست خرائط عادية ، ولكن بالنسبة للنماذج الأكثر تعقيدًا ، قد يكون عدد الاتجاهات المحتملة مرتفعًا جدًا وغير مناسب للتنفيذ.
هناك نهج آخر أكثر تعقيدًا من الناحية الرياضية ، والذي يعرض حساب الإضاءة في نظام إحداثيات مختلف: بحيث تتزامن المتجهات العادية فيه دائمًا تقريبًا مع نصف المحور الإيجابي oZ. يتم بعد ذلك تحويل النواقل الأخرى المطلوبة لحسابات الإضاءة إلى نظام الإحداثيات هذا. تجعل هذه الطريقة من الممكن استخدام خريطة عادية واحدة لأي اتجاه للكائن. ويسمى هذا النظام الإحداثي المحدد
الفضاء المماس أو
الفضاء المماس .
الفضاء المماس
وتجدر الإشارة إلى أن المتجه العادي في الخريطة العادية يتم التعبير عنه مباشرة في الفضاء المماس ، أي في مثل هذا النظام الإحداثي ، يتم توجيه الوضع الطبيعي دائمًا تقريبًا في اتجاه شبه المحور الموجب oZ. يتم تعريف الفضاء المماس كنظام إحداثي محلي لمستوى المثلث ، ويتم تحديد كل ناقل عادي في هذا النظام الإحداثي. يمكنك أن تتخيل هذا النظام كنظام إحداثي محلي لخريطة عادية: يتم توجيه جميع المتجهات الموجودة فيه نحو شبه المحور الموجب أوز ، بغض النظر عن الاتجاه النهائي للسطح. باستخدام مصفوفات التحويل المعدة خصيصًا ، من الممكن تحويل المتجهات العادية من نظام إحداثيات المماس المحلي هذا إلى العالم أو عرض الإحداثيات ، وتوجيهها وفقًا للموضع النهائي للأسطح المزخرفة.
ضع في اعتبارك المثال السابق مع الاستخدام غير الصحيح لرسم الخرائط العادية ، حيث تم توجيه الطائرة على طول المحور الإيجابي oY. نظرًا لأن خريطة المعايير يتم تحديدها في الفضاء المماس ، فإن أحد خيارات الضبط هو حساب مصفوفة الانتقال الطبيعي من الفضاء المماس إلى بحيث تصبح موجهة بشكل طبيعي إلى السطح. وهذا من شأنه أن يجعل المحاذاة الطبيعية متوازنة على طول المحور الإيجابي oY. خاصية ملحوظة من الفضاء المماس هو حقيقة أنه من خلال حساب هذه المصفوفة ، يمكننا إعادة توجيه المعايير إلى أي سطح واتجاهه.
يتم اختصار هذه المصفوفة باسم
TBN ، وهو اختصار لاسم ثلاثي
الموجه المماس ،
Bitangent والعادي . نحن بحاجة إلى العثور على هذه المتجهات الثلاثة من أجل تشكيل مصفوفة تغيير الأساس هذه. مثل هذه المصفوفة تجعل انتقال المتجه من الفضاء المماسي إلى مكان آخر ، ولتكوينه ، يلزم وجود ثلاثة ناقلات متعامدة بشكل متبادل ، يتوافق اتجاهها مع اتجاه مستوى الخريطة العادية. هذا هو ناقل الاتجاه لأعلى ولليمين وإلى الأمام ، وهي مجموعة مألوفة لنا من الدرس على
الكاميرا الافتراضية .
مع الناقل العلوي ، كل شيء واضح على الفور - هذا هو ناقلنا الطبيعي. تسمى
المتجهات اليمنى
والأمامية المماس
والبت على التوالي. يوضح الشكل التالي فكرة عن موقعها النسبي على الطائرة:
حساب المماس والمماس الثنائي ليس واضحًا مثل حساب المتجه العادي. في الشكل ، يمكنك أن ترى أن اتجاهات المماس وخريطة المماس الطبيعية تتماشى مع المحاور التي تحدد إحداثيات نسيج السطح. هذه الحقيقة هي الأساس لحساب هذين المتجهين ، الأمر الذي يتطلب بعض المهارة في الرياضيات. انظر إلى الصورة:
التغييرات في إحداثيات الملمس على طول حافة المثلث
ه 2 المعينة باسم
D ه ل ر ل U 2 و
D ه ل ر ل V 2 معبرًا عنه في نفس اتجاهات ناقلات المماس
ت والظل الثنائي
ب . بناءً على هذه الحقيقة ، يمكنك التعبير عن حواف المثلث
ه 1 و
ه 2 في شكل مجموعة خطية من ناقلات المماس ثنائية المماس:
E 1 = D e l t a U 1 T + D e l t a V 1 B
E 2 = D e l t a U 2 T + D e l t a V 2 B
بالتحول إلى سجل أحادي نحصل على:
(E1x،E1y،E1z)= DeltaU1(Tx،Ty،Tz)+ DeltaV1(Bx،By،Bz)
(E2x،E2y،E2z)= DeltaU2(Tx،Ty،Tz)+ DeltaV2(Bx،By،Bz)
E يتم حسابها كمتجه لفرق متجهين ، و
DeltaU و
DeltaV كما الفرق في إحداثيات الملمس. يبقى العثور على مجهولين في معادلتين: الظل
T والتحيز
B . إذا تذكرت دروس الجبر ، فأنت تعلم أن مثل هذه الشروط تجعل من الممكن حل النظام من أجله
T ولل
B .
يسمح لنا آخر شكل من المعادلات بإعادة كتابته في شكل ضرب المصفوفة:
\ start {bmatrix} E_ {1x} & E_ {1y} & E_ {1z} \\ E_ {2x} & E_ {2y} & E_ {2z} \ end {bmatrix} = \ start {bmatrix} \ Delta U_1 & \ Delta V_1 \\ \ Delta U_2 & \ Delta V_2 \ end {bmatrix} \ start {bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \ end {bmatrix}
حاول القيام بضرب المصفوفة في عقلك للتأكد من صحة السجل. كتابة نظام في شكل مصفوفة يجعل من الأسهل بكثير فهم نهج إيجاد
T و
B . اضرب طرفي المعادلة بعكس
DeltaU DeltaV :
\ start {bmatrix} \ Delta U_1 & \ Delta V_1 \\ \ Delta U_2 & \ Delta V_2 \ end {bmatrix} ^ {- 1} \ start {bmatrix} E_ {1x} & E_ {1y} & E_ {1z } \\ E_ {2x} & E_ {2y} & E_ {2z} \ end {bmatrix} = \ start {bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \ end {bmatrix}
نحصل على قرار بشأن
T و
B ، الذي ، مع ذلك ، يتطلب حساب المصفوفة العكسية للتغيرات في إحداثيات النسيج. لن نتطرق إلى تفاصيل حساب المصفوفات العكسية - يبدو تعبير المصفوفة العكسية وكأنه ناتج الرقم معكوس إلى محدد المصفوفة الأصلية والمصفوفة المجاورة:
\ start {bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \ end {bmatrix} = \ frac {1} {\ Delta U_1 \ Delta V_2 - \ Delta U_2 \ Delta V_1} \ بدء {bmatrix} \ Delta V_2 & - \ Delta V_1 \\ - \ Delta U_2 & \ Delta U_1 \ end {bmatrix} \ بدء {bmatrix} E_ {1x} & E_ {1y} & E_ {1z} \\ E_ {2x} & E_ {2y } و E_ {2z} \ end {bmatrix}
هذا التعبير هو صيغة لحساب متجه المماس
ت والظل الثنائي
ب استنادًا إلى إحداثيات وجوه المثلث وإحداثيات النسيج المقابلة.
لا تقلق إذا كان جوهر الحسابات الرياضية أعلاه يراوغك. إذا فهمت أننا نحصل على المماس والمماس المتحيز استنادًا إلى إحداثيات رؤوس المثلث وإحداثيات نسيجها (نظرًا لأن إحداثيات الملمس تنتمي أيضًا إلى الفضاء المماس) - فهذا بالفعل نصف المعركة.
حساب المماس و bitangents
في مثال هذا الدرس ، أخذنا طائرة بسيطة تتطلع نحو شبه المحور الإيجابي أوز. سنحاول الآن تنفيذ رسم الخرائط العادي باستخدام الفضاء المماس حتى نتمكن من توجيه الطائرة في المثال كما نشاء دون تدمير تأثير رسم الخرائط العادي. باستخدام الحساب أعلاه ، نجد يدويًا المماس والمماس الثنائي على السطح قيد النظر.
نفترض أن المستوى يتكون من القمم التالية بإحداثيات نسيج (يتم إعطاء مثلثين بواسطة المتجهات 1 و 2 و 3 و 1 و 3 و 4):
أولاً ، نحسب المتجهات التي تصف وجوه المثلث ، بالإضافة إلى دلتا إحداثيات الملمس:
glm::vec3 edge1 = pos2 - pos1; glm::vec3 edge2 = pos3 - pos1; glm::vec2 deltaUV1 = uv2 - uv1; glm::vec2 deltaUV2 = uv3 - uv1;
بعد الحصول على البيانات الأولية اللازمة ، يمكننا البدء في حساب الظل والمماس الثنائي مباشرة من الصيغ من القسم السابق:
float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y); tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x); tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y); tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z); tangent1 = glm::normalize(tangent1); bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x); bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y); bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z); bitangent1 = glm::normalize(bitangent1); [...]
أولاً ، نأخذ المكون الكسري للتعبير النهائي في متغير منفصل
f . ثم ، بالنسبة لكل مكون من مكونات المتجهات ، نقوم بإجراء الجزء المقابل من ضرب المصفوفة والضرب في
f . بمقارنة هذا الرمز مع صيغة الحساب النهائية ، يمكنك أن ترى أن هذا هو ترتيبه الحرفي. لا تنس التطبيع في النهاية ، بحيث تكون المتجهات التي تم العثور عليها وحدة.
بما أن المثلث شكل مسطح ، يكفي حساب التماس والظل الثنائي مرة واحدة لكل مثلث - سيكونان متساويين لجميع القمم. تجدر الإشارة إلى أن معظم تطبيقات العمل مع النماذج (مثل اللوادر أو مولدات المناظر الطبيعية) تستخدم مثل هذه المنظمة للمثلثات ، حيث تتشارك القمم مع المثلثات الأخرى. في مثل هذه الحالات ، يلجأ المطورون عادةً إلى متوسط المعلمات عند القمم الشائعة ، مثل المتجهات العادية ، المماس والمماس الثنائي ، للحصول على نتيجة أكثر سلاسة. تشترك المثلثات التي تتكون منها طائرتنا أيضًا في العديد من القمم ، ولكن نظرًا لأن كلاهما يكمن في نفس المستوى ، فإن المتوسط ليس مطلوبًا. ومع ذلك ، من المفيد أن نتذكر وجود مثل هذا النهج في التطبيقات والمهام الحقيقية.
يجب أن يكون للمتجهين المماس والثنائي الناتج قيم (1 ، 0 ، 0) و (0 ، 1 ، 0) ، على التوالي. هذا مع المتجه العادي (0 ، 0 ، 1) يشكل مصفوفة متعامدة TBN. إذا كنت تصور الأساس الناتج مع الطائرة ، فستحصل على الصورة التالية:
الآن ، بعد أن تم حساب المتجهات ، يمكنك المتابعة إلى التنفيذ الكامل لرسم الخرائط العادية.
رسم الخرائط الطبيعية في الفضاء المماس
تحتاج أولاً إلى إنشاء مصفوفة TBN في التظليل. لهذا الغرض ، سننقل ناقلات التماس ثنائية الظل التي تم إعدادها مسبقًا إلى تظليل الرأس من خلال سمات القمة:
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; layout (location = 3) in vec3 aTangent; layout (location = 4) in vec3 aBitangent;
في كود التظليل قمة الرأس نفسه ، نشكل المصفوفة مباشرة:
void main() { [...] vec3 T = normalize(vec3(model * vec4(aTangent, 0.0))); vec3 B = normalize(vec3(model * vec4(aBitangent, 0.0))); vec3 N = normalize(vec3(model * vec4(aNormal, 0.0))); mat3 TBN = mat3(T, B, N) }
في الكود أعلاه ، نقوم أولاً بتحويل جميع المتجهات لأساس الفضاء المماس إلى نظام إحداثيات نشعر فيه بالراحة للعمل - في هذه الحالة ، هذا هو نظام الإحداثيات العالمي ونقوم بضرب المتجهات في نموذج مصفوفة
النموذج . بعد ذلك ، نقوم بإنشاء مصفوفة TBN نفسها عن طريق تمرير جميع المتجهات الثلاثة المقابلة إلى مُنشئ من النوع
mat3 . يرجى ملاحظة أنه لكي يكون الترتيب صحيحًا تمامًا ، من الضروري مضاعفة المتجهات ليس في مصفوفة النموذج ، ولكن في المصفوفة العادية ، لأننا مهتمون فقط بتوجيه المتجهات ، ولكن ليس إزاحتها أو تحجيمها
بالمعنى الدقيق للكلمة ، ليس من الضروري نقل ناقل ثنائي الظل إلى التظليل.
بما أن الثلاثي من ناقلات TBN متعامدة بشكل متبادل ، يمكن العثور على الظل الثنائي في التظليل من خلال الضرب المتجه:
vec3 B = cross(N, T)
لذا ، يتم تلقي مصفوفة TBN ، كيف نستخدمها؟ في الواقع ، هناك طريقتان لاستخدامه في رسم الخرائط العادية:
- استخدم مصفوفة TBN لتحويل جميع المتجهات الضرورية من المماس إلى الفضاء العالمي. انقل النتائج إلى جهاز تظليل الأجزاء ، حيث ، باستخدام المصفوفة أيضًا ، قم بتحويل المتجه من الخريطة العادية إلى مساحة العالم. ونتيجة لذلك ، سيكون المتجه العادي في الفضاء حيث يتم حساب كل الإضاءة.
- خذ المصفوفة العكسية إلى TBN وقم بتحويل جميع المتجهات الضرورية من الفضاء العالمي إلى المماس. على سبيل المثال استخدم هذه المصفوفة لتحويل المتجهات المشاركة في حسابات الإضاءة إلى مساحة مظلمة. كما يظل الناقل العادي في هذه الحالة في نفس المساحة مثل المشاركين الآخرين في حساب الإضاءة.
دعونا نلقي نظرة على الخيار الأول. يتم تحديد المتجه العادي من النسيج المقابل في الفضاء المماس ، في حين يتم تحديد المتجهات الأخرى المستخدمة في حساب الإضاءة في الفضاء العالمي. من خلال تمرير مصفوفة TBN إلى تظليل الشظايا ، يمكننا تحويل المتجه العادي الذي تم الحصول عليه عن طريق أخذ العينات من الملمس من المماس إلى الفضاء العالمي ، مما يضمن وحدة أنظمة الإحداثيات لجميع عناصر حساب الإضاءة. في هذه الحالة ، ستكون جميع الحسابات (خاصة مضاعفات ناقلات العددية) صحيحة.
يتم نقل مصفوفة TBN بأبسط طريقة:
out VS_OUT { vec3 FragPos; vec2 TexCoords; mat3 TBN; } vs_out; void main() { [...] vs_out.TBN = mat3(T, B, N); }
في كود تظليل الجزء ، على التوالي ، قمنا بتعيين متغير إدخال من النوع mat3:
in VS_OUT { vec3 FragPos; vec2 TexCoords; mat3 TBN; } fs_in;
بوجود المصفوفة في متناول اليد ، يمكنك تحديد الكود للحصول على العادي من خلال التعبير عن الترجمة من الظل إلى الفضاء العالمي:
normal = texture(normalMap, fs_in.TexCoords).rgb; normal = normalize(normal * 2.0 - 1.0); normal = normalize(fs_in.TBN * normal);
نظرًا لأن الوضع الطبيعي الناتج قد تم تعيينه الآن في الفضاء العالمي ، فلا حاجة لتغيير أي شيء آخر في كود التظليل. حسابات الإضاءة ، وبالتالي تفترض ناقلًا عاديًا محددًا في إحداثيات العالم.
دعونا نلقي نظرة على النهج الثاني.
سيتطلب الحصول على مصفوفة TBN العكسية ، بالإضافة إلى نقل جميع المتجهات المشاركة في حساب الإضاءة من نظام الإحداثيات العالمي إلى الذي يتوافق مع المتجهات العادية التي تم الحصول عليها من الملمس - المماس. في هذه الحالة ، يظل تشكيل مصفوفة TBN دون تغيير ، ولكن قبل الانتقال إلى جهاز تظليل الجزء ، يجب أن نحصل على المصفوفة العكسية: vs_out.TBN = transpose(mat3(T, B, N));
,
transpose() inverse() . , ( ) . , , , .
, ,
lightDir viewDir . , – .
void main() { vec3 normal = texture(normalMap, fs_in.TexCoords).rgb; normal = normalize(normal * 2.0 - 1.0); vec3 lightDir = fs_in.TBN * normalize(lightPos - fs_in.FragPos); vec3 viewDir = fs_in.TBN * normalize(viewPos - fs_in.FragPos); [...] }
يبدو أن النهج الثاني يستغرق وقتًا أطول ويتطلب المزيد من مضاعفات المصفوفة في جهاز تظليل الأجزاء (مما يؤثر بشكل كبير على الأداء). لماذا بدأنا حتى بتفكيكها؟والحقيقة هي أن ترجمة المتجهات من إحداثيات العالم إلى الظل توفر ميزة إضافية: في الواقع ، يمكننا نقل رمز التحويل بالكامل من جزء إلى تظليل قمة الرأس! يعمل هذا النهج لأن lightPos و viewPos لا يتغيران من جزء إلى جزء ، والقيمة fs_in.FragPosيمكننا أيضًا أن نترجم إلى مساحة مظلمة في تظليل الرأس ، ستكون القيمة المستكملة عند مدخل تظليل الجزء صحيحة تمامًا. وبالتالي ، بالنسبة للنهج الثاني ، ليست هناك حاجة لترجمة كل هذه المتجهات إلى الفضاء المماس في كود تظليل الشظايا ، بينما يتطلب الأول ذلك - فالطبيعي فريد لكل جزء.ونتيجة لذلك ، نبتعد عن نقل المصفوفة العكسية إلى TBN إلى جهاز تظليل الشظايا ونقوم بدلاً من ذلك بتمريرها في اتجاه الرأس ، ومصدر الضوء والمراقب في الفضاء المماس. لذلك سوف نتخلص من مضاعفات المصفوفة المكلفة في تظليل الشظايا ، والتي ستكون تحسينًا كبيرًا ، لأنه يتم تنفيذ تظليل الرأس في كثير من الأحيان. هذه الميزة هي التي تضع النهج الثاني في فئة الاستخدام المفضل في معظم الحالات. out VS_OUT { vec3 FragPos; vec2 TexCoords; vec3 TangentLightPos; vec3 TangentViewPos; vec3 TangentFragPos; } vs_out; uniform vec3 lightPos; uniform vec3 viewPos; [...] void main() { [...] mat3 TBN = transpose(mat3(T, B, N)); vs_out.TangentLightPos = TBN * lightPos; vs_out.TangentViewPos = TBN * viewPos; vs_out.TangentFragPos = TBN * vec3(model * vec4(aPos, 0.0));
في تظليل الجزء ، ننتقل إلى استخدام متغيرات الإدخال الجديدة في حسابات الإضاءة في الفضاء المماس. نظرًا لأنه يتم تحديد المعايير بشكل مشروط في هذه المساحة ، تظل جميع الحسابات صحيحة.الآن بعد إجراء جميع حسابات التعيين العادية في مساحة الظل ، يمكننا تغيير اتجاه سطح الاختبار في التطبيق كما نريد وستظل الإضاءة صحيحة: glm::mat4 model(1.0f); model = glm::rotate(model, (float)glfwGetTime() * -10.0f, glm::normalize(glm::vec3(1.0, 0.0, 1.0))); shader.setMat4("model", model); RenderQuad();
في الواقع ، يبدو كل شيء ظاهريًا كما ينبغي:المصادر هنا .كائنات معقدة
لذا ، اكتشفنا كيفية إجراء رسم الخرائط الطبيعي في الفضاء المماس وكيفية حساب ناقلات المماس المماس والتحيز لهذا الأمر بشكل مستقل. لحسن الحظ ، ليس مثل هذا الحساب اليدوي مهمة في كثير من الأحيان: في الغالب ، يتم تنفيذ هذا الرمز من قبل المطورين في مكان ما في أحشاء محمل النموذج. في حالتنا ، ينطبق هذا على اللودر Assimp المستخدم .يوفر Assimp إشارة خيار مفيدة للغاية عند تحميل النماذج: aiProcess_CalcTangentSpace . عندما يتم تمريرها إلى وظيفة ReadFile () ، ستحسب المكتبة نفسها خطوط التماس السلس والماس الثنائي لكل من الذروات المحملة - وهي عملية مشابهة لتلك التي تمت مناقشتها هنا. const aiScene *scene = importer.ReadFile( path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace );
بعد ذلك ، يمكنك الوصول مباشرة إلى الظلات المحسوبة: vector.x = mesh->mTangents[i].x; vector.y = mesh->mTangents[i].y; vector.z = mesh->mTangents[i].z; vertex.Tangent = vector;
ستحتاج أيضًا إلى تحديث رمز التنزيل لمراعاة استلام الخرائط العادية للنماذج المزخرفة. يقوم تنسيق Wavefront Object (.obj) بتصدير الخرائط العادية بطريقة لا تضمن علامة Assimp aiTextureType_NORMAL تحميل هذه الخرائط بشكل صحيح ، بينما يعمل كل شيء بشكل صحيح مع علامة aiTextureType_HEIGHT . لذلك ، أنا شخصياً أقوم عادةً بتحميل الخرائط العادية بالطريقة التالية: vector<Texture> normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");
بالطبع ، قد لا يكون هذا النهج مناسبًا لتنسيقات وصف النموذج وأنواع الملفات الأخرى. وألاحظ أيضًا أن إعداد علامة aiProcess_CalcTangentSpace لا يعمل دائمًا. نحن نعلم أن حساب المماس يعتمد على إحداثيات النسيج ، ومع ذلك ، غالبًا ما يطبق مؤلفو النموذج حيلًا مختلفة على إحداثيات الملمس ، مما يكسر حساب المماس. لذلك ، غالبًا ما يتم استخدام صورة طبق الأصل لإحداثيات النسيج في النماذج ذات المظهر المتناظر. إذا لم يتم أخذ حقيقة النسخ المطابق في الاعتبار ، فسيكون حساب المماس غير صحيح. Assimp لا تفعل هذا المحاسبة. إن نموذج nanosuit المألوف هنا ليس مناسبًا للعرض ، لأنه يستخدم أيضًا النسخ المتطابق.ولكن مع نموذج محكم بشكل صحيح باستخدام الخرائط العادية والمخططة ، يعطي تطبيق الاختبار نتيجة جيدة جدًا:كما ترون ، فإن استخدام الخرائط العادية يوفر زيادة ملموسة في التفاصيل وهو رخيص من حيث تكاليف الأداء.لا تنس أن استخدام الخرائط العادية يمكن أن يساعد في تحسين الأداء لمشهد معين. بدون استخدامه ، لا يمكن تحقيق تفاصيل النموذج إلا من خلال زيادة كثافة الشبكة المضلعة ، الشبكة. لكن هذه التقنية تتيح لك تحقيق نفس المستوى من التفاصيل بشكل مرئي للشبكات منخفضة البولي. يمكنك أن ترى أدناه مقارنة بين هذين النهجين:لا يمكن تمييز مستوى التفاصيل في نموذج البولي عالي وفي النموذج منخفض بولي باستخدام الخرائط العادية عمليا. لذا فإن هذه التقنية هي طريقة رائعة لاستبدال النماذج عالية الكثافة في المشهد بأخرى مبسطة مع عدم فقدان جودة بصرية تقريبًا.التعليق الأخير
هناك تفاصيل فنية أخرى تتعلق بالتخطيط العادي ، مما يحسن الجودة قليلاً بتكلفة إضافية قليلة أو بدون تكلفة.في الحالات التي يتم فيها حساب المماس للشبكات الكبيرة والمعقدة مع عدد كبير من القمم التي تنتمي إلى العديد من المثلثات ، عادةً ما يتم حساب نواقل المماس للحصول على نتيجة تخطيط عادية سلسة ولطيفة بصريًا. ومع ذلك ، يخلق هذا مشكلة: بعد المتوسط ، يمكن أن تفقد ثلاثية ناقلات TBN العمودية المتبادلة ، مما يعني أيضًا فقدان التعامد لمصفوفة TBN. في الحالة العامة ، تكون نتيجة رسم الخرائط العادية التي يتم الحصول عليها على أساس مصفوفة غير متعامدة غير صحيحة إلا قليلاً ، ولكن لا يزال بإمكاننا تحسينها.للقيام بذلك ، يكفي تطبيق طريقة رياضية بسيطة:يقوم Gram-Schmidt بمعالجة أو إعادة تعامد ثلاثي ناقلاتنا TBN. في رمز تظليل الذروة: vec3 T = normalize(vec3(model * vec4(aTangent, 0.0))); vec3 N = normalize(vec3(model * vec4(aNormal, 0.0)));
يعمل هذا التعديل ، على الرغم من صغره ، على تحسين جودة رسم الخرائط العادية في مقابل النفقات العامة الهزيلة. إذا كنت مهتمًا بتفاصيل هذا الإجراء ، يمكنك مشاهدة الجزء الأخير من فيديو Normal Mapping Mathematics ، الرابط الذي يرد أدناه.موارد إضافية
ملاحظة : لدينا برقية أسيوط لتنسيق التحويلات. إذا كانت لديك رغبة جادة في المساعدة في الترجمة ، فأنت مرحب بك!