2 puan yazan GN⁺ 2025-02-08 | 1 yorum | WhatsApp'ta paylaş
  • Yalnızca 240 satır GLSL koduyla Rick animasyonu oluşturuyor; görüntü veya kütüphane olmadan GPU shader’ı her pikselin rengini zamana göre hesaplıyor
  • Temel nokta, color_for_pixel(pixel, time) fonksiyonunun tüm piksellerde çalışması ve Signed Distance Field (SDF) yapısının şekillerin içini, dışını ve konturunu mesafe değeriyle ifade etmesi
  • Yüz; round_rect(), circle(), star(), bezier(), parabola() gibi SDF şekillerinin taşınması, ölçeklenmesi, aynalanması, tekrarlanması ve birleşimiyle oluşturuluyor
  • Saç dalgaları, diş tekrarı, dudak konturu, göz kırpma, göz bebeği hareketi ve baş sallama; domain warping, mod(), sin(time), noise() ile aşamalı olarak ekleniyor
  • Tamamlanmış sürüm portal arka planını da içeriyor; ancak editörde video dışa aktarma olmadığı için glslviewer ve ffmpeg kullanan ayrı bir macOS iş akışı gerekiyor

Piksel başına rengi hesaplayan shader yapısı

  • Animasyon OpenGL Shading Language (GLSL) ile yazılmış
  • Sayfadaki canlı kodlama editöründe örnekler doğrudan çalıştırılıp değiştirilebiliyor
  • Temel giriş noktası color_for_pixel(vec2 pixel, float time) fonksiyonu
    • GPU, önizlemedeki her piksel için bu fonksiyonu çalıştırıyor
    • Fonksiyonun yanıtladığı soru şu: “Bu zamanda bu piksel hangi renkte olmalı?”
  • Basit bir örnek, piksel konumunu ve zamandaki değişimi görselleştirmek için pixel.x, pixel.y, time değerlerini renk kanallarına koyuyor
  • time, son düzenlemeden bu yana geçen saniye ve sürekli artıyor

SDF ile şekil çizme yöntemi

  • Daire, length(pixel) ile orijinden piksele olan mesafe hesaplanıp yarıçapla karşılaştırılarak çizilebiliyor
  • circle() fonksiyonu yalnızca iç/dışı gösteren bir bool değil, çevreye olan mesafeyi döndürüyor
    • Şeklin içi negatif
    • Şeklin dışı pozitif
    • Sınır 0’a yakın bir değer
  • Bu tür fonksiyonlar Signed Distance Field (SDF) fonksiyonlarıdır
  • Mesafe değerleri kullanıldığında dolgu ve kontur aynı yöntemle işlenebiliyor
    • dist < -0.01 ise iç renk
    • dist < 0.0 ise siyah kontur
  • Birden fazla SDF şekli min() ile birleştirilebiliyor
    • Rick’in başını ve kulaklarını ayrı ayrı konturlarsanız aralarında gereksiz bir çizgi oluşur
    • İki mesafe değerini min() ile birleştirince yalnızca baş ve kulakların birleşim konturu kalır
  • Başka birleştirme yöntemleri de var; referans olarak Inigo Quilez’in 2D distance functions ve primitive combination materyalleri bağlantılanıyor

Rick’in yüzünü oluşturan şekiller

  • Baş formu round_rect() ile yapılıyor, kulaklar da ayrı bir round_rect() olarak ekleniyor
  • Sezon 1 posterindeki Rick görseli önizlemenin üstüne yanıp sönecek şekilde bindirilerek değerler ayarlanıyor
    • Birçok sayı bu deneme-yanılma sürecinde bulunan değerler
    • Renk değerleri, görüntü düzenleyicinin damlalık aracıyla bulunuyor
  • Gözler dairesel SDF ve 6 uçlu star() ile oluşturuluyor
    • Göz akı, pixel.y *= .93 gibi koordinat dönüşümleriyle dikey yönde hafifçe uzatılıyor
    • Göz bebeği, 6 uçlu yıldız SDF’den küçük bir mesafe değeri çıkarılarak köşeleri yuvarlatılmış hale getiriliyor
  • İki göz, kodu kopyalamadan pixel.x = abs(pixel.x) ile sağ-sol aynalama yapılarak oluşturuluyor
  • Burun, ağız ve kaşlar bezier() ile çiziliyor
  • Saç, dikey olarak uzatılmış 11 uçlu star() ile başlıyor
  • Şekilleri çizen kodun sırası da sonucu etkiliyor
    • Önce döndürülen renk, önde görünen şekil oluyor
    • Dişler ve dil, yalnızca ağız şeklinin içindeki koşul bloğunda çizildiği için ağzın dışına taşmıyor

Saç, dişler ve dekoratif çizgileri inceltme teknikleri

  • Sert yıldız biçimli saç, domain warping ile dalgalı hale getiriliyor
    • Domain warping, piksel konumuna dayalı rastgele ofsetlerle koordinatları sarsma yöntemi
    • Aynı konuma aynı ofset uygulanarak zaman içinde tutarlı bir deformasyon oluşturulabiliyor
    • İlgili kaynak olarak Inigo Quilez’in domain warping yazısı bağlantılanıyor
  • Dişler, parabola() SDF ile bir tane oluşturulduktan sonra mod() ile yatayda tekrarlanıyor
    • mod(pixel.x, width), x koordinatını belirli aralıklarla yeniden 0’dan başlatarak aynı şeklin tekrarlanmasını sağlıyor
    • pixel.y = abs(pixel.y)-.06 ile üst ve alt dişler aynalanıyor
    • pow(pixel.x, 2.) tabanlı y ofsetiyle dişler gülümseme eğrisine uyduruluyor
    • abs(pixel.x+.06) < .194 gibi koşullarla sonsuz tekrar sınırlandırılıyor
  • Dudak ve göz altı çizgileri, SDF konturunu dışarı doğru iterek çiziliyor
    • Normal kontur abs(distance_to_shape) < thickness
    • Dışa doğru açılmış kontur abs(distance_to_shape - outset) < thickness
  • Göz altı çizgisi, konum koşulları eklenerek yalnızca gözün altındaki belirli bölgede görünür hale getiriliyor

Zamanla hareket oluşturma yöntemi

  • En basit animasyon, koda sin(time) koyma yöntemi
    • sin(), sürekli artan time değerini -1 ile 1 arasına sararak döngüsel animasyon oluşturuyor
    • sin(time)*.5 + .5 gibi ölçek ve ofsetle aralık ayarlanıyor
  • Rick’in baş dönüşü, dil dönüşü ve kaş yüksekliği bu yöntemle hareket ediyor
    • Dönüş hesaplamasına rotateAt() fonksiyonu ekleniyor
  • Göz kırpma, zamana göre çizilecek nesneyi değiştirme yöntemiyle uygulanıyor
    • mod(time, 2.) < .09 ise kapalı göz çiziliyor
    • Değilse açık göz ve göz bebeği çiziliyor
  • Göz bebeklerinin düzensiz hareketi için noise() kullanılıyor
    • Gözün sürekli yumuşakça hareket etmemesi için round(time) uygulanıp noise() fonksiyonuna aktarılıyor
  • Saçlar, time domain warping ile daha esnek hareket ediyor
    • Mekânı deforme etmek yerine zaman değeri konuma göre geciktiriliyor
    • Saç ucuna yaklaştıkça zaman gecikmesi değiştiği için tüm şekil katı biçimde dönmek yerine bükülüyor

Portal arka planı ve son dokunuşlar

  • Son sürüm, Rick’in arkasına portal efekti eklenerek tamamlanıyor
  • Portal efekti, ShaderToy kullanıcısı valena tarafından yapılan efekti temel alıyor; dipnotta bunun FabriceNeyret2 tarafından kısaltılmış bir sürüm olduğu belirtiliyor
  • Kod, performanstan çok okunabilirliği önceliklendiriyor
  • Tüm sonuç; 2D shader animasyonu için gereken şekil kombinasyonu, SDF, warping, tekrar, zamana dayalı geçiş ve noise tabanlı hareketi tek bir örnekte topluyor

Video export iş akışı

  • Sayfadaki editör henüz animasyonu video olarak dışa aktaramıyor
  • Geçici yöntem olarak glslviewer ve ffmpeg kullanan bir macOS iş akışı sunuluyor
  • Bağımlılık kurulumu örneği şöyle
brew install glslviewer ffmpeg
  • Export betiği geçici bir dizin oluşturup glslViewer’ı headless modda çalıştırıyor
    • Çözünürlük: 1920x1080
    • Sekans: 0 saniyeden 7 saniyeye kadar
    • Kare hızı: 60
    • Sonuç dosyası: animation.mp4
  • Yerel canlı kodlama örneği şu komutu kullanıyor
glslViewer shader.frag -w 575 -h 324 --noncurses -x 0 -y 0

Süper örnekleme ve yazının çıkış noktası

  • Şekil kenarlarının pürüzsüz görünmesinin nedeni, arkada süper örnekleme uygulanması
  • Süper örnekleme, ekranın tek bir pikseli içindeki 9 konumda color_for_pixel() fonksiyonunu çağırıp ortalamasını gösteren bir yöntem
  • #version 300 es kullanıldığında editörün “pro” modu açılıyor ve otomatik süper örnekleme kaldırılıyor
  • Yazının çıkış noktası, 8 ay önce yayımlanan I Made a 3D Modeler, in C, in a Week videosuydu
    • O videoda marching cubes algoritmasını açıklayan bir animasyon yer alıyor
    • Bunu geleneksel animasyon programlarıyla doğru ve hızlı üretmenin zor olduğunu hissedip kodla yapmaya başladı
    • Daha sonra insanlar animasyonun nasıl yapıldığını sorunca bunu bu yazıda derledi

1 yorum

 
GN⁺ 2025-02-08
Hacker News yorumları
  • İkinci bir supersampling geçişi olmadan yumuşak anti-aliasing kenarları elde etmek istiyorsanız SDF'de standart türevleri kullanabilirsiniz
    Temelde step fonksiyonunu aastep ile değiştirmek gibi: https://github.com/glslify/glsl-aastep
  • Gerçekten harika. Shader geliştiricileri sanki başka bir boyuttan
    Birçok kişinin alışkın olduğu web, protokol ve uygulama geliştirmeden çok farklı; yoğun ve yinelemeli bir çalışma biçimi
    Tek bir float değerini değiştirip shift-enter'a basınca sonucu hemen görmek epey tatmin edici
    • JavaScript canvas ya da onun üzerindeki p5.js gibi soyutlamalarla çalışmanın da böyle hissettirip hissettirmediğini merak ediyorum
      Genel olarak grafik programlamadan mı bahsediliyor, yoksa daha özel olarak GPU shader çalışmasından mı, tam emin değilim
  • GLSL'e giriş için çok iyi yapılandırılmış bir yazı
    Inigo Quilez'in ilgili YouTube oynatma listesi de var: https://www.youtube.com/watch?v=0ifChJ0nJfM&list=PL0EpikNmjs...
  • Referans görseli önizlemenin üzerinde yanıp sönecek şekilde gösterip kodu düzenlerken orijinalle karşılaştırma yöntemi, elle çizilmiş animasyonda kullanılan yöntemle birebir aynı
    Shader programlama gerçekten bambaşka bir dünya ve yazı da çok güzel
  • GLSL'e giriş olarak yapısı çok iyiydi
    Bunu Vulkan ya da WebGPU/WebGL ile yapmanın nasıl hissettireceğini merak ediyorum
    • Neredeyse aynı. Vulkan ve WebGL ikisi de doğrudan GLSL yazmaya izin verir
      Vulkan'da teknik olarak GLSL'den SPIR-V'ye giden bir yapı var
      Tarayıcıda çalışan WebGPU teknik olarak doğrudan GLSL kullanamaz, ancak yerel WebGPU uygulamaları GLSL alabilir ve dönüştürme de mümkündür
      Ya da sadece WGSL kullanabilirsiniz; C tarzı söz dizimi yerine Rust'tan esinlenen bir söz dizimine sahip olması dışında GLSL'e oldukça benzer
  • Bu animasyonu yapmanın 8 ay sürmüş olması müthiş bir azim gösteriyor
  • Geliştirme sürecinin sürekli küçük değerleri ayarlamaktan mı ibaret olduğunu, yoksa nasıl bir editör kullandığını merak ediyorum
    Uygun ondalık değerlere sahip 240 satırı deneme yanılmayla oluşturmak epey zaman almış olmalı
    • Sadece sayfaya gömülü kod editörünü kullandım
      İkili arama elle yapılsa bile hızlıdır
    • Bu tür işler yaparken genelde bir slider ya da giriş değeri alıp onu bir uniform'a bağlarsınız
      Uniform shader'a iletilir ve yeniden derlemeden güncellenebilir