عذاب SceneKit. تجربة ياندكس مع 3D الرسومات في دائرة الرقابة الداخلية

- أنا أصغر من أن أموت.


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



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


أبسط مشهد من ثلاثة العقد
أبسط مشهد من ثلاثة العقد مع الهندسة فيها


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


تراكب المواد
تراكب المواد


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


إضافة مصادر الإضاءة
إضافة مصادر الإضاءة


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


من خارج منطقة الجزاء
من خارج منطقة الجزاء تأثير بوك


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


الرسوم المتحركة الأثاث في SceneKit
الرسوم المتحركة البسيطة في SceneKit


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


يمكن أن يكون تفاعل مولد الجسيمات مع محرك مادي إلى حدوث أعاصير!
يمكن أن يؤدي تفاعل مولد الجسيمات مع محرك مادي إلى إعصار!


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


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


مهلا ، ليست صعبة للغاية


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

سنبدأ دراسة مفصلة بمهمة بسيطة إلى حد ما تنشأ لكل شخص يعمل بجدية مع SceneKit: كيف يمكن تحميل نموذج بهندسة معقدة والمواد المتصلة والإضاءة وحتى الرسوم المتحركة؟


هناك عدة طرق ، ولهم جميعا إيجابيات وسلبيات:


  1. SCNScene (اسمه :) - يحصل على مشهد من حزمة ،


  2. SCNScene (عنوان url: options :) - يقوم بتحميل المشهد بواسطة عنوان URL ،


  3. SCNScene (mdlAsset :) - يحول مشهد من تنسيقات مختلفة ،


  4. SCNReferenceNode (url :) - يحمّل المشهد بتكاسل.



الحصول على المشهد من الحزمة


يمكنك استخدام الطريقة القياسية : ضع نموذجنا في تنسيق dae أو scn في حزمة scnassets وقم بتحميله من هناك عن طريق القياس باستخدام UIImage (اسمه :).


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


تحميل المشهد عن طريق URL


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


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


تحويل مشهد من صيغ مختلفة


الخيار الثالث هو استخدام المنشئ مع MDLAsset . هذا هو ، أولاً ، ننشئ MDLAsset ، المتوفر في Modelio Framework ، ثم نمرره إلى المنشئ للمشهد.


هذا الخيار جيد لأنه يسمح لك بتنزيل العديد من التنسيقات المختلفة. من الناحية الرسمية ، يمكن لـ MDLAsset تحميل التنسيقات obj و ply و stl و usd ، لكن بعد نفاد قائمة بجميع التنسيقات الممكنة ، على الأقل ذات الصلة بطريقة ما برسومات الكمبيوتر ، وجدت أربعة أخرى: abc و bsp و vox و md3 ، لكن قد لا تكون مدعومة بالكامل أو لا في جميع الأنظمة ، ولهم تحتاج إلى التحقق من صحة الاستيراد.


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


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


تحميل كسول المشهد


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


لديه شيء واحد ولكن: المعايير العالمية للمشهد تضيع.


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


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


ليس على الإطلاق تحسينات سابقة لأوانها


بعد أن تعاملت مع هذه العملية لفترة طويلة ، يمكنني أن أقدم لكم بعض النصائح.


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


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


مع هذه العملية ، يمكنك الحصول على تأثير جانبي مفيد للغاية: ضغط نسيج كبير. يكفي فتحها في "العرض" والتصدير ، وتغيير التنسيق إلى heic. في المتوسط ​​، وفرت هذه العملية البسيطة 5 ميغابايت لكل نموذج.


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


يؤذيني كثيرا


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

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


تعتبر القيود في SceneKit مباشرة بعد الفيزياء.  و تقديم الإطار
تعتبر القيود في SceneKit مباشرة بعد الفيزياء. وقبل تقديم الإطار


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


SCNReplicatorConstraint
SCNReplicatorConstraint


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


انخفاض القوة 10 مرات
انخفاض القوة 10 مرات


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


زيادة وقد وتقليل القوة منخفضة 10 مرات
زيادة متزايدة وتقليل القوة بمقدار 10 مرات


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


الطائرة تواجه الكاميرا دائمًا.
الطائرة تواجه الكاميرا دائمًا.


دعنا ننتقل إلى ثابت أكثر إثارة للاهتمام: لوحة ما يسمى.


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


هنا يمكنك ذكر Look At Constraint : إنه مشابه للوحة إعلانات ، يمكن فقط تعيين الكائن في مواجهة أي كائن آخر في المشهد بدلاً من الكاميرا الحالية.


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


يحافظ على المسافة بين الكائنات
يحافظ على المسافة بين الكائنات


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


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


SCNSliderConstraint
SCNSliderConstraint


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


عكس الحركات في العمل
عكس الحركات في العمل


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


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


هناك طائرة ، لكنها ليست كذلك
هناك طائرة ، لكنها ليست كذلك


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


لكن الظلال ليست هي التأثير الوحيد الذي تم دراسته بشكل سيء في SceneKit. دعونا نتعامل مع المرايا الآن.


مرآة SCNFloor - ما يمكن أن يكون أكثر بساطة
مرآة SCNFloor - ما يمكن أن يكون أكثر بساطة


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



بالتنقيط على الزجاج ومرآة منحنية
بالتنقيط على الزجاج ومرآة منحنية


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


فوق البنفسجي


بمجرد تقبيل فتاة بعيون مفتوحة. قطعت الفتاة وجهها بالطائرة القريبة من القصاصة. منذ ذلك الحين أنا أقبل فقط بعيني مغلقة.
- جون كارماك

الظلال والمرايا - آثار مثيرة للاهتمام. ولكن هناك تأثير واحد ، عند استخدامه بمهارة ، قد يكون أكثر تشويقًا - قوام الفيديو.



أشرطة الفيديو العادية و heightmap
أشرطة الفيديو العادية و heightmap


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


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


ولكن بعد تصدير المشهد ثلاثي الأبعاد الناتج وفتحه في مكان آخر ، سنرى مربعًا أسود. بالتفتيش من خلال ملف scn ، وجدت السبب. اتضح أنه عند حفظ رمز الفيديو ، فإنه لا يحفظ URL الفيديو. يبدو أنك تأخذ والحكم. ولكن ليس كل شيء بهذه البساطة: ملف scn هو ما يسمى بـ plist ثنائي ، والذي يحتوي على نتيجة NSKeyedArchiver. والمواد ، التي هي مشهد SpriteKit ، هي نفس اللوحة الثنائية ، التي ، كما اتضح ، تقع بالفعل داخل لوحة ثنائية أخرى! من الجيد أن يكون هناك مستويان فقط من التعشيش.


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


قبل تعديل شيء ما ، تحتاج إلى فهم ما نقوم بتعديله. التظليل ، بحكم تعريفه ، هو برنامج ل GPU يعمل لكل قمة ولكل بكسل. وبالتالي ، فإن shader هو برنامج يحدد كيفية ظهور كائن ما على الشاشة.


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



تشن و المنظر رسم الخرائط
الفراء و المنظر رسم الخرائط


بمساعدة مُعدلات التظليل ، يمكنك إنشاء تأثيرات مرئية معقدة. على سبيل المثال ، اثنين من أكثر الآثار شهرة: Fur و Parallax Mapping .


#pragma arguments texture2d bg; texture2d height; float depth; float layers; #pragma transparent #pragma body constexpr sampler sm = sampler(filter::linear, s_address::repeat, t_address::repeat); float3 bitangent = cross(_surface.tangent, _surface.normal); float2 direction = float2(-dot(_surface.view.rgb, _surface.tangent), dot(_surface.view.rgb, _surface.bitangent)); _output.color.rgba = float4(0); for(int i = 0; i < int(floor(layers)); i++) { float coeff = float(i) / floor(layers); float2 defaultCoords = _surface.diffuseTexcoord + direction * (1 - coeff) * depth; float2 adjustment = float2(scn_frame.sinTime + defaultCoords.x, scn_frame.cosTime) * depth * coeff * 0.1; float2 coords = defaultCoords + adjustment; _output.color.rgb += bg.sample(sm, coords).rgb * coeff * (height.sample(sm, coords).r + 0.1) * (1.0 - coeff); _output.color.a += (height.sample(sm, coords).r + 0.1) * (1.0 - coeff); } return _output; 

راي الصب مع الكاوية في الوقت الحقيقي.
راي الصب مع الحار الوقت الحقيقي


والأمر الأكثر إثارة للاهتمام هو أنه لا أحد يكترث برفض نتائج أعمالهم بالكامل وكتابة عارضهم. على سبيل المثال ، يمكنك محاولة تطبيق Ray Casting في تظليل. وكل هذا يعمل بسرعة كافية لتوفير 30 إطارا في الثانية حتى على مثل هذه الحسابات المعقدة. لكن هذا موضوع لتقرير منفصل. هيا Mobius !


كابوس!


أنا لا أحب أن وميض ، لأن الجفون مغلقة تحميل GPU ل BDPT بحدة بسبب نقص الإضاءة.
- جون كارماك

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


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


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


عملية التسجيل
عملية التسجيل


نحن بحاجة إلى إنشاء كيان يقوم بتسجيل الملفات - AVAssetWriter ، وإضافة دفق فيديو - AVAssetWriterInput إليه ، وإنشاء محول لهذا الدفق الذي سيحول مخزن البيكسل الخاص بنا إلى التنسيق المطلوب بواسطة الدفق - AVAssetWriterPixelBufferAdaptor .


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


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


 var unsafePixelBuffer: CVPixelBuffer? CVPixelBufferPoolCreatePixelBuffer(NULL, self.pixelBufferPool, &unsafePixelBuffer) guard let pixelBuffer = maybePixelBuffer else { return } CVPixelBufferLockBaseAddress(pixelBuffer, 0) let data = CVPixelBufferGetBaseAddress(pixelBuffer) let rgbColorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) let rowBytes = NSUInteger(CVPixelBufferGetBytesPerRow(pixelBuffer)) let context = CGContext( data: data, width: image.width, height: image.height, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: bitmapInfo.rawValue ) context?.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height)) CVPixelBufferUnlockBaseAddress(pixelBuffer, 0) self.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime) 

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



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



لكن لا. مثل هذا الحل ، حتى على الهواتف القوية ، يتسبب في تباطؤ رهيب وتراجع في عدد FPS. دعونا نفعل التحسين.



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



لقد أصبح التأخر أقل ، لكن الفيديو توقف عن التسجيل ...


كل شيء بسيط. كما يقولون ، إذا كانت لديك مشكلة ، وسوف تحلها بمساعدة عدة خيوط ، سيكون لديك مشكلتان.


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



لنقم بعد ذلك بكتابة مخزن مؤقت جديد حتى تنتهي الكتابة السابقة.



حسنًا ، لقد تحسنت كثيرًا. لكن مع ذلك ، لماذا ظهرت التخلفات في البداية؟



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


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


لسوء الحظ ، فإن الوصول إلى Metal مباشرة من SCNView ليس بهذه السهولة لسبب مفهوم - في SceneKit يمكنك اختيار نوع واجهة برمجة التطبيقات بنفسك ، ولكن إذا نظرت أسفل الغطاء ونظرت إلى الطبقة ، يمكنك أن ترى أنه يتصرف كما هو ، في حالة Metal ، CAMetalLayer .


ولكن هنا أيضًا ، ينتظرنا الفشل: في CAMetalLayer ، الطريقة الوحيدة للتفاعل مع العرض هي الدالة nextDrawable ، والتي تُرجع CAMetalDrawable غير مأهولة. من المعلوم أنك ستكتب البيانات فيه وتستدعي الوظيفة الحالية عليه ، والتي ستعرضها على الشاشة.


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


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


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


الانتقال إلى الحل الحقيقي بسيط للغاية - فنحن نوفر كل من الدرج الحالي والحل السابق.


وهنا ، جاهز - الوصول المباشر إلى الذاكرة من خلال CAMetalDrawable.


 var unsafePixelBuffer: CVPixelBuffer? CVPixelBufferPoolCreatePixelBuffer(NULL, self.pixelBufferPool, &unsafePixelBuffer) guard let pixelBuffer = maybePixelBuffer else { return } CVPixelBufferLockBaseAddress(pixelBuffer, 0) let data = CVPixelBufferGetBaseAddress(pixelBuffer) let width: NSUInteger = lastDrawable.texture.width let height: NSUInteger = lastDrawable.texture.height let rowBytes: NSUInteger = NSUInteger(CVPixelBufferGetBytesPerRow(pixelBuffer) lastDrawable.texture.getBytes( data, bytesPerRow: rowBytes, fromRegion: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0 ) CVPixelBufferUnlockBaseAddress(pixelBuffer, 0) self.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime) 

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


لا يتطابق مع deviceColorSpace ... ولا يتطابق مع مسافات اللون الشائعة الاستخدام ...


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



حسنا ، كل هذه الحيل - من أجل مرشح زاحف؟


حسنا لا! في مقالة ARKit ، يمكنك أن تجد إشارة إلى أن الصورة من الكاميرا لا تستخدم مساحة اللون القياسية ، ولكن تم توسيعها. وحتى مصفوفة تحول مساحة اللون مقدمة. ولكن لماذا الانخراط في التحول ، إذا كان يمكنك محاولة تسجيل مباشرة في هذا الشكل؟ يبقى لمعرفة ما هو شكل من أصل 60 المتاحة ...



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


نتيجة لذلك ، في الشكل الأربعين ، نحصل على اسمه. تبين أنها ليست سوى kCVPixelFormatType_30RGBLEPackedWideGamut . كيف لم أخمن؟



لكن سعادتي استمرت حتى أول اختبار. لم يكن لدي كلمات. كيف؟ قضيت الكثير من الوقت في البحث عن التنسيق الصحيح. من الجيد أن تتم ترجمة المشكلة بسرعة - تم إعادة إنتاج الخلل بشكل ثابت وفقط في 6s و 6s Plus. بعد ذلك على الفور تقريبًا ، تذكرت أن شاشات العرض ذات النطاق العريض بدأت يتم تثبيتها فقط في أجهزة iPhone السابعة.


تغيير التدرج اللوني إلى 32RGBA القديم الجيد ، أحصل على سجل عملي! يبقى لفهم كيفية تحديد أن الجهاز يدعم النطاق العريض. هناك أجهزة iPad مزودة بأنواع مختلفة من شاشات العرض ، وأعتقد أنه يمكنك بالتأكيد الحصول على نوع العرض ENUM من النظام. تفتش من خلال الوثائق ، لقد وجدت ذلك - وهذا هو displayGamut في UITraitCollection .


بعد أن أعطيت التجميع للمختبرين ، تلقيت أخبارًا سارة منهم - كل شيء يعمل دون أي تأخير ، حتى على الأجهزة القديمة!


في الختام ، أريد أن أخبرك - قم بعمل رسومات ثلاثية الأبعاد! في تطبيقنا ، الذي لا يمثل الواقع المعزز حالة الاستخدام الرئيسية له ، في نهاية الأسبوع ، سافر الأشخاص أكثر من 2000 كيلومتر ، وشاهدوا أكثر من 3000 كائن وسجلوا أكثر من 1000 مقطع فيديو معهم! تخيل ما يمكنك القيام به إذا كنت تفعل ذلك بنفسك.

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


All Articles