
مقدمة
نتحدث عن تقنيات البرمجة الخاصة بـ OSG
، آخر مرة تحدثنا فيها عن آلية رد الاتصال وتطبيقها في المحرك. لقد حان الوقت للنظر في الاحتمالات التي توفرها هذه الآلية لإدارة محتويات مشهد ثلاثي الأبعاد.
إذا تحدثنا عن الرسوم المتحركة للكائنات ، فإن OSG تزود المطور بخيارين لتنفيذه:
- الرسوم المتحركة الإجرائية تنفذ برمجيا من خلال تحويل الأشياء وسماتها
- تصدير الرسوم المتحركة من محرر ثلاثي الأبعاد وإدارتها من رمز التطبيق
لتبدأ ، والنظر في الاحتمال الأول ، والأكثر وضوحا. سنتحدث بالتأكيد عن الثانية بعد قليل.
1. الإجرائية تتحول الرسوم المتحركة
عند اجتياز الرسم البياني للمشهد ، تقوم OSG بنقل البيانات إلى خط أنابيب OpenGL ، والذي يتم تشغيله في سلسلة رسائل منفصلة. يجب أن تتم مزامنة مؤشر الترابط هذا مع مؤشرات ترابط المعالجة الأخرى في كل إطار. قد يؤدي الفشل في القيام بذلك إلى إكمال طريقة الإطار () قبل معالجة البيانات الهندسية. سيؤدي هذا إلى سلوك وتعطل البرنامج غير متوقعة. يقدم OSG حلاً لهذه المشكلة في شكل طريقة setDataVariance () لفئة osg :: Object ، والتي تعتبر الأساس لجميع الكائنات الرئيسية في المشهد. يمكنك تعيين ثلاثة أوضاع معالجة للكائنات
- غير محدد (بشكل افتراضي) - يحدد OSG بشكل مستقل ترتيب معالجة الكائن.
- ثابت - الكائن غير قابل للتغيير وترتيب معالجته غير مهم. يسرع بشكل كبير تقديم.
- DYNAMIC - يجب معالجة الكائن قبل بدء العرض.
يمكن ضبط هذا الإعداد في أي وقت عن طريق الاتصال
node->setDataVariance( osg::Object::DYNAMIC );
تتمثل الممارسة المقبولة عمومًا في تعديل الهندسة "سريعًا" ، أي تغيير إحداثيات القمم ومعايير اللون والأنسجة ديناميكيًا في كل إطار ، والحصول على هندسة قابلة للتغيير. وتسمى هذه التقنية تتحول الرسوم المتحركة. في هذه الحالة ، يعد ترتيب معالجة الهندسة حاسماً - يجب إعادة حساب جميع التغييرات قبل بدء الرسم. لتوضيح هذه الخدعة ، نقوم بتعديل المثال المربّع الملون قليلاً ، مما يضطر أحد رؤوسه للتدوير حول محور X.
مثال أنيمكوادالرئيسية #ifndef MAIN_H #define MAIN_H #include <osg/Geometry> #include <osg/Geode> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
سنقوم بإنشاء مربع في وظيفة منفصلة
osg::Geometry *createQuad() { osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array; vertices->push_back( osg::Vec3(0.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 1.0f) ); vertices->push_back( osg::Vec3(0.0f, 0.0f, 1.0f) ); osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array; normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) ); osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array; colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); osg::ref_ptr<osg::Geometry> quad = new osg::Geometry; quad->setVertexArray(vertices.get()); quad->setNormalArray(normals.get()); quad->setNormalBinding(osg::Geometry::BIND_OVERALL); quad->setColorArray(colors.get()); quad->setColorBinding(osg::Geometry::BIND_PER_VERTEX); quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4)); return quad.release(); }
وصفًا ، من حيث المبدأ ، ليس مطلوبًا ، نظرًا لأننا قمنا بهذه الإجراءات مرارًا وتكرارًا. لتعديل رؤوس هذا المربع ، نكتب فئة DynamicQuadCallback ، وراثة من osg :: Drawable :: UpdateCallback
class DynamicQuadCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor *, osg::Drawable *drawable); };
تجاوز أسلوب التحديث () فيه
void DynamicQuadCallback::update(osg::NodeVisitor *, osg::Drawable *drawable) { osg::Geometry *quad = static_cast<osg::Geometry *>(drawable); if (!quad) return; osg::Vec3Array *vertices = static_cast<osg::Vec3Array *>(quad->getVertexArray()); if (!vertices) return; osg::Quat quat(osg::PI * 0.01, osg::X_AXIS); vertices->back() = quat * vertices->back(); quad->dirtyDisplayList(); quad->dirtyBound(); }
هنا نحصل على مؤشر لكائن الهندسة
osg::Geometry *quad = static_cast<osg::Geometry *>(drawable);
نقرأ من الهندسة قائمة من القمم (أو بالأحرى مؤشر إليها)
osg::Vec3Array *vertices = static_cast<osg::Vec3Array *>(quad->getVertexArray());
للحصول على العنصر الأخير (قمة الرأس الأخيرة) في الصفيف ، توفر الفئة osg :: Array الأسلوب back (). لأداء دوران الرأس بالنسبة إلى المحور X ، نقدم الربع
osg::Quat quat(osg::PI * 0.01, osg::X_AXIS);
بمعنى ، وضعنا رباعيًا يقوم بتدوير حول المحور X بزاوية 0.01 * Pi. تدوير الرأس عن طريق ضرب رباعي بواسطة ناقلات تحديد إحداثيات قمة الرأس
vertices->back() = quat * vertices->back();
المكالمات الأخيرة جهازي إعادة سرد قائمة العرض و parallelepiped الأبعاد للهندسة المعدلة
quad->dirtyDisplayList(); quad->dirtyBound();
في نص الدالة main () ، نقوم بإنشاء مربع ، وضبط وضع الرسم الديناميكي لها ، وإضافة رد اتصال لتعديل الشكل الهندسي
osg::Geometry *quad = createQuad(); quad->setDataVariance(osg::Object::DYNAMIC); quad->setUpdateCallback(new DynamicQuadCallback);
سأترك إنشاء العقدة الجذر بشكل عشوائي وإطلاق العارض ، كما فعلنا هذا بالفعل على الأقل 20 مرة بطرق مختلفة. نتيجة لذلك ، لدينا أبسط الرسوم المتحركة تتحول

الآن حاول إزالة (التعليق على) استدعاء setDataVariance (). ربما لن نرى أي شيء إجرامي في هذه الحالة - بشكل افتراضي ، يحاول OSG تحديد وقت تحديث بيانات الأشكال الهندسية تلقائيًا ، ومحاولة المزامنة مع التقديم. ثم حاول تغيير الوضع من DYNAMIC إلى STATIC وسيُرى أن الصورة لا تظهر بسلاسة ، مع تدفق الهزات والأخطاء والتحذيرات بشكل ملحوظ على وحدة التحكم
Warning: detected OpenGL error 'invalid value' at after RenderBin::draw(..)
إذا لم تقم بتنفيذ طريقة dirtyDisplayList () ، فسوف يتجاهل OpenGL جميع التغييرات في الهندسة وسيستخدم قائمة العرض التي تم إنشاؤها في البداية لإنشاء المربع للتقديم. احذف هذه المكالمة وسترى أنه لا توجد رسوم متحركة.
بدون استدعاء الأسلوب dirtyBound () ، لن يتم إعادة حساب المربع المحيط وسيقوم OSG بقص الوجوه غير المرئية بشكل غير صحيح.
2. مفهوم الاستيفاء الحركة
لنفترض أن القطار المتجه من المحطة A إلى المحطة B يستغرق 15 دقيقة للسفر. كيف يمكننا محاكاة هذا الموقف عن طريق تغيير موقف القطار في رد الاتصال؟ أسهل طريقة هي ربط موقع المحطة A بالوقت 0 ، والمحطة B لمدة 15 دقيقة ونقل القطار بالتساوي بين هذه الأوقات. ويسمى هذا النهج أبسط الاستيفاء الخطي. في الاستيفاء الخطي ، يتم وصف المتجه الذي يحدد موضع النقطة الوسيطة بواسطة الصيغة
p = (1 - t) * p0 + t * p1
حيث p0 هي نقطة البداية ؛ P1 هي نقطة النهاية ؛ t هي معلمة تختلف بشكل منتظم من 0 إلى 1. ومع ذلك ، فإن حركة القطار أكثر تعقيدًا: فهي تترك المحطة A ، وتتسارع ، ثم تتحرك بسرعة ثابتة ، ثم تبطئ وتوقف في المحطة B. لم تعد هذه العملية قادرة على وصف الاستيفاء الخطي و يبدو غير طبيعي.
يوفر OSG للمطور مكتبة osgAnimation التي تحتوي على عدد من خوارزميات الاستيفاء القياسية المستخدمة لتحريك حركة كائنات المشهد بسلاسة. تحتوي كل من هذه الوظائف عادةً على وسيطين: القيمة الأولية للمعلمة (عادةً 0) والقيمة النهائية للمعلمة (عادةً 1). يمكن تطبيق هذه الوظائف على بداية الحركة (InMotion) ، أو في نهاية الحركة (OutMotion) أو على بداية الحركة ونهايتها (InOutMotion)
نوع الحركة | في الصف | خارج الصف | في / خارج الصف |
---|
الاستيفاء الخطي | LinearMotion | - | - |
الاستكمال التربيعي | InQuadMotion | OutQuadMotion | InOutQuadMotion |
الاستيفاء المكعب | InCubicMotion | Outcubicmotion | InOutCubicMotion |
4-ترتيب الاستيفاء | InQuartMotion | OutQuartMotion | InOutQuartMotion |
ترتد تأثير الاستيفاء | InBounceMotion | OutBounceMotion | InOutBounceMotion |
انتعاش مرونة الاستيفاء | InElasticMotion | OutElasticMotion | InOutElasticMotion |
الاستيفاء الجيوب الأنفية | InSineMotion | الغموض | InOutSineMotion |
معكوس الاستيفاء | إينباكموشن | Outbackmotion | InOutBackMotion |
الاستيفاء الدائري | InCircMotion | تفوق | InOutCircMotion |
الاستيفاء الأسي | InExpoMotion | التفوق | InOutExpoMotion |
لإنشاء استيفاء خطي لحركة كائن ما ، نكتب هذا الرمز
osg::ref_ptr<osgAnimation::LinearMotion> motion = new osgAnimation::LinearMotion(0.0f, 1.0f);
3. الرسوم المتحركة من العقد التحول
الرسوم المتحركة للمسار هي أكثر أنواع الرسوم المتحركة شيوعًا في تطبيقات الرسوم. يمكن استخدام هذه التقنية لتحريك حركة السيارة أو رحلة الطائرة أو حركة الكاميرا. يتم تحديد المسار مسبقًا ، مع كل المواضع والدورات وتغييرات النطاق في النقاط الرئيسية في الوقت المناسب. عند بدء دورة المحاكاة ، يتم إعادة حساب حالة الكائن في كل إطار ، باستخدام الاستيفاء الخطي للموضع والتحجيم والاستيفاء الخطي الكروي لرباعيات الدوران. للقيام بذلك ، استخدم الطريقة الداخلية slerp () لفئة osg :: Quat.
يوفر OSG فئة osg :: AnimationPath لوصف مسار متغير بالوقت. يتم استخدام طريقة إدراج هذه الفئة () لإضافة نقاط تحكم مقابلة لنقاط معينة في الوقت المناسب للمسار. يتم وصف نقطة التحكم بواسطة فئة osg :: AnimationPath :: ControlPoint ، التي يأخذ منشئها الموضع كمعلمات ، واختيارياً ، تناوب الكائن ومعلمات القياس. على سبيل المثال
osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->insert(t1, osg::AnimationPath::ControlPoint(pos1, rot1, scale1)); path->insert(t2, ...);
هنا t1 ، t2 هي وقت instants بالثواني. rot1 هي معلمة التدوير في وقت t1 ، الموصوفة بواسطة رباعي osg :: Quat.
من الممكن التحكم في حلقات الرسوم المتحركة من خلال طريقة setLoopMode (). بشكل افتراضي ، يتم تشغيل وضع LOOP - سيتم تكرار الرسوم المتحركة بشكل مستمر. القيم المحتملة الأخرى: NO_LOOPING - العب الرسوم المتحركة مرة واحدة و SWING - قم بحركة الحركة في الاتجاهين الأمامي والخلفي.
بعد اكتمال كافة التهيئة ، نرفق كائن osg :: AnimationPath بالكائن المدمج osg :: AnimationPathCallback ، والذي يتم اشتقاقه من فئة osg :: NodeCallback.
4. مثال على الرسوم المتحركة للحركة على طول الطريق
الآن سنجعل سيسنا الخاصة بنا تتحرك في دائرة مع المركز عند النقطة (0،0،0). سيتم حساب موضع الطائرة على المسار عن طريق الاستيفاء الخطي للموقف والاتجاه بين الإطارات الرئيسية.
Animcessna سبيل المثالالرئيسية #ifndef MAIN_H #define MAIN_H #include <osg/AnimationPath> #include <osg/MatrixTransform> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
نبدأ بإنشاء مسار الطائرة ، مع تحويل هذا الرمز إلى وظيفة منفصلة
osg::AnimationPath *createAnimationPath(double radius, double time) { osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->setLoopMode(osg::AnimationPath::LOOP); unsigned int numSamples = 32; double delta_yaw = 2.0 * osg::PI / (static_cast<double>(numSamples) - 1.0); double delta_time = time / static_cast<double>(numSamples); for (unsigned int i = 0; i < numSamples; ++i) { double yaw = delta_yaw * i; osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0); osg::Quat rot(-yaw, osg::Z_AXIS); path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot)); } return path.release(); }
كمعلمات ، تأخذ الوظيفة نصف قطر الدائرة التي تتحرك خلالها الطائرة والوقت الذي ستحدث فيه ثورة واحدة. داخل الوظيفة ، قم بإنشاء كائن مسار وتشغيل وضع حلقات الرسوم المتحركة
osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->setLoopMode(osg::AnimationPath::LOOP);
بعد الكود
unsigned int numSamples = 32; double delta_yaw = 2.0 * osg::PI / (static_cast<double>(numSamples) - 1.0); double delta_time = time / static_cast<double>(numSamples);
يحسب المعلمات تقريب المسار. نقسم المسار بالكامل إلى numSamples of مقاطع مستقيمة ، ونحسب التغير في زاوية دوران الطائرة حول المحور العمودي (yaw) delta_yaw والتغيير في delta_time الزمني عند الانتقال من قسم إلى قسم. الآن إنشاء نقاط التحكم اللازمة
for (unsigned int i = 0; i < numSamples; ++i) { double yaw = delta_yaw * i; osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0); osg::Quat rot(-yaw, osg::Z_AXIS); path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot)); }
في الدورة ، يتم فرز جميع أقسام المسار من الأول إلى الأخير. تتميز كل نقطة تحكم بزاوية انحراف
double yaw = delta_yaw * i;
موقف مركز كتلة الطائرة في الفضاء
osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0);
يتم تعيين دوران الطائرة إلى زاوية الانحراف المطلوبة (بالنسبة إلى المحور العمودي) بواسطة الربع
osg::Quat rot(-yaw, osg::Z_AXIS);
ثم قم بإضافة المعلمات المحسوبة إلى قائمة نقاط التحكم في المسار
path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot));
في البرنامج الرئيسي ، نولي الاهتمام للفارق الدقيق في الإشارة إلى اسم ملف طراز الطائرة في وقت التمهيد
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg.0,0,90.rot");
- تمت إضافة لاحقة ".0،0،90.rot" إلى اسم الملف. تتيح لك آلية تحميل الأشكال الهندسية من ملف مستخدم في OSG تحديد الموضع والاتجاه الأولي للطراز بعد التحميل. في هذه الحالة ، نريد أن يتم تدوير النموذج بزاوية 90 درجة حول المحور Z بعد التحميل.
بعد ذلك ، يتم إنشاء عقدة الجذر ، وهي عقدة التحول ، ويضاف إليها كائن النموذج كعقدة فرعية
osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform; root->addChild(model.get());
الآن قم بإنشاء رد اتصال حركة مسار ، مضيفًا المسار الذي أنشأته دالة createAnimationPath () إليه
osg::ref_ptr<osg::AnimationPathCallback> apcb = new osg::AnimationPathCallback; apcb->setAnimationPath(createAnimationPath(50.0, 6.0));
نعلق هذا رد الاتصال إلى عقدة التحول
root->setUpdateCallback(apcb.get());
تهيئة العارض وتشغيله كالمعتاد.
osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run();
الحصول على الرسوم المتحركة حركة الطائرة

هل تعتقد أنك لم تجد شيئًا غريبًا في هذا المثال؟ في السابق ، على سبيل المثال ، في برنامج ما عند التقديم إلى نسيج ما ، قمت بتغيير مصفوفة التحويل بشكل صريح لتحقيق تغيير في موضع النموذج في الفضاء. هنا نقوم فقط بإنشاء عقدة تحويل وفي التعليمات البرمجية لا توجد مهمة مصفوفة صريحة في أي مكان.
السر هو أن فئة osg :: AnimationPathCallback الخاصة تقوم بهذه المهمة. وفقًا للموضع الحالي للكائن على المسار ، فإنه يحسب مصفوفة التحويل ويطبقها تلقائيًا على عقدة التحويل التي يرتبط بها ، مما يوفر للمطور من مجموعة من العمليات الروتينية.
تجدر الإشارة إلى أن ربط osg :: AnimationPathCallback بأنواع العقد الأخرى لن يكون له أي تأثير فحسب ، بل قد يؤدي أيضًا إلى سلوك غير محدد في البرنامج. من المهم أن تتذكر أن رد الاتصال هذا يؤثر فقط على عقد التحويل.
5. برامج التحكم في الرسوم المتحركة
توفر الفئة osg :: AnimationPathCallback طرقًا للتحكم في الرسوم المتحركة أثناء تنفيذ البرنامج.
- reset () - إعادة تعيين الرسوم المتحركة وتشغيلها أولاً.
- setPause () - توقف الرسوم المتحركة مؤقتًا. يأخذ قيمة منطقية كمعلمة
- setTimeOffset () - يحدد وقت الإزاحة قبل بدء الحركة.
- setTimeMultiplier () - يحدد عامل الوقت للتسارع / التباطؤ في الرسوم المتحركة.
على سبيل المثال ، لإزالة الرسوم المتحركة من الإيقاف المؤقت وإعادة التعيين ، ننفذ هذا الرمز
apcb->setPause(false); apcb->reset();
ولبدء الرسوم المتحركة من الثانية الرابعة بعد بدء البرنامج مع تسارع مزدوج ، مثل هذا الرمز
apcb->setTimeOffset(4.0f); apcb->setTimeMultiplier(2.0f);
6. ترتيب تقديم البدائية في OpenGL
يقوم OpenGL بتخزين البيانات الرأسية والبدائية في العديد من المخازن المؤقتة ، مثل مخزن مؤقت للون ، ومخزن مؤقت للعمق ، ومخزن مؤقت للاستنسل ، وما إلى ذلك. بالإضافة إلى ذلك ، لا يحل محل القمم والوجوه المثلثة المرسلة بالفعل إلى خط أنابيبه. هذا يعني أن برنامج OpenGL ينشئ هندسة جديدة ، بغض النظر عن كيفية إنشاء الهندسة الحالية. هذا يعني أن الترتيب الذي يتم به إرسال العناصر الأولية إلى خط أنابيب التقديم يؤثر بشكل كبير على النتيجة النهائية التي نراها على الشاشة.
استنادًا إلى بيانات المخزن المؤقت للعمق ، يقوم OpenGL برسم كائنات معتمة بشكل صحيح ، مع فرز وحدات البكسل وفقًا لمسافة المسافة عن المراقب. ومع ذلك ، عند استخدام تقنية خلط الألوان ، على سبيل المثال ، عند تطبيق كائنات شفافة وشفافة ، سيتم تنفيذ عملية خاصة لتحديث مخزن الألوان المؤقت. يتم خلط البيكسلات الجديدة والقديمة للصورة ، مع مراعاة قيمة قناة ألفا (مكون الألوان الرابع). هذا يؤدي إلى حقيقة أن ترتيب التقديم للحواف الشفافة والمبهمة يؤثر على النتيجة النهائية

في الشكل ، في الوضع على اليسار ، في البداية تم إرسال كائنات شفافة وغير شفافة إلى خط الأنابيب ، مما أدى إلى التحول الصحيح في المخزن المؤقت للألوان والعرض الصحيح للوجوه. في الحالة الصحيحة ، تم رسم الكائنات الشفافة الأولى ، ثم غير شفاف ، مما أدى إلى عرض غير صحيح.
تشير طريقة setRenderingHint () لفئة osg :: StateSet إلى OSG ترتيب العرض المطلوب للعُقد والكائنات الهندسية ، إذا كان يجب القيام بذلك بشكل صريح. تشير هذه الطريقة ببساطة إلى وجوب عدم مراعاة الوجوه الشفافة عند التقديم ، مما يضمن أنه في حالة وجود وجوه شفافة في المشهد ، سيتم رسم الوجوه الشفافة وغير الشفافة أولاً ، مع مراعاة المسافة من المراقب. لإبلاغ المحرك بأن هذه العقدة غير شفافة ، نستخدم هذا الرمز
node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN);
أو يحتوي على حواف شفافة
node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
7. مثال على تنفيذ الأشياء الشفافة
دعونا نحاول توضيح كل المقدمة النظرية المذكورة أعلاه مع مثال ملموس على تنفيذ جسم شفاف.
مثال الشفافيةالرئيسية #ifndef MAIN_H #define MAIN_H #include <osg/BlendFunc> #include <osg/Texture2D> #include <osg/Geometry> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array; vertices->push_back( osg::Vec3(-0.5f, 0.0f, -0.5f) ); vertices->push_back( osg::Vec3( 0.5f, 0.0f, -0.5f) ); vertices->push_back( osg::Vec3( 0.5f, 0.0f, 0.5f) ); vertices->push_back( osg::Vec3(-0.5f, 0.0f, 0.5f) ); osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array; normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) ); osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array; texcoords->push_back( osg::Vec2(0.0f, 0.0f) ); texcoords->push_back( osg::Vec2(0.0f, 1.0f) ); texcoords->push_back( osg::Vec2(1.0f, 1.0f) ); texcoords->push_back( osg::Vec2(1.0f, 0.0f) ); osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array; colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 0.5f) ); osg::ref_ptr<osg::Geometry> quad = new osg::Geometry; quad->setVertexArray(vertices.get()); quad->setNormalArray(normals.get()); quad->setNormalBinding(osg::Geometry::BIND_OVERALL); quad->setColorArray(colors.get()); quad->setColorBinding(osg::Geometry::BIND_OVERALL); quad->setTexCoordArray(0, texcoords.get()); quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4)); osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(quad.get()); osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D; osg::ref_ptr<osg::Image> image = osgDB::readImageFile("../data/Images/lz.rgb"); texture->setImage(image.get()); osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc; blendFunc->setFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); osg::StateSet *stateset = geode->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, texture.get()); stateset->setAttributeAndModes(blendFunc); osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(geode.get()); root->addChild(osgDB::readNodeFile("../data/glider.osg")); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
بالنسبة للجزء الأكبر ، لا يحتوي الكود الموضح هنا على أي شيء جديد: يتم إنشاء كائنين هندسيين - مربع محكم وطائرة شراعية معلقة ، يتم تحميل طرازها من ملف. ومع ذلك ، فإننا نطبق اللون الأبيض الشفاف على جميع القمم مربع
colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 0.5f) );
- قيمة قناة ألفا هي 0.5 ، والتي عند مزجها بألوان نسيج ، ينبغي أن تعطي تأثير كائن شفاف. بالإضافة إلى ذلك ، يجب تعيين وظيفة مزج اللون لمعالجة الشفافية.
osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc; blendFunc->setFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
تمريره إلى جهاز حالة OpenGL
stateset->setAttributeAndModes(blendFunc);
عند تجميع هذا البرنامج وتشغيله ، نحصل على النتيجة التالية

توقف عن ذلك! وأين هي الشفافية؟ الشيء هو أننا ننسى إخبار المحرك أنه يجب معالجة الحواف الشفافة ، والتي يمكن حلها بسهولة عن طريق الاتصال
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
بعد ذلك نحصل على النتيجة التي نحتاجها - يضيء الجناح المنزلق المعلقة في مربع شفاف

تعني معلمتا الدمج GL_SRC_ALPHA و GL_ONE_MINUS_SRC_ALPHA الدالتين أن بكسل الشاشة الناتج عند رسم وجه شفاف سيكون به مكونات لونية محسوبة بواسطة الصيغة
R = srcR * srcA + dstR * (1 - srcA) G = srcG * srcA + dstG * (1 - srcA) B = srcB * srcA + dstB * (1 - srcA)
حيث [srcR، srcG، srcB] هي مكونات اللون من الملمس المربع ؛ [dstR, dstG, dstB] — , , . srcA - .
seRenderingHint() , , . , .
8.
. . , .
.
. , - , , 1 — . , 0 1 .
fading-inالرئيسية #ifndef MAIN_H #define MAIN_H #include <osg/Geode> #include <osg/Geometry> #include <osg/BlendFunc> #include <osg/Material> #include <osgAnimation/EaseMotion> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
- -
class AlphaFadingCallback : public osg::StateAttributeCallback { public: AlphaFadingCallback() { _motion = new osgAnimation::InOutCubicMotion(0.0f, 1.0f); } virtual void operator() (osg::StateAttribute* , osg::NodeVisitor*); protected: osg::ref_ptr<osgAnimation::InOutCubicMotion> _motion; };
_motion , . , ,
AlphaFadingCallback() { _motion = new osgAnimation::InOutCubicMotion(0.0f, 1.0f); }

InOutCubicMotion 0 1. operator()
void AlphaFadingCallback::operator()(osg::StateAttribute *sa, osg::NodeVisitor *nv) { (void) nv; osg::Material *material = static_cast<osg::Material *>(sa); if (material) { _motion->update(0.0005f); float alpha = _motion->getValue(); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, alpha)); } }
osg::Material *material = static_cast<osg::Material *>(sa);
callback , , , . — ,
_motion->update(0.0005f);
float alpha = _motion->getValue();
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, alpha));
main(). , — OSG
osg::ref_ptr<osg::Drawable> quad = osg::createTexturedQuadGeometry( osg::Vec3(-0.5f, 0.0f, -0.5f), osg::Vec3(1.0f, 0.0f, 0.0f), osg::Vec3(0.0f, 0.0f, 1.0f));
, , . ,
osg::ref_ptr<osg::Material> material = new osg::Material; material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, 0.5f));
. Ambient color — , , . Diffuse color — , , , . FRONT_AND_BACK , , .
material->setUpdateCallback(new AlphaFadingCallback);
geode->getOrCreateStateSet()->setAttributeAndModes(material.get());
— ,
geode->getOrCreateStateSet()->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); geode->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(geode.get()); root->addChild(osgDB::readNodeFile("../data/glider.osg")); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run();

:
, . – main.h
#include <osgAnimation/EaseMotion>
OSG, , , , . osgAnimation/ , , ( )
LIBS += -losgAnimation
أن تستمر ...