Skip to content

Tarayıcıda açık dünya inşa etmek, bölüm 14: Dünya canlanıyor

Oleg Sidorkin, Cinevva CTO'su ve Kurucu Ortağı

Burada yeni misiniz? Seri rehberine göz atın. Rehber, spike'ın ne olduğunu açıklıyor ve tüm bölümlere bağlantı veriyor.

13. bölümden sonra arazi şekillendirme özelliğimiz vardı. Araziyi yükseltebiliyor, mağaralar oyabiliyor, uçurumları yumuşatabiliyorduk; üstelik bunların hepsi gerçek zamanlı gerçekleşiyor ve dikişler bozulmadan kalıyordu. Ancak dünya bir teknoloji demosu gibi görünüyordu. Hata ayıklama renkleriyle düz gölgelendirme, tel kafes katmanları, parçaları birbirinden ayırmak için LOD renkleri taşıyan gri geometri. Dünyayı düzenleyebiliyordunuz. Ama onu hissedemiyordunuz.

Bunu üç spike değiştirdi. Mimaride hiçbir değişiklik olmadı. 13. bölümdeki işlem hattı, tamponlar ve dikiş birleştirme sistemi aynen kaldı. Yalnızca araziyi gerçek bir mekân gibi hissettiren katmanları ekledik: şekle tepki veren yüzeyler, bu yüzeylerde büyüyen yaşam ve üzerlerinde yürüyen bir beden.

“Teknik olarak çalışıyor” ile “burada kalmak istiyorum” arasındaki farkın şaşırtıcı derecede küçük olduğu ortaya çıktı.

Bir uçurum şekillendirin ve kayaya dönüşmesini izleyin

Spike 28 dar kapsamlı bir soru sordu: triplanar eşlemeye sahip 4 katmanlı bir malzeme, kare süresi bütçesini tüketmeden hesaplama yoluyla üretilmiş arazide çalışabilir mi? Yanıt evetti, ancak asıl ilginç olan bundan sonra yaşananlardı.

Spike 28'i yeni bir sekmede aç ↗ · Kaynak kodunu görüntüle

Dört prosedürel doku (çim, kaya, kum ve kar), başlangıçta FBM gürültüsünden üretiliyor. Harici dosya yok, varlık işlem hattı yok; yalnızca matematik ve bir DataTexture var. Malzeme ağırlıkları yüzeyin kendisinden geliyor: eğim ve rakım. Ağaç sınırının altındaki düz alanlar çimle kaplanıyor. Dik yüzeyler kayaya dönüşüyor. Alçak zeminlere kum, yüksek zirvelere kar geliyor. Her ham ağırlık, eğim ve rakım üzerinde bir smoothstep işleminden elde ediliyor; ardından parça başına normalize edilerek iwi=1 olması sağlanıyor ve nihai renk yalnızca ağırlıklı karışım iwici oluyor. Normalizasyon, biyomların örtüştüğü yerlerde yüzeyin kararmasını veya aşırı parlaklaşmasını engelliyor. Triplanar eşleme ise üçgenlerin anlamlı UV koordinatlarına sahip olmadığı MC parçalarındaki UV izdüşümünü hallediyor.

Bizi ikna eden an şuydu: fırçayla araziyi yükseltip dik bir uçurum oluşturduğunuzda, yeni yüzeyde aynı kare içinde kaya dokusu beliriyor. Tekrar düzleştirdiğinizde yüzeyi yeniden çim kaplıyor. Malzemenin fırçadan haberi yok. Yalnızca dünya konumunu ve yüzey normalini, yani geometrinin oluşturulduğu verilerin aynısını okuyor. Şekillendirme ile görsel geri bildirim arasındaki döngü anlık ve önceden betiklenmemiş.

Spike 8, bir buçuk ay önce WebGL üzerinde statik bir mesh kullanarak arazi malzemesinin maliyetini test etmişti. Spike 28 ise bunun, altında tam şekillendirme işlem hattı çalışan dinamik ve hesaplama yoluyla üretilmiş arazide de işe yaradığını kanıtlıyor. Bunun için bütçe ayırmıştık ama her şey çalışırken 60 fps'yi koruduğunu görmek yine de rahatlatıcıydı.

80.000 çim kümesi ve bir mağara tavanı

Spike 29'da oyun geliştirme içgüdülerimiz devreye girdi.

Spike 29'u yeni bir sekmede aç ↗ · Kaynak kodunu görüntüle

Breath of the Wild'daki gibi çimler istiyorduk. Poligon sayısını değil, hissini. Doğru yerleri kaplayan, rüzgârda hareket eden ve içinden koşma isteği uyandıran çimler.

Her çim kümesi, 60 derecelik açılarla kesişen üç dörtgenden ve bükülme için dört dikey segmentten oluşuyor. Billboard hileleri kullanmadan her açıdan hacimli görünen bir haç şekli. Parça başına bir InstancedMesh, dünya genelinde 80.000 çim kümesi ve toplamda yaklaşık 2,4 milyon çim köşesi.

Asıl önemli kısım yerleştirme. CPU, her parça boyunca titreşimli bir ızgarada ilerliyor ve GPU malzeme shader'ının kullandığı eğim/rakım mantığının aynısını değerlendiriyor. Malzeme sisteminin “çim” dediği yerde çim büyüyor. Kaya veya kumun baskın olduğu yerlerde yoğunluk sıfıra düşüyor. Geçiş yumuşak, çünkü temeldeki smoothstep ağırlıkları biyom kenarlarında sürekli gradyanlar oluşturuyor. Bir sınır fark etmiyorsunuz, çünkü ortada bir sınır yok.

Ardından işe yarayıp yaramayacağından emin olmadığım bir şey yaptık: SDF yüzeylerinde çim. Dağıtma fonksiyonu, SDF hacminin her sütununda ilerliyor ve komşu vokseller arasındaki sıfır geçişlerini buluyor. SDF'nin negatiften pozitife geçtiği yerde bir yüzey vardır. Yüzey normali, alanın normalize edilmiş gradyanıdır: n^=ϕϕ. Bu değer voksel farklarından hesaplanır; eğim ise dikeyden ölçülen açı olan arccos(n^y^) değeridir. Yeterince yatıksa oraya çim yerleştirilir.

Spike 27'de SDF fırçasıyla bir mağara oyun. Spike 29'a geri döndüğünüzde mağara tavanının üzerinde çimler büyüdüğünü görürsünüz. Dağıtma kodu mağaranın ne olduğunu bilmiyor. Yalnızca doğru rakımda, uygun eğime sahip bir yüzey görüyor. Açık dünya sistemleri geliştirmeyi tatmin edici kılan türden bir beliren davranış bu.

Rüzgâr, TSL vertex shader'ındaki bir sinüs dalgası. Bu dalga, çimin V koordinatıyla modüle edildiği için uçlar sallanırken kökler sabit kalıyor. Hafif, güçlü ve kapalı seçenekleri arasında geçiş yapmak için V tuşuna basın. Rüzgâr tamamen vertex shader'ında çalıştığından, 80 bin kümenin tamamını sıfır CPU maliyetiyle aynı anda etkiliyor.

Spike 7'de 50 bin çim örneğini test etmiş ve sınıra ulaşmaktan endişelenmiştik. Spike 29; hesaplamalı arazi, dikiş birleştirme, çok malzemeli dokulama ve fırçaların üzerine 80 bin örnek çalıştırıyor. Daha az sayıda InstancedMesh çiziminde gruplamak, çim başına köşe sayısını azaltmaktan hâlâ daha önemli. Spike 7'den çıkardığımız ders geçerliliğini korudu.

Ertelediğimiz bir şey var: altındaki araziyi şekillendirdiğinizde çimler güncellenmiyor. Örnek matrisleri dağıtma anında ayarlanıyor. Bir tepeyi vadiye dönüştürürseniz, yeniden dağıtmak için G tuşuna basana kadar çimler havada asılı kalıyor. Bir spike için yeterince iyi. Üretim sürümünde kirli parçaların yeniden dağıtılması gerekecek.

İlk ayak sesi

Spike 30, dönüp dönüp baktığım çalışma oldu.

Spike 30'u yeni bir sekmede aç ↗ · Kaynak kodunu görüntüle

Fizik kütüphanesi yok. 120 Hz sabit zaman adımında çalışan özel bir kapsül var. Üretim kod tabanı, bir web worker içinde Rapier kullanıyor (Spike 2 gecikmenin sorun olmadığını kanıtladı), ancak bu spike'ın çarpışma sorgularını doğrulaması gerekiyordu. Bir karakter yükseklik haritası arazisinde yürüyebilir, şekillendirilmiş SDF arazisine adım atabilir ve içinden düşmeden kalabilir mi?

Sistemin merkezinde terrainQuery(x, y, z) adlı bir fonksiyon bulunuyor. Bu fonksiyon, konumun MC'ye kilitlenmiş bir parçanın içinde olup olmadığını kontrol ediyor. İçindeyse CPU'daki SDF kopyasında trilineer enterpolasyon yapıyor ve yüzey normali olarak gradyanı döndürüyor. Değilse merkezi fark normaliyle yükseklik haritası sorgusu yapıyor. Karakter hangi arazi sisteminin üzerinde durduğunu bilmiyor. Yalnızca zemini soruyor ve bir yanıt alıyor.

SDF çarpışmasının zor olmasını bekliyordum. Kapsülün çevresinde yedi sonda noktası var: alt, orta, üst ve dört ana yöndeki ofset. x konumundaki bir sonda, ϕ(x)<r olduğunda yüzeye nüfuz ediyor. Nüfuz derinliği p=rϕ(x), dışarı itme yönü ise alan gradyanı n^=ϕϕ. Kapsülün yüzeye yeniden girmesini önlemek için hız, içeri doğru bileşenini sıfırlayacak biçimde izdüşürülüyor:

v=v(vn^)n^

Böylece yalnızca yüzey boyunca kayan bileşen kalıyor. Bu bir fizik motoru değil. Geometri sorguları ve basit tepkilerden ibaret. Ancak mağaraları, çıkıntıları ve şekillendirilmiş tünelleri, şekil başına tek bir özel durum kodu olmadan işliyor. On saniye önce oyduğunuz bir mağaraya girdiğinizde kapsül tavanın hatlarını tam olarak takip ediyor.

Hareket modeli pratik bir yaklaşımla başladı, sonra eğlenceli hâle geldi. Yürüme, koşma, depar, zıplama ve 45 derecenin üzerindeki eğimlerde kayma. Ardından BotW tarzı bir planör ekledim ve spike kapatmak istemediğim bir şeye dönüştü.

Havadayken Space tuşuna basın. Yerçekimi -30'dan -4'e düşer. Düşüş hızı -3 ile sınırlandırılır. Kapsülden, ölçek enterpolasyonuyla bir delta kanat mesh'i açılır. Sola yatış yaptığınızda kanat eğilir. Kapsülün baktığı yön, hız vektörüne otomatik olarak hizalanır; böylece her zaman gittiğiniz yöne bakarsınız. Düşmek için Space tuşunu bırakın. Yere indiğinizde yeniden koşmaya başlarsınız.

Kamera geri çekilerek üçüncü şahıs görünümüne geçiyor (P ile değiştirin). Yaw yumuşatmasıyla oyuncuyu arkadan takip ediyor. Oyuncudan kameraya doğru yapılan bir ışın yürüyüşü, araziyi 20 adımda test ediyor. Bir mağaraya uçtuğunuzda kamera kolu kayaların içinden geçmek yerine yumuşakça kısalıyor. Diğer taraftan çıktığınızda yeniden uzuyor.

Spike'ı yapmaya değer kılan an şuydu: yükseklik haritası fırçasıyla yüksek bir uçurum şekillendirin. Üçüncü şahıs kamerasına geçin. Kenara doğru koşun. Zıplayın. Planörü açın. Aşağınızda Spike 29'da yetiştirdiğiniz çimler dalgalanırken, uçurum yüzeyinde Spike 28'in kaya dokusu görünürken ve Transvoxel dikişleri her parça sınırında sağlam kalırken az önce şekillendirdiğiniz arazinin üzerinde sola yatış yapın. Karşı tarafa inin. Her şey tek bir tarayıcı sekmesinde birlikte çalışıyor.

Kendi ayaklarınızın altını şekillendirmek

Emin olmadığım bir şey vardı: karakterin üzerinde durduğu araziyi şekillendirdiğinizde ne olur? Yükseklik haritasındaki değişiklikler, CPU tamponunu doğrudan okuduğu için terrainQuery() fonksiyonuna anında yansıyor. SDF değişiklikleri de CPU'daki SDF kopyasına aktarılıyor. Kapsül, nüfuzu bir sonraki fizik tikinde çözüyor; 120 Hz'de bu, 11208.3 ms içinde gerçekleşiyor.

Öylece çalışıyor. Oyuncunun altındaki zemini yükselttiğinizde oyuncu da onunla birlikte yükseliyor. Zemini oyduğunuzda düşüyor. Özel bir işleme gerek yok. Fizik sistemi yeterince hızlı çalıştığı için tek karelik arazi değişiklikleri hiçbir zaman büyük nüfuzlara yol açmıyor. Bu, zaman adımı için 120 Hz seçmemizin mutlu bir tesadüfüydü. Bu değeri akıcı hareket için seçmiştik; arazi düzenlemeyi de hiçbir ek maliyet olmadan güvenli hâle getirdi.

30 spike sonra

Bu seriye düz bir yükseklik haritası ve 500 küple başladık. Şimdi hacimsel mağaralara, Transvoxel dikiş birleştirmesine, yüzey şekline tepki veren çok malzemeli dokulamaya, rüzgârda salınan 80.000 çim kümesine ve tüm bunların üzerinde yürüyen, depar atan, zıplayan ve süzülen bir karaktere sahip şekillendirilmiş bir arazimiz var.

Bunların hiçbiri henüz üretim sürümünde değil. world/client/ kod tabanı hâlâ basit yükseklik haritası parçalarıyla WebGL üzerinde çalışıyor. Bu 30 spike'taki her şey bağımsız HTML sayfalarında yaşıyor. Sırada entegrasyon işi var: WebGPURenderer sistemine geçmek, hibrit HM/MC politikasını parça yöneticisine bağlamak ve fırça ile malzeme sistemlerini çok oyunculu yapıya entegre etmek.

Ancak render sistemi artık riskli kısım değil. Açık sorular veri akışıyla ilgili: düzenlemelerin kalıcı hâle getirilmesi, fırça darbelerinin ağ üzerinden senkronizasyonu ve iş birliğine dayalı şekillendirme. Yani üçgenler arasında değil, oyuncular arasında gerçekleşen şeyler.

Bu seriyi 1. bölümden beri takip ediyorsanız, dağınık kısımlarda da bizimle kaldığınız için teşekkürler. Seriyi yeni keşfettiyseniz 1. bölüme dönün. Dersler yanlış dönüşlerde saklıdır.

Bu bölümde değinilen teknolojiler

TSL (Three Shading Language). Three.js'in WebGPU renderer için geliştirilmiş, düğüm tabanlı shader sistemi. Malzemeler, JavaScript'te fonksiyon bileşimi kullanılarak düğümlerden (positionWorld, normalWorld, smoothstep, triplanarTexture) oluşturulur. Shader grafiği çalışma zamanında WGSL'ye derlenir. TSL, WebGPU hedeflerinde ham GLSL ShaderMaterial kullanımının yerini alır ve standart Three.js malzeme özellikleriyle (ışıklar, gölgeler, sis) özel parça başına mantık arasında birlikte çalışabilirlik sağlar.

Triplanar eşleme. Bir dokuyu üç kez (XY, XZ ve YZ düzlemlerinde) örnekleyen ve örnekleri yüzey normalinin yönüne göre harmanlayan bir doku izdüşüm tekniği. Bu teknik, rastgele mesh geometrilerindeki UV gerilmesini ortadan kaldırır. Üçgenlerin anlamlı UV koordinatlarına sahip olmadığı marching cubes çıktıları için bu kritik öneme sahiptir. TSL, yerleşik bir düğüm olarak triplanarTexture() sağlar.

Çapraz dörtgen çimlerle örneklenmiş bitki örtüsü. Her çim kümesi, 60 derecelik açılarla kesişen 3 dörtgenden oluşur ve her bakış yönünden hacimli görünür. Dörtgen başına 4 dikey segment, rüzgâr animasyonu için yumuşak bükülmeye olanak tanır. Tüm alan, parça başına tek bir InstancedMesh olarak render edilir. Kapasite %25 fazla ayrılır; böylece arazi düzenlendiğinde yeni örnekler GPU tamponunu yeniden ayırmadan rezerve edilmiş yuvalara yerleştirilebilir. Bitki örtüsüyle ilgili peyzaj oluşturma rehberimize bakın. Kapsül ve SDF çarpışması. Fizik motoru olmadan hacimsel araziyle karakter çarpışması. Kapsül, birden fazla noktada SDF'e karşı test edilir. Alan değerinin kapsül yarıçapından küçük olduğu yerlerde gradyan dışa dönük normali, aradaki fark ise iç içe geçme derinliğini verir. Bu yaklaşım mağaraları, çıkıntıları ve tünelleri şekle özgü kod gerektirmeden işler. Bkz. SDF arazi çarpışmaları.

Sabit zaman adımlı karakter kontrolcüsü. Fizik, kare hızından bağımsız olarak 120 Hz'de ilerler; gerçek zamanı biriktirir ve sabit boyutlu adımlarla işler. Maksimum alt adım sayısı, yavaş karelerde ölüm sarmalını önler. Zemine yapışma, eğimlerde ilerlerken kapsülün yüzeyle temasını korur. Sabit zaman adımı, gelecekteki çok oyunculu tekrar oynatımı için deterministik davranış sağlar.


29 bölümün 14. bölümü. Önceki: 13. Bölüm - Arazi şekillendirme ve matematik fonksiyonunun sonu Sonraki: 15. Bölüm - Temel sürümü değiştir, ardından eşitle Seri rehberi: /tr/blog/2026-02-25-open-world-browser-series-guide