Yerçekimi hesaplamalarını optimize etmek


23

Birbirine yönelen çeşitli boyut ve hızda nesnelerim var. Her güncellemede, her nesnenin üzerinden geçip, diğer tüm nesnelerin ağırlıklarından dolayı kuvvetler eklemeliyim. Çok iyi ölçeklenemiyor, oyunumda bulduğum iki büyük darboğazdan biri ve performansı geliştirmek için ne yapacağımdan emin değilim.

O hissediyor Ben performansını artırmak gerekir gibi. Herhangi bir zamanda, sistemdeki nesnelerin muhtemelen% 99'u yalnızca bir nesne üzerinde ihmal edilebilir bir etkiye sahip olacaktır. Tabii ki, nesneleri kütleye göre sıralayamıyorum ve sadece en büyük 10 nesneyi düşünmüyorum, çünkü kuvvet mesafe ile kütleye göre değişir (denklem çizgileri boyuncadır force = mass1 * mass2 / distance^2). Bence , dünyanın diğer tarafında yüzlerce küçük kaya parçasını göz ardı ederek, muhtemelen hiçbir şeyi etkileyemeyen en büyük nesneleri ve en yakın nesneleri göz önünde bulundurmak iyi bir yaklaşım olacaktır. En yakın ben üzerinde yinelemek zorunda tüm nesneleri ve onların pozisyonları değişiyor sürekli, bu yüzden sadece bir kere yapabilirim gibi değil.

Şu anda böyle bir şey yapıyorum:

private void UpdateBodies(List<GravitatingObject> bodies, GameTime gameTime)
{
    for (int i = 0; i < bodies.Count; i++)
    {
        bodies[i].Update(i);
    }
}

//...

public virtual void Update(int systemIndex)
{
    for (int i = systemIndex + 1; i < system.MassiveBodies.Count; i++)
    {
        GravitatingObject body = system.MassiveBodies[i];

        Vector2 force = Gravity.ForceUnderGravity(body, this);
        ForceOfGravity += force;
        body.ForceOfGravity += -force;
    }

    Vector2 acceleration = Motion.Acceleration(ForceOfGravity, Mass);
    ForceOfGravity = Vector2.Zero;

    Velocity += Motion.Velocity(acceleration, elapsedTime);
    Position += Motion.Position(Velocity, elapsedTime);
}

(Çok fazla kod kaldırdığımı unutmayın - örneğin çarpışma testleri, çarpışmaları algılamak için nesneler üzerinde ikinci kez yineleme yapmam).

Bu yüzden her zaman tüm listeyi yinelemiyorum - bunu yalnızca ilk nesne için yapıyorum ve nesne her zaman başka bir nesneye doğru hissettiği kuvveti bulduğunda, diğer nesnenin de aynı kuvveti hissettiğini, bu yüzden sadece her ikisini de güncelliyor onlar - ve sonra ilk nesnenin güncellemenin geri kalanında tekrar düşünülmesi gerekmez.

Gravity.ForceUnderGravity(...)Ve Motion.Velocity(...)vb fonksiyonlar sadece XNA en vektör matematik inşa biraz kullanabilirsiniz.

İki nesne çarpıştığında, kütlesiz enkaz oluştururlar. Ayrı bir listede tutulur ve büyük nesneler, hız hesaplamalarının bir parçası olarak enkaz üzerinde yinelenmez, ancak her enkaz parçası, büyük parçacıkların üzerinde yinelenmelidir.

Bunun inanılmaz sınırlara ölçeklendirilmesi gerekmez. Dünya sınırsız değil, sınırlarını aşan nesneleri yok eden bir sınır içeriyor - belki binlerce nesneyi ele alabilmeyi istiyorum, şu anda oyun 200 civarında boğulmaya başlıyor.

Bunu nasıl geliştirebileceğime dair bir fikrin var mı? Bazı sezgisel döngü uzunluğunu yüzlercedan birkaçına kadar tıraş etmek için kullanabilir miyim? Bazı kodlar her güncellemeden daha az sıklıkta çalıştırabilir mi? İyi bir büyüklükteki dünyaya izin verecek kadar hızlı oluncaya kadar birden fazla okutmalı mıyım? Hız hesaplamalarını GPU'ya yüklemeyi denemeli miyim? Eğer öyleyse, bunu nasıl mimarlık ederdim? GPU'da statik ve paylaşılan verileri saklayabilir miyim? GPU'da HLSL işlevleri oluşturabilir ve bunları keyfi olarak (XNA kullanarak) çağırabilir miyim yoksa çizim işleminin bir parçası mı olmak zorundalar?


1
Sadece bir notta, “büyük nesneler, hız hesaplamalarının bir parçası olarak enkaz üzerinde yinelenmez, ancak her bir enkaz parçası, büyük parçacıkların üzerinde yinelenmelidir” demiştiniz. Bundan daha etkili olduğunu varsaydığınızı düşündüm. Bununla birlikte, her biri 10 kez 100 moloz nesnenin yinelenmesi, yine de 10 büyük nesnenin 100 kez yinelenmesiyle aynıdır. Muhtemelen, enkaz nesneler döngüsündeki her enkaz nesnesini yinelemek iyi bir fikir olabilir, böylece ikinci kez yapmazsınız.
Richard Marskell - Drackir

Bir simülasyona ne kadar doğru ihtiyacınız var? Gerçekten birbirinize doğru hızlanan her şeye ihtiyacınız var mı? Ve gerçekten gerçek bir çekim hesaplama kullanman gerekiyor mu? Veya başarmaya çalıştığınız şey için bu sözleşmelerden ayrılabilir misiniz?
kaosTechnician

@Drakkir Sanırım haklısınız. Ayrılma sebeplerinin bir kısmı matematiğin kütlesiz nesneler için farklı olması ve bir kısmı da aslında yerçekimine uymadıkları için, bu yüzden onları dahil etmemek daha etkiliydi. Öyleyse bu artık
Carson Myers

@chaosTechnician çok kesin olmak zorunda değil - aslında sadece en baskın olan birkaç kuvveti hesaba katarsa, bir sistem daha kararlı olacaktır, ki bu idealdir. Ancak, hangi güçlerin en baskın olduğunu, sorun yaşadığım etkili bir şekilde tespit ediyor. Ayrıca yerçekimi hesaplaması yaklaşık olarak hesaplandı, sadece G * m1 * m2 / r^2G'nin davranışı değiştireceği yer. (sadece bir yolu izlememelerine rağmen, kullanıcı sistemi rahatsız edebildiğinden dolayı)
Carson Myers

Neden her enkaz parçası, eğer kütlesizse, büyük parçacıkların üzerinde yinelenmek zorunda? Çarpışmalar?
sam hocevar,

Yanıtlar:


26

Bu ızgara için bir iş gibi geliyor. Oyun alanınızı bir ızgaraya bölün ve her ızgara hücresi için o anda içinde bulunan nesnelerin bir listesini tutun. Nesneler hücre sınırı boyunca hareket ettiğinde, hangi listede olduklarını güncelleyin. Bir nesneyi güncellerken ve etkileşime girecek başkalarını ararken, yalnızca geçerli ızgara hücresine ve birkaç komşu olana bakabilirsiniz. En iyi performans için ızgara boyutunu küçültebilirsiniz (ızgara hücrelerinin çok küçük olması durumunda daha yüksek olan ızgara hücrelerinin güncellenmesi maliyetinin dengelenmesi), ızgara hücrelerinin de daha yüksek olduğu durumlarda aramaların maliyeti büyük).

Bu, elbette, birkaç ızgara hücresinden daha uzak olmayan nesnelerin hiç etkileşimde bulunmamasına neden olacaktır, bu büyük bir kütle birikiminin (büyük bir nesne veya çok sayıda küçük nesne kümesinin) birikmesi gerektiğinden, muhtemelen bir sorundur. Bahsettiğiniz gibi, daha geniş bir etki alanına sahiptir.

Yapabileceğiniz bir şey, her bir ızgara hücresindeki toplam kütlenin izini sürmek ve tüm hücreyi daha uzaktaki etkileşimler için tek bir nesne olarak ele almaktır. Yani: bir nesne üzerindeki kuvveti hesapladığınızda, yakındaki birkaç ızgara hücresindeki nesneler için doğrudan nesneden nesneye ivmesini hesaplayın, sonra uzaktaki her ızgara hücresi için hücreden hücreye ivmelenme ekleyin (veya belki de sadece içinde ihmal edilemez miktarda kütleye sahip olanlar). Bir hücreden hücreye hızlanma ile, iki hücrenin toplam kütlelerini ve merkezleri arasındaki mesafeyi kullanarak hesaplanan bir vektörü kastediyorum. Bu, toplam yerçekimi için bu ızgara hücresindeki tüm nesnelerden makul bir yaklaşım vermelidir, ancak çok daha ucuz.

Oyun dünyası çok büyükse, dörtlü (2D) veya octree (3D) gibi hiyerarşik bir ızgara bile kullanabilir ve benzer ilkeler uygulayabilirsiniz. Daha uzun mesafe etkileşimleri daha yüksek hiyerarşi seviyelerine karşılık gelir.


1
Izgara fikri için +1. Hesaplamaları biraz daha saf tutmak için (gerekirse) ızgara için kütle merkezini izlemenizi de öneririm.
kaosTechnician

Bu fikri çok sevdim. Nesneleri hücrelerde bulundurmayı düşünmüştüm, ancak teknik olarak farklı hücrelerde bulunan iki yakındaki nesneyi göz önüne aldığımda onu terk ettim - fakat birkaç komşu hücreyi düşünmenin yanı sıra diğerlerinin birleşik kütlesini göz önünde bulundurarak zihinsel bir sıçrama yapmadım hücreler. Doğru yaparsam bunun gerçekten iyi çalışması gerektiğini düşünüyorum.
Carson Myers,

8
Bu esasen Barnes-Hut algoritmasıdır: en.wikipedia.org/wiki/Barnes –Hut_simulation
Russell

Yerçekiminin fizikte nasıl çalışması gerektiğine benziyor - uzay zamanının bükülmesi.
Zan Lynx

Fikirden hoşlanıyorum - ama dürüst olmak gerekirse, biraz daha inceltilmesi gerektiğini düşünüyorum - iki nesne birbirine çok yakın ancak ayrı hücrelerde olursa ne olur? Üçüncü paragrafınızın nasıl yardım edebileceğini görebiliyordum - fakat neden etkileşime giren nesneler üzerinde dairesel bir geçiş yapmıyorsunuz?
Jonathan Dickinson

16

Barnes-Hut algoritması bununla gitmenin yoludur. Tam probleminizi çözmek için süper bilgisayar simülasyonlarında kullanılmıştır. Kodlaması çok zor değil ve çok verimli. Aslında bu sorunu çözmek için çok uzun zaman önce bir Java uygulaması yazdım.

Http://mathandcode.com/programs/javagrav/ adresini ziyaret edin ve "başlat" ve "dörtlüyü göster" e basın.

Seçenekler sekmesinde, parçacık sayısının 200.000'e kadar çıkabileceğini görebilirsiniz. Bilgisayarımda hesaplama yaklaşık 2 saniye içinde bitiyor (200.000 nokta çizilmesi yaklaşık 1 saniye sürüyor, ancak hesaplama ayrı bir iş parçacığında çalışıyor).

İşte uygulamamın çalışması:

  1. Rasgele kütleleri, konumları ve başlangıç ​​hızlarıyla rasgele parçacıkların bir listesini oluşturun.
  2. Bu parçacıklardan bir dörtgen oluşturun. Her dört kademe düğümü, her alt düğümün kütle merkezini içerir. Temel olarak, her düğüm için üç değeriniz vardır: massx, massy ve mass. Belirli bir düğüme bir parçacık eklediğiniz her zaman, sırasıyla partikül.x * partikül kütlesi ve partikül.y * partikül kütlesi ile kütlesini ve kütlesini arttırırsınız. Pozisyon (kütle / kütle, kütle / kütle), düğümün kütle merkezi olarak sonuçlanacaktır.
  3. Her parçacık için kuvvetleri hesaplayın ( burada tamamen açıklanmaktadır ). Bu, üst düğümden başlayarak ve verilen alt düğüm yeterince küçük oluncaya kadar her dört alt düğümden tekrarlanarak yapılır. Özyinelemeyi bıraktığınızda, parçacıktan düğümün kütle merkezine olan mesafeyi hesaplayabilir ve ardından düğümün kütlesini ve parçacık kütlesini kullanarak kuvveti hesaplayabilirsiniz.

Oyununuz kolayca birbirini çeken bin nesneyi kullanabilmelidir. Her nesne "aptal" ise (uygulamamdaki çıplak kemikli parçacıklar gibi) 8000 ila 9000 parçacıklar, belki daha fazlasını elde edebilmelisiniz. Ve bu tek iş parçacığı varsayıyor. Çok parçacıklı veya paralel hesaplama uygulamalarıyla, gerçek zamanlı olarak güncellemeden çok daha fazla parçacık elde edebilirsiniz.

: Ayrıca bkz http://www.youtube.com/watch?v=XAlzniN6L94 bu büyük bir render için


İlk bağlantı öldü. Uygulama başka bir yerde mi barındırılıyor?
Anko

1
Sabit! üzgünüm, o alanın kirasını ödemeyi unuttum ve birileri otomatik olarak aldı: \ Ayrıca, 3 dak, 1.3 yaşında bir yazı 8D'de oldukça iyi bir tepki süresidir

Ve şunu da eklemeliyim: Kaynak kodum yok. Bazı kaynak kodları arıyorsanız, bölüme bakın (c ile yazılmış). Eminim orada başkaları da vardır.

3

Nathan Reed'in mükemmel bir cevabı var. Bunun kısa versiyonu, simülasyonunuzun topolojisine uyan geniş bir teknik kullanmak ve yerçekimi hesaplamalarını sadece birbirleri üzerinde gözle görülür bir etkiye sahip olacak nesnelerin çiftleri üzerinde çalıştırmaktır. Düzenli çarpışma tespiti geniş fazı için yaptıklarınızdan gerçekten farklı değil.

Bununla devam etmek, başka bir olasılık, nesneleri yalnızca zaman zaman güncellemektir. Temel olarak, her bir zaman adımı (kare) yalnızca tüm nesnelerin bir kısmını günceller ve hızı (veya tercihinize bağlı olarak hızlanmayı) diğer nesneler için aynı bırakır. Kullanıcı, aralıklar çok uzun olmadıkça, güncellemelerdeki herhangi bir gecikmeyi fark etmeyebilir. Bu size algoritmanın doğrusal bir hızını verecektir, bu nedenle kesinlikle Nathan'ın da önerdiği gibi geniş bir fiziki tekniklere bakın; bu da bir ton nesneniz varsa çok daha önemli hızlandırmalar sağlayabilir. Hiç de aynı şekilde modellenmemiş olsa da, bu “yerçekimi dalgaları” gibi bir şey. :)

Ayrıca, tek geçişte bir yerçekimi alanı oluşturabilir, ardından ikinci geçişte nesneleri güncelleyebilirsiniz. İlk geçişte temel olarak her nesnenin gravite etkisiyle bir ızgarayı (veya daha karmaşık bir mekansal veri yapısını) dolduruyorsunuz. Sonuç şimdi, herhangi bir konumdaki bir nesneye hangi hızlanmanın uygulanacağını görmek için bile oluşturabileceğiniz (oldukça havalı görünüyor) bir ağırlık alanıdır. Sonra nesneler üzerinde yinelenirsiniz ve yerçekimi alanının etkilerini bu nesneye uygularsınız. Daha da serin, nesneleri bir dokuya daire / küre olarak gösterip ardından dokuyu okuyarak (veya GPU'da başka bir dönüştürme geri besleme geçişi kullanarak) nesnenin hızlarını değiştirmek için bunu bir GPU'da yapabilirsiniz.


Sürecin geçişlere ayrılması mükemmel bir fikirdir, çünkü güncelleme aralığı (bildiğim kadarıyla) saniyenin çok küçük bir bölümüdür. Yerçekimi alanı dokusu, harika ama belki de şu anda ulaşamadığımın biraz ötesinde.
Carson Myers

1
Uygulanan kuvvetleri, son güncellemeden bu yana kaç zaman diliminin atlandığı ile çarpmayı unutmayın.
Zan Lynx

@seanmiddlemitch: Yerçekimi alanı dokusunu biraz daha açıklayabilir misiniz? Üzgünüm, grafik programcısı değilim ama bu kulağa gerçekten ilginç geliyor; Sadece nasıl çalışması gerektiğini anlamıyorum. Ve / veya belki tekniğin açıklamasına bir link var?
Felix Dombek

@FelixDombek: Nesnelerini etki alanını temsil eden çevreler olarak oluştur. Parça gölgelendirici, nesnenin merkezine ve uygun büyüklükte (nesnenin merkezine ve nesnenin kütlesine bağlı olarak) işaret eden bir vektör yazar. Donanım karıştırma, bu vektörlerin toplama modunda toplanmasını sağlar. Sonuç doğru yerçekimi alanları olmayacak, ancak bir oyunun ihtiyaçları için neredeyse kesinlikle yeterli olacaktır. GPU'yu kullanan bir başka yaklaşım olarak, bu CUDA tabanlı N vücut yerçekimi simülasyonu tekniğine bakın: http.developer.nvidia.com/GPUGems3/gpugems3_ch31.html
Sean Middleditch


0

Sadece küçük bir bit (muhtemelen naif) girdi. Oyun programlaması yapmıyorum, ancak hissettiğim şey, temel darboğazınızın yerçekiminden-yerçekimine göre hesaplanması olduğudur. Her nesne X üzerinde yineleme yapmak ve sonra her nesne Y'den yerçekimi etkisini bulmak ve onu eklemek yerine, her bir X, Y çiftini alabilir ve aralarındaki kuvveti bulabilirsiniz. Bu, yerçekimi hesaplamalarını O'dan kesmelidir (n ^ 2). O zaman çok fazla ekleme yapacaksınız (O (n ^ 2)), fakat bu normalde daha ucuzdur.

Ayrıca bu noktada, "yerçekimi kuvveti \ epsilon'dan küçükse, bu gövdeler çok küçükse, kuvveti sıfıra ayarlayın" gibi kurallar uygulayabilirsiniz. Bu yapının başka amaçlarla da (çarpışma tespiti dahil) olması avantajlı olabilir.


Bu temelde ne yapıyorum. X ile ilgili bütün çiftleri aldıktan sonra, tekrar X üzerinde yineleme yapmıyorum. X ve Y, X ve Z, vb arasındaki kuvveti buldum ve bu kuvveti çiftin her iki nesnesine de uygularım. Döngü tamamlandıktan sonra, ForceOfGravityvektör tüm kuvvetlerin toplamıdır ve bu daha sonra bir hıza ve yeni bir konuma dönüştürülür. Yerçekimi hesaplamasının özellikle pahalı olduğundan emin değilim ve ilk önce bir eşiği geçip geçmediğini kontrol etmek kayda değer bir zaman
Carson Myers

0

Seanmiddleditch'in cevabını genişletirken, yerçekimi alanı fikrine biraz ışık tutabileceğimi düşündüm.

Öncelikle, onu bir doku olarak düşünmeyin, ancak değiştirilebilen ayrı bir değer alanı (olduğu gibi iki boyutlu bir dizi); ve simülasyonun daha sonraki doğruluğu bu alanın çözünürlüğü olabilir.

Alana bir nesneyi soktuğunuzda, çekim potansiyeli tüm çevreleyen değerler için hesaplanabilir; Böylece sahada bir yerçekimi havuzu oluşturabilirsiniz .

Fakat bu noktalardan kaç tanesini daha önce olduğu gibi veya daha önce olduğu kadar etkisiz hale gelmeden önce hesaplamalısınız? Muhtemelen çok fazla değil, 32x32 bile her nesne için yinelenecek önemli bir alan. Bu nedenle, tüm süreci birden fazla geçişte bölmek; her biri farklı çözünürlüklerde (veya hassasiyette).

Yani, birinci geçiş, bir 4x4 ızgarasında temsil edilen nesnelerin yerçekimini hesaplayabilir ve her hücre değeri uzayda bir 2D koordinatı temsil eder. Bir O (n * 4 * 4) alt toplam karmaşıklığı vermek.

İkinci geçiş 64x64 çözünürlük gravity field ile her hücre değeri uzayda bir 2D koordinatı temsil eden daha doğru olabilir. Bununla birlikte, karmaşıklık çok yüksek olduğundan, etkilenen çevredeki hücrelerin yarıçapını sınırlayabilirsiniz (belki de yalnızca çevredeki 5x5 hücreleri güncellenir).

Ek bir üçüncü geçiş, belki de 1024x1024 çözünürlüğe sahip yüksek hassasiyetli hesaplamalar için kullanılabilir. Hiçbir zaman hatırlamak, aslında 1024x1024 ayrı hesaplamalar yapmakta ancak sadece bu alanın bazı bölümlerinde (belki 6x6 alt bölümleri) çalıştırıyorsunuz.

Bu şekilde, güncelleme için genel karmaşıklığınız O (n * (4 * 4 + 5 * 5 + 6 * 6)) olur.

Daha sonra nesnelerinizin her birindeki hız değişikliklerini hesaplamak için, her bir yerçekimi alanı için (4x4, 64x64, 1024x1024) sadece nokta kütle konumlarını bir ızgara hücresine eşlersiniz, bu ızgara hücrelerini genel çekim potansiyel vektörünü yeni bir vektöre uygulayın; her "katman" veya "geçiş" için tekrarlayın; sonra onları birlikte ekleyin. Bu size iyi bir sonuç çekim kuvveti vektörü vermelidir.

Bu nedenle, genel karmaşıklık: O (n * (4 * 4 + 5 * 5 + 6 * 6) + n). Gerçekten önemli olan (karmaşıklık için), yerçekimi alanlarının toplam çözünürlüğünü değil, geçişlerdeki çekim potansiyelini hesaplarken ne kadar çevredeki hücreyi güncellediğinizdir.

Düşük çözünürlüklü alanların (ilk geçişler) nedeni evreni bir bütün olarak kuşatmak ve uzaktaki kitlelere mesafeye rağmen daha yoğun alanlara çekilmesini sağlamaktır. Daha sonra komşu gezegenlerin doğruluğunu artırmak için daha yüksek çözünürlüklü alanları ayrı katmanlar olarak kullanın.

Umarım bu mantıklı geldi.


0

Başka bir yaklaşım hakkında:

Nesnelere kütlelerine göre bir etki alanı atama - bu aralığın ötesinde ölçülebilir bir etkiye sahip olmak için çok küçükler.

Şimdi dünyanızı bir ızgaraya bölün ve her nesneyi üzerinde etkisi olan tüm hücrelerin listesine yerleştirin.

Yerçekimi hesaplamalarınızı yalnızca bir nesnenin içinde bulunduğu hücreye bağlı listedeki nesneler üzerinde yapın.

Listeleri yalnızca bir nesne yeni bir ızgara hücresine taşındığında güncellemeniz gerekir.

Izgara hücreleri ne kadar küçük olursa, güncelleme başına ne kadar az hesaplama yaparsanız o kadar fazla güncelleme listesi yaparsınız.

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.