Prosedürel doku üretimini hızlandırma


14

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:

resim açıklamasını buraya girin

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; 
}

Gürültü işleviniz için Java'da şu anda sahip olduklarınızı kaydedebilir misiniz? Ondan herhangi bir performans kazanımı olduğunu söylememekle birlikte, bazıları size bir destek vermek için bir şey tespit edebilir.
Darren Reid

Orijinal yazıda kullandığım kodu ekledim.
FalconNL

Kendi başınıza Q ile ilgili değil, ancak işiniz bittiğinde piksel haritanızda dispose () öğesini çağırmalısınız.
junkdog

Yanıtlar:


10

(2) ve (3) yaklaşımlarını şu şekilde birleştirebilirsiniz:

  • Birincisi, kullanım GPU için üretmek gürültü dokular bir dizi ve kaydedin. Bu senin "gürültü önbellek" olacak; ilk çalıştırmada yalnızca bir kez yapabilirsiniz.
  • Oyun içinde bir doku oluşturmak için önbellekten birkaç doku birleştirin - bu çok hızlı olmalı. Daha sonra, gerekirse, üzerine vorteks gibi özel efektler ekleyin.
  • Alternatif olarak, bazı "özel efektler" dokularını da önceden oluşturabilir ve nihai sonucu elde etmek için bunları karıştırabilirsiniz.

+1 Bence bir grup doku üretmek ve bunları basit etkilerle birleştirmek veya uygulamak için oyunla paketlemek en iyi yol olacaktır.
TheNickmaster21

2

Prosedürel doku üretimi, bir mofo'nun hesaplama süresi açısından ab * * ' dır . Neyse ne.

Bulduğum Simplex Gürültünün en iyi uygulaması Stefan Gustavson .

Gerçek hesaplama süresinin iyileştirilmesinin ötesinde ( 1024x1024 prosedürel dokuları hesaplarken bilgisayarınızdan çok fazla şey istemeniz gerçeğini aşmak oldukça zordur ), algılanan bekleme süresini azaltmanın en iyi yollarından biri uygulamanız mümkün olduğunca çok arka plan iş parçacığı çalışması yapmak.

Bu yüzden , kullanıcı hala seçenekler ve menü ile uğraşırken veya seviye başlangıç ​​fragmanını izlerken arka plan iş parçacığında oyun lansmanında dokular üretmeye başlayın.

Dikkate alınması gereken diğer bir şey, sadece yüzlerce üretilen dokuyu diske önbellekleyin ve yükleme sırasında bunlardan birini rastgele seçin. Daha fazla disk, ancak daha az yükleme süresi.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.