Saya sedang menulis game menggunakan C ++ dan OpenGL 2.1. Saya berpikir bagaimana saya bisa memisahkan data / logika dari rendering. Saat ini saya menggunakan kelas dasar 'Renderable' yang memberikan metode virtual murni untuk mengimplementasikan gambar. Tetapi setiap objek memiliki kode khusus, hanya objek yang tahu cara mengatur seragam shader dan mengatur data buffer vertex array dengan benar. Saya berakhir dengan banyak panggilan fungsi gl * di seluruh kode saya. Apakah ada cara umum untuk menggambar objek?
21
m_renderable
anggota tersebut. Dengan begitu, Anda dapat memisahkan logika Anda dengan lebih baik. Jangan memaksakan "antarmuka" yang dapat di-render pada objek umum yang juga memiliki fisika, ai dan yang lainnya. Setelah itu, Anda dapat mengelola renderable secara terpisah. Anda memerlukan lapisan abstrakisasi melalui pemanggilan fungsi OpenGL untuk memisahkan hal-hal lebih banyak lagi. Jadi, jangan berharap mesin yang bagus untuk memiliki panggilan API GL di dalam berbagai implementasi renderable nya. Singkatnya, singkatnya.Jawaban:
Gagasannya adalah menggunakan pola desain Pengunjung. Anda memerlukan implementasi Renderer yang tahu cara membuat alat peraga. Setiap objek dapat memanggil instance renderer untuk menangani pekerjaan render.
Dalam beberapa baris kodesemu:
Gl * stuff diimplementasikan dengan metode renderer, dan objek hanya menyimpan data yang diperlukan untuk dirender, posisi, jenis tekstur, ukuran ... dll.
Anda juga dapat mengatur renderer yang berbeda (debugRenderer, hqRenderer, ... dll) dan menggunakannya secara dinamis, tanpa mengubah objek.
Ini juga dapat dengan mudah digabungkan dengan sistem Entity / Component.
sumber
Entity/Component
alternatif karena dapat membantu penyedia geometri yang terpisah dari bagian-bagian mesin lainnya (AI, Fisika, Jaringan atau gameplay umum). +1!ObjectA
danObjectB
perDrawableComponentA
danDrawableComponentB
, dan di dalam membuat metode, menggunakan komponen lain jika Anda membutuhkannya, seperti:position = component->getComponent("Position");
Dan di lingkaran utama, Anda memiliki daftar komponen ditarik untuk memanggil menggambar dengan.Renderable
) yang memilikidraw(Renderer&)
fungsi dan semua objek yang dapat diterjemahkan mengimplementasikannya? Dalam hal manaRenderer
hanya perlu satu fungsi yang menerima objek yang mengimplementasikan antarmuka dan panggilan umumrenderable.draw(*this);
?gl_*
fungsi ke renderer (memisahkan logika dari rendering), tetapi solusi Anda memindahkangl_*
panggilan ke objek.Saya tahu Anda sudah menerima jawaban Zhen, tetapi saya ingin menempatkan yang lain di luar sana kalau-kalau itu membantu orang lain.
Untuk mengulangi masalah, OP ingin kemampuan untuk menjaga kode rendering terpisah dari logika dan data.
Solusi saya adalah menggunakan kelas yang berbeda secara bersamaan untuk membuat komponen, yang terpisah dari
Renderer
kelas logika dan. Pertama-tama perlu adaRenderable
antarmuka yang memiliki fungsibool render(Renderer& renderer);
danRenderer
kelas menggunakan pola pengunjung untuk mengambil semuaRenderable
instance, mengingat daftarGameObject
s dan merender objek yang memilikiRenderable
instance. Dengan cara ini, Renderer tidak perlu mengetahui setiap tipe objek di luar sana dan masih menjadi tanggung jawab masing-masing tipe objek untuk menginformasikannyaRenderable
melaluigetRenderable()
fungsi. Atau sebagai alternatif, Anda dapat membuatRenderableVisitor
kelas yang mengunjungi semua GameObjects dan berdasarkan padaGameObject
kondisi individu yang mereka dapat pilih untuk menambahkan / tidak-menambahkan mereka dapat diuraikan untuk pengunjung. Either way, intinya adalah bahwagl_*
panggilan semua di luar objek itu sendiri dan berada di kelas yang mengetahui detail intim objek itu sendiri, bukan yang menjadi bagian dariRenderer
.PENOLAKAN : Saya menulis sendiri kelas-kelas ini di editor sehingga ada peluang bagus bahwa saya melewatkan sesuatu dalam kode, tetapi mudah-mudahan, Anda akan mendapatkan idenya.
Untuk menampilkan contoh (sebagian):
Renderable
antarmukaGameObject
kelas:Renderer
Kelas (sebagian) .RenderableObject
kelas:ObjectA
kelas:ObjectARenderable
kelas:sumber
Bangun sistem perintah rendering. Objek tingkat tinggi, yang memiliki akses ke objek
OpenGLRenderer
dan scenegraph / gameobjects, akan mengulangi grafik adegan atau objek game dan membangun batchRenderCmds
, yang kemudian akan dikirimkan keOpenGLRenderer
yang akan menggambar masing-masing secara bergantian, dan dengan demikian berisi semua OpenGL kode terkait di dalamnya.Ada lebih banyak manfaat dari ini daripada sekadar abstraksi; akhirnya ketika kompleksitas rendering Anda bertambah, Anda dapat mengurutkan dan mengelompokkan setiap perintah render berdasarkan tekstur atau shader misalnya
Render()
untuk menghilangkan banyak kemacetan dalam panggilan draw yang dapat membuat perbedaan besar dalam kinerja.sumber
Ini sepenuhnya tergantung pada apakah Anda dapat membuat asumsi tentang apa yang umum untuk semua entitas yang dapat di render atau tidak. Di mesin saya, semua objek dirender dengan cara yang sama, jadi hanya perlu menyediakan vbos, tekstur dan transformasi. Kemudian renderer mengambil semuanya, sehingga tidak ada panggilan fungsi OpenGL yang diperlukan dalam objek yang berbeda sama sekali.
sumber
Jelas menempatkan kode rendering dan logika game di kelas yang berbeda. Komposisi (seperti yang disarankan oleh teodron) mungkin merupakan cara terbaik untuk melakukan ini; setiap Entitas di dunia game akan memiliki Renderable sendiri - atau mungkin satu set dari mereka.
Anda mungkin masih memiliki beberapa subclass Renderable, misalnya untuk menangani animasi kerangka, penghasil partikel, dan shader kompleks, selain shader bertekstur-dan-menyala dasar Anda. Kelas Renderable dan subkelasnya hanya boleh berisi informasi yang diperlukan untuk rendering: geometri, tekstur, dan shader.
Selanjutnya, Anda harus memisahkan sebuah instance dari mesh yang diberikan dari mesh itu sendiri. Katakanlah Anda memiliki seratus pohon di layar, masing-masing menggunakan jaring yang sama. Anda hanya ingin menyimpan geometri satu kali, tetapi Anda membutuhkan lokasi & matriks rotasi yang terpisah untuk setiap pohon. Objek yang lebih kompleks, seperti humanoids animasi, juga akan memiliki informasi status tambahan (seperti kerangka, rangkaian animasi yang saat ini diterapkan, dll).
Untuk membuat, pendekatan naif adalah untuk mengulangi setiap entitas game dan menyuruhnya untuk membuat itu sendiri. Bergantian, setiap entitas (ketika muncul) dapat memasukkan objek yang dapat diurai ke objek pemandangan. Kemudian, fungsi render Anda memberi tahu tempat kejadian untuk merender. Ini memungkinkan adegan untuk melakukan hal-hal terkait render yang kompleks tanpa menyematkan kode tersebut ke entitas gim atau subkelas render yang spesifik.
sumber
Nasihat ini tidak benar-benar spesifik untuk rendering tetapi harus membantu memunculkan sistem yang membuat hal-hal terpisah. Pertama-tama coba dan pisahkan data 'GameObject' dari informasi posisi.
Perlu dicatat bahwa informasi posisi XYZ sederhana mungkin tidak sesederhana itu. Jika Anda menggunakan mesin fisika maka data posisi Anda dapat disimpan di dalam mesin pihak ke-3. Anda perlu menyinkronkan di antara mereka (yang akan melibatkan banyak penyalinan memori yang tidak berguna) atau meminta informasi langsung dari mesin. Tetapi tidak semua benda membutuhkan fisika, beberapa akan diperbaiki di tempat sehingga satu set float sederhana berfungsi dengan baik di sana. Beberapa bahkan mungkin melekat pada objek lain, sehingga posisinya sebenarnya merupakan offset dari posisi lain. Dalam pengaturan lanjutan Anda mungkin memiliki posisi disimpan hanya pada GPU satu-satunya waktu yang dibutuhkan sisi komputer adalah untuk scripting, penyimpanan dan replikasi jaringan. Jadi, Anda kemungkinan akan memiliki beberapa pilihan yang memungkinkan untuk data posisi Anda. Di sini masuk akal untuk menggunakan warisan.
Daripada objek yang memiliki posisi itu, sebaliknya objek itu sendiri harus dimiliki oleh struktur data pengindeksan. Misalnya 'Level' mungkin memiliki Oktree, atau mungkin 'adegan' mesin fisika. Saat Anda ingin merender (atau mengatur adegan rendering), Anda menanyakan struktur khusus Anda untuk objek yang terlihat oleh kamera.
Ini juga membantu memberikan manajemen memori yang baik. Dengan cara ini sebuah objek yang sebenarnya tidak ada di suatu area bahkan tidak memiliki posisi yang masuk akal daripada mengembalikan 0,0 coords atau coord yang dimilikinya ketika itu terakhir di suatu area.
Jika Anda tidak lagi menyimpan koordinat di objek, alih-alih object.getX () Anda akan berakhir memiliki level.getX (objek). Masalah dengan itu adalah mencari objek di tingkat kemungkinan akan menjadi operasi yang lambat karena harus melihat semua objek itu dan cocok dengan yang Anda query.
Untuk menghindari itu saya mungkin akan membuat kelas 'tautan' khusus. Yang mengikat antara level dan objek. Saya menyebutnya "Lokasi". Ini akan berisi koordinat xyz serta pegangan ke tingkat dan pegangan ke objek. Kelas tautan ini akan disimpan dalam struktur spasial / level dan objek akan memiliki referensi yang lemah untuk itu (jika level / lokasi dihancurkan, referensi objek perlu diperbarui ke nol. Mungkin juga layak memiliki kelas Lokasi sebenarnya 'memiliki' objek, dengan cara itu jika level dihapus, demikian juga struktur indeks khusus, lokasi yang dikandungnya, dan Objeknya.
Sekarang informasi posisi disimpan hanya di satu tempat. Tidak diduplikasi antara Object, struktur pengindeksan spasial, renderer dan sebagainya.
Struktur data spasial seperti Octrees seringkali bahkan tidak perlu memiliki koordinat objek yang mereka simpan. Posisi ada disimpan di lokasi relatif dari node dalam struktur itu sendiri (bisa dianggap sebagai semacam kompresi lossy, mengorbankan akurasi untuk waktu pencarian cepat). Dengan objek lokasi di Octree maka koordinat aktual ditemukan di dalamnya setelah kueri selesai.
Atau jika Anda menggunakan mesin fisika untuk mengelola lokasi objek Anda atau campuran antara keduanya, kelas Lokasi harus menangani itu secara transparan sambil menyimpan semua kode Anda di satu tempat.
Keuntungan lain sekarang adalah posisi dan referensi ke level disimpan di lokasi yang sama. Anda dapat mengimplementasikan object.TeleportTo (other_object) dan membuatnya bekerja lintas level. Demikian pula, pencarian jalur AI dapat mengikuti sesuatu ke area yang berbeda.
Berkenaan dengan rendering. Render Anda dapat memiliki ikatan yang sama dengan Lokasi. Kecuali itu akan memiliki hal-hal khusus rendering di sana. Anda mungkin tidak perlu 'Objek' atau 'Level' untuk disimpan dalam struktur ini. Objek bisa berguna jika Anda mencoba melakukan sesuatu seperti memilih warna, atau merender hitbar yang melayang di atasnya dan seterusnya, tetapi jika tidak, pemberi render hanya peduli dengan jala dan semacamnya. RenderableStuff akan menjadi Mesh, bisa juga memiliki kotak pembatas dan sebagainya.
Anda mungkin tidak perlu melakukan ini setiap frame, Anda bisa memastikan Anda mengambil wilayah yang lebih besar dari yang ditunjukkan kamera saat ini. Cache, lacak pergerakan objek untuk melihat apakah ada kotak pembatas yang berada dalam jangkauan, lacak pergerakan kamera, dan sebagainya. Tapi jangan mulai mengacaukan hal-hal semacam itu sampai Anda telah membandingkannya.
Anda mesin fisika itu sendiri mungkin memiliki abstraksi yang sama, karena itu juga tidak memerlukan data Object, hanya tabrakan dan sifat fisika.
Semua data objek inti Anda akan berisi nama mesh yang digunakan objek. Mesin permainan kemudian dapat melanjutkan dan memuat ini dalam format apa pun yang disukainya tanpa membebani kelas objek Anda dengan sekelompok render hal-hal tertentu (yang mungkin khusus untuk rendering API Anda, yaitu DirectX vs OpenGL).
Itu juga membuat komponen yang berbeda terpisah. Ini membuatnya mudah untuk melakukan hal-hal seperti mengganti mesin fisika Anda karena hal-hal itu sebagian besar mandiri di satu lokasi. Ini juga membuat unittesting jauh lebih mudah. Anda dapat menguji hal-hal seperti kueri fisika tanpa harus memiliki pengaturan objek palsu yang sebenarnya karena semua yang Anda butuhkan adalah kelas Lokasi. Anda juga dapat mengoptimalkan barang dengan lebih mudah. Itu membuat lebih jelas kueri apa yang perlu Anda lakukan pada kelas apa dan lokasi tunggal untuk mengoptimalkannya (misalnya level di atas. GetVisibleObject akan menjadi tempat Anda dapat men-cache hal-hal jika kamera tidak banyak bergerak).
sumber