Tarayıcıda açık dünya oluşturmak, bölüm 19: Bir ormanda hayatta kalması gereken imposter
Oleg Sidorkin, Cinevva Teknoloji Direktörü 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.
18. Bölüm, içerik üreticilerine bir yamacı ağaçlarla dolduran bir fırça sundu. Ancak ekranda on binlerce ağaç olduğunda bunların maliyeti sorun hâline geliyor. Uzaktaki bir ağacın dört piksele katkıda bulunmak için 2.000 üçgene ihtiyacı yoktur. Bu bölüm, en uç LOD seviyesini ele alıyor: Üzerinde ağacın fotoğrafını taşıyan düz bir dörtgen olan imposter ve “doğru görünen tek bir ağaçtan” GPU üzerindeki bir milyon ağaca uzanan yol.
Bir ağaç, billboard üzerindeki iki dokudur
Spike 38'i yeni sekmede aç ↗ · Kaynak kodunu görüntüle
Bir imposter, bir nesneyi farklı bakış açılarından oluşan bir ızgara üzerinden önceden render ederek biri renk, diğeri dünya uzayı normalleri için olmak üzere iki doku atlasına dönüştürür. Ardından çalışma zamanında, mevcut görüşle eşleşen döşemeyi örnekleyen ve kameraya dönük tek bir dörtgen gösterir. Pişirme işlemi her döşeme için iki geçişten oluşur: Aydınlatmanın dokuya işlenmemesi için ışıksız bir malzemeyle diffuse geçişi ve silüetin piksel piksele eşleşmesi için kaynak alfayı aktaran, normalWorld × 0.5 + 0.5 olarak kodlanmış normal geçişi. Çalışma zamanı malzemesi tam bir MeshStandardNodeMaterial olduğundan imposter, diğer tüm yüzeyler gibi sahnenin güneş ışığını ve IBL'sini almaya devam eder. Kazanç geometridedir: Binlerce üçgen yerine tek bir dörtgen ve ayrıntıları taşıyan 1 MB'lık bir doku.
Bunu ayrı bir spike'a taşımak başlı başına bir dersti. Imposter, spike 37'nin dağıtım sistemindeki en uç LOD olarak başlamıştı ve pişirme üzerindeki her yinelemenin; pişirme doğruluğunun örnek matrislerinin taşınması ve LOD geçişleriyle iç içe geçtiği tam dağıtım hattı üzerinden test edilmesi gerekiyordu. Sistemi tek bir nesneye, tek bir dörtgene ve yanında duran orijinal modele ayırmak, yineleme süresini dakikalardan saniyelere indirdi.
Ders kitabındaki yanıt yanlış olduğunda
İlk uygulama, küre yönlerini bir kareye paketlemek için kullanılan klasik eşleme yöntemi olan oktahedral kodlamayı kullanıyordu. Sayısal gidiş-dönüş testlerini geçmesine rağmen kullanıcı, imposter'ın tam karşıdan değil de ağaca hafifçe yukarıdan bakıyormuş gibi görünen bir döşemeye geçtiğini gösteren ekran görüntüleri göndermeyi sürdürüyordu. Ardından altı düzeltme turu geldi (dörtgen başına görüş yönü uniform'u, kare pişirme en-boy oranı, hata ayıklama malzemeleri ve statik billboard yönelimi); bunların her biri gerçekten gerekliydi ama hiçbiri asıl hatayı çözmüyordu. Çözüm ancak “baştan düşün, KISS, geçici yama yok” yaklaşımıyla geldi.
Yeniden yazım, oktahedral katlamayı kaldırıp sade azimut-yükseklik yöntemini kullandı: az = atan2(dir.x, dir.z), el = asin(dir.y), uv = (az/2π, el/π + ½). Kodlamanın tamamı bundan ibaret; L1 normalizasyonu ve sıfırın işaretiyle ilgili köşe durumları yok. Burada daha iyi olmasının nedeni daha doğru olması değil (küreyi daha az düzenli örnekler), CPU'daki hücre seçicinin ve GPU shader'ının aynı temel işlemleri kullanmasıdır. Böylece oktahedral çiftin sessizce yaptığı gibi bir sınır yönünde anlaşmazlığa düşemezler. Atlas bir kontak baskı gibi okunur: Sütun nesnenin çevresindeki açıyı, satır ise yüksekliği gösterir ve overlay üzerinde bir bakışta anlaşılır.
Bundan sonra bile “hafifçe yukarıdan görünüyor” şikâyeti devam etti ve neden kodlama değil, bir niceleme tercihiydi. 4×4 ızgarada satır merkezleri ±22,5° ve ±67,5°'ye denk gelir; dolayısıyla tam 0° yükseklikte bir satır yoktur. Açık ara en yaygın durum olan yatay yönde bakan bir izleyici, her zaman eğimli olarak pişirilmiş bir satıra düşer. Çözüm tek N'dir: 5×5 ızgarada satır merkezleri 0°, ±36° ve ±72°'ye denk gelir; böylece yatay izleyici, tam yatay açıyla pişirilmiş bir döşeme alır. Aynı “yarım hücrelik kayma” hatası ailesi, sonraki bölümün paralaks çalışmasında yeniden karşımıza çıkıyor ve çözümü yine aynı soruda yatıyor: Standart girdi için ayrık örnekleme noktam gerçekten düşündüğüm yere denk geliyor mu?
İki unsur daha önemliydi. Billboard sürekli kameraya dönmek yerine parçalı-statik olmalıdır. Imposter, belirli bir pişirme yönünden çekilmiş düz bir fotoğraftır; bu nedenle çalışma zamanı dörtgeninin görüntü düzlemi, ilgili pişirme kamerasının düzlemiyle eşleşmelidir. Bu da tek bir hücrenin seçili kaldığı yay boyunca yönelimini koruyup sınırda aniden geçiş yapması anlamına gelir. Ayrıca bütçe, oyuncuların gerçekte baktığı yerlere göre dağıtılmalıdır: Daha sonraki bir geçiş, eğimli tepeden görünüm satırlarını tamamen kaldırarak bunların yerine 15° aralıklı 24 yatay halka yuvası ve doğrudan tepeden görünüm için tek bir döşeme kullandı; çünkü ağaçlara neredeyse her zaman göz hizasından bakılır.
Ani geçiş ve harmanlamanın bunu ortadan kaldırması
Spike 42'yi yeni sekmede aç ↗ · Kaynak kodunu görüntüle
Parçalı-statik billboard uzakta fark edilmez ancak yakından bakıldığında ani geçiş yapar; kamerayla çevresinde dönene kadar bu kabul edilebilir. Spike 42, titreşimi izole edip ortadan kaldırmak için dört varyantı yan yana yerleştirdi: Orijinal nesne, azimut/yükseklik 5×5 temel sürümü ve iki hemi-oktahedral ızgara. Ani geçişe iki artefakt neden olur. Hücre sıçraması, fragment shader'ın görüş yönünü 25 hücreden birine nicelemesi nedeniyle oluşur; dolayısıyla bir sınır geçildiğinde örneklenen döşeme değiştirilir ve dörtgen aynı karede yeniden yönlendirilir. Kutup tekilliği ise her azimutun tek bir noktada birleştiği tepeden görünümdür; halka ile tepe döşemesi arasındaki köprü, atlastaki en kötü geçiştir.
Hemi-oktahedral eşleme her ikisini de düzeltir. Üst yarımküreyi sürekli biçimde birim kareye eşler; böylece komşu 3B yönler komşu UV'lere denk gelir, kutup tekilliği ortadan kalkar ve tepeden görünüm için özel bir döşemeye gerek kalmaz. Titreşimin çözümü bilineer hücre harmanlamadır: En yakın döşemeye aniden geçmek yerine, kodlanmış yönü çevreleyen 2×2'lik döşeme grubu bulunur ve dördü birden harmanlanır; toplamda 8 doku örneği alınır (4 diffuse, 4 normal). Böylece komşu görünümler aniden değişmek yerine çapraz geçiş yapar. İki birim vektörün normal harmanı kendi başına birim uzunlukta olmadığından yeniden normalleştirilir; bu da komşu döşemeler arasındaki küçük açılar için slerp gibi davranır. Maliyeti kayda değerdir (12×12 atlas, azimut/yükseklik atlasının 1,6 MB'ına karşılık yaklaşık 9 MB'tır ve pişirme 288 render-target geçişiyle yaklaşık 5 kat daha uzun sürer), ancak pişirme yükleme sırasında bir kez yapılır ve harmanlama ani geçişsiz bir sonuç sağlar. Kameranın gerçekten hareket ettiği sırada imposter'ları kullanılabilir kılan da budur.
Bir milyon ağaç, kare başına tek kamera konumu kopyası
Spike 41'i yeni sekmede aç ↗ · Kaynak kodunu görüntüle
Spike 38'in çalışma zamanı, her dörtgen için her karede bir CPU lookAt işlemi gerçekleştirir. Bu, tek bir ağaç için sorun değildir ancak bir orman için ölümcüldür. Bir milyon ağaçta kare başına matris güncellemeleri ve instance tamponu yüklemeleri diğer her şeyin önüne geçerdi. Spike 41, kare başına işleme hattının tamamını GPU'ya taşır. Her instance'ın merkezi, yaw değeri ve ölçeği, oluşturma sırasında instanced attribute olarak bir kez yüklenir ve bir daha değişmez. Vertex shader, dünya uzayındaki camPos − center görüş yönünden billboard tabanını oluşturur ve paylaşılan birim dörtgeni dünya uzayına genişletir. Fragment shader, hemi-oktahedral kodlamayı ve bilineer harmanlamayı piksel başına gerçekleştirir. Tüm orman için kare başına yapılan tek CPU işi, kamera konumu uniform'unu güncelleyen bir Vector3.copy işlemidir ve bu maliyet ağaç sayısıyla birlikte artmaz.
Matematiğin hoş bir yanı, instance başına yaw değerinin normal çözümlemede birbirini götürmesidir. Pişirme, normalleri pişirme kamerasının koordinat sisteminde saklar. Dünya yukarı ekseni etrafındaki yaw dönüşü +Y'yi koruduğundan ve vektörel çarpım dönüşle uyumlu olduğundan, dünya yukarı referansıyla oluşturulan çalışma zamanı tabanı zaten döndürülmüş pişirme tabanına eşittir. Böylece shader, instance başına yaw değerine hiç dokunmadan normalleri doğrudan çalışma zamanı tabanının varying'leri üzerinden çözümler. Yerleştirme, tamamen rastgele dağıtım yerine jitter uygulanmış bir ızgara kullanır: Alan hücrelere bölünür ve her hücrenin merkezine sınırlı bir ofset eklenerek bir ağaç yerleştirilir. Bu yöntem, doğal bir orman görünümünü korurken minimum aralığı garanti eder; hiçbir ağaç diğerinin üzerine gelmez. Gözden kaçırılması kolay bir ayrıntı da sınırlayıcı küredir. Geometri şablonu yalnızca birim dörtgenden oluştuğundan three.js, kamera orijinden uzaklaştığı anda tüm ormanı görüş hacminin dışında sayardı. Tüm alanı ve bir dörtgenlik kenar payını kapsayan açık bir sınırlayıcı küre ayarlamak, köşelerdeki ağaçların eğik açılarda kesilmesini önler.
Bu bölümde kullanılan teknolojiler
Oktahedral ve azimut-yükseklik imposter atlasları. Bir imposter, bir nesneyi görüş yönlerinden oluşan bir ızgara üzerinden diffuse atlasına ve dünya uzayı normal atlasına pişirir; ardından eşleşen döşemeyi örnekleyen tek bir billboard render ederek binlerce üçgeni iki dokuyla değiştirir. Klasik oktahedral eşleme küreyi düzenli biçimde kapsar ancak katlama sınırlarında CPU/GPU ayrışmasına yatkındır. Sade bir azimut-yükseklik ızgarası küreyi daha az düzenli örnekler ancak hücre seçiciyle shader'ın yapısal olarak aynı sonucu vermesini garanti eder. Bir satırın tam 0° yüksekliğe denk gelmesi için tek N kullanın ve nesneler çoğunlukla göz hizasından görüldüğü için döşeme bütçesini yatay halkaya ayırın.
Parçalı-statik billboard yönelimi. Imposter belirli bir pişirme yönünden çekilmiş bir fotoğraf olduğundan çalışma zamanı dörtgeninin görüntü düzlemi, sürekli olarak çalışma zamanı kamerasına dönmek yerine pişirme kamerasının düzlemiyle eşleşmelidir. Dörtgen, tek bir hücrenin seçili kaldığı yay boyunca yönelimini korur ve sınırda aniden geçiş yapar. Bu, imposter mesafesinde fark edilmez ve yalnızca imposter'ların zaten kullanılmadığı yakın mesafede görünür.
Bilineer hücre harmanlamalı hemi-oktahedral atlas. Üst yarımküreyi sürekli biçimde birim kareye eşlemek, kutup tekilliğini ve özel tepeden görünüm döşemesini ortadan kaldırır. Ani geçiş; kodlanmış görüş yönünü çevreleyen 2×2'lik döşeme grubunun örneklenmesi ve dört döşemenin de bilineer olarak harmanlanmasıyla (8 örnek) yok edilir. Böylece komşu görünümler çapraz geçiş yapar. Harmanlanmış normaller yeniden normalleştirilerek döşemeler arasındaki küçük açı boyunca yaklaşık bir slerp elde edilir. Bunun karşılığında daha büyük bir atlas (12×12'de yaklaşık 9 MB) ve daha uzun süren tek seferlik bir pişirme gerekir; ancak kamera hareket ederken ani geçişsiz gölgelendirme sağlanır.
GPU güdümlü instanced imposter'lar. Instance başına merkez, yaw ve ölçek değerleri instanced attribute olarak bir kez yüklenir; vertex shader billboard tabanını oluşturup paylaşılan birim dörtgeni genişletir, fragment shader ise kodlama ve harmanlamayı piksel başına gerçekleştirir. Tüm orman için kare başına CPU maliyeti, instance sayısından bağımsız olarak tek bir kamera konumu uniform kopyasıdır. Dünya yukarı ekseni etrafındaki yaw, vektörel çarpım tabanının oluşturulması tarafından korunduğu için instance başına yaw değeri normal çözümlemede birbirini götürür. Ormanın tamamını kapsayan açık bir sınırlayıcı küre, kamera birim dörtgen şablonunun orijininden uzağa baktığında three.js'in tüm instanced mesh'i görüş hacminin dışında saymasını önler. Bkz. GPU güdümlü LOD.
29 bölümün 19. bölümü. Önceki: 18. Bölüm - Yapay zekâyla yerleştirilmiş hissi veren bir dağıtım fırçası Sonraki: 20. Bölüm - Düz bir yüzeyde derinlik yanılsaması yaratmak Seri rehberi: /tr/blog/2026-02-25-open-world-browser-series-guide