OpenSceneGraph: Levels of detail (LOD) dan pemuatan latar belakang objek

gambar

Pendahuluan


Salah satu tugas paling menarik yang diselesaikan dengan grafis tiga dimensi adalah penciptaan "dunia besar" - adegan panjang yang berisi sejumlah besar objek dengan kemungkinan pergerakan tanpa batas di panggung. Solusi untuk masalah ini terletak pada keterbatasan yang dapat dipahami yang melekat pada perangkat keras komputer.

Contoh khas: "dunia besar" saat memvisualisasikan kereta api pada mesin OSG. Yang hilang hanyalah para penghuni dunia yang melahap dunia di belakang kereta ...


Dalam hal ini, perlu muncul untuk mengelola sumber daya aplikasi, yang bermuara pada solusi yang jelas: memuat hanya sumber daya tersebut (model, tekstur, dan sebagainya) yang diperlukan untuk membentuk sebuah adegan pada waktu saat ini dengan posisi pengamat saat ini; pengurangan level detail objek jarak jauh; membongkar objek tidak lagi diperlukan dari memori sistem. Untuk sebagian besar, mesin grafis dan permainan menyediakan seperangkat alat tertentu untuk memecahkan masalah seperti itu. Hari ini kita melihat yang mana yang tersedia di OpenSceneGraph.

1. Menggunakan Levels of Detail (LOD)


Teknik menggunakan level detail memungkinkan Anda untuk menampilkan objek yang sama secara lebih kurang detail, tergantung pada jarak dari objek tersebut ke pengamat. Penggunaan teknik ini didasarkan pada pertimbangan sederhana bahwa detail kecil dari model tiga dimensi tidak dapat dibedakan dari jarak yang jauh, yang berarti tidak perlu menggambarnya. Di satu sisi, teknik ini memungkinkan Anda untuk mengurangi jumlah total geometri primitif yang ditampilkan dalam buffer bingkai, dan di sisi lain, tidak kehilangan rentang tampilan objek pemandangan, yang sangat berguna saat membuat dunia terbuka besar.

OSG menyediakan alat untuk menerapkan teknik ini melalui kelas osg :: LOD, yang diwarisi dari osg :: Group yang sama. Kelas ini memungkinkan Anda untuk mewakili objek yang sama dalam beberapa level detail. Setiap tingkat detail dicirikan oleh jarak minimum dan maksimum ke pengamat, di mana pengamatan objek dihidupkan dalam tingkat detail ini.

osg :: LOD memungkinkan Anda menentukan kisaran ini segera ketika mendefinisikan simpul anak, atau lebih baru, menggunakan metode setRange ()

osg::ref_ptr<osg::LOD> lodNode = new osg::LOD; lodNode->addChild(node2, 500.0f, FLT_MAX); lodNode->addChild(node1); . . . lodNode->setRange(1, 0.0f, 500.0f); 

Kami terus menyiksa Cessna dan mengilustrasikan teknik yang dijelaskan dengan sebuah contoh

Sebagai contoh
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/LOD> #include <osgDB/ReadFile> #include <osgUtil/Simplifier> #include <osgViewer/Viewer> #endif 

main.h

 #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer); osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Pertama, muat model

 osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg"); 

Sekarang Anda perlu membuat beberapa model (kami akan membatasi diri untuk dua contoh), dengan tingkat detail yang lebih rendah. Untuk melakukan ini, salin simpul yang dimuat dua kali, menggunakan teknik yang disebut salinan kelas "dalam", untuk simpul yang diimplementasikan oleh metode klon ()

 osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); 

Sekarang kita mengurangi geometri model ini menggunakan kelas osgUtil :: Simplifer. Tingkat penyederhanaan model ditentukan oleh metode setSampleRatio () dari kelas ini - semakin kecil parameter yang dilewati, semakin sedikit detail model setelah menerapkan prosedur reduksi.

 osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer); 

Ketika kami memiliki model tingkat detail yang berbeda, kami dapat mengisi daya ke simpul akar, dibuat sebagai penunjuk pintar ke osg :: LOD. Untuk setiap level detail, atur jarak tampilan level ini

 osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f); 

Dengan FLT_MAX berarti dalam beberapa cara jarak "tak terbatas" ke pengamat. Setelah memulai pemirsa, kami mendapatkan gambar berikut

Tingkat Detail 3



Tingkat Detail 2



Tingkat Detail 1



Dapat dilihat bahwa ketika kamera dipindahkan dari objek, detail geometri yang ditampilkan berkurang. Dengan menggunakan teknik ini, Anda dapat mencapai realisme adegan yang tinggi dengan konsumsi sumber daya yang rendah.

2. Teknik pemuatan latar belakang untuk node adegan


Mesin OSG menyediakan kelas osg :: ProxyNode dan osg :: PagedLOD, yang dirancang untuk menyeimbangkan beban saat merender adegan. Kedua kelas mewarisi dari osg :: Grup.

Node osg :: Jenis ProxyNode mengurangi waktu peluncuran aplikasi sebelum rendering, jika adegan memiliki sejumlah besar model yang dimuat dan ditampilkan dari disk. Ini berfungsi sebagai antarmuka ke file eksternal, memungkinkan pemuatan model yang ditangguhkan. Untuk menambahkan node anak, gunakan metode setFileName () (bukan addChild) untuk mengatur nama file model pada disk dan memuatnya secara dinamis.

Node osg :: PagedNode mewarisi metode osg :: LOD dan memuat dan membongkar level detail sedemikian rupa untuk menghindari kelebihan pipa OpenGL dan memastikan rendering adegan yang lancar.

3. Pemuatan dinamis (runtime) dari model


Mari kita lihat bagaimana proses memuat model menggunakan osg :: ProxyNode terjadi.

Contoh Proxynode
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/ProxyNode> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg"); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Proses pengunduhan di sini sedikit berbeda

 osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg"); 

Alih-alih secara eksplisit memuat model sapi, kami menunjukkan simpul akar nama file tempat model dan indeks simpul anak berada, di mana model ini harus ditempatkan setelah dimuat. Saat menjalankan program, kami mendapatkan hasil ini



Dapat dilihat bahwa sudut pandang tidak dipilih dengan cara terbaik - kamera bersandar langsung di sisi cermin sapi. Ini terjadi karena model dimuat setelah memulai render dan menginisialisasi kamera, ketika node 0 belum terlihat. Penampil tidak bisa menghitung parameter kamera yang diperlukan. Namun, model telah dimuat dan kita dapat mengonfigurasi mode tampilan dengan memanipulasi mouse



Apa yang terjadi pada contoh di atas? osg :: ProxyNode dan osg :: PagedLOD berfungsi dalam hal ini sebagai wadah. Manajer data internal OSG akan mengirim permintaan dan memuat data ke dalam grafik adegan saat diperlukan untuk file model dan tingkat detail.

Mekanisme ini bekerja di beberapa aliran latar belakang dan mengontrol pemuatan data statis yang terletak di file pada disk dan data dinamis yang dihasilkan dan ditambahkan selama eksekusi program.

Mesin secara otomatis memproses node yang tidak ditampilkan di viewport saat ini dan menghapusnya dari grafik adegan ketika render kelebihan beban. Namun, perilaku ini tidak mempengaruhi osg :: node ProxyNode.

Seperti simpul proksi, kelas osg :: PagedLOD juga memiliki metode setFileName () untuk menentukan lintasan ke model yang dimuat, namun, Anda harus menetapkan rentang visibilitas untuk itu, seperti untuk simpul osg :: LOD. Asalkan kita memiliki file cessna.osg dan model level-L1 yang rendah-poli, kita dapat mengatur paged node sebagai berikut

 osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; pagedLOD->addChild(modelL1, 200.0f, FLT_MAX ); pagedLOD->setFileName( 1, "cessna.osg" ); pagedLOD->setRange( 1, 0.0f, 200.0f ); 

Anda perlu memahami bahwa simpul modelL1 tidak dapat diturunkan dari memori, karena ini adalah simpul non-proxy anak normal.

Saat merender, perbedaan antara osg :: LOD dan osg :: PagedLOD tidak terlihat saat menggunakan hanya satu tingkat detail model. Ide yang menarik adalah mengatur sekelompok besar model Cessna menggunakan kelas osg :: MatrixTransform. Untuk ini, Anda dapat menggunakan misalnya fungsi seperti itu

 osg::Node* createLODNode( const osg::Vec3& pos ) { osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; … osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform; mt->setMatrix( osg::Matrix::translate(pos) ); mt->addChild( pagedLOD.get() ); return mt.release(); } 

Contoh program yang mengimplementasikan pemuatan latar belakang 10.000 pesawat

main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/PagedLOD> #include <osg/MatrixTransform> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osg::Node *createLODNode(const std::string &filepath, const osg::Vec3 &pos) { osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; pagedLOD->setFileName(0, filepath); pagedLOD->setRange(0, 0.0f, FLT_MAX); osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform; mt->setMatrix(osg::Matrix::translate(pos)); mt->addChild(pagedLOD.get()); return mt.release(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Group> root = new osg::Group; float dist = 50.0f; int N = 100; for (int i = 0; i < N; ++i) { float x = i * dist; for (int j = 0; j < N; ++j) { float y = j * dist; osg::Vec3 pos(x, y, 0.0f); osg::ref_ptr<osg::Node> node = createLODNode("../data/cessna.osg", pos); root->addChild(node.get()); } } osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 

Diasumsikan bahwa pesawat akan ditempatkan di pesawat dengan interval 50 unit koordinat. Saat memuat, kita akan melihat bahwa hanya cessna yang masuk ke dalam bingkai yang dimuat. Pesawat-pesawat yang menghilang dari bingkai menghilang dari pohon adegan.



Kesimpulan


Pelajaran ini dalam seri OpenSceneGraph akan menjadi yang terakhir dalam format How To. Dalam dua belas artikel, adalah mungkin untuk memenuhi prinsip-prinsip dasar bekerja dan menggunakan OpenSceneGraph dalam praktiknya. Saya sangat berharap bahwa mesin ini menjadi lebih jelas bagi pengembang yang berbahasa Rusia.

Ini tidak berarti bahwa saya menutup topik OpenSceneGraph pada sumber daya, sebaliknya, direncanakan untuk mencurahkan artikel masa depan untuk teknik yang lebih maju dan metode menggunakan OSG dalam pengembangan aplikasi grafis. Tetapi untuk ini, Anda perlu mengumpulkan materi yang bagus dan memproses banyak sumber berbahasa Inggris, dan ini membutuhkan waktu.

Tapi saya tidak mengucapkan selamat tinggal, terima kasih atas perhatian Anda dan sampai jumpa lagi !

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


All Articles