İnterpolasyon ve diş açma için veri yapıları?


20

Son zamanlarda oyunumla bazı kare hızında titreme sorunları ile uğraşıyorum ve en iyi çözüm, Glenn Fiedler (Gaffer on Games) tarafından klasik Fix Your Timestep'te önerilen çözüm olacak gibi görünüyor ! makale.

Şimdi - Güncellemem için zaten sabit bir zaman adımı kullanıyorum. Sorun, oluşturma için önerilen enterpolasyonu yapmıyorum. Sonuç olarak, oluşturma oranım güncelleme hızımla eşleşmiyorsa kareleri iki katına çıkarmam veya atlamam. Bunlar görsel olarak fark edilebilir.

Bu yüzden oyunuma enterpolasyon eklemek istiyorum - ve başkalarının bunu desteklemek için verilerini ve kodlarını nasıl yapılandırdığını bilmek istiyorum.

Açıkçası, oluşturucumla ilgili oyun durumu bilgilerinin iki kopyasını (nerede? / Nasıl?) Saklamam gerekecek, böylece aralarında enterpolasyon yapabilir.

Ayrıca - bu iş parçacığı eklemek için iyi bir yer gibi görünüyor. Bir güncelleme iş parçacığının oyun durumunun üçüncü bir kopyasında çalışabileceğini ve diğer iki kopyayı render iş parçacığı için salt okunur olarak bırakabileceğini hayal ediyorum . (Bu iyi bir fikir mi?)

- O oyunun devletin iki veya üç sürümlerine sahip performansını tanıtmak ve olabilir gibi görünüyor çok daha önemlisi - güvenilirlik ve geliştirici verimlilik problemlerini, sadece tek bir versiyonunu sahip kıyasla. Bu yüzden özellikle bu sorunları azaltmaya yönelik yöntemlerle ilgileniyorum.

Özellikle not, oyun durumuna nesne ekleme ve çıkarma işlemlerinin nasıl yapılacağı sorunudur.

Son olarak, bazı durumların oluşturma için doğrudan gerekli olmadığı veya farklı sürümlerini izlemek için çok zor olacağı görülüyor (örneğin: tek bir durumu depolayan bir üçüncü taraf fizik motoru) - bu yüzden nasıl olduğunu bilmek isterim insanlar böyle bir sistem içinde bu tür verileri ele almışlardır.

Yanıtlar:


4

Tüm oyun durumunu çoğaltmaya çalışmayın. İnterpolasyon yapmak bir kabus olur. Değişken ve ihtiyaç duyulan parçaları render ederek izole edin (buna "Görsel Durum" diyelim).

Her nesne sınıfı için, nesneyi Görsel Durumda tutabilen bir eşlik eden sınıf oluşturun. Bu nesne simülasyon tarafından üretilecek ve render tarafından tüketilecektir. İnterpolasyon kolayca bağlanacaktır. Durum değişmezse ve değere göre aktarılırsa, iş parçacığı sorunlarınız olmaz.

Oluşturmanın genellikle nesneler arasındaki mantıksal ilişkiler hakkında hiçbir şey bilmesine gerek yoktur, bu nedenle oluşturma için kullanılan yapı düz bir vektör veya en fazla basit bir ağaç olacaktır.

Misal

Geleneksel tasarım

class Actor
{
  Matrix4x3 position;
  float fuel;
  float armor;
  float stamina;
  float age;

  void Simulate(float deltaT)
  {
    age += deltaT;
    armor -= HitByAWeapon();
  }
}

Görsel durumu kullanma

class IVisualState
{
  public:
  virtual void Interpolate(const IVisualState &newVS, float f) {}
};
class Actor
{
  struct VisualState: public IVisualState
  {
    Matrix4x3 position;
    float fuel;
    float armor;
    float stamina;
    float age;

    virtual auto_ptr<IVisualState> Interpolate(const IVisualState &newVS, float f)
    {
      const VisualState &newState = static_cast<const VisualState &>(newVS);
      IVisualState *ret = new VisualState;
      ret->age = lerp(this->age,newState.age);
      // ... interpolate other properties as well, using any suitable interpolation method
      // liner, spline, slerp, whatever works best for the given property
      return ret;
    };
  };

  auto_ptr<VisualState> state_;

  void Simulate(float deltaT)
  {
    state_->age += deltaT;
    state_->armor -= HitByAWeapon();
  }
}

1
Parametre adı olarak "yeni" (C ++ 'da ayrılmış bir kelime) kullanmadıysanız örneğinizi okumak daha kolay olurdu.
Steve S

3

Benim çözümüm çok daha az zarif / karmaşık. Box2D'yi fizik motorum olarak kullanıyorum , bu yüzden sistem durumunun birden fazla kopyasını yönetilemez (fizik sistemini klonla, sonra onları senkronize tutmaya çalış, daha iyi bir yol olabilir ama gelemedim) bir).

Bunun yerine fizik kuşağının koşu sayacını tutuyorum . Her güncelleme fizik üretimini arttırır, fizik sistemi çift güncellediğinde, üretim sayacı çift güncellenir.

Oluşturma sistemi son oluşturulmuş nesli ve o nesle ait deltayı takip eder. Konumlarını enterpole etmek isteyen nesneleri oluştururken, nesnenin nerede oluşturulması gerektiğini tahmin etmek için bu değerleri konumları ve hızlarıyla birlikte kullanabilir.

Fizik motoru çok hızlı olsaydı ne yapacağımı söylemedim. Neredeyse hızlı hareket için enterpolasyon yapmamanız gerektiğini savunuyorum. Her ikisini de yaptıysanız, spriteların çok yavaş tahmin edip çok hızlı tahmin ederek etrafta zıplamasına neden olmamasına dikkat etmeniz gerekir.

İnterpolasyon şeylerini yazdığımda grafikleri 60Hz'de ve fiziği 30Hz'de çalıştırıyordum. Box2D'nin 120Hz'de çalıştırıldığında çok daha kararlı olduğu ortaya çıkıyor. Bu yüzden enterpolasyon kodum çok az kullanılıyor. Hedefi iki katına çıkararak fiziği kare başına ortalama güncellemelerle iki kez ortalayın. 1 veya 3 kez de olabilir, ama neredeyse hiç 0 veya 4+ olabilir jitter ile. Yüksek fizik oranı, enterpolasyon problemini kendi başına düzeltir. Hem fiziği hem de kare hızını 60hz'de çalıştırırken kare başına 0-2 güncelleme alabilirsiniz. 0 ile 2 arasındaki görsel fark, 1 ve 3'e kıyasla çok büyük.


3
Ben de buldum. 60Hz'e yakın çerçeve güncellemesine sahip 120Hz fizik döngüsü enterpolasyonu neredeyse değersiz hale getirir. Ne yazık ki bu sadece 120Hz fizik döngüsü sağlayabilen oyunlar için çalışıyor.

120Hz'lik güncelleme döngüsüne geçmeyi denedim. Bu, fizikimi daha kararlı hale getirmenin ve oyunumun 60Hz olmayan kare hızlarında pürüzsüz görünmesini sağlamanın iki yararı var gibi görünüyor. Dezavantajı, dikkatlice ayarlanmış tüm oyun fiziğimi kırması - bu kesinlikle bir projede erken seçilmesi gereken bir seçenektir.
Andrew Russell

Ayrıca: İnterpolasyon sisteminizle ilgili açıklamanızı gerçekten anlamıyorum. Biraz ekstrapolasyona benziyor aslında?
Andrew Russell

İyi karar. Aslında bir ekstrapolasyon sistemi tanımladım. Konum, hız ve son fizik güncellemesinden bu yana ne kadar süre geçerse, fizik motoru durmasaydı nesnenin nerede olacağını tahmin ediyorum.
deft_code

2

Timesteps'e bu yaklaşımın oldukça sık önerildiğini duydum, ancak oyunlarda 10 yıl içinde, sabit bir zamana ve enterpolasyona dayanan gerçek dünya projesinde hiç çalışmadım.

Genellikle değişken bir zaman çizelgesi sisteminden daha fazla çaba harcıyor (25Hz-100Hz aralığında makul bir çerçeve aralığı olduğu varsayılarak).

Çok küçük bir prototip için sabit timestep + enterpolasyon yaklaşımını bir kez denedim - iş parçacığı yok, ancak sabit timestep mantık güncellemesi ve bunu güncellemediğinde mümkün olduğunca hızlı oluşturma. Benim yaklaşımım, önceki / geçerli değerleri depolayan ve render kodundan kullanılan bir erişimciye sahip olan CInterpolatedVector ve CInterpolatedMatrix gibi birkaç sınıfın olmasıydı. şimdiki zamanlar)

Her oyun nesnesi, güncellemesinin sonunda, mevcut durumunu bu enterpolasyonlu vektörler / matrisler kümesine ayarlayacaktır. Bu tür şeyler diş açmayı desteklemek için genişletilebilir, en az 3 değer setine ihtiyacınız olacak - biri güncelleniyor ve en az 2 önceki değer arasında enterpolasyon yapmak için ...

Bazı değerlerin önemsiz bir şekilde enterpolasyon yapılamayacağını unutmayın (örn. 'Hareketli grafik animasyonu çerçevesi,' özel efekt etkin '). Enterpolasyonu tamamen atlayabilirsiniz veya oyununuzun ihtiyaçlarına bağlı olarak sorunlara neden olabilir.

IMHO, bir RTS veya çok sayıda nesneye sahip olduğunuz başka bir oyun yapmadıkça ve ağ oyunları için 2 bağımsız simülasyonu senkronize etmek zorunda kalmadıkça (sadece siparişleri / komutları göndermeden) nesne konumlarından ziyade ağ). Bu durumda, sabit zaman aralığı tek seçenektir.


1
En azından Quake 3 bu yaklaşımı kullanıyor gibi görünüyor, varsayılan "kene" 20 fps (50 ms).
Suma

İlginç. Daha hızlı PC'lerin / daha yüksek kare hızlarının çok fazla avantaj elde etmemesini sağlamak için yüksek rekabetçi çok oyunculu PC oyunları için avantajları olduğunu düşünüyorum (daha duyarlı kontroller veya fizik / çarpışma davranışında küçük ama istismar edilebilir farklılıklar) ?
bluescrn

1
10 yıl içinde, simülasyon ve oluşturucu ile kilitli olmayan fiziği çalıştıran bir oyuna girmediniz mi? Çünkü bunu yaptığınız an, animasyonlarınızdaki algılanan sarsıntıyı hemen hemen enterpolasyona sokmak veya kabul etmek zorunda kalacaksınız.
Kaj

2

Açıkçası, oluşturucumla ilgili oyun durumu bilgilerinin iki kopyasını (nerede? / Nasıl?) Saklamam gerekecek, böylece aralarında enterpolasyon yapabilir.

Evet, şükür ki buradaki anahtar "oluşturucumla alakalı". Bu, karışıma eski bir konum ve zaman damgası eklemekten başka bir şey olmayabilir. 2 konum verildiğinde, aralarındaki bir konuma enterpolasyon yapabilirsiniz ve bir 3D animasyon sisteminiz varsa, normalde pozu o anda kesin olarak isteyebilirsiniz.

Gerçekten çok basit - işleyicinizin oyun nesnenizi oluşturması gerektiğini düşünün. Nesneye neye benzediğini soruyordu, ama şimdi belli bir zamanda neye benzediğini sormak zorunda. Bu soruyu cevaplamak için gereken bilgileri depolamanız yeterlidir.

Ayrıca - bu iş parçacığı eklemek için iyi bir yer gibi görünüyor. Bir güncelleme iş parçacığının oyun durumunun üçüncü bir kopyasında çalışabileceğini ve diğer iki kopyayı render iş parçacığı için salt okunur olarak bırakabileceğini hayal ediyorum. (Bu iyi bir fikir mi?)

Bu noktada sadece ek ağrı için bir tarif gibi geliyor. Tüm sonuçları düşünmedim, ancak daha yüksek gecikme pahasına biraz ekstra verim elde edebileceğinizi tahmin ediyorum. Oh, ve başka bir çekirdeği kullanmaktan faydalanabilirsiniz, ama bilmiyorum.


1

Not Aslında enterpolasyona bakmıyorum, bu yüzden bu cevap buna değinmiyor; Ben sadece render iş parçacığı için oyun durumunun bir kopyasını ve güncelleme iş parçacığı için başka bir kopyasını sahip endişe duyuyorum. İnterpolasyon konusunda yorum yapamam, ancak enterpolasyon için aşağıdaki çözümü değiştirebilirsiniz.

Çok iş parçacıklı bir motor tasarlayıp düşündüğüm için bunu merak ediyorum. Bu yüzden Stack Overflow hakkında bir çeşit "günlük kaydı" veya "işlemler" tasarım modelinin nasıl uygulanacağı hakkında bir soru sordum . Bazı iyi yanıtlar aldım ve kabul edilen cevap beni gerçekten düşündürdü.

Değişmez bir nesne yaratmak zor, çünkü tüm çocukları da değişmez olmalı ve her şeyin değişmez olduğuna gerçekten dikkat etmelisiniz. Ancak gerçekten dikkatli olursanız, GameStateoyununuzdaki tüm verileri (ve alt verileri vb.) İçeren bir üst sınıf oluşturabilirsiniz; Model-View-Controller organizasyon stilinin "Model" kısmı.

Ardından, Jeffrey'in dediği gibi , GameState nesnenizin örnekleri hızlı, bellek tasarruflu ve iş parçacığı açısından güvenlidir. Büyük dezavantajı, modelle ilgili herhangi bir şeyi değiştirmek için, modeli yeniden oluşturmanız gerekir, bu nedenle kodunuzun büyük bir karmaşaya dönüşmemesine gerçekten dikkat etmeniz gerekir. GameState nesnesi içinde bir değişkeni yeni bir değere ayarlamak, var = val;kod satırları açısından daha çok işin içindedir .

Yine de çok ilgimi çekti. Tüm veri yapınızı her karede kopyalamanız gerekmez; sadece değişmez yapıya bir işaretçi kopyalarsınız. Bu çok etkileyici bir şey değil mi?


Gerçekten ilginç bir yapı. Ancak, bir oyun için iyi çalışacağından emin değilim - genel durum, her kare için tam olarak bir kez değişen oldukça düz bir nesne ağacıdır. Ayrıca dinamik bellek ayırma büyük bir hayır-hayır olduğu için.
Andrew Russell

Böyle bir durumda dinamik ayırmanın verimli bir şekilde yapılması çok kolaydır. Dairesel bir tampon kullanabilir, bir taraftan büyüyebilir, ikincisinden uzaklaşabilirsiniz.
Suma

... dinamik tahsis olmazdı, sadece önceden yerleştirilmiş belleğin dinamik kullanımı;)
Kaj

1

Sahne grafiğimdeki her düğümün oyun durumunun üç kopyasını alarak başladım. Bunlardan biri sahne grafiği iş parçacığı tarafından yazılıyor, biri oluşturucu tarafından okunuyor ve üçüncüsü bunlardan birinin değişmesi gerektiğinde okuma / yazma için kullanılabilir. Bu iyi çalıştı, ama aşırı karmaşıktı.

Daha sonra, işlenecek olanın yalnızca üç halini tutmam gerektiğini fark ettim. Güncelleme iş parçacığım şimdi çok daha küçük "RenderCommands" arabelleklerinden birini dolduruyor ve Renderer şu anda yazılmamış olan en yeni arabellekten okuyor, bu da iş parçacıklarının birbirini beklemesini engelliyor.

Benim kurulumumda, her RenderCommand 3B geometri / materyallere, bir dönüşüm matrisine ve onu etkileyen ışıkların bir listesine sahiptir (hala ileri işleme yapıyor).

Oluşturma iş parçacığım artık herhangi bir kaldırma veya ışık mesafesi hesaplaması yapmak zorunda değil ve bu, büyük sahnelerde işleri önemli ölçüde hızlandırdı.

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.