dpr=2 ayariyla olusan 9.7 megapiksellik cizim tamponu ile mutlak 5.2 megapiksel piksel butcesinin karsilastirmali gorseli

Piksel bütçeleme, çizim tamponunun (drawing buffer) boyutunu ekranın device pixel ratio (DPR) değerinden değil, doğrudan belirlenmiş bir megapiksel hedefinden türetmek demektir. Amaç basit: farklı ekranlarda aynı görsel kaliteyi, çok daha öngörülebilir bir GPU maliyetiyle elde etmek.

Bu tekniği somut sayılarla anlatan kaynak, tasarımcı ve web geliştirici Filip Zrnzevic‘in 22 Ağustos 2026’da Codrops’ta yayınladığı devlog. Yazı üç Three.js projesinin (3D Space Invaders oyunu FRONTIER, yedi fly-through’luk LXSTNGHT serisi ve 24 CRT ekranlık kurulum /fm) prodüksiyon sürecinde çıkan performans derslerini topluyor. Sitemizde daha önce aynı ekosistemin farklı bir köşesini, WebGPU ve TSL ile prosedürel geometri üretimini işlemiştik (bkz. yazının sonundaki ilgili içerik); bu yazı ise render hızını ve sinematik kurguyu konu alıyor.

Piksel Bütçesi Neden DPR’den Daha Güvenilir?

Three.js’te renderer.setPixelRatio(window.devicePixelRatio) ya da React Three Fiber’da dpr={2} yazmak sezgisel görünür, ama tampon boyutunu tarayıcının insafına bırakır. Zrnzevic’in ölçümüne göre dpr={2} ayarı büyük bir pencerede sessizce 9,7 megapiksellik bir çizim tamponu oluşturuyor — 5K bir ekranla 14 inçlik bir laptop, aynı DPR değerinde bile piksel sayısında 2 kattan fazla farklılaşabiliyor.

Çözüm olarak FRONTIER projesi mutlak bir piksel bütçesi (5,2 megapiksel) belirliyor ve DPR’yi bu bütçeden geriye doğru hesaplıyor — DPR’den bütçeyi türetmek yerine. Yazarın kendi ifadesiyle: “Budget pixels, not device-pixel-ratio.” Sonuç, aynı görsel çıktıyı yaklaşık yarı dolgu (fill) maliyetiyle vermek.

Pratikte güvenilir bir piksel bütçesi stratejisi şu üç adımı içeriyor:

  • onayHedef bir megapiksel sınırı belirle (ör. düşük/yüksek performans katmanı için 3,2 MP / 5,2 MP gibi iki kademe).
  • onayDPR’yi bu bütçeden hesapla: dpr = sqrt(budgetMP * 1_000_000 / (width * height)) mantığıyla, doğrudan sabit değer atamak yerine.
  • çarpı5K/Retina ekranlarda DPR’ye körü körüne güvenme — aynı DPR değeri farklı fiziksel ekranlarda çok farklı piksel sayılarına karşılık gelebiliyor.

Draw Call Birleştirme, Kırpmayı Bozar

LXSTNGHT serisinin longeron parçasında sahne başlangıçta 1.421 draw call ve 568K üçgenle render ediliyordu. Geometriyi malzemeye göre birleştirmek bunu 25 draw call’a indirdi — ama beklenmedik bir yan etkisi vardı: tek bir dev mesh’te frustum culling artık tek tek nesnelere değil, birleşik geometrinin bütün sınır kutusuna bakıyor, yani kameranın arkasındaki nesneler de işleniyor.

Düzeltme, sahneyi 3 metrelik z-segmentlere bölüp her segmenti ayrı birleştirmekti. Bu, culling’i geri kazandırdı ve gönderilen üçgen sayısında %21-37 azalma sağladı — draw call kazanımını korurken.

Aynı devlog’da geçen bir başka batching uyarısı da pratik: geometri birleştirme (batching), per-mesh castShadow gibi bayrakları siler. Bir nesnenin gölge davranışını değiştirmek istiyorsanız, o nesneyi ayrı bir batch’e almanız gerekiyor.

LOD Yerine Görünürlük Anahtarı: Shader Recompile Tuzağı

Geleneksel LOD (level of detail — uzaklaştıkça daha düşük çözünürlüklü geometri kullanma) yerine FRONTIER’de farklı bir performans sorunu ortaya çıktı: bir ışığın görünürlüğünü visible özelliğiyle açıp kapatmak, GPU’da yaklaşık 20 shader program’ının yeniden derlenmesini tetikliyordu — bu da orta-çatışma anlarında fark edilir bir donmaya yol açıyordu.

Çözüm, nesneyi gizlemek yerine ışık şiddetini (intensity) sıfıra çekmekti — ışık GPU’nun gözünde “her zaman sayılan” bir eleman olarak kalıyor, ama katkısı sıfırlanıyor, böylece program yeniden derlenmiyor. Bu değişiklik, toplam shader program sayısını 140’tan 98’e düşürdü ve çatışma ortasındaki derleme sayısını 1-2’den sıfıra indirdi.

// Kaynak: Codrops, "Sixty Frames for the Record" (Ağustos 2026)
// Işığı gizlemek yerine yoğunluğunu sıfırlamak, shader recompile'ı önler
<group>                                     {/* her zaman görünür */}
  <group ref={meshes} visible={false}>      {/* gövde (hull) açılıp kapanıyor */}
    <instancedMesh ... />
  </group>
  <pointLight intensity={0} ... />          {/* her zaman "sayılıyor", ama katkısız */}
</group>

HDR Post-Processing Zinciri: 3 Sessiz Hata

Sinematik görünümün büyük kısmı bloom, tonlama (grade) ve renk düzeltmesinden geliyor — ama HDR (yüksek dinamik aralık) post-processing zincirinde üç hata sessizce görüntüyü bozabiliyor:

  • 8-bit composer buffer → “beyaz kanama.” Emissive yoğunluk 1.0’a kliplendiği için parlak yüzeyler düz beyaza yayılmış gibi görünüyor. Düzeltme: frameBufferType: THREE.HalfFloatType.
  • Bloom’un varsayılan SCREEN karışımı → siyah halolar. Negatif emissive değerler SCREEN blend’de ters dönüp nesnelerin etrafında koyu bir halka bırakıyor. Düzeltme: BlendFunction.SCREEN yerine BlendFunction.ADD — additif karışım hiçbir zaman ters çevrilmiyor.
  • Grade adımı NaN üretiyor → beyaz “speckle” gürültüsü. Kontrast 0,12’ye çıkınca siyahlar -0,068’e düşüyor; pow(negatif, 0.4167) tanımsız (NaN) sonuç veriyor. Düzeltme: grade sonrası 5 satırlık bir clamp(color, 0.0, 1.0) adımı.

Frame Budget: Medyan Yalan Söyler, p95 Söylemez

FRONTIER’de M1 Max üzerinde Chrome/Metal ile ölçülen GPU süresi, 5,2 megapiksellik tampon ve tüm efektler (gölge, bloom, bileşik) açıkken en kötü yüzdebirlikte (p95) 2 milisaniye. Ama bu tek proje için geçerli bir sayı — genel ders başka bir örnekte çıkıyor: LXSTNGHT serisinin breach parçasında medyan (p50) kare süresi temiz 60 fps’e karşılık gelirken, p95 kare süresi 49,6 milisaniyeye çıkıyor, yani 20 fps’in altına düşüyor.

Kullanıcı “ortalama fps”i değil, en kötü anları hisseder. Bu yüzden profiling yaparken medyan yerine p95 veya p99 yüzdebirlikleri izlemek, gerçek “takılma” bütçesini gösteren tek yöntem.

Blend Mode Tuzağı: CSS’i WebGL Canvas’ın Üstüne Koymayın

Bir WebGL canvas’ının üzerine CSS mix-blend-mode uygulamak cazip bir kısayol gibi görünse de, tarayıcının kompozit motorunda (Skia) ekstra bir katman gerektiriyor ve kare başına 30-45 milisaniyelik bir gecikme ekliyor — bu tek başına frame budget’ın tamamını tüketebilecek bir maliyet.

  • çarpıYapmayın: canvas { mix-blend-mode: screen; } gibi bir CSS kuralını WebGL canvas’ına uygulamak.
  • onayYapın: Aynı karışım matematiğini yaklaşık 15 satırlık bir GLSL fragment shader’da yeniden üretip efekti render zincirinin içine gömmek.

Aynı ekosistemin başka bir köşesini merak ediyorsan, Three.js’in WebGPU render motoru ve TSL ile prosedürel geometri ürettiğimiz Three.js ve WebGPU ile Prosedürel Geometri yazımıza; web platformundaki başka bir yeni CSS özelliğini merak ediyorsan CSS’e Gap Decorations Geldi yazımıza bakabilirsin.

Sık Sorulan Sorular

Three.js’te dpr={2} kullanmak neden performans sorunu yaratır?

Çünkü tampon boyutunu ekranın device pixel ratio değerine bırakır ve büyük/yüksek DPR’li ekranlarda beklenenden çok daha büyük bir çizim tamponu oluşabilir (ör. 9,7 megapiksel). Bunun yerine mutlak bir megapiksel bütçesi belirleyip DPR’yi o bütçeden hesaplamak, aynı görsel kaliteyi çok daha düşük dolgu maliyetiyle verir.

HDR post-processing zincirinde HalfFloatType kullanmak zorunlu mu?

Bloom gibi HDR efektleri kullanıyorsanız pratikte evet: varsayılan 8-bit composer buffer, 1.0’ın üzerindeki emissive değerleri kliplendiği için parlak yüzeylerde “beyaz kanama” görünür. frameBufferType: THREE.HalfFloatType ayarı bu kliplemeyi ortadan kaldırır.

Draw call birleştirme (batching) frustum culling’i nasıl etkiler?

Birden fazla nesneyi tek bir mesh’te birleştirdiğinizde, kırpma artık tek tek nesnelere değil birleşik geometrinin toplam sınır kutusuna bakar — bu da kameranın görüş alanı dışındaki nesnelerin de render edilmesine yol açabilir. Sahneyi mesafeye göre segmentlere (ör. 3 metrelik dilimlere) bölüp her segmenti ayrı birleştirmek, culling’i geri kazandırır.

WebGL canvas üzerine CSS mix-blend-mode koymak neden yavaşlatır?

Tarayıcının kompozit motoru, CSS blend modunu uygulamak için canvas’ı ayrı bir katman olarak işlemek zorunda kalır; bu da kare başına 30-45 milisaniyelik ek bir gecikme yaratabilir. Aynı karışım efektini GLSL fragment shader içinde yeniden üretmek, bu maliyeti ortadan kaldırır.

Aytaç Özdalgıç

Hakkında

Aytaç Özdalgıç

Full Stack Designer — Grafik Tasarım Yöneticisi, KJ Power

UX odaklı tasarım kararlarını yazılım mimarisiyle ilişkilendirerek sürdürülebilir dijital sistemler kurguluyorum. 20 yıla yakın kariyerimde, kullanıcı araştırmasından frontend’e uzanan uçtan uca ürün geliştirme süreçlerinde çalıştım.

Hakkımda daha fazlası →

Bir Yorum Yazın

Aytaç Özdalgıç sitesinden daha fazla şey keşfedin

Okumaya devam etmek ve tüm arşive erişim kazanmak için hemen abone olun.

Okumaya Devam Edin