1 puan yazan GN⁺ 2024-07-22 | 1 yorum | WhatsApp'ta paylaş
  • CSS box-shadow normalde arayüze derinlik vermek için kullanılan bir özellik olsa da, tek bir div üzerinde yüzlerce hatta binlercesi üst üste bindirildiğinde adeta bir çizim API'si gibi çalışabiliyor
  • Kutu olma kısıtı bulunsa da yuvarlatılmış köşeler, çoklu gölge zincirleri ve renk·blur·opaklık kombinasyonlarıyla dairesel noktalar ve katmanlı efektler üretilebiliyor
  • Deneylerde blur ve opaklık animasyon yapılabilen öğe sayısını ciddi biçimde azalttı; bunlar çıkarıldığında ise M1 dizüstünde binlerce box-shadow çizilebildi
  • Top simülasyonu, görüntü pikselleri, küp·küre yüzey noktaları ve müziğe tepki veren görselleştirmelerin tamamı her karede box-shadow dizesini güncelleme yöntemiyle gerçekleştirildi
  • Son bölümde CPU tabanlı bir ray tracer Web Workers ile paralelleştirilerek, yalnızca tek bir div ve box-shadow ile bile sahne render edilebildiği gösteriliyor

box-shadow'u bir çizim aracı gibi kullanmak

  • CSS box-shadow, bir drop shadow türüdür; asıl amacı görüntülerin veya UI öğelerinin arkasına gölge çizerek derinlik hissi vermektir
  • Tipik bir drop shadow, görsel rasterını x/y ekseninde kaydırıp tek bir renkle orijinalin arkasına çizerek derinlik oluşturur
  • CSS filter: drop-shadow() x/y ofseti ve renk alır; isteğe bağlı olarak blur değeri de destekler
  • Birden fazla drop-shadow filtresini katmanlamak, ekran kompozisyonuna daha fazla derinlik katabilir

box-shadow'un özellikleri ve kısıtları

  • box-shadow içindeki “Box”, gölge biçiminin container'ın bounding box'ı ile sınırlı olduğu anlamına gelir
  • Bu kısıt ilk bakışta sınırlayıcı görünse de, çoğu arayüz kutulardan oluştuğu için pratikte kullanışlıdır
  • CSS box-shadow uygulaması, yuvarlatılmış kutuları düşük maliyetle çizmeye yönelik matematiksel optimizasyonlar içerir; yuvarlatılmış kutular da daire gibi görünebilir
  • Tasarımcılar, önceden render edilmiş görseller kullanmadan box-shadow ile indirme boyutunu artırmaktan kaçınabilir
  • Tek bir div üzerine birden fazla box-shadow zincir halinde eklenebilir; örnekler de renkleri ve gölgeleri bu şekilde kuruyor
  • border-radius ayarlarını CSS border-radius generator üzerinde deneyebilirsiniz

box-shadow'u “yanlış” kullanmak

  • Normal UI tasarımında gölgeler; boşluk, padding, tipografi ve erişilebilirlikle birlikte durumları ve etkileşimleri ayırt etmeye yardımcı olan destekleyici bir unsurdur
  • Ancak box-shadow, ekrana istenen sayıda dikdörtgen boyayan ve isteğe bağlı blur uygulayan bir boyama API'si gibi de ele alınabilir
  • İlk denemeler, önceki yazıdaki minimal sanat ile başladı ve yalnızca basit renk bloklarıyla görsel sonuçlar üretildi
  • Daha sonra daha fazla box-shadow, blur ve opaklığın performansı nasıl etkilediğini görmek için bir görselleştirme aracı yapıldı
    • Büyük bir box-shadow dizesi üretilip tek bir div'e atanıyor
    • Animasyon başlangıçta her 300 ms'de bir box-shadow dizesini değiştirip bunu transition: all ile yürütme yaklaşımını kullanıyordu
    • Bu yöntem jank üretiyordu ve her karede doğrudan box-shadow atamaktan daha yavaştı
  • 100 box-shadow örneği, renk paletini dokunarak remiksliyor ve solda son 10 paletin geçmişini gösteriyor
  • Blur eklendiğinde animasyon yapılabilen sayı düştü; yarı saydam renkler de çizilebilen öğe sayısını ciddi ölçüde azalttı
  • div boyutu da performansı etkiliyordu; animasyon sırasında yazılım rasterizer'ın devrede olduğu izlenimi oluştu
  • Opaklık ve blur kullanılmadığında M1 dizüstü binlerce box-shadow çizebildi

box-shadow ile top simülasyonu yapmak

  • box-shadow döndürülemez, ancak border-radius sayesinde daire gibi gösterilebilir
  • Birden fazla dairesel gölge top gibi ele alındı ve z değerine göre boyut ölçeklenerek sahte bir 3D derinlik hissi üretildi
  • Uygulama, requestAnimationFrame içinde oyun durumunu güncelleyip her karede büyük bir box-shadow dizesini div'e atama mantığıyla çalışıyor
  • Render süreci şöyle işliyor
    • Toplar z değerine göre sıralanıyor
    • Boyutlar z değerine göre hesaplanıyor
    • Her topun x/y konumu, spread değeri ve rengi bir box-shadow öğesine dönüştürülüyor
    • Öğeler virgülle birleştirilip tek bir dize haline getiriliyor
  • 50 toplu örnek, sürükleyerek topları hareket ettirmenize ve kutu içinde sekmelerine izin veriyor
  • Sahte 3D ölçekleme belli ölçüde derinlik veriyor, ancak toplar “kameraya” yaklaştığında daireler bozulmuş görünebiliyor
    • Bunun nedeni, box-shadow üreten temel div'in ölçekleme yaklaşımına kıyasla fazla küçük kalması
    • Container boyutu artırılarak çözülebiliyor, fakat container büyüdükçe performans yavaşlıyor
  • Topların birbiriyle çarpıştığı sürüm, n^2 çarpışma kontrolü kullanıyor ve çarpışma anında yalnızca hız yansıtılıyor
    • Gerçek fiziksel etkileşim simüle edilmiyor
    • Görselliği korumak için z konumu sabitlenip 2D'ye indirgenmiş
  • Topların rastgele başlangıç konumlarına dönmeye çalıştığı bir örnek de var; dokunma kuvvetiyle topları çekince süngerden parçalar kopuyormuş gibi bir etki oluşuyor

Görseller ve 3D nokta bulutları

  • Sonraki deneyde bir görselin pikselleri 2D düzlemde noktalara eşleniyor ve her nokta box-shadow ile çiziliyor
  • Görsel piksel konumu ve rengi okunup state.particles içine kaydediliyor; her piksel tek bir parçacık olarak kullanılıyor
  • Kaynak kod CodeSandbox örneğinde yer alıyor
  • Bu örnek, 3D uzayda binlerce box-shadow render ediyor ve sürüklediğinizde görseli dağıtıyormuş hissi veren bir etkileşim sunuyor
  • Otomatik dönen canlı sürüm /box-shadow/v3?width=80&size=5&autoRotate=1 adresinde sunuluyor ve çalıştırıldığında pil tüketimi uyarısı veriyor
  • Daha yüksek sayılı örnek yaklaşık 12.000 box-shadow ölçeğindeydi ve bu seviyede takılmalar görülüyordu
  • M1 çok fazla box-shadow işleyebildi, ancak masaüstü sistemler, iPhone ve eski Android cihazlar aynı seviyeyi kaldıramadı

Küp ve küre yüzeylerine nokta yerleştirmek

  • Denemeler, noktaları ağ yüzeylerine eşit biçimde projekte etmeye doğru devam etti
  • Küp, noktaları her yüzeye yerleştirme mantığıyla üretildi
    • Tüm kenar uzunluklarının eşit olduğu varsayılıyor
    • Her yüzey dolaşılıp verilen boyuta göre noktalar dolduruluyor
    • Nokta sayısı arttıkça küpün hassasiyeti yükseliyor
  • Küp örneği dokunarak etkileşime girilebiliyor ve fare konumunu izleyen küçük bir ışıkla derinlik hissi artırılıyor
  • Işık hesaplaması doğru değil ve çeşitli “magic constants” kullanıyor
  • Küp parçacık eşleme fonksiyonu ile çeşitli matematik yardımcıları gypity kullanılarak elde edildi
    • İlk sonuç rastgele dağılımdı
    • İkinci denemede küp yüzeyinde eşit dağılım elde edildi
  • Küre yüzeyinde eşit dağılım daha karmaşıktı; bu yüzden spiral discretization yöntemi kullanıldı
    • Fikir, küreyi alttan üste saran bir çizgi üzerine noktaları eşit yerleştirmek
    • Bu, bir ipin topu sarmasına benzetiliyor
  • Bu doğrusal eşleme kullanılarak müziğin frekans değerleri dizisine bağlanan bir animasyon da yapıldı
  • Noktalara zaman içinde enterpole edilen gradyan paleti animasyonu uygulandı; bu bölüm önceki çalışmalardan daha zordu
  • Daha az dönüşlü küre örneğinde spiral, eşit dağılımı yaklaşık olarak verir; ancak yeterli dönüş olmadığında bozulmalar görünür hale gelir

box-shadow ray tracer

  • Üçgenler box-shadow ile çizilebiliyorsa, neredeyse her türlü CGI üretilebilir düşüncesi bu ray tracing deneyine yol açtı
  • Hedef, yalnızca tek bir div ve box-shadow ile sahne çizebilmekti
  • Sonraki örnekler düşük hassasiyetli canlı render ile yüksek hassasiyetli görüntü render'ını birlikte kullanıyor; ayrıca hassasiyet sınırı koymayan örnekler de olduğundan çalıştırırken dikkat gerekiyor
  • Ray tracer ve ray marcher, doğru ama yavaş görüntü üretim yöntemleridir ve CGI endüstrisinin genelinde kullanılır
  • Burada GPU yerine CPU tabanlı bir tracer kullanılıyor
    • Çünkü GPU kullanmak denemenin amacını zayıflatırdı ve uygulaması da daha zordu
  • İlk tracer yalnızca küre render edecek şekilde yapıldı; sahne veri modeli kamera, birden çok küre ve malzeme bilgisi içeriyordu
  • gypity ile alınan ilk kod gerçek zamanlı çalışıyordu ama sorunları vardı
    • Örnekleme optimizasyonunda gerekli bias coefficient'i ekleyen kod eksikti
    • İlgili yorum vardı ama gerekli satır yoktu; örnekleme tekniği araştırıldıktan sonra düzeltildi

Progressive rendering ve Three.js

  • Sonrasında kod progressive rendering yapısına geçirildi
  • Progressive rendering, çok sayıdaki ray hesabını birden çok kareye bölerek render'ın giderek “ground truth”a yaklaşmasını gösterir
  • Bu yapı, kamerayı etkileşimli biçimde hareket ettirme özelliğiyle iyi uyum sağladı
  • Kamera ve orbit controls için Three.js kütüphanesi kullanıldı
    • Matris matematiğini ve mobil desteği sıfırdan yazmamak için
  • Bu sürüm yalnızca küre render edebiliyor ve sahnedeki tüm öğeler bir ölçüde ölçeklenmiş kürelerden oluşuyor
  • Örnek varsayılan olarak tam çözünürlüğün yaklaşık %6'sından biraz fazlasında çalışıyor
  • Ekrandan uzaklaştıkça düşük çözünürlüklü sahne daha net görünürken, yaklaştıkça hassasiyet eksikliği daha belirgin hale geliyor
  • Sorgu parametreleriyle çözünürlük ölçeği, pixelSize, ray bounce sayısı ve maksimum örnek sayısı ayarlanabiliyor
  • Çözünürlük ve örnek sayısı arttıkça sonuç iyileşiyor, ancak ciddi biçimde yavaşlıyor

Tracer uygulaması ve performans darboğazları

  • Render, her piksel için kameradan bir ray gönderip rengini hesaplayan ve ardından kareler arasında ortalamasını alan standart yöntemle yapılıyor
  • Tracer Three.js kullandığı için çok sayıda yeni nesne oluşturuluyor ve hızla çöp nesneye dönüşüyordu
  • Nesne yeniden kullanımı kısmen denendi, ancak performansın son damlasına kadar sıkılması istenseydi Three.js kullanmamak daha doğru olurdu
  • Profiler'a göre garbage collection büyük bir fark yaratmıyordu; bu yüzden Three.js kullanımına devam edildi
  • Tracer'ın temel fikri, ışınların sahnedeki nesnelere çarpıp ışığa ulaşana kadar sekmesi ve nesne ile ışık özelliklerine göre renk döndürmesidir
  • Çok sayıda ray atılmasının nedeni, bazı ray'lerin ışık kaynağına ulaşmaması ve bazılarının ulaşmasıdır
  • Bu tracer basit bir aydınlatma modeli kullanıyor
    • Fiziksel olarak doğru BRDF yok
    • Doku yok
    • Subsurface scattering yok
    • Basit diffuse ışık ve specular reflection kullanılıyor
  • Ray tracing öğrenmek için Ray Tracing in One Weekend kaynağına bakılabilir
  • Daha sonra plane render etme desteği eklendi ve floating point değerleri bit düzeyinde işlerken yapılan hata düzeltildikten sonra ışık dikdörtgenleri de render edilebilir hale geldi

Web Workers ile paralelleştirme

  • Performans artışı, Three.js GC sorununu çözmek yerine Web Workers ile çok iş parçacıklılığına yönelerek ele alındı
  • Ray tracing, her hesaplamanın tek bir sonuç döndürmesi ve yan etki içermemesi nedeniyle paralelleştirmeye çok uygundur
  • Worker manager bir worker havuzu oluşturuyor ve çalışma anında sahneyi değiştirebilmek için render ile updateScene adında iki yöntem sunuyor
  • Worker kodu mevcut tracer'ın kopyalanmış haliydi; sahne değişimi için veri marshaling gerekliyordu
  • postMessage ek yükünü azaltmak için worker'lar tek piksel yerine tüm kareyi render ediyor
  • Tam ekran örneği önceki sürüme göre çok daha hızlı çalışıyor
  • Dezavantajı, kamera veya sahne değiştiğinde önceki ray hesaplarının tümü geçersiz hale geldiği için ekranın siyaha dönebilmesi
  • Çözüm olarak giriş olaylarında isDirty bayrağı set ediliyor ve güncelleme döngüsünde karenin temizlenip temizlenmeyeceğine karar veriliyor
  • Worker'ların eski sahne verisiyle hesapladığı bir kareyi, sahne güncellemesinden hemen sonra göndermesi mümkün
    • Bu kareler timestamp veya scene id bilgisi postMessage içine eklenerek atılabilir
    • Deneyde ise tek karelik hatalı ray verisi kısa sürede ortalamaya dahil olup kaybolduğu için olduğu gibi bırakılmış
  • Sonuç olarak box-shadow tabanlı ray tracing yeterince çalışır durumdaydı; kaynak kod CodeSandbox üzerinde bulunuyor
  • Performans istatistikleri toplam tahmini ray sayısını, örnek sayısını ve render çözünürlüğünü gösteriyor
  • Varsayılan örnekleme 1200'de duruyor, ancak değiştirilebiliyor
  • Daha hızlı ama daha az doğru alternatif bir aydınlatma modeli de açılıp kapatılabiliyor
  • Sahne verisi JSON formatında olduğundan elle düzenlenmesi kolay

Sonuç: mümkün ama tavsiye edilmeyen bir deney

  • Tek bir div üzerindeki box-shadow ile top simülasyonu, nokta bulutları, görüntü tabanlı parçacıklar, küp·küre yüzeyleri ve hatta ray tracing sahneleri bile çizilebiliyor
  • Ancak tüm deney, pratik kullanım senaryosu olmayan ve daha çok “yapılmaması gerekenler” kategorisine giren bir yaklaşım
  • Triangle mesh yükleme, acceleration structure ve daha doğru aydınlatma modelleri eklenirse geliştirme alanı var
  • gypity başlangıçta box-shadow ray tracer'ın imkânsız olduğunu söylemişti, ama sonunda çalışan bir örnek üretildi
  • CSS sezgisel bir araç değil, ancak bazen açıkça çalışan tuhaf olasılıklar sunabiliyor

1 yorum

 
GN⁺ 2024-07-22
Hacker News yorumları
  • Şeffaf renk kullanınca GPU çizim batch işleme kısıtlandığı için yavaşlıyor
    Opak çizimde derinlik tamponu kullanılarak sıra serbestçe değiştirilebilir ve batch işleme en üst düzeye çıkarılabilir. Şeffaf durumda ise blending’in doğru çalışması için painter sırasına göre çizmek gerekir

    • Bence bundan daha karmaşık. Web tarayıcıları da GPU rendering kullanıyor ama oyun motoru değiller; ekrandaki tüm nesneleri her karede yeniden çizmek büyük ve karmaşık sayfalarda kolayca takılmalara yol açabilir
      Özellikle Chromium toplam katman sayısını en aza indirmeye çalışıyor, her katmanı bir piksel haritası olarak render edip sonra her karede görünen katmanları nihai görüntüye compositing ile birleştiriyor. Pratikte pikselleri değişmeyip sadece konumu hareket eden çok sayıda katman olduğundan, her karede rasterize etmek yerine sadece compositing yapmak yeterli oluyor
      Opak box-shadow çoksa Chromium hepsini tek seferde tek bir katmanda rasterize edebilir, ama şeffaf box-shadow çoksa her birini ayrı katman yapabilir. Bu durumda verimsiz olabilir, ama yarı saydam box-shadow’ların sayfada bağımsız hareket etmesi gerekiyorsa gerekli bir yöntemdir
    • Ters painter sırasının painter sırasından daha iyi olduğu durumlar vardır. Çünkü tamamen gizlenen nesneler atlanabilir
      Tamamen şeffaf bir tamponla (α=0.0) başlanır, önden arkaya her yüzey gezilir ve her piksel için yeni pikselin 1.0-buffer.α kadarı mevcut tampona blend edilir. buffer.α == 1.0 olduğunda derinlik tamponundaki gibi tamamen atlanabilir
      Yine de şeffaf bir nesnenin arkasında başka bir şeffaf nesne olduğundaki matematiği yeniden kontrol etmek gerekir; yüzeylerin döngüsel biçimde çakışması veya birbirini kesmesi zorlayıcıdır
    • GPU overdraw’dan da hoşlanmaz, bu yüzden birden fazla şeffaf öğeyi üst üste bindirmekten kaçınmak iyi olur
      Şeffaf texture yerine daha fazla üçgen çizmenin genelde daha iyi olmasının sebeplerinden biri de budur
    • Sıralama aşamasının kendisi genelde büyük sorun değildir. Draw call’ları göndermeden önce zaten çoğu zaman sıralama yapılır. Asıl maliyet overdraw’dan gelir
      Opak rendering önden arkaya yapılabildiği için nihai framebuffer’da gerçekten görünenler render edilir ve derinlik geçişinden sonraki piksel sayısı framebuffer boyutuyla orantılıdır
      Şeffaf rendering ise arkadan öne yapılmalıdır ve daha sonra başka çokgenler tarafından kısmen gizlenecek sahnelerin de büyük bölümü render edilir. Bu yüzden shader pipeline’dan geçen piksel sayısı mesh boyutuyla orantılı olacak kadar artabilir
      Birbirinin üstüne binmeyen öğelerde şeffaflık neredeyse hiç yavaşlatmaz. Sonuçta her piksele zaten bir kez dokunmak gerekir; değişen yalnızca shader formülüdür
    • Dikkate alınacak bir şey daha var; özellikle mobil cihazlarda bellek bant genişliği darboğaz olabilir
      Opak çizim başka çizimlerin üzerine biniyorsa en iyi durumda çakışan çizim işleri tamamen elenebilir, en kötü durumda bile yalnızca tekil çizim kadar bant genişliği kullanılır
      Şeffaflıkta ise işlemler bir şekilde birleştirilemiyorsa çakışan tüm alanın yeniden okunması gerekir; bu yüzden her şeffaf çizimde en az nihai framebuffer boyutunun iki katına denk bir bitmap bellek veri yolu üzerinde gidip gelir
      Pek çok mobil cihazın 60fps’yi korumak için gereken sürede ekranın tamamını iki kez blit edecek kadar bellek bant genişliğine bile sahip olmadığını düşününce bu oldukça büyük bir sorun olur
  • Gerçekten çok eğlenceli bir incelemeydi
    Katmanlamanın önemli anahtar kelime olduğu da doğru. 14 yıl önce yaptığım, absürt ama bazen havalı görünen text shadow projesinde de işin özü katmanlamaydı: https://paulirish.github.io/mothereffingtextshadow/

    • Bu yazıda da birkaç katman var gibi; aslında yalnızca box-shadow hakkında bir yazı da değil sanki
  • Ancak son paragrafa gelince gypitynin ChatGPT’yi kastettiğini fark ettim, biraz utandım

    • Primeagen YouTube kanalında kullanılan bir ifade
    • Bunu görünce kelimenin bir iş arkadaşının ürettiği şirket içi şaka değil, meme hâline gelmeye başlayan bir ifade olduğunu öğrenmiş oldum
    • gyp ile ilgili istemci tarafı bir araç falan sanmıştım
  • Güzel, eski usul pratik olmayan hack’lere tamamen varım. Ama zaten canvas var ve böyle işleri canvas’ın daha kolay, daha hızlı ve daha iyi yaptığını unutmamak gerek

    • Ama komedi değeri daha düşük. Bunu sadece box-shadow ile yapmanın komik olmasının nedeni tam da bu kadar kullanışsız olması
    • Canvas özellikle erişilebilirlik tarafında çok şeyden vazgeçmek demek
    • Canvas daha iyi olabilir ama bu, sabit boyutlu bir kutunun içinde kalan bir şey yaptığınız durumlar için geçerli
    • Tabii bu, “zaten klavye var, o yüzden müzik yapmak için floppy sürücüye ya da lastik tavuğa gerek yok” demeye de benziyor
  • Müzik görselleştirmesi kesinlikle harikaydı. Eski Winamp günlerindeki gibi müziği açıp görselleştirmeyi tam ekrana verdiğimiz zamanları gerçekten özlüyorum
    Keşke günümüzün streaming ses oynatıcılarında da bu özellik olsa

    • Hâlâ Winamp’ı, daha doğrusu WACUP’ı, Milkdrop görselleştirmeleriyle birlikte neredeyse her gün kullanıyorum
      Ama streaming ses oynatıcılarının gerçekten yalnızca en temel özelliklere sahip yazılımlar olduğu da doğru
  • Sonuçta Firefox ve Chrome tarayıcı yakınlaştırması %150 iken 1px box-shadow’u hâlâ farklı render ediyor
    Umudumu Baseline 2025’e bağladım

  • “UI insanlarının sevdiği yuvarlatılmış kutuları çok ucuza çizen matematik hack’leri” hakkında daha fazla şey öğrenmek için iyi kaynaklar olup olmadığını merak ediyorum

  • Sevdiğim türden bir hack
    Bu konuda okuduğum Josh Comeau yazılarının neredeyse tam tersi gibi: https://www.google.com/search?q=josh+comeau+shadows

  • Bu yıl okuduğum yazılar arasında belki de en iyisi olabilecek kadar harika bir yazı, sonunda you're yerine your welcome ile bitmiş
    Hızla düzeltilmesi lazım. Tabii şakayı kaçıran ben de olabilirim

  • Son 30 yılda programlamada iyileştim ama oyun sevmediğim için grafiklerle neredeyse hiç uğraşmadım
    Şimdi bunun büyük bir hata olduğunu düşünüyorum ve bir yılı aşkın süredir açığı kapatmaya çalışıyorum; gerçekten çok zor

    • Ben önce oyunlarla başladım, sonra CRUD ya da basit Oracle Forms programlamasına geçtim ve bir gecede eski oyun programcısı maaşımın üç katını almaya başladım