3D لعبة تظليل للمبتدئين: الآثار

[ الجزء الأول ]

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

حدود



يعطي إنشاء معالم حول هندسة المشهد للعبة مظهرًا فريدًا يشبه الكوميديا ​​أو الرسوم.

نشر المواد


يحتاج تظليل الكنتور إلى مادة إدخال للتعرف على الحواف وتلوينها. يمكن للمرشحين لمثل هذا الملمس الوارد أن يكون لونًا منتشرًا من مواد أو ألوان من مواد منتشرة أو أشكال رأسية عادية أو حتى ألوان من الخرائط العادية.

uniform struct { vec4 diffuse ; } p3d_Material; out vec4 fragColor; void main() { vec3 diffuseColor = p3d_Material.diffuse.rgb; fragColor = vec4(diffuseColor, 1); } 

هذا هو تظليل شظية صغير يجعل اللون المنتشر لمادة هندسية في نسيج عازلة للإطار. سيكون هذا الملمس اللوني المنتشر من المخزن المؤقت للإطار هو نسيج الإدخال لتظليل المسار.


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

تجدر الإشارة إلى أن اللون المنتشر للمواد لن يعمل إذا لم يكن لدى أجزاء معينة من المشهد لون منتشر خاص بها من المادة.

خلق حواف



يشبه إنشاء الحواف استخدام مرشحات التعرف على الحواف في GIMP .

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

 // ... uniform sampler2D materialDiffuseTexture; // ... vec2 texSize = textureSize(materialDiffuseTexture, 0).xy; vec2 texCoord = gl_FragCoord.xy; // ... 

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

  // ... int separation = 1; // ... 

يمكن تخصيص separation ليناسب ذوقك. كلما زاد الفصل ، زادت سماكة الحواف أو الخطوط.

  // ... float threshold = 0; // ... vec4 mx = vec4(0); vec4 mn = vec4(1); int x = -1; int y = -1; for (int i = 0; i < 9; ++i) { vec4 color = texture ( materialDiffuseTexture , (texCoord + (vec2(x, y) * separation)) / texSize ); mx = max(color, mx); mn = min(color, mn); x += 1; if (x >= 2) { x = -1; y += 1; } } float alpha = ((mx.r + mx.g + mx.b) / 3) - ((mn.r + mn.g + mn.b) / 3); if (alpha > threshold) { alpha = 1; } // ... 


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

  // ... vec3 lineRgb = vec3(0.012, 0.014, 0.022); // ... vec4 lineColor = vec4(lineRgb, alpha); // ... fragColor = lineColor; // ... 

يتم استخدام هذا الاختلاف في قناة ألفا من لون الإخراج. إذا لم يكن هناك فرق ، فلا يتم رسم الحافة أو الخط. إذا كان هناك فرق ، يتم رسم الحافة.

  // ... float threshold = 0; // ... if (alpha > threshold) { alpha = 1; } // ... 

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

شفرة المصدر



ضباب



يضيف الضباب (أو الضباب ، كما يطلق عليه في Blender) الضباب في الجو ، مما يخلق أجزاء بارزة غامضة ناعمة. تظهر الأجزاء البارزة عندما تسقط بعض الأشكال الهندسية فجأة في هرم رؤية الكاميرا.

 // ... uniform struct p3d_FogParameters { vec4 color ; float start ; float end ; } p3d_Fog; // ... 

يحتوي Panda3D على بنية بيانات مريحة تحتوي على جميع معلمات الضباب ، ولكن يمكنك نقلها إلى تظليلك يدويًا.

  // ... float fogIntensity = clamp ( ( p3d_Fog.end - vertexPosition.y) / ( p3d_Fog.end - p3d_Fog.start) , 0 , 1 ); fogIntensity = 1 - fogIntensity; // ... 

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

  // ... fragColor = mix ( outputColor , p3d_Fog.color , fogIntensity ); // ... 

استنادًا إلى سطوع الضباب ، نمزج لون الضباب مع لون المخرجات. مع اقتراب fogIntensity الوحدة ، سيكون هناك أقل وأقل من outputColor ولون ضباب أكثر فأكثر. عندما يصل fogIntensity إلى الوحدة ، fogIntensity فقط لون الضباب.

ضباب على الخطوط




 // ... uniform sampler2D positionTexture; // ... vec4 position = texture(positionTexture, texCoord / texSize); float fogIntensity = clamp ( ( p3d_Fog.end - position.y) / ( p3d_Fog.end - p3d_Fog.start) , 0 , 1 ); fogIntensity = 1 - fogIntensity; vec4 lineWithFogColor = mix ( lineColor , p3d_Fog.color , fogIntensity ); fragColor = vec4(lineWithFogColor.rgb, alpha); // ... 

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

positionTexture هو نسيج عازل إطار يحتوي على مواضع رؤوس مساحة العرض. سوف تتعلم عن هذا عندما نطبق تظليل SSAO.

شفرة المصدر



إزهار



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

  //... float separation = 3; int samples = 15; float threshold = 0.5; float amount = 1; // ... 

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

  // ... int size = samples; int size2 = size * size; int x = 0; int y = 0; // ... float value = 0; vec4 result = vec4(0); vec4 color = vec4(0); // ... for (int i = 0; i < size2; ++i) { // ... } // ... 

تبدأ هذه التقنية بتمرير samples بحجم النافذة على samples تمركزها بالنسبة للجزء الحالي. يبدو وكأنه نافذة تستخدم لإنشاء مسارات.

  // ... color = texture ( bloomTexture , ( gl_FragCoord.xy + vec2(x * separation, y * separation) ) / texSize ); value = ((0.3 * color.r) + (0.59 * color.g) + (0.11 * color.b)); if (value < threshold) { color = vec4(0); } result += color; // ... 

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

مرورا بجميع العينات داخل النافذة ، فإنه يجمع كل قيمها في result .

  // ... result = result / size2; // ... 

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


هنا ترى عملية تنفيذ خوارزمية ازهر.

شفرة المصدر



انسداد مساحة الشاشة المحيطة (SSAO)



SSAO هو أحد تلك الآثار التي لا تعرف وجودها ، ولكن بمجرد أن تعرف أنه لم يعد بإمكانك العيش بدونها. يمكنه تحويل المشهد المتوسط ​​إلى مشهد رائع! في المشاهد الساكنة ، يمكن تغطية الانسداد المحيط في النسيج ، ولكن في المشاهد الأكثر ديناميكية نحتاج إلى تظليل. تعد SSAO واحدة من أساليب التظليل الأكثر تطوراً ، ولكن بمجرد معرفة ذلك ، ستصبح أداة تظليل رئيسية.

لاحظ أن مصطلح "مساحة الشاشة" في العنوان غير صحيح تمامًا ، لأنه لا يتم تنفيذ جميع العمليات الحسابية في مساحة الشاشة.

البيانات الواردة


سوف SSAO تظليل تحتاج إلى المدخلات التالية.

  • ناقلات مواقف قمة الرأس في مساحة المشاهدة.
  • ناقلات طبيعية إلى القمم في مساحة العرض.
  • ناقلات عينة في مساحة الظل.
  • ناقلات الضوضاء في الفضاء الظل.
  • مصفوفة الإسقاط على عدسة الكاميرا.

موقف



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

 PT(Texture) depthTexture = new Texture("depthTexture"); depthTexture->set_format(Texture::Format::F_depth_component32); PT(GraphicsOutput) depthBuffer = graphicsOutput->make_texture_buffer("depthBuffer", 0, 0, depthTexture); depthBuffer->set_clear_color(LVecBase4f(0, 0, 0, 0)); NodePath depthCameraNP = window->make_camera(); DCAST(Camera, depthCameraNP.node())->set_lens(window->get_camera(0)->get_lens()); PT(DisplayRegion) depthBufferRegion = depthBuffer->make_display_region(0, 1, 0, 1); depthBufferRegion->set_camera(depthCameraNP); 

إذا قررت استخدام المخزن المؤقت للعمق ، فإليك كيفية تكوينه في Panda3D.

 in vec4 vertexPosition; out vec4 fragColor; void main() { fragColor = vertexPosition; } 

هنا تظليل بسيط لتقديم مواضع قمة الرأس في مساحة المشاهدة إلى نسيج عازلة للإطار. تتمثل المهمة الأكثر صعوبة في ضبط نسيج المخزن المؤقت للإطار بحيث لا تقتصر مكونات متجه الجزء الذي تم الحصول عليه بواسطته على الفاصل الزمني [0, 1] ، ولكل منهما دقة عالية بما فيه الكفاية (عدد كبير بشكل كافٍ من البتات). على سبيل المثال ، إذا كان هناك نوع من موضع قمة الرأس <-139.444444566, 0.00000034343, 2.5> ، فلا يمكنك حفظه في النسيج كـ <0.0, 0.0, 1.0> .

  // ... FrameBufferProperties fbp = FrameBufferProperties::get_default(); // ... fbp.set_rgba_bits(32, 32, 32, 32); fbp.set_rgb_color(true); fbp.set_float_color(true); // ... 

فيما يلي رمز مثال يُعد نسيجًا مؤقتًا للإطار لتخزين مواضع الذروة. يحتاج إلى 32 بت للأحمر والأخضر والأزرق والألفا ، لذا فهو يعطل تقييد القيم بواسطة الفاصل الزمني [0, 1] . استدعاء set_rgba_bits(32, 32, 32, 32) تعيّن حجم البت وتعطيل التقييد.

  glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGB32F , 1200 , 900 , 0 , GL_RGB , GL_FLOAT , nullptr ); 

هذه مكالمة مماثلة على OpenGL. GL_RGB32F يعين البتات ويعطل التقييد.

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

مصدر


هنا ترى مواقف القمم. المحور ص هو ما يصل.

تذكر أن Panda3D يعرّف المحور z بأنه متجه يشير إلى الأعلى ، بينما في OpenGL ، يبحث المحور y. يعرض تظليل الموضع مواضع القمم بعلامة z التصاعدي ، لأنه في Panda3D
تم تكوين المعلمة gl-coordinate-system default .

الأوضاع الطبيعية



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

 in vec3 vertexNormal; out vec4 fragColor; void main() { vec3 normal = normalize(vertexNormal); fragColor = vec4(normal, 1); } 

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


يتم عرض الأوضاع الطبيعية في القمم هنا ؛ المحور ص هو ما يصل.

تذكر أن Panda3D تعتبر المحور z هو المتجه التصاعدي ، و OpenGL للمحور y. يعرض التظليل العادي مواضع القمم مع توجيه المحور z لأعلى ، لأن تكوين gl-coordinate-system default في Panda3D.

عينات


لتحديد قيمة الإغلاق المحيط لأي جزء مفرد ، نحتاج إلى أخذ عينات من المنطقة المحيطة.

  // ... for (int i = 0; i < numberOfSamples; ++i) { LVecBase3f sample = LVecBase3f ( randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) ).normalized(); float rand = randomFloats(generator); sample[0] *= rand; sample[1] *= rand; sample[2] *= rand; float scale = (float) i / (float) numberOfSamples; scale = lerp(0.1, 1.0, scale * scale); sample[0] *= scale; sample[1] *= scale; sample[2] *= scale; ssaoSamples.push_back(sample); } // ... 

يولد رمز العينة 64 عينة عشوائية موزعة في نصف الكرة الغربي. سيتم تمرير هذه ssaoSamples إلى تظليل SSAO.

  LVecBase3f sample = LVecBase3f ( randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 ).normalized(); 

إذا كنت ترغب في توزيع العينات الخاصة بك عبر كرة ، فقم بتغيير الفاصل الزمني للمكون العشوائي z بحيث يتغير من ناقص واحد إلى واحد.

الضوضاء


  // ... for (int i = 0; i < 16; ++i) { LVecBase3f noise = LVecBase3f ( randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 , 0.0 ); ssaoNoise.push_back(noise); } // ... 

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

انسداد المحيطة



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


هنا ترى مساحة أعلى السطح الذي تم أخذ عينات منه للانسداد.

  // ... float radius = 1.1; float bias = 0.026; float lowerRange = -2; float upperRange = 2; // ... 

مثل بعض التقنيات الأخرى ، فإن shader SSAO لديه العديد من معلمات التحكم التي يمكن تغييرها للحصول على المظهر المطلوب. يضاف التحيز إلى المسافة من العينة إلى الكاميرا. هذه المعلمة يمكن استخدامها لمكافحة البقع. يزيد نصف القطر أو يقلل من مساحة تغطية مساحة العينة. LowerRange و upperRange يغيران النطاق القياسي لمقياس العامل من [0, 1] إلى أي قيمة تحددها. عن طريق زيادة النطاق ، يمكنك زيادة التباين.

  // ... vec4 position = texture(positionTexture, texCoord); vec3 normal = texture(normalTexture, texCoord).xyz; int noiseX = int(gl_FragCoord.x - 0.5) % 4; int noiseY = int(gl_FragCoord.y - 0.5) % 4; vec3 random = noise[noiseX + (noiseY * 4)]; // ... 

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

  // ... vec3 tangent = normalize(random - normal * dot(random, normal)); vec3 binormal = cross(normal, tangent); mat3 tbn = mat3(tangent, binormal, normal); // ... 

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

  // ... float occlusion = NUM_SAMPLES; for (int i = 0; i < NUM_SAMPLES; ++i) { // ... } // ... 

عند وجود مصفوفة ، يمكن للتظليل إجراء حلقة من خلال جميع العينات الموجودة في الحلقة ، وطرح عدد العينات غير المفتوحة.

  // ... vec3 sample = tbn * samples[i]; sample = position.xyz + sample * radius; // ... 

باستخدام المصفوفة ، ضع العينة بجانب موضع قمة الرأس / الشظية وحجمها بواسطة نصف القطر.

  // ... vec4 offset = vec4(sample, 1.0); offset = lensProjection * offset; offset.xyz /= offset.w; offset.xyz = offset.xyz * 0.5 + 0.5; // ... 

باستخدام موضع العينة في مساحة المشاهدة ، نقوم بتحويلها من مساحة المشاهدة إلى مساحة القطع ، ثم إلى مساحة الأشعة فوق البنفسجية.

 -1 * 0.5 + 0.5 = 0 1 * 0.5 + 0.5 = 1 

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

  // ... vec4 offsetPosition = texture(positionTexture, offset.xy); float occluded = 0; if (sample.y + bias <= offsetPosition.y) { occluded = 0; } else { occluded = 1; } // ... 

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

  // ... float intensity = smoothstep ( 0.0 , 1.0 , radius / abs(position.y - offsetPosition.y) ); occluded *= intensity; occlusion -= occluded; // ... 

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

  // ... occlusion /= NUM_SAMPLES; // ... fragColor = vec4(vec3(occlusion), position.a); // ... 

اقسم عدد التداخل على عدد العينات لتحويل مؤشر الإغلاق من الفاصل الزمني [0, NUM_SAMPLES] إلى الفاصل الزمني [0, 1] . الصفر يعني انسداد كامل ، والوحدات لا تعني أي انسداد. الآن قم بتعيين انسداد على لون الجزء ، وهذا كل شيء.

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

طمس (طمس)



تكون بنية مخزن إطار SSAO صاخبة بعض الشيء ، لذا يجب عليك طمسها لتنعيمها.

  // ... for (int i = 0; i < size2; ++i) { x = size - xCount; y = yCount - size; result += texture ( ssaoTexture , texCoord + vec2(x * parameters.x, y * parameters.x) ).rgb; xCount -= 1; if (xCount < countMin) { xCount = countMax; yCount -= 1; } } result = result / size2; // ... 

SSAO طمس تظليل هو مربع منتظم طمس. مثل تظليل bloom ، يرسم نافذة على القوام الوارد ويعدل كل جزء بقيم جيرانه.

لاحظ أن parameters.x هي معلمة فصل.

اللون المحيط


  // ... vec2 ssaoBlurTexSize = textureSize(ssaoBlurTexture, 0).xy; vec2 ssaoBlurTexCoord = gl_FragCoord.xy / ssaoBlurTexSize; float ssao = texture(ssaoBlurTexture, ssaoBlurTexCoord).r; vec4 ambient = p3d_Material.ambient * p3d_LightModel.ambient * diffuseTex * ssao; // ... 

التحدي الأخير ل SSAO هو مرة أخرى في حسابات الإضاءة. هنا نرى كيف يتم العثور على انسداد في نسيج SSAO العازلة الملمس ويتم تضمينها في حساب الإضاءة المحيطة.

شفرة المصدر



عمق الميدان



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

في التركيز


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

خارج التركيز


  // ... vec4 result = vec4(0); for (int i = 0; i < size2; ++i) { x = size - xCount; y = yCount - size; result += texture ( blurTexture , texCoord + vec2(x * parameters.x, y * parameters.x) ); xCount -= 1; if (xCount < countMin) { xCount = countMax; yCount -= 1; } } result = result / size2; // ... 

والخطوة الثانية هي طمس المشهد كما لو كان خارج نطاق التركيز تمامًا. كما هو الحال مع ازهر و SSAO ، يمكنك استخدام مربع طمس. تقديم هذا المشهد defocused إلى نسيج الإطار العازلة. ستكون قيمة إدخال أخرى لعمق تظليل الحقل.

لاحظ أن parameters.x هي معلمة فصل.

خلط




  // ... float focalLengthSharpness = 100; float blurRate = 6; // ... 

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

  // ... vec4 focusColor = texture(focusTexture, texCoord); vec4 outOfFocusColor = texture(outOfFocusTexture, texCoord); // ... 

سنحتاج الألوان في التركيز وفي صورة defocused.

  // ... vec4 position = texture(positionTexture, texCoord); // ... 

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

  // ... float blur = clamp ( pow ( blurRate , abs(position.y - focalLength.x) ) / focalLengthSharpness , 0 , 1 ); // ... fragColor = mix(focusColor, outOfFocusColor, blur); // ... 

وهنا يحدث الارتباك. كلما اقتربنا blurمن واحد ، كلما استخدم أكثر outOfFocusColor. قيمة الصفر blurتعني أن هذه القطعة في تركيز كامل. مع blur >= 1هذا الجزء هو defocused تماما.

شفرة المصدر



على posterization



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

  // ... float levels = 8; // ... 

يمكنك تجربة هذه المعلمة. كلما زاد حجمها ، ستظل المزيد من الزهور نتيجة لذلك.

  // ... vec4 texColor = texture(posterizeTexture, texCoord); // ... 

سنحتاج اللون الوارد.

  // ... vec3 grey = vec3((texColor.r + texColor.g + texColor.b) / 3.0); vec3 grey1 = grey; grey = floor(grey * levels) / levels; texColor.rgb += (grey - grey1); // ... 

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

  // ... fragColor = texColor; // ... 

لا تنسَ تعيين قيمة لون الإدخال إلى لون القطعة.

سل التظليل



يمكن أن تعطي الصورة الخلفية مظهر تظليل cel ، لأن تظليل cel هو عملية لتقدير الألوان المنتشرة والمنتشرة في ظلال منفصلة. نريد استخدام ألوان منتشرة صلبة فقط دون تفاصيل دقيقة للخريطة العادية وقيمة صغيرة levels.

شفرة المصدر



البيكسيلاشن



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

  // ... int pixelSize = 5; // ... 

يمكنك ضبط حجم البكسل بنفسك. كلما زاد حجمها ، كلما كانت الصورة أقسى.



  // ... float x = int(gl_FragCoord.x) % pixelSize; float y = int(gl_FragCoord.y) % pixelSize; x = floor(pixelSize / 2.0) - x; y = floor(pixelSize / 2.0) - y; x = gl_FragCoord.x + x; y = gl_FragCoord.y + y; // ... 

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

  // ... fragColor = texture(pixelizeTexture, vec2(x, y) / texSize); // ... 

بعد أن نحدد تنسيق الجزء المطلوب استخدامه ، خذ لونه من النسيج الوارد وقم بتعيينه للون التجزئة.

شفرة المصدر



شحذ



يزيد تأثير التوضيح (التوضيح) التباين عند حواف الصورة. يصبح مفيدًا عندما تكون الرسومات ناعمة جدًا.

  // ... float amount = 0.8; // ... 

عن طريق تغيير القيمة ، يمكننا التحكم في حجم حدة النتيجة. إذا كانت القيمة صفرًا ، فلن تتغير الصورة. مع القيم السلبية ، تبدأ الصورة لتبدو غريبة.

  // ... float neighbor = amount * -1; float center = amount * 4 + 1; // ... 

الشظايا المجاورة مضروبة في amount * -1. الشطر الحالي مضروب في amount * 4 + 1.

  // ... vec3 color = texture(sharpenTexture, vec2(gl_FragCoord.x + 0, gl_FragCoord.y + 1) / texSize).rgb * neighbor + texture(sharpenTexture, vec2(gl_FragCoord.x - 1, gl_FragCoord.y + 0) / texSize).rgb * neighbor + texture(sharpenTexture, vec2(gl_FragCoord.x + 0, gl_FragCoord.y + 0) / texSize).rgb * center + texture(sharpenTexture, vec2(gl_FragCoord.x + 1, gl_FragCoord.y + 0) / texSize).rgb * neighbor + texture(sharpenTexture, vec2(gl_FragCoord.x + 0, gl_FragCoord.y - 1) / texSize).rgb * neighbor ; // ... 

الشظايا المجاورة في الأعلى والأسفل واليسار واليمين. بعد ضرب الجيران والجزء الحالي بقيمهم ، تتم إضافة النتيجة.

  // ... fragColor = vec4(color, texture(sharpenTexture, texCoord).a); // ... 

هذا المبلغ هو اللون النهائي للجزء.

شفرة المصدر



فيلم الحبوب



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

لاحظ أن حبة الفيلم تكون عادةً آخر تأثير يتم تطبيقه على الإطار قبل عرضه.

قيمة


  // ... float amount = 0.1; // ... 

amountيتحكم في رؤية فيلم الحبوب. كلما زادت القيمة ، زاد "تساقط الثلوج" في الصورة.

سطوع عشوائي


 // ... uniform float osg_FrameTime; //... float toRadians = 3.14 / 180; //... float randomIntensity = fract ( 10000 * sin ( ( gl_FragCoord.x + gl_FragCoord.y * osg_FrameTime ) * toRadians ) ); // ... 

تحسب هذه الشفرة السطوع العشوائي اللازم لضبط القيمة.

 Time Since F1 = 00 01 02 03 04 05 06 07 08 09 10 Frame Number = F1 F3 F4 F5 F6 osg_FrameTime = 00 02 04 07 08 

القيمة osg_FrameTime المقدمة من Panda3D. وقت الإطار هو طابع زمني يحتوي على معلومات حول عدد الثواني التي مرت منذ الإطار الأول. يستخدم نموذج التعليمة البرمجية لتحريك حبة الفيلم ، والتي osg_FrameTimeستكون مختلفة في كل إطار.

  // ... ( gl_FragCoord.x + gl_FragCoord.y * 8009 // Large number here. // ... 

بالنسبة للحبوب الثابتة ، يجب استبدال الأفلام osg_FrameTimeبعدد كبير. لتجنب رؤية الأنماط ، يمكنك تجربة أرقام مختلفة.



  // ... * sin ( ( gl_FragCoord.x + gl_FragCoord.y * someNumber // ... 

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

في الكود ، يتم ضرب إحداثي واحد بآخر لتدمير التماثل المائل.


بالطبع ، يمكنك التخلص من مضاعف الإحداثيات ، والحصول على تأثير مطر مقبول تمامًا.

لاحظ أنه لتحريك تأثير المطر ، اضرب الناتج sinب osg_FrameTime.

قم بتجربة إحداثيات x و y لتغيير اتجاه المطر. للاستحمام للأسفل ، اترك إحداثي x فقط.

 input = (gl_FragCoord.x + gl_FragCoord.y * osg_FrameTime) * toRadians frame(10000 * sin(input)) = fract(10000 * sin(6.977777777777778)) = fract(10000 * 0.6400723818964882) = 

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

 fract(10000 * sin(6.977777777777778)) = fract(10000 * 0.6400723818964882) = fract(6400.723818964882) = 0.723818964882 

sinفي تركيبة مع fractتستخدم أيضا كمولد عدد عشوائي الزائفة.

 >>> [floor(fract(4 * sin(x * toRadians)) * 10) for x in range(0, 10)] [0, 0, 1, 2, 2, 3, 4, 4, 5, 6] >>> [floor(fract(10000 * sin(x * toRadians)) * 10) for x in range(0, 10)] [0, 4, 8, 0, 2, 1, 7, 0, 0, 5] 

إلقاء نظرة أولى على الصف الأول من الأرقام ، ثم في الصف الثاني. كل صف هو حتمية ، ولكن النمط أقل وضوحا في الثانية من الثانية. لذلك ، على الرغم من حقيقة أن الإخراج fract(10000 * sin(...))حتمية ، يتم التعرف على النمط أضعف بكثير.


هنا نرى كيف يكون العامل sinالأول 1 ، ثم 10 ، ثم 100 ، ثم 1000.

ومع زيادة مضاعف قيم المخرجات ، sinيصبح النموذج أقل وضوحًا. لهذا السبب ، sinيتم ضرب الكود بـ 10،000.

شظية اللون


  // ... vec2 texSize = textureSize(filmGrainTexture, 0).xy; vec2 texCoord = gl_FragCoord.xy / texSize; vec4 color = texture(filmGrainTexture, texCoord); // ... 

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

  // ... amount *= randomIntensity; color.rgb += amount; // ... 

قم بتغيير القيمة إلى سطوع عشوائي وإضافتها إلى اللون.

  // ... fragColor = color; // ... 

اضبط لون القطعة ، وهذا كل شيء.

شفرة المصدر



شكر


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


All Articles