Sehubungan dengan Java vs C ++, saya telah menulis mesin voxel di keduanya (versi C ++ ditunjukkan di atas). Saya juga sudah menulis mesin voxel sejak 2004 (ketika mereka tidak mode). :) Saya dapat mengatakan dengan sedikit keraguan bahwa kinerja C ++ jauh lebih unggul (tetapi juga lebih sulit untuk dikodekan). Ini kurang tentang kecepatan komputasi, dan lebih banyak tentang manajemen memori. Hands down, ketika Anda mengalokasikan / mendeallocating data sebanyak yang ada di dunia voxel, C (++) adalah bahasa yang harus dikalahkan. Namun, Anda harus memikirkan tujuan Anda. Jika kinerja adalah prioritas tertinggi Anda, lanjutkan dengan C ++. Jika Anda hanya ingin menulis permainan tanpa kinerja yang luar biasa, Java pasti dapat diterima (seperti yang dibuktikan oleh Minecraft). Ada banyak kasus sepele / tepi, tetapi secara umum Anda dapat mengharapkan Java berjalan sekitar 1,75-2,0 kali lebih lambat dari (ditulis dengan baik) C ++. Anda dapat melihat versi mesin saya yang kurang optimal dan dioptimalkan sedang berjalan di sini (EDIT: versi yang lebih baru di sini ). Sementara generasi chunk mungkin tampak lambat, perlu diingat itu menghasilkan diagram voronoi 3D secara volumetrik, menghitung permukaan normal, pencahayaan, AO, dan bayangan pada CPU dengan metode brute-force. Saya telah mencoba berbagai teknik dan saya bisa mendapatkan sekitar 100x generasi chunk lebih cepat menggunakan berbagai teknik caching dan instancing.
Untuk menjawab sisa pertanyaan Anda, ada banyak hal yang dapat Anda lakukan untuk meningkatkan kinerja.
- Caching. Di mana pun Anda bisa, Anda harus menghitung data sekali. Sebagai contoh, saya memanggang pencahayaan ke tempat kejadian. Itu bisa menggunakan pencahayaan dinamis (dalam ruang layar, sebagai pasca-proses), tetapi memanggang dalam pencahayaan berarti bahwa saya tidak harus melewati normals untuk segitiga, yang berarti ....
Berikan data sesedikit mungkin ke kartu video. Satu hal yang cenderung dilupakan orang adalah semakin banyak data yang Anda berikan ke GPU, semakin banyak waktu yang diperlukan. Saya lulus dalam satu warna dan posisi simpul. Jika saya ingin melakukan siklus siang / malam, saya bisa melakukan gradasi warna, atau saya bisa menghitung ulang pemandangan saat matahari berangsur-angsur berubah.
Karena mengirimkan data ke GPU sangat mahal, dimungkinkan untuk menulis mesin dalam perangkat lunak yang lebih cepat dalam beberapa hal. Keuntungan dari perangkat lunak adalah dapat melakukan semua jenis manipulasi data / akses memori yang tidak mungkin dilakukan pada GPU.
Bermain dengan ukuran bets. Jika Anda menggunakan GPU, kinerja dapat bervariasi secara dramatis berdasarkan seberapa besar setiap larik simpul yang Anda lewati. Dengan demikian, bermain-main dengan ukuran potongan (jika Anda menggunakan potongan). Saya telah menemukan bahwa potongan 64x64x64 bekerja cukup baik. Apa pun yang terjadi, pertahankan potongan kubik Anda (tidak ada prisma persegi panjang). Ini akan membuat pengkodean dan berbagai operasi (seperti transformasi) lebih mudah, dan dalam beberapa kasus, lebih banyak performan. Jika Anda hanya menyimpan satu nilai untuk panjang setiap dimensi, ingatlah bahwa ada dua register yang lebih sedikit yang dipertukarkan saat menghitung.
Pertimbangkan daftar tampilan (untuk OpenGL). Meskipun mereka adalah cara "lama", mereka bisa lebih cepat. Anda harus memanggang daftar tampilan menjadi variabel ... jika Anda memanggil operasi pembuatan daftar tampilan secara realtime, itu akan lambat sekali. Bagaimana daftar tampilan lebih cepat? Itu hanya memperbarui status, vs atribut per-simpul. Ini berarti saya bisa melewatkan hingga enam wajah, lalu satu warna (vs warna untuk setiap simpul voxel). Jika Anda menggunakan GL_QUADS dan voxels kubik, ini bisa menghemat hingga 20 byte (160 bit) per voxel! (15 byte tanpa alfa, meskipun biasanya Anda ingin menjaga hal-hal 4-byte selaras.)
Saya menggunakan metode brute-force rendering "chunk", atau halaman data, yang merupakan teknik umum. Tidak seperti octrees, jauh lebih mudah / lebih cepat untuk membaca / memproses data, meskipun jauh lebih sedikit memori-friendly (namun, hari ini Anda bisa mendapatkan 64 gigabytes memori untuk $ 200- $ 300) ... bukan berarti bahwa rata-rata pengguna memiliki itu. Jelas, Anda tidak dapat mengalokasikan satu array besar untuk seluruh dunia (1024x1024x1024 set voxels adalah 4 gigabytes memori, dengan asumsi 32-bit int digunakan per voxel). Jadi, Anda mengalokasikan / membatalkan banyak array kecil, berdasarkan kedekatannya dengan pemirsa. Anda juga dapat mengalokasikan data, mendapatkan daftar tampilan yang diperlukan, lalu membuang data untuk menghemat memori. Saya pikir kombo yang ideal mungkin menggunakan pendekatan hibrida dari octrees dan array - menyimpan data dalam array ketika melakukan generasi prosedural dunia, pencahayaan, dll.
Jadikan dekat ke jauh ... piksel yang terpotong menghemat waktu. GPU akan melempar piksel jika tidak lulus uji buffer kedalaman.
Berikan hanya potongan / halaman di viewport (cukup jelas). Bahkan jika gpu tahu cara klip polgyons di luar viewport, melewati data ini masih membutuhkan waktu. Saya tidak tahu seperti apa struktur yang paling efisien untuk ini ("memalukan," Saya tidak pernah menulis pohon BSP), tetapi bahkan raycast sederhana pada basis per potong dapat meningkatkan kinerja, dan jelas menguji terhadap frustum penglihatan akan menghemat waktu.
Info yang jelas, tetapi untuk pemula: singkirkan setiap poligon tunggal yang tidak ada di permukaan - yaitu jika voxel terdiri dari enam wajah, lepaskan wajah yang tidak pernah di-render (menyentuh voxel lain).
Sebagai aturan umum semua yang Anda lakukan dalam pemrograman: CACHE LOCALITY! Jika Anda dapat menyimpan hal-hal cache-lokal (bahkan untuk sejumlah kecil waktu, itu akan membuat perbedaan besar. Ini berarti menjaga data Anda kongruen (di wilayah memori yang sama), dan tidak mengganti area memori untuk diproses terlalu sering. Jadi , idealnya, bekerja pada satu chunk per thread, dan jaga agar memori itu eksklusif untuk thread.Ini tidak hanya berlaku untuk cache CPU. Pikirkan hirarki cache seperti ini (paling lambat hingga tercepat): jaringan (cloud / database / dll) -> hard drive (dapatkan SSD jika Anda belum memilikinya), ram (dapatkan saluran tripple atau RAM lebih besar jika Anda belum memilikinya), CPU Cache (s), register. Coba simpan data Anda di akhir yang terakhir, dan tidak menukar lebih dari yang Anda harus.
Threading. Lakukan. Dunia Voxel sangat cocok untuk threading, karena setiap bagian dapat dihitung (sebagian besar) secara independen dari yang lain ... Saya melihat peningkatan hampir-4x (pada inti 4, inti 8 i7) dalam generasi dunia prosedural ketika saya menulis rutinitas untuk threading.
Jangan gunakan tipe data char / byte. Atau celana pendek. Rata-rata konsumen Anda akan memiliki prosesor AMD atau Intel modern (seperti halnya Anda, mungkin). Prosesor ini tidak memiliki register 8 bit. Mereka menghitung byte dengan menempatkannya ke dalam slot 32 bit, kemudian mengubahnya kembali (mungkin) dalam memori. Kompiler Anda dapat melakukan semua jenis voodoo, tetapi menggunakan nomor 32 atau 64 bit akan memberi Anda hasil yang paling dapat diprediksi (dan tercepat). Demikian juga, nilai "bool" tidak membutuhkan 1 bit; kompiler akan sering menggunakan 32 bit penuh untuk bool. Mungkin tergoda untuk melakukan jenis kompresi tertentu pada data Anda. Misalnya, Anda dapat menyimpan 8 voxel sebagai satu angka (2 ^ 8 = 256 kombinasi) jika semuanya jenis / warna yang sama. Namun, Anda harus berpikir tentang konsekuensi dari ini - mungkin menghemat banyak memori, tetapi itu juga dapat menghambat kinerja, bahkan dengan waktu dekompresi yang kecil, karena bahkan jumlah waktu ekstra yang kecil itu secara kubik dengan ukuran dunia Anda. Bayangkan menghitung raycast; untuk setiap langkah raycast, Anda harus menjalankan algoritma dekompresi (kecuali jika Anda menemukan cara cerdas untuk menggeneralisasi perhitungan untuk 8 voxels dalam satu langkah ray).
Seperti yang dikatakan Jose Chavez, pola desain kelas terbang dapat bermanfaat. Sama seperti Anda akan menggunakan bitmap untuk mewakili ubin dalam game 2D, Anda dapat membangun dunia Anda dari beberapa jenis ubin (atau blok) 3D. Kelemahan dari ini adalah pengulangan tekstur, tetapi Anda dapat memperbaiki ini dengan menggunakan tekstur varians yang cocok bersama. Sebagai aturan praktis, Anda ingin memanfaatkan instancing di mana pun Anda bisa.
Hindari pemrosesan simpul dan piksel dalam shader saat mengeluarkan geometri. Dalam mesin voxel Anda pasti akan memiliki banyak segitiga, sehingga bahkan shader piksel sederhana dapat sangat mengurangi waktu render Anda. Lebih baik me-render ke buffer, lalu membuat Anda pixel shader sebagai proses pasca. Jika Anda tidak bisa melakukan itu, coba lakukan perhitungan di vertex shader Anda. Kalkulasi lain harus dimasukkan ke dalam data titik jika memungkinkan. Akses tambahan menjadi sangat mahal jika Anda harus merender ulang semua geometri (seperti pemetaan bayangan atau pemetaan lingkungan). Terkadang lebih baik untuk melepaskan adegan dinamis demi detail yang lebih kaya. Jika gim Anda memiliki adegan yang dapat dimodifikasi (yaitu medan yang dapat dirusak), Anda selalu dapat menghitung ulang adegan saat semuanya dihancurkan. Rekompilasi tidak mahal dan harus di bawah satu detik.
Lepaskan loop Anda dan jaga agar array tetap rata! Jangan lakukan ini:
for (i = 0; i < chunkLength; i++) {
for (j = 0; j < chunkLength; j++) {
for (k = 0; k < chunkLength; k++) {
MyData[i][j][k] = newVal;
}
}
}
//Instead, do this:
for (i = 0; i < chunkLengthCubed; i++) {
//figure out x, y, z index of chunk using modulus and div operators on i
//myData should have chunkLengthCubed number of indices, obviously
myData[i] = newVal;
}
EDIT: Melalui pengujian yang lebih luas, saya telah menemukan ini bisa salah. Gunakan kasing yang paling sesuai untuk skenario Anda. Secara umum, array harus rata, tetapi menggunakan multi-index loop seringkali bisa lebih cepat tergantung pada case