Son zamanlarda, prosedürel olarak oluşturulmuş bir güneş sisteminde yer alan bir oyun üzerinde çalışmaya başladım. Biraz öğrenme eğrisinden sonra (daha önce Scala, OpenGL 2 ES veya Libgdx ile çalışmadı), prosedürel olarak dokulu tek bir gezegenin etrafında döndüğünüz temel bir teknik demom var:
Karşılaştığım sorun doku üretiminin performansı. Yaptığım şeylere hızlı bir genel bakış: bir gezegen, bir küreye deforme olmuş bir küp. Her bir tarafa, parça gölgelendiriciye gönderilen bir 8n xn dokuda paketlenmiş olan anxn (örneğin 256 x 256) doku uygulanır. Son iki boşluk kullanılmıyor, sadece genişliğin 2 gücü olduğundan emin olmak için oradalar. Doku şu anda CPU'da üretiliyor, 'Simplex' kağıdına bağlı simpleks gürültü algoritmasının güncellenmiş 2012 sürümü kullanılarak gürültü demystified '. Algoritmayı test etmek için kullandığım sahne iki küre içeriyor: gezegen ve arka plan. Her ikisi de altı adet 3D simpleks parazitten oluşan gri tonlamalı bir doku kullanır, bu nedenle örneğin doku boyutu olarak 128x128'i seçersek, 128 x 128 x 6 x 2 x 6 = gürültü işlevine yaklaşık 1,2 milyon çağrı vardır.
Gezegene en yakın ekran görüntüsünde gösterilenlerle ilgilidir ve oyunun hedef çözünürlüğü 1280x720 olduğundan 512x512 doku kullanmayı tercih ederim. Gerçek dokuların elbette temel gürültüden daha karmaşık olacağı gerçeğini birleştirin (Güneş ışığına dayalı parça gölgelendiricisinde ve bir spekulum maskesinde harmanlanmış bir gündüz ve gece dokusu olacak. Kıtalar için gürültü, arazi rengi değişimi , bulutlar, şehir ışıkları vb.) ve yalnızca gezegen için 512 x 512 x 6 x 3 x 15 = 70 milyon gürültü çağrısına bakıyoruz. Son oyunda, gezegenler arasında seyahat ederken etkinlikler olacak, bu yüzden seyahat ederken arka plandaki dokuyu hesaplayabildiğim için 5 veya 10 saniye, muhtemelen 20 saniye beklemek kabul edilebilir.
Test sahnemize geri dönersek, bilgisayarımdaki performans çok kötü değil, ancak nihai sonuç yaklaşık 60 kat daha kötü olacağı düşünüldüğünde hala çok yavaş:
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
Scala'da bunu yapmaya çalışmak çok daha kötüydü, çünkü performans açısından kritik tüm kodları Java'ya taşıdım. Ancak bunu telefonumda (Samsung Galaxy S3) çalıştırmak daha sorunlu bir sonuç verir:
128x128 : 2s
256x256 : 7s
512x512 : 29s
Zaten çok uzun ve bu, son versiyonda saniyeler yerine dakikalar olacağı gerçeğini bile hesaba katmıyor. Açıkçası bir şeylerin yapılması gerekiyor. Şahsen, birkaç potansiyel yol görüyorum, ancak henüz bunlardan hiçbirine meraklı değilim:
- Dokuları önceden hesaplamayın, ancak parça gölgelendiricinin her şeyi hesaplamasına izin verin. Muhtemelen mümkün değil, çünkü bir noktada bir piksel gölgelendirici ile tam ekran dörtlü olarak arka planım vardı ve telefonumda yaklaşık 1 fps aldım.
- Dokuyu bir kez oluşturmak için GPU'yu kullanın, saklayın ve daha sonra depolanan dokuyu kullanın. Upside: GPU'nun kayan nokta hesaplamalarında daha hızlı olması gerektiği için CPU'da yapmaktan daha hızlı olabilir. Dezavantajı: simpleks gürültünün (örn. Gaz gezegeni girdapları, ay kraterleri, vb.) İşlevleri olarak (kolayca) ifade edilemeyen efektlerin GLSL'de kodlanması Scala / Java'dan çok daha zordur.
- Çok miktarda gürültü dokusunu hesaplayın ve uygulama ile gönderin. Mümkünse bundan kaçınmak istiyorum.
- Çözünürlüğü düşürün. Bana 4x performans kazancı sağlıyor, bu da yeterli değil artı bir çok kalite kaybediyorum.
- Daha hızlı bir gürültü algoritması bulun. Eğer biri varsa ben tamamen kulaklarım, ama simpleks zaten perlin daha hızlı olması gerekiyordu.
- Daha düşük çözünürlüklü dokular ve daha az gürültü oktavları için bir piksel sanat stili benimseyin. Oyunu aslında bu tarzda hayal ederken, gerçekçi yaklaşımı tercih etmeye geldim.
- Yanlış bir şey yapıyorum ve performans zaten bir veya iki büyüklükte daha iyi olmalı. Bu durumda lütfen bana bildirin.
Herhangi bir öneri, ipucu, geçici çözüm veya bu sorunla ilgili başka yorumlar varsa, onları duymak isterim.
Layoric yanıt olarak, ben kullanıyorum kodu:
//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;
}