Menyimpan voxel untuk Mesin voxel di C ++

9

Saya mencoba menulis mesin voxel kecil karena menyenangkan, tetapi berjuang untuk menemukan cara terbaik untuk menyimpan voxel yang sebenarnya. Saya sadar saya akan membutuhkan beberapa jenis sehingga saya tidak perlu memiliki seluruh dunia dalam memori, dan saya sadar saya perlu membuat mereka dengan kinerja yang masuk akal.

Saya membaca tentang oktaf dan dari apa yang saya pahami dimulai dengan 1 kubus, dan dalam kubus itu bisa 8 kubus lagi, dan 8 kubus itu bisa 8 kubus, dll. Tapi saya rasa ini tidak cocok dengan mesin voxel saya karena kubus / item voxel saya semua akan memiliki ukuran yang sama persis.

Jadi pilihan lain adalah dengan hanya membuat array ukuran 16 * 16 * 16 dan memilikinya menjadi satu potongan, dan Anda mengisinya dengan item. Dan bagian di mana tidak ada item akan memiliki nilai 0 sebagai (0 = udara). Tapi saya khawatir ini akan menghabiskan banyak memori dan tidak akan terlalu cepat.

Kemudian opsi lain adalah vektor untuk setiap potongan, dan mengisinya dengan kubus. Dan kubus memegang posisinya di chunk. Ini menghemat memori (tidak ada blok udara), tetapi membuat mencari kubus di lokasi tertentu jauh lebih lambat.

Jadi saya tidak bisa menemukan solusi yang baik, dan saya berharap seseorang dapat membantu saya dengan itu. Jadi apa yang akan Anda gunakan dan mengapa?

Tetapi masalah lain adalah rendering. Hanya membaca setiap potongan dan mengirimkannya ke GPU menggunakan OpenGL itu mudah, tetapi sangat lambat. Menghasilkan satu mesh per chunk akan lebih baik, tetapi itu berarti setiap kali saya memecahkan satu blok, saya harus membangun kembali seluruh chunk yang bisa memakan sedikit waktu menyebabkan cegukan kecil tapi nyata, yang jelas saya tidak mau. Jadi itu akan lebih sulit. Jadi bagaimana saya membuat kubus? Cukup buat semua kubus dalam satu dhuwur buffer per chunk dan render itu dan mungkin mencoba untuk meletakkannya di thread lain, atau apakah ada cara lain?

Terima kasih!

Clonkex
sumber
1
Anda harus menggunakan instancing untuk merender kubus Anda. Anda dapat menemukan tutorial di sini learnopengl.com/Advanced-OpenGL/Instancing . Untuk menyimpan kubus: apakah Anda memiliki kendala memori yang kuat pada perangkat keras? 16 ^ 3 kubus sepertinya tidak terlalu banyak memori.
Turms
@ Turms Terima kasih atas komentar Anda! Saya tidak memiliki kendala memori yang kuat, ini hanya PC biasa. Tapi saya pikir, jika setiap bagian paling atas adalah 50% udara, dan dunia sangat besar, maka pasti ada sedikit memori yang terbuang. Tapi mungkin tidak seperti yang Anda katakan. Jadi saya harus pergi untuk potongan 16 * 16 * 16 dengan jumlah blok statis? Dan juga, Anda mengatakan saya harus menggunakan instancing, apakah itu benar-benar diperlukan? Gagasan saya adalah menghasilkan mesh untuk setiap bidak, karena dengan begitu saya bisa menghilangkan semua segitiga yang tidak terlihat.
6
Saya tidak merekomendasikan menggunakan instancing untuk kubus seperti yang dijelaskan Turms. Ini hanya akan mengurangi panggilan undian Anda, tetapi tidak melakukan apa pun untuk overdraw dan wajah tersembunyi - sebenarnya itu mengikat tangan Anda dari menyelesaikan masalah itu, karena untuk instancing bekerja semua kubus harus sama - Anda tidak dapat menghapus wajah tersembunyi dari beberapa kubus atau menggabungkan wajah coplanar menjadi poligon tunggal yang lebih besar.
DMGregory
Memilih mesin voxel terbaik bisa menjadi tantangan. Pertanyaan besar untuk Anda tanyakan pada diri sendiri adalah "operasi apa yang harus saya lakukan pada voxel saya?" Itu memandu operasi. Misalnya, Anda khawatir tentang seberapa sulit untuk mengetahui voxel mana yang berada di pohon oktober. Algoritma oct-tree sangat bagus untuk masalah yang dapat menghasilkan informasi ini sesuai kebutuhan saat berjalan di pohon (seringkali dengan cara rekursif). Jika Anda memiliki masalah khusus di mana ini terlalu mahal, maka Anda dapat melihat opsi lain.
Cort Ammon
Pertanyaan besar lainnya adalah seberapa sering voxel diperbarui. Beberapa algoritma sangat bagus jika mereka dapat melakukan pra-proses data untuk menyimpannya secara efisien, tetapi kurang efisien jika data terus diperbarui (seperti data dalam simulasi fluida berbasis partikel)
Cort Ammon

Jawaban:

23

Menyimpan blok sebagai posisi dan nilai sebenarnya sangat tidak efisien. Bahkan tanpa overhead yang disebabkan oleh struct atau objek yang Anda gunakan, Anda perlu menyimpan 4 nilai berbeda per blok. Masuk akal untuk menggunakannya di atas metode "menyimpan blok dalam array tetap" (yang Anda jelaskan sebelumnya) adalah ketika hanya seperempat dari blok yang solid, dan dengan cara ini Anda bahkan tidak mengambil metode optimasi lainnya ke dalam Akun.

Oktaf sebenarnya bagus untuk permainan berbasis voxel, karena mereka berspesialisasi dalam menyimpan data dengan fitur yang lebih besar (misalnya tambalan dari blok yang sama). Untuk mengilustrasikan hal ini, saya menggunakan quadtree (pada dasarnya octrees dalam 2d):

Ini adalah set awal saya yang berisi ubin 32x32, yang akan sama dengan nilai 1024: masukkan deskripsi gambar di sini

Menyimpan ini sebagai 1024 nilai terpisah sepertinya tidak efisien, tetapi begitu Anda mencapai ukuran peta yang mirip dengan game, seperti Terraria , memuat layar akan membutuhkan waktu beberapa detik. Dan jika Anda meningkatkannya ke dimensi ketiga, itu mulai menggunakan semua ruang dalam sistem.

Quadtrees (atau octrees dalam 3d) dapat membantu situasi. Untuk membuat satu, Anda bisa pergi dari ubin dan kelompokkan bersama-sama, atau pergi dari satu sel besar dan Anda membaginya sampai Anda mencapai ubin. Saya akan menggunakan pendekatan pertama, karena lebih mudah untuk divisualisasikan.

Jadi, dalam iterasi pertama Anda mengelompokkan semuanya menjadi sel 2x2, dan jika sel hanya berisi ubin dari jenis yang sama, Anda menjatuhkan ubin dan hanya menyimpan jenisnya. Setelah satu iterasi, peta kita akan terlihat seperti ini:

masukkan deskripsi gambar di sini

Garis merah menandai apa yang kami simpan. Setiap kotak hanya memiliki 1 nilai. Ini membawa ukuran turun dari nilai 1024 ke 439, itu penurunan 57%.

Tapi Anda tahu mantra . Mari selangkah lebih maju dan kelompokkan ini ke dalam sel:

masukkan deskripsi gambar di sini

Ini mengurangi jumlah nilai yang disimpan menjadi 367. Itu hanya 36% dari ukuran aslinya.

Anda jelas perlu melakukan pembagian ini sampai setiap 4 sel yang berdekatan (8 blok berdekatan dalam 3d) di dalam chunk disimpan di dalam satu sel, pada dasarnya mengubah chunk menjadi satu sel besar.

Ini juga memiliki beberapa manfaat lain, terutama ketika melakukan tabrakan, tetapi Anda mungkin ingin membuat octree terpisah untuk itu, yang hanya peduli apakah satu blok solid atau tidak. Dengan begitu, alih-alih memeriksa tabrakan untuk setiap blok di dalam chunk, Anda bisa melakukannya melawan sel.

Bálint
sumber
Terima kasih untuk balasan Anda! Tampaknya octree adalah cara untuk pergi. (Karena mesin voxel saya akan menjadi 3D) Saya punya beberapa pertanyaan yang ingin saya tanyakan: Gambar terakhir Anda menunjukkan bagian hitam memiliki kotak yang lebih besar, karena saya berniat memiliki minecraft seperti mesin di mana Anda dapat memodifikasi medan voxel, saya lebih suka untuk menjaga segala sesuatu yang memiliki ukuran blok yang sama, karena kalau tidak itu akan membuat hal-hal yang sangat rumit, itu mungkin benar? (saya masih akan menyederhanakan slot kosong / udara ofcourse.) Kedua , apakah ada semacam tutorial tentang cara memprogram octree? Terima kasih!
7
@ appmaker1358 tidak ada masalah sama sekali. Jika pemain mencoba untuk memodifikasi blok besar, maka Anda memecahnya menjadi blok yang lebih kecil pada saat itu . Tidak perlu menyimpan nilai "rock" 16x16x16 ketika Anda bisa mengatakan "seluruh chunk ini adalah solid rock" sampai itu tidak lagi benar.
DMGregory
1
@ appmaker1358 Seperti yang dikatakan DMGregory, memperbarui data yang disimpan dalam sebuah octree relatif mudah. Yang harus Anda lakukan adalah membagi sel perubahan yang terjadi sampai setiap sub sel hanya berisi satu jenis blok. Berikut ini contoh interaktif dengan quadtree . Membangkitkannya juga sederhana. Anda membuat satu sel besar, yang sepenuhnya berisi potongan, kemudian Anda secara rekursif menelusuri setiap sel daun (sel yang tidak memiliki anak), periksa apakah bagian dari medan yang diwakilinya berisi beberapa jenis blok, jika ya, bagi lagi sel
Bálint
@ appmaker1358 Masalah yang lebih besar sebenarnya adalah kebalikannya - memastikan octree tidak menjadi penuh dengan hanya satu blok, yang dapat dengan mudah terjadi dalam gim bergaya Minecraft. Namun, ada banyak solusi untuk masalah ini - ini hanya soal memilih apa pun yang Anda anggap tepat. Dan itu hanya menjadi masalah nyata ketika ada banyak bangunan terjadi.
Luaan
Pelayan tidak selalu merupakan pilihan terbaik. di sini adalah bacaan yang menarik: 0fps.net/2012/01/14/an-analysis-of-minecraft-like-engines
Polygnome
7

Ada octrees untuk menyelesaikan masalah yang Anda uraikan dengan tepat, memungkinkan penyimpanan data yang padat tanpa waktu pencarian yang besar.

Fakta bahwa voxel Anda memiliki ukuran yang sama hanya berarti bahwa octree Anda memiliki kedalaman yang tetap. misalnya. untuk potongan 16x16x16, Anda membutuhkan paling banyak 5 level pohon:

  • root chunk (16x16x16)
    • oktan tingkat pertama (8x8x8)
      • oktan tingkat kedua (4x4x4)
        • oktan tingkat ketiga (2x2x2)
          • voxel tunggal (1x1x1)

Ini berarti Anda memiliki paling banyak 5 langkah untuk mencari tahu apakah ada voxel pada posisi tertentu di chunk:

  • chunk root: apakah seluruh chunk bernilai sama (mis. semua udara)? Jika demikian, kita sudah selesai. Jika tidak...
    • tingkat pertama: apakah oktan yang berisi posisi ini semuanya bernilai sama? Jika tidak...
      • tingkat kedua ...
        • tingkat ketiga ...
          • sekarang kami sedang menangani satu voxel, dan dapat mengembalikan nilainya.

Jauh lebih pendek daripada memindai bahkan 1% dari jalan melalui array hingga 4.096 vox!

Perhatikan bahwa ini memungkinkan kita memampatkan data di mana pun ada oktan penuh dengan nilai yang sama - apakah nilai itu semua udara atau semua batu atau apa pun. Hanya di mana oktan mengandung nilai campuran yang perlu kita bagi lagi, hingga batas node daun voxel tunggal.


Untuk mengatasi anak-anak dari sepotong, biasanya kami akan melanjutkan dalam urutan Morton , kira-kira seperti ini:

  1. X- Y- Z-
  2. X- Y- Z +
  3. X- Y + Z-
  4. X- Y + Z +
  5. X + Y- Z-
  6. X + Y- Z +
  7. X + Y + Z-
  8. X + Y + Z +

Jadi, navigasi simpul Octree kami mungkin terlihat seperti ini:

GetOctreeValue(OctreeNode node, int depth, int3 nodeOrigin, int3 queryPoint) {
    if(node.IsAllOneValue)
        return node.Value;

    int childIndex =  0;
    childIndex += (queryPoint.x > nodeOrigin.x) ? 4 : 0;
    childIndex += (queryPoint.y > nodeOrigin.y) ? 2 : 0;
    childIndex += (queryPoint.z > nodeOrigin.z) ? 1 : 0;

    OctreeNode child = node.GetChild(childIndex);

    return GetOctreeValue(
                child, 
                depth + 1,
                nodeOrigin + childOffset[depth, childIndex],
                queryPoint
    );
}
DMGregory
sumber
Terima kasih untuk balasan Anda! Tampaknya octree adalah cara untuk pergi. Tapi saya punya 2 pertanyaan, Anda mengatakan octree lebih cepat daripada memindai melalui array, yang benar. Tapi saya tidak perlu melakukan itu karena array bisa statis artinya saya dapat menghitung di mana kubus yang saya butuhkan. Jadi mengapa saya harus memindai? Pertanyaan kedua, di tingkat terakhir dari octree (1x1x1), bagaimana saya tahu kubus mana, karena jika saya memahaminya dengan benar, dan octree node memiliki 8 node lagi, bagaimana Anda tahu node mana yang termasuk dalam posisi 3d mana (Atau apakah saya harus mengingatnya sendiri?)
Ya, Anda sudah membahas kasus susunan lengkap voxel 16x16x16 dalam pertanyaan Anda, dan sepertinya menolak jejak memori 4K per potongan (dengan asumsi setiap ID voxel adalah byte) sebagai berlebihan. Pencarian yang Anda sebutkan muncul ketika menyimpan daftar voxel dengan posisi, memaksa Anda untuk memindai daftar untuk menemukan voxel di posisi target Anda. 4096 di sini adalah batas atas dari panjang daftar itu - biasanya akan lebih kecil dari itu, tetapi umumnya masih lebih dalam dari pencarian octree yang sesuai.
DMGregory