- 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
Hacker News yorumları
Temelde
stepfonksiyonunuaastepile değiştirmek gibi: https://github.com/glslify/glsl-aastepBirç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
floatdeğerini değiştiripshift-enter'a basınca sonucu hemen görmek epey tatmin edicicanvasya da onun üzerindeki p5.js gibi soyutlamalarla çalışmanın da böyle hissettirip hissettirmediğini merak ediyorumGenel olarak grafik programlamadan mı bahsediliyor, yoksa daha özel olarak GPU shader çalışmasından mı, tam emin değilim
Inigo Quilez'in ilgili YouTube oynatma listesi de var: https://www.youtube.com/watch?v=0ifChJ0nJfM&list=PL0EpikNmjs...
Shader programlama gerçekten bambaşka bir dünya ve yazı da çok güzel
Bunu Vulkan ya da WebGPU/WebGL ile yapmanın nasıl hissettireceğini merak ediyorum
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
Uygun ondalık değerlere sahip 240 satırı deneme yanılmayla oluşturmak epey zaman almış olmalı
İkili arama elle yapılsa bile hızlıdır
Uniform shader'a iletilir ve yeniden derlemeden güncellenebilir