Minecraft tarzı bir oyunda voksel tabanlı aydınlatmayı oklüzyonla nasıl uygulayabilirim?


13

C # ve XNA kullanıyorum. Mevcut aydınlatma algoritması özyinelemeli bir yöntemdir. Bununla birlikte, her 8 saniyede bir 8x128x8 yığının hesaplandığı noktaya kadar pahalıdır .

  • Değişken-karanlık gölgeler yaratacak başka aydınlatma yöntemleri var mı?
  • Yoksa özyinelemeli yöntem iyi mi, belki de sadece yanlış yapıyorum?

Sadece özyinelemeli şeyler temelde pahalı gibi görünüyor (yığın başına yaklaşık 25 bin blok geçmek zorunda). Işın izlemeye benzer bir yöntem kullanmayı düşünüyordum, ama bunun nasıl çalışacağına dair hiçbir fikrim yok. Denediğim başka bir şey ışık kaynaklarını bir Listede saklamaktı ve her blok için her bir ışık kaynağına olan mesafeyi almak ve bunu doğru seviyeye aydınlatmak için kullanmaktı, ancak daha sonra aydınlatma duvarlardan geçecekti.

Mevcut özyineleme kodum aşağıda. Bu, güneş ışığını ve meşale ışığını temizleyip yeniden ekledikten sonra, yığın içinde ışık seviyesi sıfır olmayan herhangi bir yerden çağrılır.

world.get___atblokları bu yığının dışında alabilen bir işlevdir (bu yığın sınıfının içindedir). Locationkendi yapım a gibi Vector3, ancak kayan nokta değerleri yerine tamsayılar kullanıyor. light[,,]yığın için ışık haritasıdır.

    private void recursiveLight(int x, int y, int z, byte lightLevel)
    {
        Location loc = new Location(x + chunkx * 8, y, z + chunky * 8);
        if (world.getBlockAt(loc).BlockData.isSolid)
            return;
        lightLevel--;
        if (world.getLightAt(loc) >= lightLevel || lightLevel <= 0)
            return;
        if (y < 0 || y > 127 || x < -8 || x > 16 || z < -8 || z > 16)
            return;
        if (x >= 0 && x < 8 && z >= 0 && z < 8)
            light[x, y, z] = lightLevel;

        recursiveLight(x + 1, y, z, lightLevel);
        recursiveLight(x - 1, y, z, lightLevel);
        recursiveLight(x, y + 1, z, lightLevel);
        recursiveLight(x, y - 1, z, lightLevel);
        recursiveLight(x, y, z + 1, lightLevel);
        recursiveLight(x, y, z - 1, lightLevel);
    }

1
Öbek başına 2 milyon blok yapıyorsanız korkunç bir şey yanlıştır - özellikle 8 * 128 * 8 yığınında sadece 8.192 blok olduğu için. Her bloktan ~ 244 kez geçiyorsan ne yapabilirdin? (255 olabilir mi?)
doppelgreener

1
Matematiğimi yanlış yaptım. Üzgünüm: S. Değiştirme. Ancak bu kadar çok gitmeniz gerekmesinin nedeni, ayarınızdakinden daha büyük bir ışık seviyesine ulaşana kadar her bloktan "baloncuğu" atmanızdır. Bu, her bloğun gerçek ışık seviyesine ulaşmadan 5-10 kez üzerine yazılabileceği anlamına gelir. 8x8x128x5 = çok

2
Voksellerinizi nasıl saklıyorsunuz? Geçiş sürelerini azaltmak önemlidir.
Samaursa

1
Aydınlatma algoritmanızı gönderebilir misiniz? (bunu kötü
yapıp yapmadığınızı soruyorsunuz

Onları bir dizi "Blok" içinde saklıyorum ve bir Blok malzeme için bir numaralandırma artı gelecekteki kullanım için bir meta veri bayt oluşur.

Yanıtlar:


6
  1. Her ışığın hassas (kayan nokta) konumu ve skaler ışık yarıçapı değeri ile tanımlanan sınırlayıcı küre vardır LR.
  2. Her vokselin merkezinde, ızgaradaki konumundan kolayca hesaplayabileceğiniz hassas (kayan nokta) bir konum vardır.
  3. 8192 vokselin her birini sadece bir kez çalıştırın ve her biri için, |VP - LP| < LRVP'nin vokselin başlangıç ​​noktasına göre konum vektörü ve LPışığın başlangıç ​​noktasına göre konum vektörü olup olmadığını kontrol ederek N ışıklarının küresel sınırlama hacimlerinin her birinin içine girip girmediğine bakın . Yarıçapı mevcut vokselin bulunduğu her ışık için, ışık faktörünü ışık merkezinden uzaklaştırarak artırın |VP - LP|. Bu vektörü normalleştirir ve ardından büyüklüğünü alırsanız, bu 0.0-> 1.0 aralığında olacaktır. Bir vokselin ulaşabileceği maksimum ışık seviyesi 1.0'dır.

Çalışma zamanı olup O(s^3 * n)burada, sşimdi voksel bölgesinin yan uzunluğu (128) ve nışık kaynaklarının sayısıdır. Işık kaynaklarınız sabitse, bu sorun değil. Işık kaynaklarınız gerçek zamanlı olarak hareket ederse, her güncellemenin tamamını yeniden hesaplamak yerine yalnızca deltalar üzerinde çalışabilirsiniz.

Hatta her ışığın etkilediği vokselleri o ışıkta referans olarak saklayabilirsiniz. Bu şekilde, ışık hareket ettiğinde veya yok edildiğinde, tüm kübik ızgarayı tekrar geçmek zorunda kalmadan ışık değerlerini buna göre ayarlayarak sadece bu listeden geçebilirsiniz.


Algoritmasını doğru anladıysam, ışığın bazı köşelere "gitmesi" anlamına gelse bile uzaktaki yerlere ulaşmasına izin vererek bir çeşit sahte-radyoloji yapmaya çalışır. Veya başka bir deyişle, başlangıç ​​noktasından (ışık kaynağı) ve mesafeden (ve ondan ışık zayıflaması) sınırlandırılan maksimum boşluğa sahip "boş" (katı olmayan) boşlukların taşkın doldurma algoritması başlangıç ​​noktasına giden en kısa yol. Yani - şu anda önerdiğin gibi değil.
Martin Sojka

Detay için teşekkürler @MartinSojka. Evet daha çok akıllı bir sel dolgusu gibi geliyor. Küresel aydınlatmaya yönelik herhangi bir girişimde, akıllı optimizasyonlarda bile maliyetler yüksek olma eğilimindedir. Bu yüzden önce bu sorunları 2B'de denemek iyidir ve hatta uzaktan pahalılarsa, 3B'de ellerinizde kesin bir zorluk yaşayacağınızı bilin.
Mühendis

4

Minecraft'ın kendisi güneş ışığını bu şekilde yapmaz.

Güneş ışığını yukarıdan aşağıya doğru doldurursunuz, her katman bir önceki katmandaki komşu voksellerden zayıflama ile ışık toplar. Çok hızlı - tek geçiş, liste yok, veri yapısı yok, özyineleme yok.

Daha sonraki bir geçişte meşaleler ve diğer sel olmayan ışıkları eklemelisiniz.

Fantezi yönlü ışık yayılımı vb.Dahil olmak için bunu yapmanın başka birçok yolu vardır, ancak açıkça daha yavaştırlar ve bu cezalar verildiğinde ek gerçekçiliğe yatırım yapmak isteyip istemediğinizi anlamanız gerekir.


Bekle, öyleyse Minecraft tam olarak nasıl yapıyor? Söylediklerinizi tam olarak anlayamadım ... "Her katman, zayıflama ile bir önceki katmandaki komşu voksellerden ışık topluyor" ne anlama geliyor?

2
En üstteki katmanla (sabit yükseklikte dilim) başlayın. Güneş ışığı ile doldurun. Sonra aşağıdaki katmana gidin ve oradaki her voksel aydınlatmasını bir önceki katmandaki (üzerindeki) en yakın voksellerden alır. Katı voksellere sıfır ışık koyun. "Çekirdek" e karar vermek için birkaç yolunuz var, yukarıdaki voksellerden gelen katkıların ağırlıkları, Minecraft bulunan maksimum değeri kullanır, ancak yayılma düz değilse, 1 azaltır. Bu yanal zayıflamadır, bu nedenle bloke edilmemiş dikey voksel sütunları tam güneş ışığının yayılmasını ve köşelerin etrafındaki ışıkların bükülmesini sağlayacaktır.
Bjorn Wesen

1
Bu yöntemin hiçbir şekilde herhangi bir gerçek fiziğe dayanmadığını lütfen unutmayın :) Temel sorun, esas olarak, yönsüz bir ışığa (atmosferik saçılma) yaklaşmak ve basit bir sezgiselliği ile zıplayan radyoyu yaklaştırmaktır. Oldukça iyi görünüyor.
Bjorn Wesen

3
Asılı bir "dudak" ne olacak, ışık oraya nasıl çıkıyor? Işık nasıl yukarı doğru hareket eder? Yalnızca yukarıdan aşağıya gittiğinizde çıkıntıları doldurmak için yukarı geri dönemezsiniz. Ayrıca meşaleler / diğer ışık kaynakları. Bunu nasıl yapardın? (sadece aşağı inebilirler!)

1
@Felheart: Buna bir süre baktım, ancak özünde, çıkıntıların altında genellikle yeterli olan minimum bir ortam ışığı seviyesi var, bu yüzden tamamen siyah değiller. Bunu kendim uyguladığımda, aşağıdan yukarıya ikinci bir geçiş ekledim, ancak ortam yöntemine kıyasla gerçekten büyük bir estetik gelişme görmedim. Meşaleler / spot ışıkları ayrı ayrı ele alınmalıdır - Bence bir duvarın ortasına bir meşale koyar ve biraz deney yaparsanız MC'de kullanılan yayılma modelini görebilirsiniz. Testlerimde, onları ayrı bir ışık alanında yayarım, sonra eklerim.
Bjorn Wesen

3

Birisi çözdüysen kendi sorunuza cevap vermesini söyledi, evet. Bir yöntem buldum.

Ne yapıyorum bu: İlk olarak, yığın üzerinde bindirilmiş "zaten değiştirilmiş bloklar" bir 3d boolean dizi oluşturun. Sonra, güneş ışığı, fener, vb doldurun (sadece açık olan bloğu aydınlatır, henüz sel doldurmaz). Bir şeyi değiştirdiyseniz, o konumdaki "değiştirilen bloklar" a doğru basın. Ayrıca olsa gidin ve her katı blok (ve dolayısıyla aydınlatma hesaplamak için gerek yok) "zaten değişti" olarak değiştirin.

Şimdi ağır şeyler için: 16 geçişli (her ışık seviyesi için) tüm parçaya gidin ve eğer 'zaten değişmişse' devam edin. Sonra kendi etrafındaki bloklar için ışık seviyesini elde edin. En yüksek ışık seviyesini elde edin. Bu ışık seviyesi, geçerli geçişlerin ışık seviyesine eşitse, bulunduğunuz bloğu geçerli seviyeye ayarlayın ve o konum için "zaten değiştirilmiş" değerini true olarak ayarlayın. Devam et.

Bunun karmaşık olduğunu biliyorum, elimden gelenin en iyisini yapmaya çalıştım. Ama önemli olan, işe yarıyor ve hızlı.


2

Çoklu geçiş çözümünüzü orijinal özyinelemeli yöntemle birleştiren ve büyük olasılıkla her ikisinden de biraz daha hızlı bir algoritma öneririm .

Her bir ışık seviyesi için bir tane olmak üzere 16 listeye (veya her türlü koleksiyona) ihtiyacınız olacaktır. (Aslında, bu algoritmayı daha az liste kullanacak şekilde optimize etmenin yolları vardır, ancak bu şekilde tanımlanması en kolay yöntemdir.)

İlk olarak, listeleri temizleyin ve tüm blokların ışık seviyesini sıfıra ayarlayın ve ardından ışık çözümlerini geçerli çözümünüzde yaptığınız gibi başlatın. Bundan sonra (veya sırasında), sıfır olmayan ışık seviyesine sahip blokları ilgili listeye ekleyin.

Şimdi, ışık seviyesi 16 olan bloklar listesini gözden geçirin. Yanlarındaki bloklardan herhangi biri 15'ten az ışık seviyesine sahipse, ışık seviyelerini 15'e ayarlayın ve uygun listeye ekleyin. (Zaten başka bir listedeyse, onları listeden kaldırabilirsiniz, ancak yapmasanız bile zarar vermez.)

Ardından, diğer tüm listeler için parlaklık sırasını azaltarak aynı işlemi tekrarlayın. Listedeki bir bloğun zaten bu listede olması gerekenden daha yüksek bir ışık seviyesine sahip olduğunu fark ederseniz, zaten işlendiğini ve komşularını kontrol etmeyi bile zahmet etmediğinizi varsayabilirsiniz. (Sonra tekrar, sadece komşuları kontrol etmek daha hızlı olabilir - bu ne sıklıkta olduğuna bağlıdır. Muhtemelen her iki şekilde de denemeli ve hangi yolun daha hızlı olduğunu görmelisiniz.)

Listelerin nasıl saklanması gerektiğini belirtmediğimi not edebilirsiniz; Gerçekten de, herhangi bir makul uygulama, belirli bir bloğu eklemek ve keyfi bir bloğu çıkarmak olduğu sürece, hızlı işlemlerdir. Bağlantılı bir liste işe yarayacaktır, ancak değişken uzunluktaki dizilerin yarıya uygun olarak uygulanması da faydalı olacaktır. Sadece sizin için en uygun olanı kullanın.


Zeyilname: Işıklarınızın çoğu çok sık hareket etmiyorsa (ve duvarları da hareket etmezse), yanan her blok için ışık kaynağını (veya ışıklardan birini) belirten bir işaretçi saklamak daha da hızlı olabilir. birkaç tane bağlıysa). Bu şekilde, küresel aydınlatma güncellemelerinden neredeyse tamamen kaçınabilirsiniz: yeni bir ışık kaynağı eklenirse (veya mevcut bir ışıklı ortam aydınlatılmışsa), etrafındaki bloklar için yalnızca tek bir özyinelemeli ışıklandırma geçişi yapmanız gerekir. soluk), yalnızca ona işaret eden blokları güncellemeniz gerekir.

Duvar değişikliklerini bile bu şekilde halledebilirsiniz: bir duvar kaldırıldığında, o blokta yeni bir yinelenen aydınlatma geçişi başlatmanız yeterlidir; Biri eklendiğinde, yeni duvarlı blokla aynı ışık kaynağına işaret eden tüm bloklar için bir aydınlatma yeniden hesaplaması yapın.

(Aynı anda birden fazla aydınlatma değişikliği meydana gelirse - örneğin bir kaldırma ve ekleme olarak sayılan bir ışık hareket ettirildiyse - yukarıdaki algoritmayı kullanarak güncellemeleri tek bir kombinasyonda birleştirmelisiniz. kaldırılan ışık kaynaklarına işaret eden blokları, etrafındaki yanan blokları ve yeni ışık kaynaklarını (veya sıfırlanmış alanlarda mevcut ışık kaynaklarını) uygun listelere ekleyin ve güncellemeleri yukarıdaki gibi çalıştırın.)

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.