رسومات ثلاثية الأبعاد على STM32F103

صورة

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

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

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

عندما مرت النشوة الأولى من امتلاك مثل هذه السلطة ، ظهرت رغبة في استكشاف حدودها. كمثال فعال ، اخترت حساب الرسومات ثلاثية الأبعاد مع كل هذه المصفوفات والإضاءة والنماذج المضلعة والعازلة Z مع شاشة 320 × 240 على وحدة تحكم ili9341. أكثر المشاكل وضوحًا التي يجب حلها هي السرعة والحجم. حجم الشاشة 320 × 240 عند 16 بت لكل لون يعطي 150 كيلو بايت لكل إطار. لكن إجمالي ذاكرة الوصول العشوائي لدينا هو 20 كيلوبايت فقط ... ويجب تحويل هذه الـ 150 كيلوبايت إلى الشاشة 10 مرات على الأقل في الثانية ، أي يجب أن يكون سعر الصرف 1.5 ميجابايت / ثانية على الأقل أو 12 ميجابايت / ثانية ، والذي يبدو بالفعل كحمولة كبيرة على القلب. لحسن الحظ ، يوجد في وحدة التحكم هذه وحدة RAP (وصول مباشر للذاكرة ، ويعرف أيضًا باسم الوصول المباشر للذاكرة ، DMA) ، والتي تسمح بعدم تحميل النواة بعمليات نقل من فارغة إلى فارغة.أي أنه يمكنك إعداد المخزن المؤقت ، وإخبار الوحدة "هنا لديك المخزن المؤقت للبيانات ، العمل!" ، وفي هذا الوقت قم بإعداد البيانات للنقل التالي. ومع الأخذ في الاعتبار قدرة الشاشة على استقبال البيانات في دفق ، تظهر الخوارزمية التالية: يتم تمييز المخزن الأمامي الأمامي ، الذي ينقل منه DMA البيانات إلى الشاشة ، والمخزن المؤقت الخلفي الذي يتم فيه العرض ، والمخزن المؤقت Z المستخدم للقطع بعمق. المخازن المؤقتة هي صف واحد (أو عمود ، أيا كان) من الشاشة. وبدلاً من 150 كيلوبايت ، نحتاج فقط إلى 1920 بايت (320 بكسل لكل سطر * 3 مخازن * 2 بايت لكل نقطة) ، والتي تناسب الذاكرة تمامًا. يعتمد الاختراق الثاني على حقيقة أن حساب مصفوفات التحويل وإحداثيات القمة لا يمكن إجراؤها لكل صف ، وإلا سيتم تشويه الصورة بالطرق الأكثر غرابة ، وهي غير مواتية في السرعة. بدلاً من ذلك ، الحسابات "الخارجية" ،بمعنى ، يتم إعادة حساب مصفوفات التحويل وتطبيقها على القمم في كل إطار ، ثم يتم تحويلها إلى تمثيل وسيط ، والذي يتم تحسينه لعرضه في صورة 320 × 1.

لأسباب تتعلق بالشغب ، ستشبه المكتبة برنامج OpenGL من الخارج. كما هو الحال في OpenGL الأصلي ، يبدأ التقديم بتشكيل مصفوفة التحويل - مسح glLoadIdentity () يجعل وحدة المصفوفة الحالية ، ثم مجموعة من التحولات glRotateXY (...) ، glTranslate (...) ، يتم ضرب كل منها في المصفوفة الحالية. نظرًا لأن هذه الحسابات سيتم إجراؤها مرة واحدة فقط لكل إطار ، فلا توجد متطلبات خاصة للسرعة ، يمكنك القيام بها مع العوامات البسيطة ، بدون انحرافات بأرقام نقطة ثابتة. المصفوفة نفسها عبارة عن صفيف من العوامة [4] [4] ، تم تعيينها إلى صفيف أحادي البعد من العوامة [16] - في الواقع ، تُستخدم هذه الطريقة عادةً للمصفوفات الديناميكية ، ولكن يمكنك أيضًا الحصول على القليل من الفوائد من السكون الثابت. اختراق قياسي آخر: بدلاً من حساب الجيوب وجيب التمام باستمرار ، وهما كثيران في مصفوفات الدوران ،احسبهم مقدمًا واكتبهم على الجهاز اللوحي. للقيام بذلك ، قم بتقسيم الدائرة الكاملة إلى 256 جزءًا ، وحساب قيمة الجيب لكل منها وتفريغها في صفيف sin_table []. حسنًا ، يمكن لأي شخص من المدرسة الحصول على جيب التمام من الجيب. تجدر الإشارة إلى أن وظائف الدوران تأخذ زاوية ليس بالتقدير الدائري ، ولكن في أجزاء من ثورة كاملة ، بعد التخفيض إلى المدى [0 ... 255]. ومع ذلك ، تم تنفيذ وظائف "صادقة" تؤدي التحويل من الزاوية إلى الفصوص تحت غطاء المحرك.إجراء التحويل من الزاوية إلى الفصوص تحت غطاء المحرك.إجراء التحويل من الزاوية إلى الفصوص تحت غطاء المحرك.

عندما تصبح المصفوفة جاهزة ، يمكنك البدء في رسم البدائيات. بشكل عام ، في الرسومات ثلاثية الأبعاد ، هناك ثلاثة أنواع من الأوليات - نقطة وخط ومثلث. ولكن إذا كنا مهتمين بالنماذج متعددة الأضلاع ، فيجب الانتباه فقط إلى المثلث. يحدث "التقديم" في الدالة glDrawTriangle () أو glDrawTriangleV (). يتم تضمين كلمة "التقديم" بين علامتي اقتباس لأنه لا يوجد عرض في هذه المرحلة. نقوم فقط بضرب جميع نقاط البدائي في مصفوفة التحويل ، ثم نستخرج منها الصيغ التحليلية للحواف y = ky * x + by ، والتي تسمح لنا بإيجاد تقاطعات جميع حواف المثلث الثلاثة مع خط الإخراج الحالي. نتجاهل أحدهم ، لأنه لا يقع على الفاصل الزمني بين القمم ، ولكن على استمراره.أي ، لرسم إطار ، ما عليك سوى المرور عبر جميع الخطوط ولكل طلاء المنطقة بين نقاط التقاطع. ولكن إذا قمت بتطبيق هذه الخوارزمية "وجها لوجه" ، فإن كل بدائي سيتداخل مع تلك التي تم رسمها في وقت سابق. نحتاج إلى النظر في الإحداثي Z (العمق) بحيث تتقاطع المثلثات بشكل جميل. بدلاً من الطباعة ببساطة نقطة بنقطة ، سننظر في إحداثيات Z الخاصة بها ، وبالمقارنة مع إحداثيات Z المخزنة في المخزن المؤقت للعمق ، إما إخراج (تحديث المخزن المؤقت Z) أو تجاهلها. ولحساب إحداثيات Z لكل نقطة من الخط الذي يهمنا ، نستخدم نفس صيغة الخط المستقيم z = kz * y + bz المحسوبة بنفس نقطتي التقاطع مع الحواف. ونتيجة لذلك ، يتكون كائن مثلث المثلث "شبه النهائي" glTriangle من ثلاثة إحداثيات X للقمم (لا معنى في تخزين الإحداثيات Y و Z ، سيتم حسابها) و k ،ب المعاملات المباشرة ، حسنا ، لون كومة الذاكرة المؤقتة. هنا ، على النقيض من حساب مصفوفات التحويل ، تعد السرعة حاسمة ، لذلك نستخدم بالفعل أرقام النقطة الثابتة. علاوة على ذلك ، إذا كانت للمصطلح b نفس الدقة كافية للإحداثيات (2 بايت) ، فإن دقة العامل k ، كلما كان ذلك أفضل ، لذا نأخذ 4 بايت. ولكن ليس تعويمًا ، لأن العمل مع الأعداد الصحيحة لا يزال أسرع ، حتى بنفس الحجم.

لذا ، من خلال استدعاء مجموعة من glDrawTriangle () قمنا بإعداد مجموعة من المثلثات شبه النهائية. في تنفيذي ، يتم استنتاج المثلثات واحدًا تلو الآخر من خلال استدعاءات الوظائف الصريحة. في الواقع ، سيكون من المنطقي أن يكون لديك مجموعة من المثلثات مع عناوين القمم ، ولكن هنا قررت عدم تعقيدها. على أي حال ، تتم كتابة وظيفة التقديم بواسطة الروبوتات ، ولا يهمهم ما إذا كان عليهم ملء مجموعة ثابتة أو كتابة ثلاثمائة مكالمة متطابقة. حان الوقت لترجمة المنتجات شبه النهائية للمثلثات إلى صورة جميلة على الشاشة. للقيام بذلك ، يتم استدعاء الدالة glSwapBuffers (). كما هو موضح أعلاه ، فإنه يمر عبر خطوط العرض ، ويبحث عن كل نقطة تقاطع مع جميع المثلثات ويرسم الأجزاء وفقًا للترشيح حسب العمق. بعد عرض كل سطر ، تحتاج إلى إرسال هذا الخط إلى الشاشة. للقيام بذلك ، يتم تشغيل DMA ، مما يشير إلى عنوان السلسلة وحجمها.في هذه الأثناء ، يعمل DMA ، يمكنك التبديل إلى مخزن مؤقت آخر وتقديم الخط التالي. الشيء الرئيسي هو ألا تنسى الانتظار حتى نهاية التحويل إذا انتهيت فجأة من التقديم في وقت سابق. لتصور نسبة السرعات ، أضفت إدراج LED أحمر بعد نهاية العرض وإيقاف التشغيل بعد الانتهاء من انتظار DMA. اتضح شيء مثل PWM ، الذي يضبط السطوع اعتمادًا على الكمون. نظريًا ، بدلاً من الانتظار "البكم" ، يمكن استخدام مقاطعات DMA ، ولكن بعد ذلك لم أستطع استخدامها ، وستصبح الخوارزمية أكثر تعقيدًا. بالنسبة لبرنامج تجريبي ، هذا أمر زائد.لتصور نسبة السرعات ، أضفت إدراج LED أحمر بعد نهاية العرض وإيقاف التشغيل بعد الانتهاء من انتظار DMA. اتضح شيء مثل PWM ، الذي يضبط السطوع اعتمادًا على الكمون. نظريًا ، بدلاً من الانتظار "البكم" ، يمكن استخدام مقاطعات DMA ، ولكن بعد ذلك لم أستطع استخدامها ، وستصبح الخوارزمية أكثر تعقيدًا. بالنسبة لبرنامج تجريبي ، هذا أمر زائد.لتصور نسبة السرعات ، أضفت إدراج LED أحمر بعد نهاية العرض وإيقاف التشغيل بعد الانتهاء من انتظار DMA. اتضح شيء مثل PWM ، الذي يضبط السطوع اعتمادًا على الكمون. نظريًا ، بدلاً من الانتظار "البكم" ، يمكن استخدام مقاطعات DMA ، ولكن بعد ذلك لم أستطع استخدامها ، وستصبح الخوارزمية أكثر تعقيدًا. بالنسبة لبرنامج تجريبي ، هذا أمر زائد.

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

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

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

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

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


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

إذا كان أحد الأشخاص مهتمًا فجأة ، فإن شفرة المصدر متاحة هنا .

All Articles