Three.js · Görüntü sonrası · Render-to-texture
Render Target: ekranın ötesindeki tuval
Varsayılan akışta renderer, ürettiği pikselleri doğrudan ekranın bağlı olduğu tampona (default framebuffer) yazar; kullanıcı da sonucu anında görür. Bir render target kullandığımızdaysa aynı çizim komutları, bu kez bellekte tutulan bir hedefe — çoğu zaman bir renk eklerine bağlı doku ve eşlik eden derinlik/stencil tamponlarına — işlenir. Yani sahne «önce bir görüntü dosyası gibi» oluşturulur; sonra bu görüntü materyale map edilir, efekt zincirinde parçalanır ya da başka bir kameranın «film şeridi» olarak ana sahneye geri beslenir.
WebGL dünyasında bu fikir, soyut olarak bir framebuffer object (FBO) üzerinden yaşar; Three.js tarafında ise benzer rolü çoğu projede WebGLRenderTarget (ve ihtiyaca göre çoklu örnekleme / özel biçimler) üstlenir. Bu sayfa API kaynak kodunu çözmekten çok, neden ara hedefe çizdiğimizi ve pass zinciriyle ilişkisini kurmayı hedefler — çünkü burası post-processing ile sahne-içi yansıma/portal gibi efektlerin ortak mühendislik zemini.
Tam genel hat için önce Render pipeline · Post-processing ve Framebuffer bölümlerini hatırlamak iyi bir bağlam sağlar; aşağıda ise pipeline’daki «son dokunuşlar» cümlesini, ara tampon üretimi perspektifiyle genişletiyoruz.
Neden doğrudan ekrana çizmiyoruz?
Doğrudan ekrana çizmek, bir restoranda yemeği «ocaktan tabağa» sürmek gibidir: en az ara adım, en düşük gecikme. Oysa grafik üretiminde sık ihtiyaç duyulan şey, bir ara ürün çıkarmaktır — tıpkı aşçının sosu son dakikada karıştırıp tabağa koyması gibi. Ara hedefe çizmek, görüntüyü bir kerelik matematiksel nesneye dönüştürür; böylece onu bir doku örneği gibi okuyabilir, yeniden örnekleyebilir, bulanıklaştırabilir veya ikinci bir shader ile yeniden boyayabilirsiniz.
- Görüntüyü manipüle etme: Renk düzeltmesi, selektif bulanıklaştırma, kenar vurgusu veya tamamen soyut bir renk uzayı dönüşümü — hepsi, ham karenin bir kopyasının elinizde olmasına bağlıdır. Ekrana doğrudan yazılan bir zincirde bu «aradaki kopya» yoktur; her şey monitörün gösterdiği akışa kilitlenir.
- Recursive / iç içe bakış: Bir aynanın içindeki dünya, güvenlik kamerası ekranı veya üç boyutlu bir «mini harita» yüzeyi için sahne — bazen farklı bir kamera ile — önce bir render target’a çizilir; çıkan doku ana materyalde kullanılır. Burada kritik ayrıntı çözünürlük ve güncelleme sıklığı seçimidir: her karede tam çözünürlükte ikinci bir sahne çizmek pahalıdır; düşük çözünürlük veya atlama kareleri ile maliyet dengelenir.
- Post-processing için besleme: Bir sonraki bölümde göreceğimiz EffectComposer, tam da bu ara görüntüleri zincire dizerek çalışır — yani render target düşüncesi yalnızca «sahne içi televizyon» efektleri için değil; birçok modern görsel kalite adımı için de gereklidir.
Küçük bir uyarı: ara hedef her zaman «bedava ikinci sahne» değildir. Ek tampon demek, ek bellek bandı ve çoğu zaman ek bir tam ekran geçiş (full-screen pass) demektir — bu yüzden performans bölümündeki uyarılarla birlikte düşünmek gerekir.
EffectComposer: efekt zincirinin orkestra şefi
Three.js ekosisteminde post-processing’i düzenli kurmak için yaygın kalıp, EffectComposer ve ona eklenen Pass nesneleridir. Composer, birbirinin çıktısını girdi olarak kullanan geçişleri sırayla çalıştırır; zihinsel olarak Photoshop’taki katman / işlem sırasına yaklaşır — fakat burada katmanlar çoğu zaman tam ekran düzleminde, GPU shader’larıyla işlenir.
Efekt zinciri nasıl işler?
Zinciri şöyle düşünebilirsiniz: önce «fotoğrafı çekersiniz», sonra o fotoğraf üzerinde sırayla «fotoğrafçılık ve karanlık oda işlemleri» yaparsınız. İlk adım sahne render’ıdır; devamındaki adımların çoğu ekrana bakıldığında zaten üç boyutlu dünya yoktur — yalnızca bir renk (ve gerekiyorsa derinlik / normal gibi yardımcı) tamponu vardır. Bu yüzden geçişlerin sırası, yalnızca estetik değil; bazen «hangi bilgi henüz mevcut?» sorusunun cevabıdır.
- RenderPass: Çoğu örnekte zincirin ilk halkasıdır: seçtiğiniz kamera ile sahne, varsayılan ekran yerine (veya ekrana ek olarak) bir ara render target’a çizilir. Böylece elinizde «ham kare» — renk tamponu — oluşur; birçok efekt için ayrıca derinlik tamponu da bu aşamada üretilir. Derinlik yoksa veya yanlış paylaşılırsa, ertesi geçişlerde bulanıklık, odak veya gölge benzeri ekran-içi efektler ya hiç çalışmaz ya da tamamen yanlış örneklenir.
- ShaderPass: Tam ekran bir düzlem (fullscreen quad) çizilir; fragment shader her piksel için önceki tampondan UV ile renk okur ve yeni bir matematik uygular — örneğin kontrast eğrisi, vinnette maske veya iki tamponu karıştırma. Dışarıdan «tek bir filtre» gibi görünür; oysa arka planda GPU tüm çözünürlük için bu işlemi tekrarlar. Bu yüzden basit bir görünüm bile yüksek çözünürlükte pahalı olabilir.
- Bloom / Glitch / film görünümü gibi özel geçişler: Hepsi bir önceki görüntüyü okuyup yeni bir yazma hedefine çıktı üretir. Çoklu bulanıklaştırma veya çift yönlü örnekleme gerektiren efektlerde yapı, iki ara tampon arasında mekik dokur: A→B, sonra B→A… Bu düzene kısaca ping-pong denir; böylece her geçiş «bir öncekinin üstüne» yazmak zorunda kalmaz ve ara sonuçları güvenle saklarsınız.
- Final output: Son geçişin çıktısı kullanıcıya gösterilir: ya doğrudan tuval (canvas) hedefine kopyalanır ya da kayıt / başka bir motor katmanı için başka bir hedefe aktarılır. Bazı projede zincir «ön izleme» için düşük çözünürlükte işlenip son adımda yükseltilir; kritik nokta, son adımın hangi renk uzayında ve hangi tonemap ile kilitlendiğidir — yoksa ekranda gördüğünüz ile dışa aktardığınız dosya birbirinden sapar.
Bu rol dağılımı Three.js addons/examples/jsm/postprocessing örneklerinden özetlenir; sürüm ve projeye göre sınıf adları veya küçük kurulum farkları olabilir. Holodepth için değişmeyen sözleşme şudur: geçiş sırası bir «tarif»tir — iki malzemeyi karıştırır gibi yer değiştirmek sonucu değiştirir. Örneğin parlaklığı bastıran bir tonemap ile bloom’u yanlış sırada kullanmak, ya gereğinden parlak bir «lapa» ya da detayı öldüren düz bir görüntü üretebilir. Benzer şekilde derinlik tamponunu temizlemeden veya yanlış maskelerle yazmak, bir sonraki geçişin «boşluğa örneklemesi»ne yol açar; sonuç bazen tam siyah, bazen de tamamen yanlış kesilmiş kenarlardır.
Post-processing geçitleri (Passes): sık kullanılan başlıklar
Aşağıdaki başlıklar «tek doğru liste» değil; Holodepth içinde sık referans vereceğimiz temsilî geçiş aileleri. Gerçek projede kalite / hız dengesi için çözünürlük, örnekleme ve hangi tamponların okunduğu birlikte seçilir.
- UnrealBloomPass: Parlak pikselleri genelde bir eşik (threshold) ile ayırır; bu «parlaklık maskesi» çok geçişli bir bulanıklaştırma zincirinden geçirilir ve ana görüntüye geri karıştırılır. Gördüğünüz yumuşak glow, tek bir düğmenin sonucu değil; çoğu zaman ayır → genişlet → karıştır adımlarının toplamıdır — bu da doğal olarak birden fazla tampon okumasını beraberinde getirir. Eşik çok düşük seçilirse dünya adeta sürekli «ışık sızıntısı» içinde kalır; çok yüksek seçilirse efekt neredeyse görünmez olur.
- SMAAPass / FXAAPass: İkisi de düşük çözünürlükte veya ince çizgilerde çıkan merdiven kenarlarını (aliasing) yumuşatır; fakat strateji farklıdır. FXAA tek geçişte yakın komşu renklere bakarak hızlıca yumuşatır — kolaydır fakat bazen ince detayı «bulanık çimen» gibi gösterir. SMAA kenarı daha iyi koruma eğilimindedir; bedeli genelde daha fazla iş ve bazı mobil profillerde gözle görülür süre farkıdır. «Hangisi daha iyi?» sorusunun pratik cevabı çoğu zaman «hangi cihazda, hangi hızda kabul edilebilir?»dır.
- SAOPass (Screen-space ambient occlusion): Her piksel çevresindeki derinlik ilişkisine bakarak köşe ve birbirine yakın yüzeylerde okkalı gölgelenme hissi üretir. Bunun bedeli şudur: yöntem ekrandaki derinlik projeksiyonuna bağlıdır; gerçek fiziksel ölçekten ziyade «ekranda ne kadar üst üste biniyorlar?» sorusuna yanıt verir. Bu yüzden ince ayar yapılmazsa gölge «kirli leke» gibi yüzeylerde kayar veya kamera hareket edince titreşir (temporal noise). Bazı projelerde kaliteyi artırmak için ek görüntü kararlılığı (temporal filtering) gerekir — bu da maliyeti yeniden tartmanız anlamına gelir.
Bu başlıkları tek tek ezberlemek şart değil; ortak mekanik şudur: bunların hiçbiri «boş uzayda» çalışmaz. Hepsi, önceki bir adımda oluşturulmuş renk ve çoğu zaman derinlik tamponuna dokunarak yaşar — yani ya bir RenderPass çıktısı ya da composer’ın içinde hazırlanmış ara yüzeyler olmadan, shader’ın örnekleyeceği anlamlı bir doku yoktur. Bu yüzden bazen efekt «çalışıyor» sanılır; oysa tampon boştur veya yanlış ölçektedir — sonuç düz gri, siyah veya titrek bir maske gibi görünür.
Performans uyarısı: bellek ve GPU yükü
Her yeni anlamlı pass, çoğu zaman GPU’nun hedef tamponu baştan sona bir kez daha işlemesi anlamına gelir. Bu yalnızca «bir kez daha çiz» demek değildir; shader karmaşıklığı, örnekleme ve okunan ek tamponlar (derinlik, normal vb.) ile maliyet katlanır.
- Piksel maliyeti: Kabaca düşünün: çözünürlük genişlik × yükseklik kadar piksel üretir; her tam ekran geçiş bu alanın üzerinden bir kez daha iş hesabı yapar. Beş geçiş demek, aynı makro alanı defalarca dolaşmak demektir — üstüne her geçişin kendi shader kompleksitesi eklenir. 4K sınıfında bu, masaüstünde bile GPU zaman grafiğinde ani sıçramalar gösterebilir; mobilde ise hem kare süresi hem termal sınırlama nedeniyle çok çabuk «tavan»a vurursunuz. Bu yüzden efekt sayısı yerine «toplam kaç tam ekran turu atıyorum?» sorusunu sormak daha doğrudur.
- Bellek ayak izi: Her ara render target, renk tamponu için bellek ayırır; derinlik / stencil veya çoklu rendertarget (MRT) varsa fatura büyür. Bellek yalnızca «yer kapladı» demek değildir: bazı cihazlarda bant genişliği dolar ve aynı sahne, düşük çözünürlükte akıcıyken yüksek çözünürlükte takılır. Çoğu zaman en ucuz kazanım, ara efekt tamponlarını ana çözünürlükten daha küçük tutmak veya gereksiz ara yüzeyleri tamamen kaldırmaktır — bazen bu, yeni bir matematik yazmaktan daha çok kare kazandırır.
- Çözüm yönleri: Single-pass yaklaşımı, birkaç basit rengi aynı fragment içinde birleştirmeyi önerir; okunabilirlik ve bakım maliyeti artabilir ama geçiş turu sayısı düşer. Bazı efektler için düşük çözünürlükte ön-işlem (ör. bulanıklığı yarım çözünürlükte hesaplayıp yükseltme) gözle görülür tasarruf sağlar. Üçüncü kol, sahne-içi kullanımda her kare güncellememek — örneğin yansıma veya portal dokusunu iki kareden bir atlamak; kullanıcı hareket hızına göre fark edilmeyebilir. Son olarak gereksiz çoklu çıktı kanallarını kapatmak (MRT) hem bellek hem okuma yükünü düşürür.
Piksel oranı ve boyut seçiminin genel etkisini Renderer · Piksel oranı ile birlikte okumak, render target çözünürlük kararlarını daha bilinçli yapmanızı sağlar.
Uygulama: «render to texture» sahne içinde
Ara hedef düşüncesi yalnızca post-processing için geçerli değildir; gerçek zamanlı sahnelerde «özel malzemeler» için de aynı altyapı kullanılır. Ortak desen şudur: ikinci bir bakış açısı üret → dokuya yaz → ana materyalde örnekle.
- Su yansımaları: Su yüzeyi düzlemi için uygun açıdan dünya bir render target’a çizilir; elde edilen doku hem yansıma hem kırılma hesaplarında referans olabilir. Düzlem konumu ve kırpma (clipping) yanlışsa yansıma «uçan leke» gibi kayar.
- Portallar: Bir kapıdan içeri bakıldığında başka bir konum göstermek için ikinci bir kameranın çıktısı render target üzerinden ana sahneye taşınır; burada senkronizasyon ve clip düzlemi seçimi kullanıcıyı motion sickness’ten koruyan ince ayarlara ihtiyaç duyar.
Bu örneklerde post-processing bölümündeki «katmanlar» metaforu yerini, dünya uzayında bir yüzeye yapıştırılmış canlı doku metaforuna bırakır — ama altyapı hâlâ aynı: ara tampon, doğru temizlik ve doğru sıra.
Holodepth teknik notu
Render target debug etmek için zihinsel kontrol listesi: (1) Hangi tampona yazıyorum — renk mi, derinlik mi? (2) Temizleme rengi ve derinlik maskesi doğru mu? (3) Pass sırası — tonemap ile bloom sırası değişince sonuç mantıklı mı? (4) Çözünürlük — ana sahne ile ara hedef aynı mı, bilerek mi farklı? Bu dört soru çoğu «siyah ekran / beyaz ekran / garip parlama» vakasını kök nedene indirir.