OpenSceneGraph: Animasi prosedural dari atribut geometri dan status

gambar

Pendahuluan


Berbicara tentang teknik pemrograman khusus untuk OSG , terakhir kali kami berbicara tentang mekanisme Callback dan implementasinya di mesin. Sudah saatnya untuk melihat kemungkinan yang disediakan mekanisme ini untuk mengelola konten adegan tiga dimensi.

Jika kita berbicara tentang animasi objek, OSG menyediakan pengembang dengan dua opsi untuk implementasinya:

  1. Animasi prosedural diimplementasikan secara terprogram melalui transformasi objek dan atributnya
  2. Mengekspor animasi dari editor 3D dan mengelolanya dari kode aplikasi

Untuk memulai, pertimbangkan kemungkinan pertama, sebagai yang paling jelas. Kami pasti akan membicarakan yang kedua nanti.

1. Animasi morphing prosedural


Saat melintasi grafik adegan, OSG mentransfer data ke pipa OpenGL, yang berjalan di utas terpisah. Utas ini harus disinkronkan dengan utas pemrosesan lainnya di setiap bingkai. Kegagalan untuk melakukannya dapat menyebabkan metode frame () selesai sebelum memproses data geometri. Ini akan menyebabkan perilaku dan crash program tidak dapat diprediksi. OSG menawarkan solusi untuk masalah ini dalam bentuk metode setDataVariance () dari kelas osg :: Object, yang merupakan basis untuk semua objek pemandangan. Anda dapat mengatur tiga mode pemrosesan untuk objek

  1. UNSPECIFIED (secara default) - OSG secara independen menentukan urutan pemrosesan objek.
  2. STATIC - objek tidak dapat diubah dan urutan pemrosesan tidak penting. Mempercepat rendering secara signifikan.
  3. DINAMIK - objek harus diproses sebelum mulai rendering.

Pengaturan ini dapat diatur kapan saja dengan menelepon

node->setDataVariance( osg::Object::DYNAMIC ); 

Praktik yang diterima secara umum adalah untuk memodifikasi geometri "on the fly", yaitu, mengubah koordinat simpul, normals warna, dan tekstur secara dinamis di setiap frame, memperoleh geometri yang bisa berubah. Teknik ini disebut animasi morphing. Dalam hal ini, urutan pemrosesan geometri sangat menentukan - semua perubahannya harus dihitung ulang sebelum menggambar dimulai. Untuk menggambarkan trik ini, kita sedikit memodifikasi contoh kotak berwarna, memaksa salah satu simpulnya berputar di sekitar sumbu X.

Contoh animquad
main.h

 #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(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ 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(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::Geometry *quad = createQuad(); quad->setDataVariance(osg::Object::DYNAMIC); quad->setUpdateCallback(new DynamicQuadCallback); osg::ref_ptr<osg::Geode> root = new osg::Geode; root->addDrawable(quad); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Kami akan membuat kotak dalam fungsi terpisah

 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(); } 

deskripsi yang, pada prinsipnya, tidak diperlukan, karena kami telah melakukan tindakan seperti itu berulang kali. Untuk memodifikasi simpul dari persegi ini, kita menulis kelas DynamicQuadCallback, mewarisinya dari osg :: Drawable :: UpdateCallback

 class DynamicQuadCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor *, osg::Drawable *drawable); }; 

mengganti metode pembaruan () di dalamnya

 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(); } 

Di sini kita mendapatkan pointer ke objek geometri

 osg::Geometry *quad = static_cast<osg::Geometry *>(drawable); 

kita membaca dari geometri daftar simpul (atau lebih tepatnya pointer ke sana)

 osg::Vec3Array *vertices = static_cast<osg::Vec3Array *>(quad->getVertexArray()); 

Untuk mendapatkan elemen terakhir (titik terakhir) dalam array, kelas osg :: Array menyediakan metode back (). Untuk memutar simpul relatif terhadap sumbu X, kami memperkenalkan angka empat

 osg::Quat quat(osg::PI * 0.01, osg::X_AXIS); 

yaitu, kami menetapkan angka empat yang mengimplementasikan rotasi di sekitar sumbu X dengan sudut 0,01 * Pi. Putar titik dengan mengalikan angka empat dengan vektor yang menentukan koordinat titik tersebut

 vertices->back() = quat * vertices->back(); 

Dua panggilan terakhir menceritakan daftar tampilan dan pararel dimensionalepiped untuk geometri yang dimodifikasi

 quad->dirtyDisplayList(); quad->dirtyBound(); 

Dalam tubuh fungsi utama (), kita membuat kotak, mengatur mode gambar dinamis untuknya, dan menambahkan panggilan balik memodifikasi geometri

 osg::Geometry *quad = createQuad(); quad->setDataVariance(osg::Object::DYNAMIC); quad->setUpdateCallback(new DynamicQuadCallback); 

Saya akan meninggalkan sembarang membuat simpul root dan meluncurkan viewer, karena kami telah melakukan ini setidaknya dua puluh kali dengan cara yang berbeda. Hasilnya, kami memiliki animasi morphing yang paling sederhana



Sekarang cobalah untuk menghapus (mengomentari) panggilan setDataVariance (). Mungkin kita tidak akan melihat kejahatan apa pun dalam kasus ini - secara default, OSG mencoba menentukan secara otomatis kapan akan memperbarui data geometri, mencoba menyinkronkan dengan rendering. Kemudian cobalah mengubah mode dari DINAMIK ke STATIC dan akan terlihat bahwa gambar tidak membuat lancar, dengan tersentak terlihat, kesalahan dan peringatan seperti ini mengalir ke konsol

 Warning: detected OpenGL error 'invalid value' at after RenderBin::draw(..) 

Jika Anda tidak menjalankan metode dirtyDisplayList (), maka OpenGL akan mengabaikan semua perubahan pada geometri dan akan menggunakan daftar tampilan yang dibuat di awal untuk membuat kotak untuk rendering. Hapus panggilan ini dan Anda akan melihat bahwa tidak ada animasi.

Tanpa memanggil metode dirtyBound (), kotak pembatas tidak akan dihitung ulang dan OSG akan memangkas wajah yang tidak terlihat dengan salah.

2. Konsep interpolasi gerak


Misalkan kereta yang berangkat dari stasiun A ke stasiun B membutuhkan waktu 15 menit untuk bepergian. Bagaimana kita bisa mensimulasikan situasi ini dengan mengubah posisi kereta di callback? Cara termudah adalah dengan mengaitkan posisi stasiun A dengan waktu 0, dan stasiun B dengan 15 menit dan memindahkan kereta secara merata di antara waktu-waktu ini. Pendekatan paling sederhana ini disebut interpolasi linier. Dalam interpolasi linier, vektor yang menentukan posisi titik tengah dijelaskan oleh rumus

 p = (1 - t) * p0 + t * p1 

di mana p0 adalah titik awal; p1 adalah titik akhir; t adalah parameter yang bervariasi seragam dari 0 hingga 1. Namun, pergerakan kereta jauh lebih rumit: ia meninggalkan stasiun A, berakselerasi, kemudian bergerak dengan kecepatan konstan, dan kemudian melambat, berhenti di stasiun B. Proses semacam itu tidak lagi mampu menggambarkan interpolasi linier dan Itu terlihat tidak alami.

OSG menyediakan pengembang dengan perpustakaan osgAnimation, yang berisi sejumlah algoritma interpolasi standar yang digunakan untuk dengan lancar menggerakkan pergerakan objek adegan. Masing-masing fungsi ini biasanya memiliki dua argumen: nilai awal parameter (biasanya 0) dan nilai akhir parameter (biasanya 1). Fungsi-fungsi ini dapat diterapkan pada awal gerakan (InMotion), ke akhir gerakan (OutMotion) atau ke awal dan akhir gerakan (InOutMotion)

Jenis gerakandi kelaskeluar kelasdi / keluar kelas
Interpolasi linierLinearMotion--
Interpolasi kuadratikInQuadMotionOutQuadMotionInOutQuadMotion
Interpolasi kubikInCubicMotionPromosi luarInOutCubicMotion
Interpolasi 4-urutanInQuartMotionOutQuartMotionInOutQuartMotion
Interpolasi Efek BouncingInBounceMotionOutBounceMotionInOutBounceMotion
Interpolasi rebound elastisInElasticMotionOutElasticMotionInOutElasticMotion
Interpolasi sinusoidalInSineMotionEmosi luar biasaInOutSineMotion
Interpolasi terbalikInbackmotionOutbackmotionInOutBackMotion
Interpolasi melingkarInCircMotionPeredaranInOutCircMotion
Interpolasi eksponensialInExpoMotionEksploitasi berlebihanInOutExpoMotion

Untuk membuat interpolasi linier dari pergerakan suatu objek, kita menulis kode seperti itu

 osg::ref_ptr<osgAnimation::LinearMotion> motion = new osgAnimation::LinearMotion(0.0f, 1.0f); 

3. Animasi node transformasi


Animasi lintasan adalah jenis animasi yang paling umum dalam aplikasi grafis. Teknik ini dapat digunakan untuk menghidupkan gerakan mobil, penerbangan pesawat, atau gerakan kamera. Lintasan sudah ditentukan sebelumnya, dengan semua posisi, rotasi, dan skala berubah pada titik-titik utama dalam waktu. Ketika siklus simulasi dimulai, keadaan objek dihitung ulang di setiap frame, menggunakan interpolasi linier untuk posisi dan penskalaan dan interpolasi linear bola untuk angka empat rotasi. Untuk melakukan ini, gunakan metode internal slerp () dari kelas osg :: Quat.

OSG menyediakan kelas osg :: AnimationPath untuk menjelaskan jalur yang bervariasi waktu. Metode sisipan kelas ini () digunakan untuk menambahkan titik kontrol yang sesuai dengan titik-titik tertentu dalam waktu ke lintasan. Titik kontrol dijelaskan oleh kelas osg :: AnimationPath :: ControlPoint, konstruktor yang mengambil posisi sebagai parameter, dan, secara opsional, rotasi objek dan parameter penskalaan. Sebagai contoh

 osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->insert(t1, osg::AnimationPath::ControlPoint(pos1, rot1, scale1)); path->insert(t2, ...); 

Di sini t1, t2 adalah contoh waktu dalam detik; rot1 adalah parameter rotasi pada waktu t1, dijelaskan oleh angka empat :: jumlah Quat.

Dimungkinkan untuk mengontrol loop animasi melalui metode setLoopMode (). Secara default, mode LOOP dihidupkan - animasi akan terus diulang. Nilai lain yang mungkin: NO_LOOPING - mainkan animasi sekali dan SWING - putar gerakan ke arah maju dan mundur.

Setelah semua inisialisasi selesai, kami lampirkan objek osg :: AnimationPath ke objek built-in osg :: AnimationPathCallback, yang berasal dari kelas osg :: NodeCallback.

4. Contoh animasi gerakan di sepanjang jalan


Sekarang kita akan membuat cessna kita bergerak dalam lingkaran dengan pusat di titik (0,0,0). Posisi pesawat pada lintasan akan dihitung dengan menginterpolasi posisi dan orientasi antara bingkai kunci secara linear.

Contoh animcessna
main.h

 #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(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg.0,0,90.rot"); osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform; root->addChild(model.get()); 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(); } 


Kami mulai dengan membuat lintasan pesawat, mengambil kode ini menjadi fungsi terpisah

 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(); } 

Sebagai parameter, fungsi tersebut mengambil jari-jari lingkaran di mana pesawat bergerak dan waktu selama itu akan membuat satu revolusi. Di dalam fungsinya, buat objek lintasan dan nyalakan mode perulangan animasi

 osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->setLoopMode(osg::AnimationPath::LOOP); 

Kode berikut

 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); 

menghitung parameter perkiraan lintasan. Kami membagi seluruh lintasan menjadi numContoh bagian lurus, dan menghitung perubahan sudut rotasi pesawat di sekitar sumbu vertikal (yaw) delta_yaw dan perubahan waktu delta_time ketika bergerak dari bagian ke bagian. Sekarang buat titik kontrol yang diperlukan

 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)); } 

Dalam siklus, semua bagian lintasan dari yang pertama ke yang terakhir diurutkan. Setiap titik kontrol ditandai dengan sudut yaw

 double yaw = delta_yaw * i; 

posisi pusat massa pesawat di ruang angkasa

 osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0); 

Rotasi pesawat ke sudut yaw yang diinginkan (relatif terhadap sumbu vertikal) diatur oleh angka empat

 osg::Quat rot(-yaw, osg::Z_AXIS); 

dan kemudian menambahkan parameter yang dihitung ke daftar titik kontrol jalan

 path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot)); 

Dalam program utama, kami memperhatikan nuansa dalam menunjukkan nama file model pesawat saat boot

 osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg.0,0,90.rot"); 

- akhiran ".0,0,90.rot" telah ditambahkan ke nama file. Mekanisme untuk memuat geometri dari file yang digunakan dalam OSG memungkinkan Anda menentukan posisi awal dan orientasi model setelah memuat. Dalam hal ini, kami ingin model diputar 90 derajat di sekitar sumbu Z setelah pemuatan.

Selanjutnya, simpul akar dibuat, yang merupakan simpul transformasi, dan objek model ditambahkan padanya sebagai simpul anak

 osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform; root->addChild(model.get()); 

Sekarang buat callback animasi lintasan, tambahkan jalur yang dibuat oleh fungsi createAnimationPath () ke dalamnya

 osg::ref_ptr<osg::AnimationPathCallback> apcb = new osg::AnimationPathCallback; apcb->setAnimationPath(createAnimationPath(50.0, 6.0)); 

Lampirkan panggilan balik ini ke node transformasi

 root->setUpdateCallback(apcb.get()); 

Penampil diinisialisasi dan diluncurkan seperti biasa.

 osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); 

Dapatkan animasi gerakan pesawat



Pikirkan Anda menemukan sesuatu yang aneh dalam contoh ini? Sebelumnya, misalnya, dalam sebuah program saat merender ke suatu tekstur, Anda secara eksplisit mengubah matriks transformasi untuk mencapai perubahan posisi model dalam ruang. Di sini kita hanya membuat simpul transformasi dan dalam kode tidak ada tugas matriks eksplisit di mana pun.

Rahasianya adalah kelas khusus osg :: AnimationPathCallback melakukan pekerjaan ini. Sesuai dengan posisi objek saat ini di jalan, ia menghitung matriks transformasi dan secara otomatis menerapkannya pada node transformasi yang dilampirkan, menyelamatkan pengembang dari banyak operasi rutin.

Perlu dicatat bahwa melampirkan osg :: AnimationPathCallback ke jenis node lainnya tidak hanya tidak akan berpengaruh, tetapi juga dapat menyebabkan perilaku program yang tidak ditentukan. Penting untuk diingat bahwa panggilan balik ini hanya memengaruhi node transformasi.

5. Animasi kontrol perangkat lunak


Kelas osg :: AnimationPathCallback menyediakan metode untuk mengontrol animasi selama eksekusi program.

  1. reset () - reset animasi dan mainkan terlebih dahulu.
  2. setPause () - menjeda animasi. Mengambil nilai boolean sebagai parameter
  3. setTimeOffset () - mengatur offset waktu sebelum dimulainya animasi.
  4. setTimeMultiplier () - mengatur faktor waktu untuk akselerasi / perlambatan animasi.

Misalnya, untuk menghapus animasi dari jeda dan mengatur ulang, kami mengeksekusi kode tersebut

 apcb->setPause(false); apcb->reset(); 

dan untuk memulai animasi dari detik keempat setelah memulai program dengan akselerasi ganda, kode seperti itu

 apcb->setTimeOffset(4.0f); apcb->setTimeMultiplier(2.0f); 

6. Urutan rendering primitif di OpenGL


OpenGL menyimpan data titik dan primitif dalam berbagai buffer, seperti buffer warna, buffer kedalaman, buffer stensil, dan sebagainya. Selain itu, ia tidak menimpa simpul dan wajah segitiga yang sudah dikirim ke pipanya. Ini berarti bahwa OpenGL membuat geometri baru, terlepas dari bagaimana geometri yang ada dibuat. Ini berarti bahwa urutan primitif dikirim ke jalur render secara signifikan mempengaruhi hasil akhir yang kita lihat di layar.

Berdasarkan data buffer kedalaman, OpenGL akan menggambar objek buram dengan benar, menyortir piksel berdasarkan jaraknya dari pengamat. Namun, ketika menggunakan teknik pencampuran warna, misalnya, ketika mengimplementasikan objek transparan dan tembus cahaya, operasi khusus akan dilakukan untuk memperbarui buffer warna. Pixel baru dan lama dari gambar dicampur, dengan mempertimbangkan nilai saluran alpha (komponen warna keempat). Ini mengarah pada fakta bahwa urutan rendering dari tepi transparan dan buram mempengaruhi hasil akhir



Dalam gambar, dalam situasi di sebelah kiri, pada objek buram pertama dan kemudian transparan dikirim ke pipa, yang menyebabkan pergeseran yang benar dalam buffer warna dan tampilan wajah yang benar. Dalam situasi yang tepat, objek transparan pertama digambar, dan kemudian buram, yang menyebabkan tampilan yang salah.

Metode setRenderingHint () dari kelas osg :: StateSet menunjukkan kepada OSG urutan render node dan objek geometri yang diperlukan, jika ini perlu dilakukan secara eksplisit. Metode ini hanya menunjukkan apakah wajah transparan harus diperhitungkan atau tidak harus diperhitungkan saat rendering, dengan demikian memastikan bahwa jika ada wajah transparan dalam adegan, buram dan kemudian wajah transparan akan ditarik terlebih dahulu, dengan mempertimbangkan jarak dari pengamat. Untuk memberi tahu mesin bahwa simpul ini buram, kami menggunakan kode ini

 node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); 

atau mengandung tepi transparan

 node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); 

7. Contoh implementasi objek yang tembus cahaya


Mari kita coba mengilustrasikan semua pengantar teoretis di atas dengan contoh nyata implementasi objek yang tembus cahaya.

Contoh transparansi
main.h

 #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(); } 


Sebagian besar, kode yang ditampilkan di sini tidak mengandung sesuatu yang baru: dua objek geometrik dibuat - kotak bertekstur dan hang glider, model yang diambil dari file. Namun, kami menerapkan warna transparan putih ke semua simpul persegi

 colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 0.5f) ); 

- nilai kanal alfa adalah 0,5, yang bila dicampur dengan warna tekstur, akan memberikan efek objek yang tembus cahaya. Selain itu, fungsi pencampuran warna harus diatur untuk pemrosesan transparansi.

 osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc; blendFunc->setFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 

meneruskannya ke mesin status OpenGL

 stateset->setAttributeAndModes(blendFunc); 

Ketika menyusun dan menjalankan program ini, kami mendapatkan hasil berikut



Hentikan itu! Dan di mana transparansi? Masalahnya adalah kita lupa memberi tahu mesin bahwa tepian transparan harus diproses, yang mudah diselesaikan dengan menelepon

 stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); 

setelah itu kita mendapatkan hasil yang kita butuhkan - sayap peluncur layang bersinar melalui kotak bertekstur transparan



Parameter dari fungsi pencampuran GL_SRC_ALPHA dan GL_ONE_MINUS_SRC_ALPHA berarti bahwa piksel layar yang dihasilkan saat menggambar wajah transparan akan memiliki komponen warna yang dihitung oleh rumus.

 R = srcR * srcA + dstR * (1 - srcA) G = srcG * srcA + dstG * (1 - srcA) B = srcB * srcA + dstB * (1 - srcA) 

di mana [srcR, srcG, srcB] adalah komponen warna dari tekstur kuadrat;[dstR, dstG, dstB] - komponen warna dari setiap piksel area di mana wajah yang transparan ditumpangkan, mengingat latar belakang dan tepi buram sayap glider sudah digambar di tempat ini. Maksud saya srcA adalah komponen alfa dari warna kotak.

Metode seRenderingHint () dengan sempurna mengatur rendering primitif, tetapi menggunakannya tidak sangat efisien, karena menyortir objek transparan dengan kedalaman ketika rendering frame adalah operasi yang sangat intensif sumber daya. Oleh karena itu, pengembang harus mengurus urutan menggambar wajah mereka sendiri, jika mungkin pada tahap awal persiapan adegan.

8. Animasi atribut negara


Menggunakan animasi, Anda juga dapat mengontrol atribut negara. Seluruh efek visual dapat dihasilkan dengan mengubah properti dari satu atau lebih atribut render. Animasi semacam ini yang mengubah keadaan atribut render mudah diterapkan melalui mekanisme panggilan balik saat memperbarui adegan.

Kelas interpolasi standar juga dapat digunakan untuk menentukan fungsi mengubah parameter atribut.

Kami sudah memiliki pengalaman dalam membuat objek transparan. Kita tahu bahwa jika komponen alfa warna adalah nol, kita mendapatkan objek yang sepenuhnya transparan, dengan nilai 1 - benar-benar buram. Jelas bahwa dengan memvariasikan parameter ini dari 0 hingga 1 dalam waktu, efek dari penampilan bertahap atau hilangnya suatu objek dapat diperoleh. Kami menggambarkan ini dengan contoh nyata.

Misalnya memudar
main.h

 #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; }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ 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)); } } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; 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::Geode> geode = new osg::Geode; geode->addDrawable(quad.get()); 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)); 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(); } 


Kami mulai dengan membuat penangan panggilan balik untuk mengubah nilai saluran alpha dari waktu ke waktu

 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; }; 

Parameter _motion yang dilindungi akan menentukan fungsi dengan mana nilai alpha akan berubah seiring waktu. Untuk contoh ini, kita memilih pendekatan spline kubik, mengaturnya segera, di konstruktor kelas

 AlphaFadingCallback() { _motion = new osgAnimation::InOutCubicMotion(0.0f, 1.0f); } 

Ketergantungan ini dapat diilustrasikan oleh kurva seperti itu.



Dalam konstruktor objek InOutCubicMotion, kita menentukan batas nilai yang diperkirakan dari 0 hingga 1. Selanjutnya, kita mendefinisikan kembali operator () untuk kelas ini dengan cara ini

 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)); } } 

Dapatkan pointer ke materi

 osg::Material *material = static_cast<osg::Material *>(sa); 

Nilai abstrak atribut datang ke callback, namun kami akan melampirkan handler ini ke materi, oleh karena itu adalah pointer ke material yang akan datang, oleh karena itu kami dapat dengan aman mengkonversi atribut state ke pointer ke material. Selanjutnya, kita mengatur interval pembaruan dari fungsi yang diperkirakan - semakin besar, semakin cepat parameter akan berubah dalam rentang yang ditentukan

 _motion->update(0.0005f); 

Kami membaca nilai fungsi aproksimasi

 float alpha = _motion->getValue(); 

dan memberikan materi nilai warna difus baru

 material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, alpha)); 

Sekarang mari kita membentuk adegan dalam fungsi utama (). Saya pikir Anda lelah setiap kali membangun persegi pada simpul, jadi kami menyederhanakan tugas - kami menghasilkan poligon persegi dengan fungsi OSG standar

 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)); 

Parameter pertama adalah titik dari mana sudut kiri bawah kotak akan dibangun, dua parameter lainnya menentukan koordinat diagonal. Setelah menemukan kotak, kami membuat bahan untuk itu

 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)); 

Kami menunjukkan opsi warna material. Warna ambient adalah parameter yang mencirikan warna material di area yang diarsir, tidak dapat diakses oleh sumber warna. Warna difus adalah warna bahan itu sendiri, yang mencirikan kemampuan permukaan untuk meredakan warna yang jatuh di atasnya, yaitu apa yang biasa kita sebut warna dalam kehidupan sehari-hari. Parameter FRONT_AND_BACK menunjukkan bahwa atribut warna ini ditetapkan untuk sisi depan dan belakang wajah geometri.

Tetapkan materi untuk penangan yang dibuat sebelumnya.

 material->setUpdateCallback(new AlphaFadingCallback); 

Tetapkan materi yang dibuat ke kotak

 geode->getOrCreateStateSet()->setAttributeAndModes(material.get()); 

dan mengatur atribut lainnya - fungsi pencampuran warna dan menunjukkan bahwa objek ini memiliki tepi transparan

 geode->getOrCreateStateSet()->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); geode->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); 

Kami menyelesaikan pembentukan adegan dan menjalankan pemirsa

 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(); 

Kami mendapatkan hasilnya dalam bentuk kotak yang muncul dengan mulus di tempat kejadian



Alih-alih kesimpulan: komentar kecil tentang dependensi


Tentunya contoh Anda tidak dikompilasi, memberikan kesalahan pada tahap pembuatan. Ini bukan kebetulan - perhatikan baris di file header main.h

 #include <osgAnimation/EaseMotion> 

Direktori header OSG dari mana file header diambil biasanya menunjuk ke perpustakaan yang berisi implementasi fungsi dan kelas yang dijelaskan dalam header. Oleh karena itu, tampilan osgAnimation / direktori harus menyarankan bahwa perpustakaan dengan nama yang sama harus ditambahkan ke daftar tautan skrip build proyek, kira-kira seperti ini (dengan mempertimbangkan jalur ke perpustakaan dan versi build)

 LIBS += -losgAnimation 

Dilanjutkan ...

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


All Articles