Baru-baru ini saya mulai mengerjakan permainan yang terjadi di tata surya yang dihasilkan secara prosedural. Setelah sedikit kurva belajar (tidak pernah bekerja dengan Scala, OpenGL 2 ES atau Libgdx sebelumnya), saya memiliki demo teknologi dasar yang akan Anda gunakan untuk memutar satu planet bertekstur prosedural:
Masalah yang saya hadapi adalah kinerja generasi tekstur. Tinjauan singkat tentang apa yang saya lakukan: sebuah planet adalah kubus yang telah berubah bentuk menjadi bola. Untuk setiap sisi, tekstur anxn (misalnya 256 x 256) diterapkan, yang dibundel dalam satu tekstur 8n xn yang dikirim ke shader fragmen. Dua ruang terakhir tidak digunakan, mereka hanya di sana untuk memastikan lebarnya adalah kekuatan 2. Tekstur saat ini dihasilkan pada CPU, menggunakan versi 2012 yang diperbarui dari algoritma kebisingan simpleks yang terhubung ke dalam kertas 'Simplex kebisingan didemistifikasi '. Adegan yang saya gunakan untuk menguji algoritma berisi dua bidang: planet dan latar belakang. Keduanya menggunakan tekstur abu-abu yang terdiri dari enam oktaf noise simpleks 3D, jadi misalnya jika kita memilih 128x128 karena ukuran teksturnya 128 x 128 x 6 x 2 x 6 = sekitar 1,2 juta panggilan ke fungsi noise.
Yang paling dekat dengan planet ini adalah tentang apa yang ditampilkan di tangkapan layar dan karena resolusi target game adalah 1280x720 yang berarti saya lebih suka menggunakan tekstur 512x512. Gabungkan bahwa dengan fakta tekstur sebenarnya tentu saja akan lebih rumit daripada kebisingan dasar (Akan ada tekstur siang dan malam, dicampur dalam shader fragmen berdasarkan sinar matahari, dan topeng specular. Saya perlu noise untuk benua, variasi warna medan , awan, lampu kota, dll.) dan kita sedang melihat sesuatu seperti 512 x 512 x 6 x 3 x 15 = 70 juta panggilan suara untuk planet ini saja. Dalam permainan terakhir, akan ada kegiatan saat bepergian di antara planet-planet, jadi menunggu 5 atau 10 detik, mungkin 20, akan dapat diterima karena saya dapat menghitung tekstur di latar belakang saat bepergian, meskipun jelas semakin cepat semakin baik.
Kembali ke tempat pengujian kami, kinerja pada PC saya tidak terlalu buruk, meskipun masih terlalu lambat mengingat hasil akhir akan menjadi sekitar 60 kali lebih buruk:
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
Ini setelah saya memindahkan semua kode kinerja-kritis ke Jawa, karena mencoba melakukannya di Scala jauh lebih buruk. Menjalankan ini di ponsel saya (Samsung Galaxy S3), bagaimanapun, menghasilkan hasil yang lebih bermasalah:
128x128 : 2s
256x256 : 7s
512x512 : 29s
Sudah terlalu lama, dan itu bahkan tidak memperhitungkan fakta bahwa itu akan menjadi menit, bukan detik di versi final. Jelas sesuatu harus dilakukan. Secara pribadi, saya melihat beberapa jalan potensial, meskipun saya belum terlalu tertarik pada salah satu dari mereka:
- Jangan menghitung ulang teksturnya, tetapi biarkan shader fragmen menghitung semuanya. Mungkin tidak layak, karena pada satu titik saya memiliki latar belakang sebagai quad layar penuh dengan pixel shader dan saya mendapat sekitar 1 fps di ponsel saya.
- Gunakan GPU untuk merender tekstur sekali, menyimpannya dan menggunakan tekstur yang tersimpan sejak saat itu. Terbalik: mungkin lebih cepat daripada melakukannya pada CPU karena GPU seharusnya lebih cepat pada perhitungan floating point. Kelemahan: efek yang tidak dapat (dengan mudah) dinyatakan sebagai fungsi noise simpleks (mis. Vortisitas planet gas, kawah bulan, dll.) Jauh lebih sulit untuk dikodekan dalam GLSL daripada di Scala / Java.
- Hitung sejumlah besar tekstur noise dan kirimkan bersama aplikasi. Saya ingin menghindari ini jika memungkinkan.
- Turunkan resolusi. Memberi saya kenaikan kinerja 4x, yang tidak cukup dan saya kehilangan banyak kualitas.
- Temukan algoritma noise yang lebih cepat. Kalau ada yang punya saya semua telinga, tapi simpleks sudah seharusnya lebih cepat dari perlin.
- Mengadopsi gaya seni pixel, memungkinkan tekstur resolusi lebih rendah dan oktaf noise lebih sedikit. Sementara saya awalnya membayangkan permainan dalam gaya ini, saya datang untuk lebih menyukai pendekatan realistis.
- Saya melakukan sesuatu yang salah dan kinerja seharusnya sudah satu atau dua kali lipat lebih baik. Jika ini masalahnya, beri tahu saya.
Jika ada yang punya saran, tips, solusi, atau komentar lain tentang masalah ini saya ingin mendengarnya.
Menanggapi Layoric, inilah kode yang saya gunakan:
//The function that generates the simplex noise texture
public static Texture simplex(int size) {
byte[] data = new byte[size * size * columns * 4];
int offset = 0;
for (int y = 0; y < size; y++) {
for (int s = 0; s < columns; s++) {
for (int x = 0; x < size; x++) {
//Scale x and y to [-1,1] range
double tx = ((double)x / (size - 1)) * 2 - 1;
double ty = 1 - ((double)y / (size - 1)) * 2;
//Determine point on cube in worldspace
double cx = 0, cy = 0, cz = 0;
if (s == 0) { cx = 1; cy = tx; cz = ty; }
else if (s == 1) { cx = -tx; cy = 1; cz = ty; }
else if (s == 2) { cx = - 1; cy = -tx; cz = ty; }
else if (s == 3) { cx = tx; cy = - 1; cz = ty; }
else if (s == 4) { cx = -ty; cy = tx; cz = 1; }
else if (s == 5) { cx = ty; cy = tx; cz = - 1; }
//Determine point on sphere in worldspace
double sx = cx * Math.sqrt(1 - cy*cy/2 - cz*cz/2 + cy*cy*cz*cz/3);
double sy = cy * Math.sqrt(1 - cz*cz/2 - cx*cx/2 + cz*cz*cx*cx/3);
double sz = cz * Math.sqrt(1 - cx*cx/2 - cy*cy/2 + cx*cx*cy*cy/3);
//Generate 6 octaves of noise
float gray = (float)(SimplexNoise.fbm(6, sx, sy, sz, 8) / 2 + 0.5);
//Set components of the current pixel
data[offset ] = (byte)(gray * 255);
data[offset + 1] = (byte)(gray * 255);
data[offset + 2] = (byte)(gray * 255);
data[offset + 3] = (byte)(255);
//Move to the next pixel
offset += 4;
}
}
}
Pixmap pixmap = new Pixmap(columns * size, size, Pixmap.Format.RGBA8888);
pixmap.getPixels().put(data).position(0);
Texture texture = new Texture(pixmap, true);
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
return texture;
}
//SimplexNoise.fbm
//The noise function is the same one found in http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
//the only modification being that I replaced the 32 in the last line with 16 in order to end up with
//noise in the range [-0.5, 0.5] instead of [-1,1]
public static double fbm(int octaves, double x, double y, double z, double frequency) {
double value = 0;
double f = frequency;
double amp = 1;
for (int i = 0; i < octaves; i++) {
value += noise(x*f, y*f, z*f) * amp;
f *= 2;
amp /= 2;
}
return value;
}