تعلم برنامج OpenGL. الدرس 5.8 - بلوم

OGL3

بلوم


نظرًا لنطاق السطوع المحدود المتاح للشاشات التقليدية ، فإن مهمة عرض مصادر الإضاءة الساطعة بشكل مقنع والأسطح ذات الإضاءة الساطعة أمر صعب من حيث التعريف. إحدى الطرق الشائعة لإبراز المناطق الساطعة على الشاشة هي تقنية تضيف هالة من التوهج حول الأجسام الساطعة ، مما يعطي الانطباع عن "انتشار" الضوء خارج مصدر الضوء. ونتيجة لذلك ، يعطي المراقب انطباعًا عن سطوع عالي لهذه المناطق المضاءة أو مصادر الضوء.

يتم تحقيق التأثير الموصوف لهالة وخروج الضوء خارج المصدر بواسطة تقنية ما بعد المعالجة تسمى bloom . يضيف تطبيق التأثير هالة مميزة من التوهج إلى جميع المناطق الساطعة في المشهد المعروض ، والتي يمكن رؤيتها في المثال أدناه:



يضيف Bloom دليلًا بصريًا مميزًا للصورة حول السطوع الكبير للكائنات التي تغطيها الهالة من التأثير المطبق. من خلال تطبيقه بشكل انتقائي وإلى حد دقيق (والذي لا تستطيع العديد من الألعاب ، للأسف التعامل معه) ، يمكن للتأثير أن يحسن بشكل كبير من التعبير البصري للإضاءة المستخدمة في المشهد ، بالإضافة إلى إضافة الدراما في مواقف معينة.

تعمل هذه التقنية جنبًا إلى جنب مع تقديم HDR تقريبًا كإضافة ذاتية الوضوح. على ما يبدو ، وبسبب هذا ، يخلط الكثير من الناس عن طريق الخطأ هذين المصطلحين مع قابلية التبادل الكامل. ومع ذلك ، فإن هذه التقنيات مستقلة تمامًا ويتم استخدامها لأغراض مختلفة. من الممكن تنفيذ التفتح باستخدام مخزن الإطار الافتراضي بعمق ألوان 8 بت ، تمامًا مثل تطبيق عرض HDR دون اللجوء إلى استخدام التفتح. الشيء الوحيد هو أن عرض HDR يسمح لك بتنفيذ التأثير بطريقة أكثر كفاءة (سنرى ذلك لاحقًا).

لتنفيذ الإزهار ، يتم تقديم المشهد المضاء أولاً بالطريقة المعتادة. بعد ذلك ، يتم استخراج مخزن مؤقت للألوان HDR ومخزن مؤقت للألوان يحتوي على أجزاء مشرقة فقط من المشهد. يتم بعد ذلك تشويه صورة الجزء المشرق المستخرج وتراكبها فوق صورة HDR الأصلية للمشهد.

لجعل الأمر أكثر وضوحًا ، سنقوم بتحليل العملية خطوة بخطوة. اعرض مشهدًا يحتوي على 4 مصادر إضاءة ساطعة معروضة كمكعبات ملونة. كلها لها قيمة سطوع في النطاق من 1.5 إلى 15.0. إذا تم إخراج المخزن المؤقت للألوان إلى HDR ، فستكون النتيجة كما يلي:


من هذا المخزن المؤقت للألوان بتقنية HDR ، نستخرج جميع الأجزاء التي يتجاوز سطوعها الحد المحدد مسبقًا. اتضح صورة تحتوي فقط على مناطق مضاءة بشكل مشرق:


علاوة على ذلك ، هذه الصورة من المناطق الساطعة غير واضحة. يتم تحديد شدة التأثير بشكل أساسي من خلال قوة ونصف قطر مرشح الضبابية المطبق:


الصورة الضبابية الناتجة عن المناطق الساطعة هي أساس التأثير النهائي للهالات حول الأجسام الساطعة. يتم خلط هذا الملمس ببساطة مع صورة HDR الأصلية للمشهد. منذ أن تم تعتيم المناطق الساطعة ، زادت أحجامها ، مما يعطي في النهاية تأثيرًا بصريًا للإضاءة يتجاوز حدود مصادر الضوء:


كما ترى ، فإن bloom ليس أكثر التقنيات تعقيدًا ، ولكن تحقيق جودة بصرية عالية وموثوقية ليس دائمًا سهلاً. بالنسبة للجزء الأكبر ، يعتمد التأثير على جودة ونوع مرشح التعتيم المطبق. حتى التغييرات الصغيرة في معلمات المرشح يمكن أن تغير بشكل كبير الجودة النهائية للمعدات.

لذلك ، تعطينا الإجراءات المذكورة أعلاه خوارزمية خطوة بخطوة لتأثير ما بعد المعالجة لتأثير التفتح. الصورة أدناه تلخص الإجراءات المطلوبة:


بادئ ذي بدء ، نحن بحاجة إلى معلومات حول الأجزاء الساطعة من المشهد بناءً على قيمة عتبة معينة. هذا ما سنفعله.

استخراج مقتطفات


لذا ، بالنسبة للمبتدئين ، نحتاج إلى الحصول على صورتين بناءً على مشهدنا. سيكون من السذاجة أن يتم العرض مرتين ، ولكن استخدم طريقة أهداف العرض المتعددة الأكثر تقدمًا ( MRT ): نحدد أكثر من ناتج واحد في أداة تظليل الجزء النهائي ، وبفضل هذا ، يمكن استخراج صورتين في مسار واحد! لتحديد المخزن المؤقت اللوني الذي سيتم فيه إخراج جهاز التظليل ، يتم استخدام محدد التخطيط :

layout (location = 0) out vec4 FragColor; layout (location = 1) out vec4 BrightColor; 

بالطبع ، ستعمل الطريقة فقط إذا قمنا بإعداد العديد من المخازن المؤقتة للكتابة. بمعنى آخر ، لتنفيذ مخرجات متعددة من جهاز تظليل الأجزاء ، يجب أن يحتوي المخزن المؤقت للإطار المستخدم في هذه اللحظة على عدد كاف من المخازن المؤقتة للألوان المتصلة. إذا انتقلنا إلى الدرس حول المخزن المؤقت للإطار ، فيُستذكر أنه عند ربط النسيج كمخزن مؤقت للألوان ، يمكننا الإشارة إلى رقم مرفق اللون . حتى الآن ، لم نكن بحاجة إلى استخدام مرفق بخلاف GL_COLOR_ATTACHMENT0 ، ولكن هذه المرة سيكون GL_COLOR_ATTACHMENT1 مفيدًا ، لأننا نحتاج إلى هدفين للتسجيل في وقت واحد:

 //       unsigned int hdrFBO; glGenFramebuffers(1, &hdrFBO); glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO); unsigned int colorBuffers[2]; glGenTextures(2, colorBuffers); for (unsigned int i = 0; i < 2; i++) { glBindTexture(GL_TEXTURE_2D, colorBuffers[i]); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); //     glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0 ); } 

أيضًا ، من خلال استدعاء glDrawBuffers ، ستحتاج إلى إخبار OpenGL صراحة أننا سنخرج إلى العديد من المخازن المؤقتة. خلاف ذلك ، ستظل المكتبة تخرج فقط للمرفق الأول ، متجاهلة عمليات الكتابة إلى المرفقات الأخرى. كحجة للدالة ، يتم تمرير صفيف من معرفات المرفقات المستخدمة من التعداد المقابل:

 unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers(2, attachments); 

بالنسبة لمخزن الإطار المؤقت هذا ، ستكتب أي أداة تظليل جزء تحدد محدد موقع لمخرجاته إلى المخزن المؤقت للألوان المقابل. وهذه أخبار رائعة ، لأنه بهذه الطريقة نتجنب تمرير العرض غير الضروري لاستخراج البيانات حول الأجزاء الساطعة من المشهد - يمكنك القيام بكل شيء في وقت واحد في تظليل واحد:

 #version 330 core layout (location = 0) out vec4 FragColor; layout (location = 1) out vec4 BrightColor; [...] void main() { [...] //      FragColor = vec4(lighting, 1.0); //         //   -    ,    float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722)); if(brightness > 1.0) BrightColor = vec4(FragColor.rgb, 1.0); else BrightColor = vec4(0.0, 0.0, 0.0, 1.0); } 

في هذا الجزء ، يتم حذف الجزء الذي يحتوي على الرمز النموذجي لحساب الإضاءة. تتم كتابة نتيجتها على الإخراج الأول للتظليل - متغير FragColor . بعد ذلك ، يتم استخدام اللون الناتج للجزء لحساب قيمة السطوع. لهذا ، يتم تنفيذ ترجمة مرجحة في التدرج الرمادي (عن طريق الضرب الحجمي ، نقوم بضرب المكونات المقابلة للمتجهات ونضيفها معًا ، مما يؤدي إلى قيمة واحدة). ثم ، عندما يتم تجاوز سطوع جزء من عتبة معينة ، نسجل لونه في الإخراج الثاني للتظليل. بالنسبة للمكعبات التي تحل محل مصادر الضوء ، يتم تنفيذ هذا التظليل أيضًا.

بعد معرفة الخوارزمية ، يمكننا أن نفهم لماذا تعمل هذه التقنية بشكل جيد مع عرض HDR. يسمح العرض بتنسيق HDR لمكونات الألوان بتجاوز الحد الأعلى البالغ 1.0 ، والذي يسمح لك بتعديل عتبة السطوع بمرونة أكبر خارج الفاصل الزمني القياسي [0. ، 1.] ، مما يوفر القدرة على الضبط الدقيق لأجزاء المشهد التي تعتبر مشرقة. بدون استخدام HDR ، يجب أن تكون راضيًا عن حد سطوع في الفاصل الزمني [0. ، 1.] ، وهو أمر مقبول تمامًا ، ولكنه يؤدي إلى قطع "أكثر حدة" في السطوع ، مما يجعل الإزهار متطفلاً ومبهجًا كثيرًا (تخيل نفسك في حقل ثلجي مرتفع في الجبال) .

بعد تنفيذ التظليل ، سيحتوي مخزنان مؤقتان للهدف على صورة طبيعية للمشهد ، بالإضافة إلى صورة تحتوي على مناطق ساطعة فقط.


يجب الآن معالجة صورة المناطق الساطعة باستخدام التمويه. يمكنك تحقيق ذلك باستخدام مرشح ( مربع ) مستطيل بسيط ، تم استخدامه في قسم ما بعد المعالجة من درس المخزن المؤقت للإطارات . ولكن يتم الحصول على نتيجة أفضل بكثير من خلال تصفية Gauss .

غاوس طمس


أعطانا درس ما بعد المعالجة فكرة التعتيم باستخدام متوسط ​​لون بسيط لأجزاء الصورة المجاورة. طريقة التمويه هذه بسيطة ، لكن الصورة الناتجة قد تبدو أكثر جاذبية. يعتمد التمويه الغوسي على منحنى التوزيع على شكل الجرس الذي يحمل نفس الاسم: تقع القيم العالية للوظيفة بالقرب من مركز المنحنى وتقع على جانبيها. رياضيا ، يمكن التعبير عن منحنى غاوس بمعلمات مختلفة ، لكن الشكل العام للمنحنى يبقى كما يلي:


التمويه بالأوزان استنادًا إلى قيم منحنى Gauss يبدو أفضل بكثير من المرشح المستطيل: نظرًا لحقيقة أن المنحنى يحتوي على مساحة أكبر بالقرب من مركزه ، والذي يتوافق مع الأوزان الأكبر للشظايا بالقرب من مركز قلب المرشح. على سبيل المثال ، نواة 32x32 ، سنستخدم عوامل الترجيح الأصغر ، وكلما كانت الشقّة أكبر من الجزء المركزي. إن خاصية الفلتر هذه هي التي تعطي نتيجة ضبابية غوسية أكثر إرضاء بصريًا.

سيتطلب تطبيق المرشح مجموعة ثنائية الأبعاد من معاملات الترجيح ، والتي يمكن ملؤها على أساس التعبير الثنائي الأبعاد الذي يصف المنحنى الغوسي. ومع ذلك ، سنواجه على الفور مشكلة في الأداء: حتى النواة الضبابية الصغيرة نسبيًا في جزء 32 × 32 ستتطلب 1024 عينة نسيج لكل جزء من الصورة المعالجة!

لحسن الحظ بالنسبة لنا ، فإن تعبير المنحنى الغوسي له خاصية رياضية مريحة للغاية - قابلية الفصل ، مما سيجعل من الممكن عمل تعبيرين أحادي البعد من تعبير ثنائي الأبعاد يصف المكونات الأفقية والرأسية. سيتيح هذا التمويه بدوره بنهجين: أفقيًا ، ثم رأسيًا مع مجموعات من الأوزان المقابلة لكل اتجاه. ستكون الصورة الناتجة هي نفسها عند معالجة خوارزمية ثنائية الأبعاد ، ولكنها تتطلب طاقة معالجة أقل بكثير لمعالج الفيديو: بدلاً من 1024 عينة من النسيج ، نحتاج فقط 32 + 32 = 64! هذا هو جوهر الترشيح الغوسي بتمريرين.


بالنسبة لنا ، كل هذا يعني شيئًا واحدًا: يجب أن يتم تعتيم صورة واحدة مرتين ، وهنا سيكون استخدام الكائنات العازلة للإطار مفيدًا. نقوم بتطبيق ما يسمى بتقنية ping-pong: هناك زوجان من كائنات الإطارات المؤقتة ويتم عرض محتويات المخزن المؤقت اللوني الخاص بإطار إطار واحد مع بعض المعالجة في المخزن المؤقت اللوني الخاص بإطار الإطار الحالي ، ثم يتم تبادل غلاف الإطار المصدر وجهاز استقبال الإطار Framebuffer ويتم تكرار هذه العملية لعدد معين من المرات. في الواقع ، يتم ببساطة تبديل المخزن المؤقت للإطار الحالي لعرض الصورة ، ومعه ، يتم استخدام النسيج الحالي الذي يتم منه أخذ العينات للعرض. تسمح لك الطريقة بتمويه الصورة الأصلية عن طريق وضعها في المخزن المؤقت للإطار الأول ، ثم تمويه محتويات المخزن المؤقت للإطار الأول ، ووضعها في الثانية ، ثم تمويه الصورة الثانية ، ووضعها في الأول وهكذا.

قبل الانتقال إلى رمز ضبط المخزن المؤقت للإطار ، دعنا نلقي نظرة على كود تظليل التمويه Gaussian blur:

 #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D image; uniform bool horizontal; uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); void main() { //     vec2 tex_offset = 1.0 / textureSize(image, 0); //    vec3 result = texture(image, TexCoords).rgb * weight[0]; if(horizontal) { for(int i = 1; i < 5; ++i) { result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; } } else { for(int i = 1; i < 5; ++i) { result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i]; result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i]; } } FragColor = vec4(result, 1.0); } 

كما ترى ، نستخدم عينة صغيرة إلى حد ما من معاملات المنحنى الغوسي ، والتي تستخدم كأوزان للعينات أفقياً أو عمودياً بالنسبة للجزء الحالي. يحتوي الكود على فرعين رئيسيين يقسمان الخوارزمية إلى تمرير رأسي وأفقي بناءً على قيمة الزي الأفقي . يتم تعيين الإزاحة لكل عينة مساوية لحجم texel ، الذي يتم تعريفه على أنه متبادل لحجم النسيج (قيمة من النوع vec2 التي يتم إرجاعها بواسطة الدالة نسيج الحجم ()).

إنشاء إطارين مؤقتين يحتويان على مخزن مؤقت للون واحد بناءً على النسيج:

 unsigned int pingpongFBO[2]; unsigned int pingpongBuffer[2]; glGenFramebuffers(2, pingpongFBO); glGenTextures(2, pingpongBuffer); for (unsigned int i = 0; i < 2; i++) { glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]); glBindTexture(GL_TEXTURE_2D, pingpongBuffer[i]); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffer[i], 0 ); } 

بعد أن نحصل على نسيج HDR للمشهد ونستخرج نسيج المناطق الساطعة ، نملأ المخزن المؤقت اللوني لأحد زوج من إطارات الإطارات المحضرة بنسيج السطوع ونبدأ عملية كرة الطاولة عشر مرات (خمس مرات رأسيًا ، وخمس أفقياً):

 bool horizontal = true, first_iteration = true; int amount = 10; shaderBlur.use(); for (unsigned int i = 0; i < amount; i++) { glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]); shaderBlur.setInt("horizontal", horizontal); glBindTexture( GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongBuffers[!horizontal] ); RenderQuad(); horizontal = !horizontal; if (first_iteration) first_iteration = false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); 

في كل تكرار ، نختار ونثبت أحد المخازن المؤقتة للإطارات استنادًا إلى ما إذا كان هذا التكرار سوف يطمس أفقيًا أو رأسيًا ، ثم يتم استخدام المخزن المؤقت للألوان لقائم الإطارات الآخر كنسيج الإدخال لتظليل التعتيم. في التكرار الأول ، يجب أن نستخدم بشكل صريح صورة تحتوي على مناطق ساطعة ( BrightnessTexture ) - وإلا سيبقى كل من حشو الإطارات ping-pong فارغًا. بعد عشر تمريرات ، تأخذ الصورة الأصلية شكل خمس مرات غير واضحة بواسطة مرشح غاوسي كامل. تتيح لنا الطريقة المستخدمة تغيير درجة التمويه بسهولة: فكلما زادت تكرارات كرة الطاولة ، كلما كان التمويه أقوى.

في حالتنا ، تبدو نتيجة التمويه شيئًا مثل هذا:


لإكمال التأثير ، يبقى فقط لدمج الصورة الضبابية مع صورة HDR الأصلية للمشهد.

مزج الملمس


مع وجود نسيج HDR للمشهد المعروض والملمس الباهت للمناطق المعرضة للضوء ، كل ما تحتاجه لتحقيق تأثير التوهج الشهير أو التوهج هو الجمع بين هاتين الصورتين. يقوم جهاز تظليل الجزء الأخير (مشابه جدًا لتلك الموجودة في الدرس حول تنسيق HDR ) بذلك - فهو يمزج بشكل إضافي مادتين:

 #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D scene; uniform sampler2D bloomBlur; uniform float exposure; void main() { const float gamma = 2.2; vec3 hdrColor = texture(scene, TexCoords).rgb; vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; hdrColor += bloomColor; // additive blending //   vec3 result = vec3(1.0) - exp(-hdrColor * exposure); //     - result = pow(result, vec3(1.0 / gamma)); FragColor = vec4(result, 1.0); } 

ما الذي تبحث عنه: يتم المزج قبل تطبيق تعيين النغمة . سيؤدي هذا إلى ترجمة السطوع الإضافي بشكل صحيح من التأثير إلى نطاق LDR ( النطاق الديناميكي المنخفض ) ، مع الحفاظ على توزيع السطوع النسبي في المشهد.

نتيجة المعالجة - تلقت جميع المناطق المضيئة تأثير توهج ملحوظ:


تبدو المكعبات التي تحل محل مصادر الضوء أكثر إشراقًا بكثير وتنقل الانطباع عن مصدر الضوء بشكل أفضل. هذا المشهد بدائي تمامًا ، لأن تنفيذ تأثير الحماس الخاص لن يسبب ، ولكن في المشاهد المعقدة ذات الإضاءة المدروسة ، يمكن أن يكون الإزهار المحقق نوعيًا عنصرًا مرئيًا حاسمًا يضيف الدراما.

كود المصدر للمثال هنا .

ألاحظ أن الدرس استخدم مرشحًا بسيطًا إلى حد ما مع خمس عينات فقط في كل اتجاه. من خلال إجراء المزيد من العينات في نصف قطر أكبر أو من خلال إجراء عدة تكرارات للمرشح ، يمكنك تحسين التأثير بشكل مرئي. أيضًا ، من الجدير بالذكر أن جودة التأثير كله مرئيًا تعتمد بشكل مباشر على جودة خوارزمية التمويه المستخدمة. من خلال تحسين الفلتر ، يمكنك تحقيق تحسن كبير وتأثير كامل. على سبيل المثال ، تظهر نتيجة أكثر إثارة للإعجاب من خلال الجمع بين عدة فلاتر ذات أحجام أساسية مختلفة أو منحنيات غوسية مختلفة. فيما يلي موارد إضافية من Kalogirou و EpicGames التي تتناول كيفية تحسين جودة bloom عن طريق تعديل التمويه الغوسي.

موارد إضافية


Source: https://habr.com/ru/post/ar420375/


All Articles