Cnrtnbl

Unreal MVP
24 Kas 2020
216
162
43
36
(35) İzmir
Herkese merhaba benim ustalara bir sorum olacak. Önce yapmak istediğimi anlatayım.

Yaklaşık 4 gündür ileride kullanmayı düşündüğüm bir özellik üzerine araştırmalar yapıyorum. Sonsuz prosedürel oluşturma ve zeminle oyun içinde oynama. Unreal Engine de bu özellik yok. Voxel Plugin pro ile bunu yapabiliyorsun fiyatı sanırım 3000 TL ye yakın tutuyordu. Bir plugin ile yapılabiliyorsa bir yolu var demek ki dedim ve nasıl yapabilirim araştırmaya başladım. Sonuç olarak procedural mesh component ile gayet güzel yapabildim.

Süreç basitçe şöyle: Birkaç kaç farklı frekanstaki perlin noise üst üste birleştirilip bir zemin yapılabiliyor. Tabi bu çok sıradan görünüyor. Gerçekçi ve daha dramatik dağlar ve zeminler için varyasyon ve biraz daha modifiye bir fonksiyon lazımdı. World creator 2 de her katmanı detaylı incelemeye çalıştım. Gördüm ki küçük detaylar yamaçları daha çok etkileyecek şekilde kodlanmış. Eğimin olmadığı yerler daha düzgün kalıyor. Filtreler de yamaçları daha çok etkiliyor.

Yani bir şekilde perlin noise fonksiyonunda yamaçta olduğunu anlayıp ona göre küçük detayların etkisini değiştirecek bir şekilde modifiye ettim. Şu an oynanıp kurulabilecek genişlikte düz pürüzsüz zeminler de pürüzlü düzensiz yamaçlar elde edebiliyorum.

Kod şu şekilde isteyen varsa:

// Main custom procedural noise function

float AProceduralTerrain::CustomProceduralNoise(const float fx, const float fy, const FNoiseParameters& parameters)
{
float fxLocal = fx;
float fyLocal = fy;
float amplitutude = 0.5f;
float gain = parameters.gain;
float lacunarity = parameters.lacunarity;
float value = 0.5f;
int32 octaves = parameters.octave;

float octave2Height = 0.0f;
float slopeAmplitude = 1.0f;

for (int i = 0; i <octaves; i++)
{

value += FMath::perlinNoise2D(FVector2D(fxLocal, fyLocal))*amplitutude*slopeAmplitude;
fxLocal *= lacunarity;
fyLocal *= lacunarity;
amplitutude *= gain;
if (i == 1)
{
octave2Height = value;
slopeAmplitude = GetSlopeAmplitude(octave2Height, parameters);
slope = octave2Height;
}
}

return value;
}


Bu fonksiyondan çıkan noise değeri -1 ile 1.5 arasında değişebiliyor. Bunu bir maksimum yükseklik değeriyle çarpıp hesaplanan noktanın X, Y koordinatını girince bize bir vertex vermiş oluyor. 

Bu fonksiyonu şöyle çağırıyoruz. Diyelim ki 100 metre * 100 metre bir alan yaratacağız. her vertex arası 1 metre olursa size X = 100, sizeY = 100 oluyor. Her bir nokta için bir hesaplama yapacağız. Bir hesaplamada 10.000 loop eder. Bu 10 bin loop içinde bir de 5-7 tane oktav hesaplayacağız olacak size 50.000-100.000 arası hesaplama.

Bitmedi bir de bu hesaplanan noktaları birbirine bağlayıp triangle yapcağız. Sonra UV hesaplayıp normal ve tanjantlarını hesaplayıp procedural mesh componentte create mesh section deyip bir de bu büyüklükte alan için colision ayarlatacağız. 1 milyona yaklaşıyor hesaplama sayısı.

Peki bu işlem kaç saniye sürüyor derseniz yaklaşık 5-6 saniye kitliyor oyunu. Ayrıca 150*150 ve üzeri büyüklükte iterasyon 1 milyonu geçti deyip hesaplamıyor bile. Başka sorunlar da var LOD ayarlayamamak gibi.

Çıkan sonucun resmi şu şekilde:
i9oixr8.png
[/url][/img]

Araştırmaya devam edip voxel plugin bunu nasıl yapmış diye anlamaya çalıştım. Multithread ile arka planda yaptığını düşünüyordum. Free versiyonunu indirdim ve ufak chunklara bölünmüş şekide. Her tik bir chunk güncelleniyor gibi. Güncelleme işlemi başka threadda yapılıyor gibi.

Bende 100*100 yerine 20*20 yapayım dedim ve her tick bir chunk hesaplanıp haritaya eklenecekti. Bu sandığımdan hızlı oldu ve her bir hesaplama yaklaşık 2ms tuttu ve fps'e etki bile etmedi. 2-3 saniye içinde 300-400 chunk güncelleyip yaratabildim.

Bir gözlem daha yapmak için sadece hesaplamalar kaç ms tutuyor diye baktım. Tüm o vektör hesaplamaları matematiksel işlemler 1ms tutuyor, 1ms de komponentin yaratılması tutuyor. 

Şimdi sorum şu. Ben bu işlemin neresini, ne kadarını başka threadda yapabilirim? Hiç detaylı bir bilgi bulamadım bununla alakalı. Diğer threadlarla direkt iletişim kuramadığımızı, crash yedirecek riskler olduğunu biliyorum. Ama meraktan bu bu threadlara ramdeki adres bilgilerini aktarabiliyorsak bu threadda referans gönderip değişiklik yapabiliyor muyuz diye baktım. Bazıları evet diyor, bazıları yapmayın diyor. Ama sonunda şunu okudum. Procedural mesh componentte create mesh section fonksiyonu game threadda çağırılmak zorundaymış o yüzden o 1ms her türlü bana mal olacak.

Peki ben bu matematiksel vektör hesaplamalarını başka threadda yapıp sonra game threadda nasıl kullanacağım? Bu konuda hiç tecrubem yok. Neyin ne kadarını yapabilirim aydınlatabilirseniz çok sevinirim.
 
Multithread programlama dedigimiz sey aslinda operating systemin bize processi fork ettigi zamanda, o processe ait olan address space'in belirli porsiyonlara belirli threadleri atayip CPU Cycle duzenini nasil etkili kullanacagina dair bir programlama. 

Sen aktor uzerinden ilerliyorsn aslinda kendi threadini GameThread uzerinde yaratiyorsun motorda bir cok farkli thread var, GameThreadNetworkThreadRenderThread vs... bunlarin hepsi oyun motorunun baslangic fonksiyonunda birbirine join(birlikte calistirilarak) edilerek es zamanli olarak calismaya basliyor, durdurulabiliniyor. 

Mesela oyun pause edildigi zaman, GameThread paused thread konumunu aliyor, CPU Cycle dedigimiz yerde isleme sokulmuyor. Ama sen sonucta render edilen image goruyorsun RenderThread vesaire.

Yaratilan her bir threadin kendine ait stackprogram counter ve data segment dedigimiz alani oluyor. Ve kendine ait bir thread address space oluyor. Thread process'in lightweight edilmis olayi.

PrimeNumber, basit olarak 0 dan belirli sayiya kadar olan sayilari toplattir cart curt gibi hesaplamalar yaparken thread safety acisindan yasayabilecegin bir problem yok. Cunku onun degerleri belli ilk threade atiyorum sen 1 ile 50 arasini topla, ikinciye 50 ile 100 arasini toplattiriver diyosun.

Ama birden cok thread AYNI eger address space erisiyorsa (global variablesstatic degiskenler vs..) o zaman race condition denilan  bakmak gerekiyor cunku race condition saglanmasa ayni anda eristikleri address spacedeki program counterin nerede hesaplayacagini birden cok thread calistigi icin bilemeyiz.

Sorunu bunun cozumu cok var semaphorelardan tut, dining philosophiers denilen  ama en basit yapim sekli Mutual Exclusion kullanmak.

Unreal uzerinde FRunnable uzerinden derive ediyorsun multithread edecegin fonksiyonu, Bunu FRunnableThread e pointer olusturup yaratiyorsun

Race conditionlari onlemek icin

Tabii bunun deadlock kismi da var ::) O da iki ayri thread birbirini wait etmesi olayi, yani birisinin resource digerinin resource bekliyor, obur threadin resourcesi da digerini bekliyor

Race condiiton dedigimiz de, mutual exclusiona girmemis olan global variablenin iki ayri thread space kullanip birbirlerinin degerini degsitirmesi olayi

Eger bilgisayar bilimlerine merak sardiysan sana cok guzel bir dining philosophers problem gercekten bilgisayar bilimlerine en komik orneklerden birisidir :)

Yolluyorum belki ilgini cekerse:
https://www.wikiwand.com/en/Dining_philosophers_problem