خوارزمية التفاعل لمئات الآلاف من الجزيئات الفريدة على GPU ، في GLES3 و WebGL2

وصف الخوارزمية المنطقية وتحليل مثال عملي في شكل لعبة تكنو تجريبي


إصدار WebGL2 من هذا العرض التوضيحي https://danilw.itch.io/flat-maze-web للحصول على روابط أخرى ، راجع المقال.



ينقسم المقال إلى قسمين ، الأول عن المنطق ، والجزء الثاني حول التطبيق في اللعبة ، الجزء الأول :


  • الميزات الرئيسية
  • الروابط وصفا موجزا.
  • خوارزمية المنطق.
  • حدود المنطق. البق / الميزات ، والبق زاوية.
  • الوصول إلى فهرس البيانات.

وصف إضافي للعرض التجريبي ، الجزء الثاني :


  • تستخدم ميزات هذا المنطق. وتقديم سريع للجسيمات مليون بكسل.
  • التنفيذ ، بعض التعليقات على الكود ، وصف الاصطدام في اتجاهين. والتفاعل مع اللاعب.
  • روابط إلى الرسومات المستخدمة مع opengameart ، وتظليل للظلال. و رابط المقال إلى cyberleninka.ru

الجزء 1


1. الميزات الرئيسية


الفكرة هي تصادم / فيزياء لمئات الآلاف من الجزيئات فيما بينها ، في الوقت الحقيقي ، حيث يكون لكل جسيم ID فريد.


عند فهرسة كل جسيم ، يمكن التحكم في أي معلمات لأي جسيم ، على سبيل المثال الكتلة أو صحته (حصان) أو التلف أو التسارع أو التباطؤ ، والتي تعترض مواجهتها وردود الفعل على الحدث اعتمادًا على نوع / مؤشر الجسيم ، وأيضًا توقيتات فريدة لكل جسيم ، وهلم جرا حسب الضرورة.


كل منطق على GLSL محمول بالكامل لأي محرك ألعاب وأي نظام تشغيل لديه دعم GLES3.


الحد الأقصى لعدد الجسيمات يساوي حجم أداة تثبيت الإطارات (fbo ، جميع وحدات البكسل).


هناك عدد مريح من الجسيمات (عندما يكون هناك مجال لتفاعل الجزيئات) هو (Resolution.x*Resolution.y/2)/2 هو كل بكسل في الثانية x وكل بكسل في الثانية ، وهذا هو السبب في أن وصف المنطق يقول ذلك.


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


2. الروابط والوصف القصير


لقد قدمت ثلاثة عروض تجريبية على هذا المنطق:


1. في جزء GLSL-shader ، على shadertoy https://www.shadertoy.com/view/tstSz7 ، راجع رمز BufferC فيه كل المنطق. يسمح لك هذا الرمز أيضًا بعرض مئات الآلاف من الجزيئات باستخدام الأشعة فوق البنفسجية ، في وضع تعسفي ، على وحدة تظليل بدون استخدام جزيئات مثبتة.



2. نقل منطق إلى جزيئات instanced (المستخدمة من قبل Godot كمحرك)



الروابط إصدار الويب ، exe (win) ، مصادر مشروع particles_2D_self_collision .


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


3. اللعبة ، حول هذا الموضوع أدناه في وصف اللعبة. الروابط نسخة الويب ، إكس (وين) ، المصادر


3. خوارزمية المنطق


باختصار:


يشبه المنطق الرمال المتساقطة ، حيث يحافظ كل بكسل على القيمة الكسرية للموضع (تحول داخل البكسل) والتسارع الحالي.


يتحقق المنطق من البيكسلات الموجودة في نصف القطر 1 ، وأن موضعهم التالي يريد الانتقال إلى هذا البيكسل (بسبب هذا التقييد ، انظر القيود أدناه) ، وكذلك البيكسلات الموجودة في نصف القطر 2 للتنافر (التصادم).


يتم حفظ الفهرس الفريد من خلال ترجمة المنطق إلى int-float ، وتقليل حجم موضع معين pos وسرعة pos .


يتم تخزين البيانات بهذه الطريقة: (بسبب هذا الخطأ ، راجع القيود)


 pixel.rgba r=[0xfffff-posx, 0xf-data] g=[0xfffff-posy, 0xf-data] b=[0xffff-velx, 0xff-data] a=[0xffff-vely, 0xff-data] 


في الكود ، أرقام الأسطر لـ BufC https://www.shadertoy.com/view/tstSz7 ، 115 فحص انتقال ، 139 فحص تصادم.


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


هذا هو كل منطق الجسيمات.


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


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


4. حدود المنطق. البق / الميزات ، والبق ANGLE


  1. يجب أن يكون حجم البيكسل ، BALL_SIZE في الكود ، ضمن حدود الحساب ، أكبر من sqrt(2)/2 وأقل من 1 . كلما اقتربت من 1 ، قلت مساحة المشي داخل البكسل (البكسل نفسه) ، كلما قلت المساحة. هناك حاجة إلى مثل هذا الحجم حتى لا تقع وحدات البكسل في بعضها البعض ، ويمكن تعيين أقل من 1 عندما يكون لديك كائنات صغيرة ، ويتم إنشاء وهم أقل من 1 بكسل (محسوب).
  2. لا يمكن أن تزيد السرعة عن 1 بكسل وإلا ستختفي البكسل. ولكن يمكنك الحصول على سرعة تزيد عن 1 لكل إطار - إذا قمت بعمل عدة إطارات (fbo / viewport) وقمت بمعالجة عدة خطوات منطقية في وقت واحد ، فسوف تزيد سرعة الإطار بمقدار عدد المرات المساوية لعدد fbo الإضافي. هذا هو ما فعلته في عرض الفاكهة ، واستخدام الرابط إلى shadertoy (bufC نسخ إلى bufD).
  3. الحد من الضغط (مثل الجاذبية ، أو غيرها من القوة الطبيعية الخريطة). إذا اتخذت عدة وحدات بكسل مجاورة موضع ذلك (انظر الصورة أعلاه) ، فسيتم حفظ واحد فقط ، تختفي وحدات البكسل المتبقية. من السهل ملاحظة ذلك في العرض التوضيحي على shadertoy ، وتعيين الماوس على فرض ، وتغيير قيمة MOUSE_F في عام إلى 10 ، وتوجيه الجزيئات إلى زاوية الشاشة ، وسوف تختفي في بعضها البعض. أو الشيء نفسه مع maxG قيمة الجاذبية في عام.
  4. علة في زاوية. لكي يعمل هذا المنطق في جسيمات GPU (instanced) ، من الأفضل (أرخص وأسرع) حساب الموضع وكافة معلمات الجسيمات الأخرى للعرض ، في مثيل تظليل . لكن Angle لا تسمح باستخدام أكثر من نسيج fbo واحد لتظليل ، لذلك يجب نقل حساب جزء من المنطق إلى Vertex-shader حيث يتم نقل رقم الفهرس من shader المثيل. هذا ما فعلته في كلا التجريبيين مع جزيئات GPU.
  5. خطأ جسيم في كلا الإصدارين التجريبيين (باستثناء اللعبة) ، سيتم فقد قيمة المركز إذا لم تكن مضاعفات 1/0xfffff اختبار الأخطاء هنا https://www.shadertoy.com/view/WdtSWS
    بتعبير أدق ، هذا ليس خطأً ، ويجب أن يكون كذلك ، من أجل البساطة ، وكجزء من هذه الخوارزمية ، أسميتها خطأ.

إصلاح الخلل:
لا تقم بتحويل قيمة الموضع إلى int-float ، نظرًا لأن 0xff هذه ستختفي ، فإن 8 بتات متاحة للبيانات ، ولكن ستبقى قيمة 0xffff للبيانات ، والتي قد تكون كافية للعديد من الأشياء.
لقد فعلت ذلك تمامًا في العرض التوضيحي للعبة ، استخدم فقط 0xffff للبيانات حيث يتم تخزين نوع الجسيمات ، وتوقيت الحركة ، والصحة ، وما زالت هناك مساحة خالية.


5. الوصول إلى فهرس البيانات


يحتوي instanced-particle على INSTANCE_ID الخاص به ، ويستغرق بكسلًا من نسيج واقي الإطار مع منطق الجسيمات (bufC ، على سبيل المثال لتظليل) ، إذا قمنا بتفريغ معرف الجسيمات (انظر تخزين البيانات) الخاص بهذا الجسيم ، من خلال هذا المعرف ، نقرأ الملمس مع البيانات الخاصة بالجسيمات (bufB) ، مثال على تظليل).


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


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


الجزء 2


1. تستخدم ميزات هذا المنطق. وتقديم سريع للجسيمات مليون بكسل


حجم فرامبوفير (fbo / viewport) للجسيمات هو 1280 × 720 ، الأجزاء تقع بعد 1 ، وهذا هو 230 ألف جسيم نشط (العناصر النشطة في المتاهة).
لا يوجد دائمًا أكثر من 12 ألف جسيمات مثبتة على وحدة معالجة الرسومات على الشاشة.


يستخدم المنطق:


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

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


يبدو مثل هذا:



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


2. التنفيذ ، بعض التعليقات على الكود.


كل رمز اللعبة هو على GPU.


منطق حساب تحول الجسيمات في الشاشة مع زيادة في وظيفة قمة الرأس في ملف / shaders/scene2/particle_logic2.shader هو ملف تظليل الجسيمات (قمة الرأس وشظية) ، وليس تظليل مدرج ، تظليل مدمج لا يفعل شيئًا ، ويمر فقط مؤشره بسبب علة المذكورة أعلاه.


الجسيمات حسب النوع والمنطق الكامل لتفاعل الجسيمات في ملف ، هذا هو ملف لتظليل تظليل الإطار shader file shaders / scene2 / particles_fbo_logic.shader


 // 1-2 ghost // 3-zombi // 4-18 blocks // +20 is on fire // 40 is bullet(right) 41 left 42 top 43 down 

بكسل تخزين البيانات [pos.x, pos.y, [0xffff-vel.x, 0xff-data1],[0xffff-vel.y, 0xff-data2]]
data1 هو نوع ، data2 عبارة عن HP أو مؤقت.


يعمل المؤقت على إطارات في كل جسيم ، والحد الأقصى لقيمة الموقت هو 255 ، ولا أحتاج إلى ذلك كثيرًا ، وأنا أستخدم فقط 1-16 كحد أقصى ( 0xf ) ، ولا يزال 0xf غير مستخدم حيث يمكنك على سبيل المثال تخزين قيمة HP الحقيقية ، ولا يتم استخدامها بالنسبة لي. (أي ، نعم ، أستخدم 0xff ، لكن في الحقيقة ليس لدي سوى أقل من 16 إطار رسوم متحركة ، وكان 0xf كافياً ، لكنني لم أكن بحاجة إلى بيانات إضافية)
في الواقع 0xff استخدام 0xff فقط في توقيت الأشجار المحترقة ، فإنها تتحول إلى زومبي بعد 255 إطارًا. منطق الموقت موجود جزئيًا في type_hp_logic في تظليل framebuffer type_hp_logic (الرابط أعلاه).


مثال على عملية تصادم ثنائية الاتجاه عندما تنطلق كرة نارية من الضربة الأولى ، ويقوم الكائن الذي تم ضربه أيضًا بعمله.


تظليل الملف / scene2 / particles_fbo_logic.shader سطر 438:


 if (((real_index == 40) || (real_index == 41) || (real_index == 42) || (real_index == 43)) && (type_hp.y > 22)) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if ((hreal_index != 40) && (hreal_index != 41) && (hreal_index != 42) && (hreal_index != 43)) type_hp.y = 22; } else { if (!need_upd) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if (((hreal_index == 40) || (hreal_index == 41) || (hreal_index == 42) || (hreal_index == 43)) && (htype_hp.y > 22)) { need_upd = true; } } } 

real_index هو نوع ، الأنواع المذكورة أعلاه ، 40-43 هي كرة نارية .
المزيد type_hp.y > 22 هي قيمة المؤقت ، إذا كانت أكبر من 22 ، فإن كرة النار لم تصادف أي شيء.
h_id = get_id(... خذ قيمة النوع و HP (مؤقت) للجسيم الذي واجهته
hreal_index != 40... النوع المتجاهل ( كرة نارية أخرى)
type_hp.y = 22 تم ضبط مؤقت على 22 ، وهذا مؤشر على أن كرة النار هذه اصطدمت بكائن واحد.
else { if (!need_upd) يتحقق المتغير need_upd من عدم وجود تصادمات متكررة ، لأن الوظيفة في حلقة ، نواجه كرة نارية واحدة.
h_id = get_id(... إذا لم يكن هناك تصادم بعد ، فنحن نأخذ كائنًا من النوع والموقت.
hreal_index == 40...htype_hp.y > 22 أن كائن التصادم هو كرة نارية ولا يخرج.
need_upd = true علم need_upd = true بأنه من الضروري تحديث النوع لأنه صادف كرة نارية .


خط إضافي 481
if((need_upd)&&(real_index<24)){ real_index <24 حسب النوع أقل من 24 هناك أشجار غيبوبة وأشباح غير محترقة ، ثم نقوم في هذه الحالة بتحديث النوع وفقًا للنوع الحالي.


وبالتالي ، يمكن إجراء أي تفاعل للأجسام تقريبًا.


التفاعل مع اللاعب:


ملف تظليل / scene2 / logic.shader سطر 143 وظيفة player_collision


يقرأ هذا المنطق البيكسلات الموجودة حول المشغل بنصف قطر 4 × 4 بكسل ، ويأخذ موضع كل بكسل ويقارنها بموضع اللاعب ، إذا تم العثور على عنصر ثم اكتب check check هو التالي ، إذا كان هذا وحشًا ، فسنأخذ HP من المشغل.


هذا يعمل بشكل غير دقيق قليلاً ولم أكن أريد إصلاحه ، يمكن جعل هذه الوظيفة أكثر دقة.


تدفع الجزيئات بعيدًا عن اللاعب وتأثير التنافر أثناء الهجوم:


يتم استخدام أداة ضبط الإطار (إطار العرض) لكتابة الإجراءات الحالية ، وتقوم الجسيمات ( particles_fbo_logic.shader ) بأخذ هذا الملمس (العادي) في موضعه وتطبيق القيمة على سرعته وموضعه. الرمز الكامل لهذا المنطق هو حرفيًا مجرد سطرين ، file force_collision.shader


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


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


3. وصلات إلى الرسومات المستخدمة مع opengameart ، وتظليل الظل


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


يعمل shader shadow ببساطة شديدة ، بناءً على هذا shader https://www.shadertoy.com/view/XsK3RR (لدي رمز معدّل)
شادر يبني 1D شعاعي Lightmap



والتظليل في تظليل رمز الكلمة الكلمة / scene2 / mainImage.shader


روابط إلى الرسومات المستخدمة ، وجميع الرسومات في اللعبة من الموقع https://opengameart.org
كرة نارية https://opengameart.org/content/animated-traps-and-obstacles
حرف https://opengameart.org/content/legend-of-faune
الأشجار والكتل https://opengameart.org/content/lolly-set-01
(وزوجين من الصور مع opengameart)


تم الحصول على الرسومات في القائمة بواسطة تظليل 2D_GI ، وهي أداة لإنشاء مثل هذه القوائم:



الذين قرأوا إلى النهاية - أحسنت :)
إذا كان لديك أسئلة ، اسأل ، يمكنني استكمال الوصف عند الطلب.

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


All Articles